From b3397bd54cd3a2e0ca551485158074d7df64c360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Fri, 3 May 2013 13:36:23 +0200 Subject: [PATCH 0001/1200] Move all resources inside a "resources" directory (cleaner). --HG-- rename : LICENSE_php-epub-meta => resources/php-epub-meta/LICENSE_php-epub-meta rename : epub.php => resources/php-epub-meta/epub.php rename : tbszip.php => resources/php-epub-meta/tbszip.php --- LICENSE_php-epub-meta | 22 - book.php | 2 +- epub.php | 736 --------------------------------- tbszip.php | 924 ------------------------------------------ 4 files changed, 1 insertion(+), 1683 deletions(-) delete mode 100644 LICENSE_php-epub-meta delete mode 100644 epub.php delete mode 100644 tbszip.php diff --git a/LICENSE_php-epub-meta b/LICENSE_php-epub-meta deleted file mode 100644 index 2fcf6f944..000000000 --- a/LICENSE_php-epub-meta +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012 Andreas Gohr - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -All code created or modified by Sébastien Lucas is licensed -under GPL 2 (http://www.gnu.org/licenses/gpl.html). \ No newline at end of file diff --git a/book.php b/book.php index 4690191ad..a7d434c85 100644 --- a/book.php +++ b/book.php @@ -12,7 +12,7 @@ require_once('tag.php'); require_once ("customcolumn.php"); require_once('data.php'); -require_once('epub.php'); +require_once('resources/php-epub-meta/epub.php'); // Silly thing because PHP forbid string concatenation in class const define ('SQL_BOOKS_LEFT_JOIN', "left outer join comments on comments.book = books.id diff --git a/epub.php b/epub.php deleted file mode 100644 index f0aabb826..000000000 --- a/epub.php +++ /dev/null @@ -1,736 +0,0 @@ - - * @author Sébastien Lucas - */ - -require_once('tbszip.php'); - -define ("METADATA_FILE", "META-INF/container.xml"); - -class EPub { - public $xml; //FIXME change to protected, later - public $toc; - protected $xpath; - protected $toc_xpath; - protected $file; - protected $meta; - protected $zip; - protected $coverpath=''; - protected $namespaces; - protected $imagetoadd=''; - - /** - * Constructor - * - * @param string $file path to epub file to work on - * @throws Exception if metadata could not be loaded - */ - public function __construct($file){ - // open file - $this->file = $file; - $this->zip = new clsTbsZip(); - if(!$this->zip->Open($this->file)){ - throw new Exception('Failed to read epub file'); - } - - // read container data - if (!$this->zip->FileExists(METADATA_FILE)) { - throw new Exception ("Unable to find metadata.xml"); - } - - $data = $this->zip->FileRead(METADATA_FILE); - if($data == false){ - throw new Exception('Failed to access epub container data'); - } - $xml = new DOMDocument(); - $xml->registerNodeClass('DOMElement','EPubDOMElement'); - $xml->loadXML($data); - $xpath = new EPubDOMXPath($xml); - $nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]'); - $this->meta = $nodes->item(0)->attr('full-path'); - - // load metadata - if (!$this->zip->FileExists($this->meta)) { - throw new Exception ("Unable to find " . $this->meta); - } - - $data = $this->zip->FileRead($this->meta); - if(!$data){ - throw new Exception('Failed to access epub metadata'); - } - $this->xml = new DOMDocument(); - $this->xml->registerNodeClass('DOMElement','EPubDOMElement'); - $this->xml->loadXML($data); - $this->xml->formatOutput = true; - $this->xpath = new EPubDOMXPath($this->xml); - } - - public function initSpineComponent () - { - $spine = $this->xpath->query('//opf:spine')->item(0); - $tocid = $spine->getAttribute('toc'); - $tochref = $this->xpath->query("//opf:manifest/opf:item[@id='$tocid']")->item(0)->attr('href'); - $tocpath = dirname($this->meta).'/'.$tochref; - // read epub toc - if (!$this->zip->FileExists($tocpath)) { - throw new Exception ("Unable to find " . $tocpath); - } - - $data = $this->zip->FileRead($tocpath); - $this->toc = new DOMDocument(); - $this->toc->registerNodeClass('DOMElement','EPubDOMElement'); - $this->toc->loadXML($data); - $this->toc_xpath = new EPubDOMXPath($this->toc); - $rootNamespace = $this->toc->lookupNamespaceUri($this->toc->namespaceURI); - $this->toc_xpath->registerNamespace('x', $rootNamespace); - } - - /** - * file name getter - */ - public function file(){ - return $this->file; - } - - /** - * Close the epub file - */ - public function close (){ - $this->zip->FileCancelModif($this->meta); - // TODO : Add cancelation of cover image - $this->zip->Close (); - } - - /** - * Remove iTunes files - */ - public function cleanITunesCrap () { - if ($this->zip->FileExists("iTunesMetadata.plist")) { - $this->zip->FileReplace ("iTunesMetadata.plist", false); - } - if ($this->zip->FileExists("iTunesArtwork")) { - $this->zip->FileReplace ("iTunesArtwork", false); - } - } - - /** - * Writes back all meta data changes - */ - public function save(){ - $this->download (); - $this->zip->close(); - } - - /** - * Get the updated epub - */ - public function download($file=false){ - $this->zip->FileReplace($this->meta,$this->xml->saveXML()); - // add the cover image - if($this->imagetoadd){ - $this->zip->FileReplace($this->coverpath,file_get_contents($this->imagetoadd)); - $this->imagetoadd=''; - } - if ($file) $this->zip->Flush(TBSZIP_DOWNLOAD, $file); - } - - /** - * Get the components list as an array - */ - public function components(){ - $spine = array(); - $nodes = $this->xpath->query('//opf:spine/opf:itemref'); - foreach($nodes as $node){ - $idref = $node->getAttribute('idref'); - $spine[] = $this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href'); - } - return $spine; - } - - /** - * Get the component content - */ - public function component($comp) { - $path = dirname($this->meta).'/'.$comp; - if (!$this->zip->FileExists($path)) { - throw new Exception ("Unable to find " . $path); - } - - $data = $this->zip->FileRead($path); - $data = preg_replace ("/src=[\"']([\w\/\.]*?)[\"']/", "src='epubfs.php?comp=$1'", $data); - $data = preg_replace ("/href=[\"']([\w\/\.]*?)[\"']/", "href='epubfs.php?comp=$1'", $data); - return $data; - } - - /** - * Get the component content type - */ - public function componentContentType($comp) { - return $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0)->getAttribute('media-type'); - } - - /** - * Get the Epub content (TOC) as an array - * - * For each chapter there is a "title" and a "src" - */ - public function contents(){ - $contents = array(); - $nodes = $this->toc_xpath->query('//x:ncx/x:navMap/x:navPoint'); - foreach($nodes as $node){ - $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; - $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); - $contents[] = array("title" => $title, "src" => $src); - } - return $contents; - } - - - /** - * Get or set the book author(s) - * - * Authors should be given with a "file-as" and a real name. The file as - * is used for sorting in e-readers. - * - * Example: - * - * array( - * 'Pratchett, Terry' => 'Terry Pratchett', - * 'Simpson, Jacqeline' => 'Jacqueline Simpson', - * ) - * - * @params array $authors - */ - public function Authors($authors=false){ - // set new data - if($authors !== false){ - // Author where given as a comma separated list - if(is_string($authors)){ - if($authors == ''){ - $authors = array(); - }else{ - $authors = explode(',',$authors); - $authors = array_map('trim',$authors); - } - } - - // delete existing nodes - $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - foreach($nodes as $node) $node->delete(); - - // add new nodes - $parent = $this->xpath->query('//opf:metadata')->item(0); - foreach($authors as $as => $name){ - if(is_int($as)) $as = $name; //numeric array given - $node = $parent->newChild('dc:creator',$name); - $node->attr('opf:role', 'aut'); - $node->attr('opf:file-as', $as); - } - - $this->reparse(); - } - - // read current data - $rolefix = false; - $authors = array(); - $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - if($nodes->length == 0){ - // no nodes where found, let's try again without role - $nodes = $this->xpath->query('//opf:metadata/dc:creator'); - $rolefix = true; - } - foreach($nodes as $node){ - $name = $node->nodeValue; - $as = $node->attr('opf:file-as'); - if(!$as){ - $as = $name; - $node->attr('opf:file-as',$as); - } - if($rolefix){ - $node->attr('opf:role','aut'); - } - $authors[$as] = $name; - } - return $authors; - } - - /** - * Set or get the book title - * - * @param string $title - */ - public function Title($title=false){ - return $this->getset('dc:title',$title); - } - - /** - * Set or get the book's language - * - * @param string $lang - */ - public function Language($lang=false){ - return $this->getset('dc:language',$lang); - } - - /** - * Set or get the book' publisher info - * - * @param string $publisher - */ - public function Publisher($publisher=false){ - return $this->getset('dc:publisher',$publisher); - } - - /** - * Set or get the book's copyright info - * - * @param string $rights - */ - public function Copyright($rights=false){ - return $this->getset('dc:rights',$rights); - } - - /** - * Set or get the book's description - * - * @param string $description - */ - public function Description($description=false){ - return $this->getset('dc:description',$description); - } - - /** - * Set or get the book's ISBN number - * - * @param string $isbn - */ - public function ISBN($isbn=false){ - return $this->getset('dc:identifier',$isbn,'opf:scheme','ISBN'); - } - - /** - * Set or get the Google Books ID - * - * @param string $google - */ - public function Google($google=false){ - return $this->getset('dc:identifier',$google,'opf:scheme','GOOGLE'); - } - - /** - * Set or get the Amazon ID of the book - * - * @param string $amazon - */ - public function Amazon($amazon=false){ - return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON'); - } - - /** - * Set or get the Calibre UUID of the book - * - * @param string $uuid - */ - public function Calibre($uuid=false){ - return $this->getset('dc:identifier',$uuid,'opf:scheme','calibre'); - } - - /** - * Set or get the Serie of the book - * - * @param string $serie - */ - public function Serie($serie=false){ - return $this->getset('opf:meta',$serie,'name','calibre:series','content'); - } - - /** - * Set or get the Serie Index of the book - * - * @param string $serieIndex - */ - public function SerieIndex($serieIndex=false){ - return $this->getset('opf:meta',$serieIndex,'name','calibre:series_index','content'); - } - - /** - * Set or get the book's subjects (aka. tags) - * - * Subject should be given as array, but a comma separated string will also - * be accepted. - * - * @param array $subjects - */ - public function Subjects($subjects=false){ - // setter - if($subjects !== false){ - if(is_string($subjects)){ - if($subjects === ''){ - $subjects = array(); - }else{ - $subjects = explode(',',$subjects); - $subjects = array_map('trim',$subjects); - } - } - - // delete previous - $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach($nodes as $node){ - $node->delete(); - } - // add new ones - $parent = $this->xpath->query('//opf:metadata')->item(0); - foreach($subjects as $subj){ - $node = $this->xml->createElement('dc:subject',htmlspecialchars($subj)); - $node = $parent->appendChild($node); - } - - $this->reparse(); - } - - //getter - $subjects = array(); - $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach($nodes as $node){ - $subjects[] = $node->nodeValue; - } - return $subjects; - } - - /** - * Read the cover data - * - * Returns an associative array with the following keys: - * - * mime - filetype (usually image/jpeg) - * data - the binary image data - * found - the internal path, or false if no image is set in epub - * - * When no image is set in the epub file, the binary data for a transparent - * GIF pixel is returned. - * - * When adding a new image this function return no or old data because the - * image contents are not in the epub file, yet. The image will be added when - * the save() method is called. - * - * @param string $path local filesystem path to a new cover image - * @param string $mime mime type of the given file - * @return array - */ - public function Cover($path=false, $mime=false){ - // set cover - if($path !== false){ - // remove current pointer - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - foreach($nodes as $node) $node->delete(); - // remove previous manifest entries if they where made by us - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="php-epub-meta-cover"]'); - foreach($nodes as $node) $node->delete(); - - if($path){ - // add pointer - $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $parent->newChild('opf:meta'); - $node->attr('opf:name','cover'); - $node->attr('opf:content','php-epub-meta-cover'); - - // add manifest - $parent = $this->xpath->query('//opf:manifest')->item(0); - $node = $parent->newChild('opf:item'); - $node->attr('id','php-epub-meta-cover'); - $node->attr('opf:href','php-epub-meta-cover.img'); - $node->attr('opf:media-type',$mime); - - // remember path for save action - $this->imagetoadd = $path; - } - - $this->reparse(); - } - - // load cover - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if(!$nodes->length) return $this->no_cover(); - $coverid = (String) $nodes->item(0)->attr('opf:content'); - if(!$coverid) return $this->no_cover(); - - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); - if(!$nodes->length) return $this->no_cover(); - $mime = $nodes->item(0)->attr('opf:media-type'); - $path = $nodes->item(0)->attr('opf:href'); - $path = dirname('/'.$this->meta).'/'.$path; // image path is relative to meta file - $path = ltrim($path,'/'); - - $zip = new ZipArchive(); - if(!@$zip->open($this->file)){ - throw new Exception('Failed to read epub file'); - } - $data = $zip->getFromName($path); - - return array( - 'mime' => $mime, - 'data' => $data, - 'found' => $path - ); - } - - public function getCoverItem () { - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if(!$nodes->length) return NULL; - - $coverid = (String) $nodes->item(0)->attr('opf:content'); - if(!$coverid) return NULL; - - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); - if(!$nodes->length) return NULL; - - return $nodes->item(0); - } - - public function updateForKepub () { - $item = $this->getCoverItem (); - if (!is_null ($item)) { - $item->attr('opf:properties', 'cover-image'); - } - } - - public function Cover2($path=false, $mime=false){ - $hascover = true; - $item = $this->getCoverItem (); - if (is_null ($item)) { - $hascover = false; - } else { - $mime = $item->attr('opf:media-type'); - $this->coverpath = $item->attr('opf:href'); - $this->coverpath = dirname('/'.$this->meta).'/'.$this->coverpath; // image path is relative to meta file - $this->coverpath = ltrim($this->coverpath,'\\'); - $this->coverpath = ltrim($this->coverpath,'/'); - } - - // set cover - if($path !== false){ - if (!$hascover) return; // TODO For now only update - - if($path){ - $item->attr('opf:media-type',$mime); - - // remember path for save action - $this->imagetoadd = $path; - } - - $this->reparse(); - } - - if (!$hascover) return $this->no_cover(); - } - - /** - * A simple getter/setter for simple meta attributes - * - * It should only be used for attributes that are expected to be unique - * - * @param string $item XML node to set/get - * @param string $value New node value - * @param string $att Attribute name - * @param string $aval Attribute value - * @param string $datt Destination attribute - */ - protected function getset($item,$value=false,$att=false,$aval=false,$datt=false){ - // construct xpath - $xpath = '//opf:metadata/'.$item; - if($att){ - $xpath .= "[@$att=\"$aval\"]"; - } - - // set value - if($value !== false){ - $value = htmlspecialchars($value); - $nodes = $this->xpath->query($xpath); - if($nodes->length == 1 ){ - if($value === ''){ - // the user want's to empty this value -> delete the node - $nodes->item(0)->delete(); - }else{ - // replace value - if ($datt){ - $nodes->item(0)->attr ($datt, $value); - }else{ - $nodes->item(0)->nodeValue = $value; - } - } - }else{ - // if there are multiple matching nodes for some reason delete - // them. we'll replace them all with our own single one - foreach($nodes as $n) $n->delete(); - // readd them - if($value){ - $parent = $this->xpath->query('//opf:metadata')->item(0); - - $node = $parent->newChild ($item); - if($att) $node->attr($att,$aval); - if ($datt){ - $node->attr ($datt, $value); - }else{ - $node->nodeValue = $value; - } - } - } - - $this->reparse(); - } - - // get value - $nodes = $this->xpath->query($xpath); - if($nodes->length){ - if ($datt){ - return $nodes->item(0)->attr ($datt); - }else{ - return $nodes->item(0)->nodeValue; - } - }else{ - return ''; - } - } - - /** - * Return a not found response for Cover() - */ - protected function no_cover(){ - return array( - 'data' => base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'), - 'mime' => 'image/gif', - 'found' => false - ); - } - - /** - * Reparse the DOM tree - * - * I had to rely on this because otherwise xpath failed to find the newly - * added nodes - */ - protected function reparse() { - $this->xml->loadXML($this->xml->saveXML()); - $this->xpath = new EPubDOMXPath($this->xml); - } -} - -class EPubDOMXPath extends DOMXPath { - public function __construct(DOMDocument $doc){ - parent::__construct($doc); - - if(is_a($doc->documentElement, 'EPubDOMElement')){ - foreach($doc->documentElement->namespaces as $ns => $url){ - $this->registerNamespace($ns,$url); - } - } - } -} - -class EPubDOMElement extends DOMElement { - public $namespaces = array( - 'n' => 'urn:oasis:names:tc:opendocument:xmlns:container', - 'opf' => 'http://www.idpf.org/2007/opf', - 'dc' => 'http://purl.org/dc/elements/1.1/' - ); - - - public function __construct($name, $value='', $namespaceURI=''){ - list($ns,$name) = $this->splitns($name); - $value = htmlspecialchars($value); - if(!$namespaceURI && $ns){ - $namespaceURI = $this->namespaces[$ns]; - } - parent::__construct($name, $value, $namespaceURI); - } - - - /** - * Create and append a new child - * - * Works with our epub namespaces and omits default namespaces - */ - public function newChild($name, $value=''){ - list($ns,$local) = $this->splitns($name); - if($ns){ - $nsuri = $this->namespaces[$ns]; - if($this->isDefaultNamespace($nsuri)){ - $name = $local; - $nsuri = ''; - } - } - - // this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value); - $node = new EPubDOMElement($name,$value,$nsuri); - return $this->appendChild($node); - } - - /** - * Split given name in namespace prefix and local part - * - * @param string $name - * @return array (namespace, name) - */ - public function splitns($name){ - $list = explode(':',$name,2); - if(count($list) < 2) array_unshift($list,''); - return $list; - } - - /** - * Simple EPub namespace aware attribute accessor - */ - public function attr($attr,$value=null){ - list($ns,$attr) = $this->splitns($attr); - - $nsuri = ''; - if($ns){ - $nsuri = $this->namespaces[$ns]; - if(!$this->namespaceURI){ - if($this->isDefaultNamespace($nsuri)){ - $nsuri = ''; - } - }elseif($this->namespaceURI == $nsuri){ - $nsuri = ''; - } - } - - if(!is_null($value)){ - if($value === false){ - // delete if false was given - if($nsuri){ - $this->removeAttributeNS($nsuri,$attr); - }else{ - $this->removeAttribute($attr); - } - }else{ - // modify if value was given - if($nsuri){ - $this->setAttributeNS($nsuri,$attr,$value); - }else{ - $this->setAttribute($attr,$value); - } - } - }else{ - // return value if none was given - if($nsuri){ - return $this->getAttributeNS($nsuri,$attr); - }else{ - return $this->getAttribute($attr); - } - } - } - - /** - * Remove this node from the DOM - */ - public function delete(){ - $this->parentNode->removeChild($this); - } - -} - - diff --git a/tbszip.php b/tbszip.php deleted file mode 100644 index fbbb8cbe6..000000000 --- a/tbszip.php +++ /dev/null @@ -1,924 +0,0 @@ -Meth8Ok = extension_loaded('zlib'); // check if Zlib extension is available. This is need for compress and uncompress with method 8. - $this->DisplayError = true; - $this->ArchFile = ''; - $this->Error = false; - } - - function CreateNew($ArchName='new.zip') { - // Create a new virtual empty archive, the name will be the default name when the archive is flushed. - if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility - $this->Close(); // note that $this->ArchHnd is set to false here - $this->Error = false; - $this->ArchFile = $ArchName; - $this->ArchIsNew = true; - $bin = 'PK'.chr(05).chr(06).str_repeat(chr(0), 18); - $this->CdEndPos = strlen($bin) - 4; - $this->CdInfo = array('disk_num_curr'=>0, 'disk_num_cd'=>0, 'file_nbr_curr'=>0, 'file_nbr_tot'=>0, 'l_cd'=>0, 'p_cd'=>0, 'l_comm'=>0, 'v_comm'=>'', 'bin'=>$bin); - $this->CdPos = $this->CdInfo['p_cd']; - } - - function Open($ArchFile, $UseIncludePath=false) { - // Open the zip archive - if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility - $this->Close(); // close handle and init info - $this->Error = false; - $this->ArchFile = $ArchFile; - $this->ArchIsNew = false; - // open the file - $this->ArchHnd = fopen($ArchFile, 'rb', $UseIncludePath); - $ok = !($this->ArchHnd===false); - if ($ok) $ok = $this->CentralDirRead(); - return $ok; - } - - function Close() { - if (isset($this->ArchHnd) and ($this->ArchHnd!==false)) fclose($this->ArchHnd); - $this->ArchFile = ''; - $this->ArchHnd = false; - $this->CdInfo = array(); - $this->CdFileLst = array(); - $this->CdFileNbr = 0; - $this->CdFileByName = array(); - $this->VisFileLst = array(); - $this->ArchCancelModif(); - } - - function ArchCancelModif() { - $this->LastReadComp = false; // compression of the last read file (1=compressed, 0=stored not compressed, -1= stored compressed but read uncompressed) - $this->LastReadIdx = false; // index of the last file read - $this->ReplInfo = array(); - $this->ReplByPos = array(); - $this->AddInfo = array(); - } - - function FileAdd($Name, $Data, $DataType=TBSZIP_STRING, $Compress=true) { - - if ($Data===false) return $this->FileCancelModif($Name, false); // Cancel a previously added file - - // Save information for adding a new file into the archive - $Diff = 30 + 46 + 2*strlen($Name); // size of the header + cd info - $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $Name); - if ($Ref===false) return false; - $Ref['name'] = $Name; - $this->AddInfo[] = $Ref; - return $Ref['res']; - - } - - function CentralDirRead() { - $cd_info = 'PK'.chr(05).chr(06); // signature of the Central Directory - $cd_pos = -22; - $this->_MoveTo($cd_pos, SEEK_END); - $b = $this->_ReadData(4); - if ($b!==$cd_info) return $this->RaiseError('The End of Central Rirectory Record is not found.'); - - $this->CdEndPos = ftell($this->ArchHnd) - 4; - $this->CdInfo = $this->CentralDirRead_End($cd_info); - $this->CdFileLst = array(); - $this->CdFileNbr = $this->CdInfo['file_nbr_curr']; - $this->CdPos = $this->CdInfo['p_cd']; - - if ($this->CdFileNbr<=0) return $this->RaiseError('No header found in the Central Directory.'); - if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory.'); - - $this->_MoveTo($this->CdPos); - for ($i=0;$i<$this->CdFileNbr;$i++) { - $x = $this->CentralDirRead_File($i); - if ($x!==false) { - $this->CdFileLst[$i] = $x; - $this->CdFileByName[$x['v_name']] = $i; - } - } - return true; - } - - function CentralDirRead_End($cd_info) { - $b = $cd_info.$this->_ReadData(18); - $x = array(); - $x['disk_num_curr'] = $this->_GetDec($b,4,2); // number of this disk - $x['disk_num_cd'] = $this->_GetDec($b,6,2); // number of the disk with the start of the central directory - $x['file_nbr_curr'] = $this->_GetDec($b,8,2); // total number of entries in the central directory on this disk - $x['file_nbr_tot'] = $this->_GetDec($b,10,2); // total number of entries in the central directory - $x['l_cd'] = $this->_GetDec($b,12,4); // size of the central directory - $x['p_cd'] = $this->_GetDec($b,16,4); // position of start of central directory with respect to the starting disk number - $x['l_comm'] = $this->_GetDec($b,20,2); // .ZIP file comment length - $x['v_comm'] = $this->_ReadData($x['l_comm']); // .ZIP file comment - $x['bin'] = $b.$x['v_comm']; - return $x; - } - - function CentralDirRead_File($idx) { - - $b = $this->_ReadData(46); - - $x = $this->_GetHex($b,0,4); - if ($x!=='h:02014b50') return $this->RaiseError("Signature of Central Directory Header #".$idx." (file information) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd) - 46)."."); - - $x = array(); - $x['vers_used'] = $this->_GetDec($b,4,2); - $x['vers_necess'] = $this->_GetDec($b,6,2); - $x['purp'] = $this->_GetBin($b,8,2); - $x['meth'] = $this->_GetDec($b,10,2); - $x['time'] = $this->_GetDec($b,12,2); - $x['date'] = $this->_GetDec($b,14,2); - $x['crc32'] = $this->_GetDec($b,16,4); - $x['l_data_c'] = $this->_GetDec($b,20,4); - $x['l_data_u'] = $this->_GetDec($b,24,4); - $x['l_name'] = $this->_GetDec($b,28,2); - $x['l_fields'] = $this->_GetDec($b,30,2); - $x['l_comm'] = $this->_GetDec($b,32,2); - $x['disk_num'] = $this->_GetDec($b,34,2); - $x['int_file_att'] = $this->_GetDec($b,36,2); - $x['ext_file_att'] = $this->_GetDec($b,38,4); - $x['p_loc'] = $this->_GetDec($b,42,4); - $x['v_name'] = $this->_ReadData($x['l_name']); - $x['v_fields'] = $this->_ReadData($x['l_fields']); - $x['v_comm'] = $this->_ReadData($x['l_comm']); - - $x['bin'] = $b.$x['v_name'].$x['v_fields'].$x['v_comm']; - - return $x; - } - - function RaiseError($Msg) { - if ($this->DisplayError) echo ''.get_class($this).' ERROR : '.$Msg.'
'."\r\n"; - $this->Error = $Msg; - return false; - } - - function Debug($FileHeaders=false) { - - $this->DisplayError = true; - - if ($FileHeaders) { - // Calculations first in order to have error messages before other information - $idx = 0; - $pos = 0; - $pos_stop = $this->CdInfo['p_cd']; - $this->_MoveTo($pos); - while ( ($pos<$pos_stop) && ($ok = $this->_ReadFile($idx,false)) ) { - $this->VisFileLst[$idx]['p_this_header (debug_mode only)'] = $pos; - $pos = ftell($this->ArchHnd); - $idx++; - } - } - - $nl = "\r\n"; - echo "
";
-		
-		echo "-------------------------------".$nl;
-		echo "End of Central Directory record".$nl;
-		echo "-------------------------------".$nl;
-		print_r($this->DebugArray($this->CdInfo));
-
-		echo $nl;
-		echo "-------------------------".$nl;
-		echo "Central Directory headers".$nl;
-		echo "-------------------------".$nl;
-		print_r($this->DebugArray($this->CdFileLst));
-
-		if ($FileHeaders) {
-			echo $nl;
-			echo "------------------".$nl;
-			echo "Local File headers".$nl;
-			echo "------------------".$nl;
-			print_r($this->DebugArray($this->VisFileLst));
-		}
-
-		echo "
"; - - } - - function DebugArray($arr) { - foreach ($arr as $k=>$v) { - if (is_array($v)) { - $arr[$k] = $this->DebugArray($v); - } elseif (substr($k,0,2)=='p_') { - $arr[$k] = $this->_TxtPos($v); - } - } - return $arr; - } - - function FileExists($NameOrIdx) { - return ($this->FileGetIdx($NameOrIdx)!==false); - } - - function FileGetIdx($NameOrIdx) { - // Check if a file name, or a file index exists in the Central Directory, and return its index - if (is_string($NameOrIdx)) { - if (isset($this->CdFileByName[$NameOrIdx])) { - return $this->CdFileByName[$NameOrIdx]; - } else { - return false; - } - } else { - if (isset($this->CdFileLst[$NameOrIdx])) { - return $NameOrIdx; - } else { - return false; - } - } - } - - function FileGetIdxAdd($Name) { - // Check if a file name exists in the list of file to add, and return its index - if (!is_string($Name)) return false; - $idx_lst = array_keys($this->AddInfo); - foreach ($idx_lst as $idx) { - if ($this->AddInfo[$idx]['name']===$Name) return $idx; - } - return false; - } - - function FileRead($NameOrIdx, $Uncompress=true) { - - $this->LastReadComp = false; // means the file is not found - $this->LastReadIdx = false; - - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.'); - - $pos = $this->CdFileLst[$idx]['p_loc']; - $this->_MoveTo($pos); - - $this->LastReadIdx = $idx; // Can be usefull to get the idx - - $Data = $this->_ReadFile($idx, true); - - // Manage uncompression - $Comp = 1; // means the contents stays compressed - $meth = $this->CdFileLst[$idx]['meth']; - if ($meth==8) { - if ($Uncompress) { - if ($this->Meth8Ok) { - $Data = gzinflate($Data); - $Comp = -1; // means uncompressed - } else { - $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because extension Zlib is not installed.'); - } - } - } elseif($meth==0) { - $Comp = 0; // means stored without compression - } else { - if ($Uncompress) $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because it is compressed with method '.$meth.'.'); - } - $this->LastReadComp = $Comp; - - return $Data; - - } - - function _ReadFile($idx, $ReadData) { - // read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position - - $b = $this->_ReadData(30); - - $x = $this->_GetHex($b,0,4); - if ($x!=='h:04034b50') return $this->RaiseError("Signature of Local File Header #".$idx." (data section) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd)-30)."."); - - $x = array(); - $x['vers'] = $this->_GetDec($b,4,2); - $x['purp'] = $this->_GetBin($b,6,2); - $x['meth'] = $this->_GetDec($b,8,2); - $x['time'] = $this->_GetDec($b,10,2); - $x['date'] = $this->_GetDec($b,12,2); - $x['crc32'] = $this->_GetDec($b,14,4); - $x['l_data_c'] = $this->_GetDec($b,18,4); - $x['l_data_u'] = $this->_GetDec($b,22,4); - $x['l_name'] = $this->_GetDec($b,26,2); - $x['l_fields'] = $this->_GetDec($b,28,2); - $x['v_name'] = $this->_ReadData($x['l_name']); - $x['v_fields'] = $this->_ReadData($x['l_fields']); - - $x['bin'] = $b.$x['v_name'].$x['v_fields']; - - // Read Data - if (isset($this->CdFileLst[$idx])) { - $len_cd = $this->CdFileLst[$idx]['l_data_c']; - if ($x['l_data_c']==0) { - // Sometimes, the size is not specified in the local information. - $len = $len_cd; - } else { - $len = $x['l_data_c']; - if ($len!=$len_cd) { - //echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd."."; - } - } - } else { - $len = $x['l_data_c']; - if ($len==0) $this->RaiseError("File Data #".$idx." cannt be read because no length is specified in the Local File Header and its Central Directory information has not been found."); - } - - if ($ReadData) { - $Data = $this->_ReadData($len); - } else { - $this->_MoveTo($len, SEEK_CUR); - } - - // Description information - $desc_ok = ($x['purp'][2+3]=='1'); - if ($desc_ok) { - $b = $this->_ReadData(12); - $s = $this->_GetHex($b,0,4); - $d = 0; - // the specification says the signature may or may not be present - if ($s=='h:08074b50') { - $b .= $this->_ReadData(4); - $d = 4; - $x['desc_bin'] = $b; - $x['desc_sign'] = $s; - } else { - $x['desc_bin'] = $b; - } - $x['desc_crc32'] = $this->_GetDec($b,0+$d,4); - $x['desc_l_data_c'] = $this->_GetDec($b,4+$d,4); - $x['desc_l_data_u'] = $this->_GetDec($b,8+$d,4); - } - - // Save file info without the data - $this->VisFileLst[$idx] = $x; - - // Return the info - if ($ReadData) { - return $Data; - } else { - return true; - } - - } - - function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) { - // Store replacement information. - - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.'); - - $pos = $this->CdFileLst[$idx]['p_loc']; - - if ($Data===false) { - // file to delete - $this->ReplInfo[$idx] = false; - $Result = true; - } else { - // file to replace - $Diff = - $this->CdFileLst[$idx]['l_data_c']; - $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx); - if ($Ref===false) return false; - $this->ReplInfo[$idx] = $Ref; - $Result = $Ref['res']; - } - - $this->ReplByPos[$pos] = $idx; - - return $Result; - - } - - function FileCancelModif($NameOrIdx, $ReplacedAndDeleted=true) { - // cancel added, modified or deleted modifications on a file in the archive - // return the number of cancels - - $nbr = 0; - - if ($ReplacedAndDeleted) { - // replaced or deleted files - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx!==false) { - if (isset($this->ReplInfo[$idx])) { - $pos = $this->CdFileLst[$idx]['p_loc']; - unset($this->ReplByPos[$pos]); - unset($this->ReplInfo[$idx]); - $nbr++; - } - } - } - - // added files - $idx = $this->FileGetIdxAdd($NameOrIdx); - if ($idx!==false) { - unset($this->AddInfo[$idx]); - $nbr++; - } - - return $nbr; - - } - - function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') { - - if ( ($File!=='') && ($this->ArchFile===$File)) { - $this->RaiseError('Method Flush() cannot overwrite the current opened archive: \''.$File.'\''); // this makes corrupted zip archives without PHP error. - return false; - } - - $ArchPos = 0; - $Delta = 0; - $FicNewPos = array(); - $DelLst = array(); // idx of deleted files - $DeltaCdLen = 0; // delta of the CD's size - - $now = time(); - $date = $this->_MsDos_Date($now); - $time = $this->_MsDos_Time($now); - - if (!$this->OutputOpen($Render, $File, $ContentType)) return false; - - // output modified zipped files and unmodified zipped files that are beetween them - ksort($this->ReplByPos); - foreach ($this->ReplByPos as $ReplPos => $ReplIdx) { - // output data from the zip archive which is before the data to replace - $this->OutputFromArch($ArchPos, $ReplPos); - // get current file information - if (!isset($this->VisFileLst[$ReplIdx])) $this->_ReadFile($ReplIdx, false); - $FileInfo =& $this->VisFileLst[$ReplIdx]; - $b1 = $FileInfo['bin']; - if (isset($FileInfo['desc_bin'])) { - $b2 = $FileInfo['desc_bin']; - } else { - $b2 = ''; - } - $info_old_len = strlen($b1) + $this->CdFileLst[$ReplIdx]['l_data_c'] + strlen($b2); // $FileInfo['l_data_c'] may have a 0 value in some archives - // get replacement information - $ReplInfo =& $this->ReplInfo[$ReplIdx]; - if ($ReplInfo===false) { - // The file is to be deleted - $Delta = $Delta - $info_old_len; // headers and footers are also deleted - $DelLst[$ReplIdx] = true; - } else { - // prepare the header of the current file - $this->_DataPrepare($ReplInfo); // get data from external file if necessary - $this->_PutDec($b1, $time, 10, 2); // time - $this->_PutDec($b1, $date, 12, 2); // date - $this->_PutDec($b1, $ReplInfo['crc32'], 14, 4); // crc32 - $this->_PutDec($b1, $ReplInfo['len_c'], 18, 4); // l_data_c - $this->_PutDec($b1, $ReplInfo['len_u'], 22, 4); // l_data_u - if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 8, 2); // meth - // prepare the bottom description if the zipped file, if any - if ($b2!=='') { - $d = (strlen($b2)==16) ? 4 : 0; // offset because of the signature if any - $this->_PutDec($b2, $ReplInfo['crc32'], $d+0, 4); // crc32 - $this->_PutDec($b2, $ReplInfo['len_c'], $d+4, 4); // l_data_c - $this->_PutDec($b2, $ReplInfo['len_u'], $d+8, 4); // l_data_u - } - // output data - $this->OutputFromString($b1.$ReplInfo['data'].$b2); - unset($ReplInfo['data']); // save PHP memory - $Delta = $Delta + $ReplInfo['diff'] + $ReplInfo['len_c']; - } - // Update the delta of positions for zipped files which are physically after the currently replaced one - for ($i=0;$i<$this->CdFileNbr;$i++) { - if ($this->CdFileLst[$i]['p_loc']>$ReplPos) { - $FicNewPos[$i] = $this->CdFileLst[$i]['p_loc'] + $Delta; - } - } - // Update the current pos in the archive - $ArchPos = $ReplPos + $info_old_len; - } - - // Ouput all the zipped files that remain before the Central Directory listing - if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdPos); // ArchHnd is false if CreateNew() has been called - $ArchPos = $this->CdPos; - - // Output file to add - $AddNbr = count($this->AddInfo); - $AddDataLen = 0; // total len of added data (inlcuding file headers) - if ($AddNbr>0) { - $AddPos = $ArchPos + $Delta; // position of the start - $AddLst = array_keys($this->AddInfo); - foreach ($AddLst as $idx) { - $n = $this->_DataOuputAddedFile($idx, $AddPos); - $AddPos += $n; - $AddDataLen += $n; - } - } - - // Modifiy file information in the Central Directory for replaced files - $b2 = ''; - $old_cd_len = 0; - for ($i=0;$i<$this->CdFileNbr;$i++) { - $b1 = $this->CdFileLst[$i]['bin']; - $old_cd_len += strlen($b1); - if (!isset($DelLst[$i])) { - if (isset($FicNewPos[$i])) $this->_PutDec($b1, $FicNewPos[$i], 42, 4); // p_loc - if (isset($this->ReplInfo[$i])) { - $ReplInfo =& $this->ReplInfo[$i]; - $this->_PutDec($b1, $time, 12, 2); // time - $this->_PutDec($b1, $date, 14, 2); // date - $this->_PutDec($b1, $ReplInfo['crc32'], 16, 4); // crc32 - $this->_PutDec($b1, $ReplInfo['len_c'], 20, 4); // l_data_c - $this->_PutDec($b1, $ReplInfo['len_u'], 24, 4); // l_data_u - if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 10, 2); // meth - } - $b2 .= $b1; - } - } - $this->OutputFromString($b2); - $ArchPos += $old_cd_len; - $DeltaCdLen = $DeltaCdLen + strlen($b2) - $old_cd_len; - - // Output until "end of central directory record" - if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdEndPos); // ArchHnd is false if CreateNew() has been called - - // Output file information of the Central Directory for added files - if ($AddNbr>0) { - $b2 = ''; - foreach ($AddLst as $idx) { - $b2 .= $this->AddInfo[$idx]['bin']; - } - $this->OutputFromString($b2); - $DeltaCdLen += strlen($b2); - } - - // Output "end of central directory record" - $b2 = $this->CdInfo['bin']; - $DelNbr = count($DelLst); - if ( ($AddNbr>0) or ($DelNbr>0) ) { - // total number of entries in the central directory on this disk - $n = $this->_GetDec($b2, 8, 2); - $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 8, 2); - // total number of entries in the central directory - $n = $this->_GetDec($b2, 10, 2); - $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 10, 2); - // size of the central directory - $n = $this->_GetDec($b2, 12, 4); - $this->_PutDec($b2, $n + $DeltaCdLen, 12, 4); - $Delta = $Delta + $AddDataLen; - } - $this->_PutDec($b2, $this->CdPos+$Delta , 16, 4); // p_cd (offset of start of central directory with respect to the starting disk number) - $this->OutputFromString($b2); - - $this->OutputClose(); - - return true; - - } - - // ---------------- - // output functions - // ---------------- - - function OutputOpen($Render, $File, $ContentType) { - - if (($Render & TBSZIP_FILE)==TBSZIP_FILE) { - $this->OutputMode = TBSZIP_FILE; - if (''.$File=='') $File = basename($this->ArchFile).'.zip'; - $this->OutputHandle = @fopen($File, 'w'); - if ($this->OutputHandle===false) { - $this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.'); - return false; - } - } elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) { - $this->OutputMode = TBSZIP_STRING; - $this->OutputSrc = ''; - } elseif (($Render & TBSZIP_DOWNLOAD)==TBSZIP_DOWNLOAD) { - $this->OutputMode = TBSZIP_DOWNLOAD; - // Output the file - if (''.$File=='') $File = basename($this->ArchFile); - if (($Render & TBSZIP_NOHEADER)==TBSZIP_NOHEADER) { - } else { - header ('Pragma: no-cache'); - if ($ContentType!='') header ('Content-Type: '.$ContentType); - header('Content-Disposition: attachment; filename="'.$File.'"'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: public'); - header('Content-Description: File Transfer'); - header('Content-Transfer-Encoding: binary'); - $Len = $this->_EstimateNewArchSize(); - if ($Len!==false) header('Content-Length: '.$Len); - } - } - - return true; - - } - - function OutputFromArch($pos, $pos_stop) { - $len = $pos_stop - $pos; - if ($len<0) return; - $this->_MoveTo($pos); - $block = 1024; - while ($len>0) { - $l = min($len, $block); - $x = $this->_ReadData($l); - $this->OutputFromString($x); - $len = $len - $l; - } - unset($x); - } - - function OutputFromString($data) { - if ($this->OutputMode===TBSZIP_DOWNLOAD) { - echo $data; // donwload - } elseif ($this->OutputMode===TBSZIP_STRING) { - $this->OutputSrc .= $data; // to string - } elseif (TBSZIP_FILE) { - fwrite($this->OutputHandle, $data); // to file - } - } - - function OutputClose() { - if ( ($this->OutputMode===TBSZIP_FILE) && ($this->OutputHandle!==false) ) { - fclose($this->OutputHandle); - $this->OutputHandle = false; - } - } - - // ---------------- - // Reading functions - // ---------------- - - function _MoveTo($pos, $relative = SEEK_SET) { - fseek($this->ArchHnd, $pos, $relative); - } - - function _ReadData($len) { - if ($len>0) { - $x = fread($this->ArchHnd, $len); - return $x; - } else { - return ''; - } - } - - // ---------------- - // Take info from binary data - // ---------------- - - function _GetDec($txt, $pos, $len) { - $x = substr($txt, $pos, $len); - $z = 0; - for ($i=0;$i<$len;$i++) { - $asc = ord($x[$i]); - if ($asc>0) $z = $z + $asc*pow(256,$i); - } - return $z; - } - - function _GetHex($txt, $pos, $len) { - $x = substr($txt, $pos, $len); - return 'h:'.bin2hex(strrev($x)); - } - - function _GetBin($txt, $pos, $len) { - $x = substr($txt, $pos, $len); - $z = ''; - for ($i=0;$i<$len;$i++) { - $asc = ord($x[$i]); - if (isset($x[$i])) { - for ($j=0;$j<8;$j++) { - $z .= ($asc & pow(2,$j)) ? '1' : '0'; - } - } else { - $z .= '00000000'; - } - } - return 'b:'.$z; - } - - // ---------------- - // Put info into binary data - // ---------------- - - function _PutDec(&$txt, $val, $pos, $len) { - $x = ''; - for ($i=0;$i<$len;$i++) { - if ($val==0) { - $z = 0; - } else { - $z = intval($val % 256); - if (($val<0) && ($z!=0)) { // ($z!=0) is very important, example: val=-420085702 - // special opration for negative value. If the number id too big, PHP stores it into a signed integer. For example: crc32('coucou') => -256185401 instead of 4038781895. NegVal = BigVal - (MaxVal+1) = BigVal - 256^4 - $val = ($val - $z)/256 -1; - $z = 256 + $z; - } else { - $val = ($val - $z)/256; - } - } - $x .= chr($z); - } - $txt = substr_replace($txt, $x, $pos, $len); - } - - function _MsDos_Date($Timestamp = false) { - // convert a date-time timstamp into the MS-Dos format - $d = ($Timestamp===false) ? getdate() : getdate($Timestamp); - return (($d['year']-1980)*512) + ($d['mon']*32) + $d['mday']; - } - function _MsDos_Time($Timestamp = false) { - // convert a date-time timstamp into the MS-Dos format - $d = ($Timestamp===false) ? getdate() : getdate($Timestamp); - return ($d['hours']*2048) + ($d['minutes']*32) + intval($d['seconds']/2); // seconds are rounded to an even number in order to save 1 bit - } - - function _MsDos_Debug($date, $time) { - // Display the formated date and time. Just for debug purpose. - // date end time are encoded on 16 bits (2 bytes) : date = yyyyyyymmmmddddd , time = hhhhhnnnnnssssss - $y = ($date & 65024)/512 + 1980; - $m = ($date & 480)/32; - $d = ($date & 31); - $h = ($time & 63488)/2048; - $i = ($time & 1984)/32; - $s = ($time & 31) * 2; // seconds have been rounded to an even number in order to save 1 bit - return $y.'-'.str_pad($m,2,'0',STR_PAD_LEFT).'-'.str_pad($d,2,'0',STR_PAD_LEFT).' '.str_pad($h,2,'0',STR_PAD_LEFT).':'.str_pad($i,2,'0',STR_PAD_LEFT).':'.str_pad($s,2,'0',STR_PAD_LEFT); - } - - function _TxtPos($pos) { - // Return the human readable position in both decimal and hexa - return $pos." (h:".dechex($pos).")"; - } - - function _DataOuputAddedFile($Idx, $PosLoc) { - - $Ref =& $this->AddInfo[$Idx]; - $this->_DataPrepare($Ref); // get data from external file if necessary - - // Other info - $now = time(); - $date = $this->_MsDos_Date($now); - $time = $this->_MsDos_Time($now); - $len_n = strlen($Ref['name']); - $purp = 2048 ; // purpose // +8 to indicates that there is an extended local header - - // Header for file in the data section - $b = 'PK'.chr(03).chr(04).str_repeat(' ',26); // signature - $this->_PutDec($b,20,4,2); //vers = 20 - $this->_PutDec($b,$purp,6,2); // purp - $this->_PutDec($b,$Ref['meth'],8,2); // meth - $this->_PutDec($b,$time,10,2); // time - $this->_PutDec($b,$date,12,2); // date - $this->_PutDec($b,$Ref['crc32'],14,4); // crc32 - $this->_PutDec($b,$Ref['len_c'],18,4); // l_data_c - $this->_PutDec($b,$Ref['len_u'],22,4); // l_data_u - $this->_PutDec($b,$len_n,26,2); // l_name - $this->_PutDec($b,0,28,2); // l_fields - $b .= $Ref['name']; // name - $b .= ''; // fields - - // Output the data - $this->OutputFromString($b.$Ref['data']); - $OutputLen = strlen($b) + $Ref['len_c']; // new position of the cursor - unset($Ref['data']); // save PHP memory - - // Information for file in the Central Directory - $b = 'PK'.chr(01).chr(02).str_repeat(' ',42); // signature - $this->_PutDec($b,20,4,2); // vers_used = 20 - $this->_PutDec($b,20,6,2); // vers_necess = 20 - $this->_PutDec($b,$purp,8,2); // purp - $this->_PutDec($b,$Ref['meth'],10,2); // meth - $this->_PutDec($b,$time,12,2); // time - $this->_PutDec($b,$date,14,2); // date - $this->_PutDec($b,$Ref['crc32'],16,4); // crc32 - $this->_PutDec($b,$Ref['len_c'],20,4); // l_data_c - $this->_PutDec($b,$Ref['len_u'],24,4); // l_data_u - $this->_PutDec($b,$len_n,28,2); // l_name - $this->_PutDec($b,0,30,2); // l_fields - $this->_PutDec($b,0,32,2); // l_comm - $this->_PutDec($b,0,34,2); // disk_num - $this->_PutDec($b,0,36,2); // int_file_att - $this->_PutDec($b,0,38,4); // ext_file_att - $this->_PutDec($b,$PosLoc,42,4); // p_loc - $b .= $Ref['name']; // v_name - $b .= ''; // v_fields - $b .= ''; // v_comm - - $Ref['bin'] = $b; - - return $OutputLen; - - } - - function _DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx) { - - if (is_array($Compress)) { - $result = 2; - $meth = $Compress['meth']; - $len_u = $Compress['len_u']; - $crc32 = $Compress['crc32']; - $Compress = false; - } elseif ($Compress and ($this->Meth8Ok)) { - $result = 1; - $meth = 8; - $len_u = false; // means unknown - $crc32 = false; - } else { - $result = ($Compress) ? -1 : 0; - $meth = 0; - $len_u = false; - $crc32 = false; - $Compress = false; - } - - if ($DataType==TBSZIP_STRING) { - $path = false; - if ($Compress) { - // we compress now in order to save PHP memory - $len_u = strlen($Data); - $crc32 = crc32($Data); - $Data = gzdeflate($Data); - $len_c = strlen($Data); - } else { - $len_c = strlen($Data); - if ($len_u===false) { - $len_u = $len_c; - $crc32 = crc32($Data); - } - } - } else { - $path = $Data; - $Data = false; - if (file_exists($path)) { - $fz = filesize($path); - if ($len_u===false) $len_u = $fz; - $len_c = ($Compress) ? false : $fz; - } else { - return $this->RaiseError("Cannot add the file '".$path."' because it is not found."); - } - } - - // at this step $Data and $crc32 can be false only in case of external file, and $len_c is false only in case of external file to compress - return array('data'=>$Data, 'path'=>$path, 'meth'=>$meth, 'len_u'=>$len_u, 'len_c'=>$len_c, 'crc32'=>$crc32, 'diff'=>$Diff, 'res'=>$result); - - } - - function _DataPrepare(&$Ref) { - // returns the real size of data - if ($Ref['path']!==false) { - $Ref['data'] = file_get_contents($Ref['path']); - if ($Ref['crc32']===false) $Ref['crc32'] = crc32($Ref['data']); - if ($Ref['len_c']===false) { - // means the data must be compressed - $Ref['data'] = gzdeflate($Ref['data']); - $Ref['len_c'] = strlen($Ref['data']); - } - } - } - - function _EstimateNewArchSize($Optim=true) { - // Return the size of the new archive, or false if it cannot be calculated (because of external file that must be compressed before to be insered) - - if ($this->ArchIsNew) { - $Len = strlen($this->CdInfo['bin']); - } else { - $Len = filesize($this->ArchFile); - } - - // files to replace or delete - foreach ($this->ReplByPos as $i) { - $Ref =& $this->ReplInfo[$i]; - if ($Ref===false) { - // file to delete - $Info =& $this->CdFileLst[$i]; - if (!isset($this->VisFileLst[$i])) { - if ($Optim) return false; // if $Optimization is set to true, then we d'ont rewind to read information - $this->_MoveTo($Info['p_loc']); - $this->_ReadFile($i, false); - } - $Vis =& $this->VisFileLst[$i]; - $Len += -strlen($Vis['bin']) -strlen($Info['bin']) - $Info['l_data_c']; - if (isset($Vis['desc_bin'])) $Len += -strlen($Vis['desc_bin']); - } elseif ($Ref['len_c']===false) { - return false; // information not yet known - } else { - // file to replace - $Len += $Ref['len_c'] + $Ref['diff']; - } - } - - // files to add - $i_lst = array_keys($this->AddInfo); - foreach ($i_lst as $i) { - $Ref =& $this->AddInfo[$i]; - if ($Ref['len_c']===false) { - return false; // information not yet known - } else { - $Len += $Ref['len_c'] + $Ref['diff']; - } - } - - return $Len; - - } - -} \ No newline at end of file From 4c6725b20be5b34c25a8e707f306e47cfaf454f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Sun, 5 May 2013 21:09:40 +0200 Subject: [PATCH 0002/1200] change the default value : COPS should not rely on external resources. --- config_default.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_default.php b/config_default.php index e8dabb7ad..340827c31 100644 --- a/config_default.php +++ b/config_default.php @@ -172,6 +172,6 @@ * 1 : Yes (enable) * 0 : No */ - $config['cops_use_local_resources'] = "0"; + $config['cops_use_local_resources'] = "1"; ?> \ No newline at end of file From 97177538c1bd53aa3df3e2df4f213153c73f06d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Sun, 5 May 2013 21:11:29 +0200 Subject: [PATCH 0003/1200] Update fancyBox from this source https://github.com/seblucas/fancyBox to prevent this bug : https://github.com/fancyapps/fancyBox/issues/569 Should fix fancybox issues with multiple database. re #40 --- fancybox/jquery.fancybox.pack.js | 46 +------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/fancybox/jquery.fancybox.pack.js b/fancybox/jquery.fancybox.pack.js index 9f6a628eb..6ef73d951 100644 --- a/fancybox/jquery.fancybox.pack.js +++ b/fancybox/jquery.fancybox.pack.js @@ -1,45 +1 @@ -/*! fancyBox v2.1.4 fancyapps.com | fancyapps.com/fancybox/#license */ -(function(C,z,f,r){var q=f(C),n=f(z),b=f.fancybox=function(){b.open.apply(this,arguments)},H=navigator.userAgent.match(/msie/),w=null,s=z.createTouch!==r,t=function(a){return a&&a.hasOwnProperty&&a instanceof f},p=function(a){return a&&"string"===f.type(a)},F=function(a){return p(a)&&0
',image:'',iframe:'",error:'

The requested content cannot be loaded.
Please try again later.

',closeBtn:'',next:'',prev:''},openEffect:"fade",openSpeed:250,openEasing:"swing",openOpacity:!0, -openMethod:"zoomIn",closeEffect:"fade",closeSpeed:250,closeEasing:"swing",closeOpacity:!0,closeMethod:"zoomOut",nextEffect:"elastic",nextSpeed:250,nextEasing:"swing",nextMethod:"changeIn",prevEffect:"elastic",prevSpeed:250,prevEasing:"swing",prevMethod:"changeOut",helpers:{overlay:!0,title:!0},onCancel:f.noop,beforeLoad:f.noop,afterLoad:f.noop,beforeShow:f.noop,afterShow:f.noop,beforeChange:f.noop,beforeClose:f.noop,afterClose:f.noop},group:{},opts:{},previous:null,coming:null,current:null,isActive:!1, -isOpen:!1,isOpened:!1,wrap:null,skin:null,outer:null,inner:null,player:{timer:null,isActive:!1},ajaxLoad:null,imgPreload:null,transitions:{},helpers:{},open:function(a,d){if(a&&(f.isPlainObject(d)||(d={}),!1!==b.close(!0)))return f.isArray(a)||(a=t(a)?f(a).get():[a]),f.each(a,function(e,c){var k={},g,h,j,m,l;"object"===f.type(c)&&(c.nodeType&&(c=f(c)),t(c)?(k={href:c.data("fancybox-href")||c.attr("href"),title:c.data("fancybox-title")||c.attr("title"),isDom:!0,element:c},f.metadata&&f.extend(!0,k, -c.metadata())):k=c);g=d.href||k.href||(p(c)?c:null);h=d.title!==r?d.title:k.title||"";m=(j=d.content||k.content)?"html":d.type||k.type;!m&&k.isDom&&(m=c.data("fancybox-type"),m||(m=(m=c.prop("class").match(/fancybox\.(\w+)/))?m[1]:null));p(g)&&(m||(b.isImage(g)?m="image":b.isSWF(g)?m="swf":"#"===g.charAt(0)?m="inline":p(c)&&(m="html",j=c)),"ajax"===m&&(l=g.split(/\s+/,2),g=l.shift(),l=l.shift()));j||("inline"===m?g?j=f(p(g)?g.replace(/.*(?=#[^\s]+$)/,""):g):k.isDom&&(j=c):"html"===m?j=g:!m&&(!g&& -k.isDom)&&(m="inline",j=c));f.extend(k,{href:g,type:m,content:j,title:h,selector:l});a[e]=k}),b.opts=f.extend(!0,{},b.defaults,d),d.keys!==r&&(b.opts.keys=d.keys?f.extend({},b.defaults.keys,d.keys):!1),b.group=a,b._start(b.opts.index)},cancel:function(){var a=b.coming;a&&!1!==b.trigger("onCancel")&&(b.hideLoading(),b.ajaxLoad&&b.ajaxLoad.abort(),b.ajaxLoad=null,b.imgPreload&&(b.imgPreload.onload=b.imgPreload.onerror=null),a.wrap&&a.wrap.stop(!0,!0).trigger("onReset").remove(),b.coming=null,b.current|| -b._afterZoomOut(a))},close:function(a){b.cancel();!1!==b.trigger("beforeClose")&&(b.unbindEvents(),b.isActive&&(!b.isOpen||!0===a?(f(".fancybox-wrap").stop(!0).trigger("onReset").remove(),b._afterZoomOut()):(b.isOpen=b.isOpened=!1,b.isClosing=!0,f(".fancybox-item, .fancybox-nav").remove(),b.wrap.stop(!0,!0).removeClass("fancybox-opened"),b.transitions[b.current.closeMethod]())))},play:function(a){var d=function(){clearTimeout(b.player.timer)},e=function(){d();b.current&&b.player.isActive&&(b.player.timer= -setTimeout(b.next,b.current.playSpeed))},c=function(){d();f("body").unbind(".player");b.player.isActive=!1;b.trigger("onPlayEnd")};if(!0===a||!b.player.isActive&&!1!==a){if(b.current&&(b.current.loop||b.current.index=c.index?"next":"prev"],b.router=e||"jumpto",c.loop&&(0>a&&(a=c.group.length+a%c.group.length),a%=c.group.length),c.group[a]!==r&&(b.cancel(),b._start(a)))},reposition:function(a,d){var e=b.current,c=e?e.wrap:null,k;c&&(k=b._getPosition(d),a&&"scroll"===a.type?(delete k.position,c.stop(!0,!0).animate(k,200)):(c.css(k),e.pos=f.extend({}, -e.dim,k)))},update:function(a){var d=a&&a.type,e=!d||"orientationchange"===d;e&&(clearTimeout(w),w=null);b.isOpen&&!w&&(w=setTimeout(function(){var c=b.current;c&&!b.isClosing&&(b.wrap.removeClass("fancybox-tmp"),(e||"load"===d||"resize"===d&&c.autoResize)&&b._setDimension(),"scroll"===d&&c.canShrink||b.reposition(a),b.trigger("onUpdate"),w=null)},e&&!s?0:300))},toggle:function(a){b.isOpen&&(b.current.fitToView="boolean"===f.type(a)?a:!b.current.fitToView,s&&(b.wrap.removeAttr("style").addClass("fancybox-tmp"), -b.trigger("onUpdate")),b.update())},hideLoading:function(){n.unbind(".loading");f("#fancybox-loading").remove()},showLoading:function(){var a,d;b.hideLoading();a=f('
').click(b.cancel).appendTo("body");n.bind("keydown.loading",function(a){if(27===(a.which||a.keyCode))a.preventDefault(),b.cancel()});b.defaults.fixed||(d=b.getViewport(),a.css({position:"absolute",top:0.5*d.h+d.y,left:0.5*d.w+d.x}))},getViewport:function(){var a=b.current&&b.current.locked|| -!1,d={x:q.scrollLeft(),y:q.scrollTop()};a?(d.w=a[0].clientWidth,d.h=a[0].clientHeight):(d.w=s&&C.innerWidth?C.innerWidth:q.width(),d.h=s&&C.innerHeight?C.innerHeight:q.height());return d},unbindEvents:function(){b.wrap&&t(b.wrap)&&b.wrap.unbind(".fb");n.unbind(".fb");q.unbind(".fb")},bindEvents:function(){var a=b.current,d;a&&(q.bind("orientationchange.fb"+(s?"":" resize.fb")+(a.autoCenter&&!a.locked?" scroll.fb":""),b.update),(d=a.keys)&&n.bind("keydown.fb",function(e){var c=e.which||e.keyCode,k= -e.target||e.srcElement;if(27===c&&b.coming)return!1;!e.ctrlKey&&(!e.altKey&&!e.shiftKey&&!e.metaKey&&(!k||!k.type&&!f(k).is("[contenteditable]")))&&f.each(d,function(d,k){if(1h[0].clientWidth||h[0].clientHeight&&h[0].scrollHeight>h[0].clientHeight),h=f(h).parent();if(0!==c&&!j&&1g||0>k)b.next(0>g?"up":"right");d.preventDefault()}}))},trigger:function(a,d){var e,c=d||b.coming||b.current;if(c){f.isFunction(c[a])&&(e=c[a].apply(c,Array.prototype.slice.call(arguments,1)));if(!1===e)return!1;c.helpers&&f.each(c.helpers,function(d, -e){e&&(b.helpers[d]&&f.isFunction(b.helpers[d][a]))&&(e=f.extend(!0,{},b.helpers[d].defaults,e),b.helpers[d][a](e,c))});f.event.trigger(a+".fb")}},isImage:function(a){return p(a)&&a.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp)((\?|#).*)?$)/i)},isSWF:function(a){return p(a)&&a.match(/\.(swf)((\?|#).*)?$/i)},_start:function(a){var d={},e,c;a=l(a);e=b.group[a]||null;if(!e)return!1;d=f.extend(!0,{},b.opts,e);e=d.margin;c=d.padding;"number"===f.type(e)&&(d.margin=[e,e,e,e]);"number"===f.type(c)&& -(d.padding=[c,c,c,c]);d.modal&&f.extend(!0,d,{closeBtn:!1,closeClick:!1,nextClick:!1,arrows:!1,mouseWheel:!1,keys:null,helpers:{overlay:{closeClick:!1}}});d.autoSize&&(d.autoWidth=d.autoHeight=!0);"auto"===d.width&&(d.autoWidth=!0);"auto"===d.height&&(d.autoHeight=!0);d.group=b.group;d.index=a;b.coming=d;if(!1===b.trigger("beforeLoad"))b.coming=null;else{c=d.type;e=d.href;if(!c)return b.coming=null,b.current&&b.router&&"jumpto"!==b.router?(b.current.index=a,b[b.router](b.direction)):!1;b.isActive= -!0;if("image"===c||"swf"===c)d.autoHeight=d.autoWidth=!1,d.scrolling="visible";"image"===c&&(d.aspectRatio=!0);"iframe"===c&&s&&(d.scrolling="scroll");d.wrap=f(d.tpl.wrap).addClass("fancybox-"+(s?"mobile":"desktop")+" fancybox-type-"+c+" fancybox-tmp "+d.wrapCSS).appendTo(d.parent||"body");f.extend(d,{skin:f(".fancybox-skin",d.wrap),outer:f(".fancybox-outer",d.wrap),inner:f(".fancybox-inner",d.wrap)});f.each(["Top","Right","Bottom","Left"],function(a,b){d.skin.css("padding"+b,x(d.padding[a]))});b.trigger("onReady"); -if("inline"===c||"html"===c){if(!d.content||!d.content.length)return b._error("content")}else if(!e)return b._error("href");"image"===c?b._loadImage():"ajax"===c?b._loadAjax():"iframe"===c?b._loadIframe():b._afterLoad()}},_error:function(a){f.extend(b.coming,{type:"html",autoWidth:!0,autoHeight:!0,minWidth:0,minHeight:0,scrolling:"no",hasError:a,content:b.coming.tpl.error});b._afterLoad()},_loadImage:function(){var a=b.imgPreload=new Image;a.onload=function(){this.onload=this.onerror=null;b.coming.width= -this.width;b.coming.height=this.height;b._afterLoad()};a.onerror=function(){this.onload=this.onerror=null;b._error("image")};a.src=b.coming.href;!0!==a.complete&&b.showLoading()},_loadAjax:function(){var a=b.coming;b.showLoading();b.ajaxLoad=f.ajax(f.extend({},a.ajax,{url:a.href,error:function(a,e){b.coming&&"abort"!==e?b._error("ajax",a):b.hideLoading()},success:function(d,e){"success"===e&&(a.content=d,b._afterLoad())}}))},_loadIframe:function(){var a=b.coming,d=f(a.tpl.iframe.replace(/\{rnd\}/g, -(new Date).getTime())).attr("scrolling",s?"auto":a.iframe.scrolling).attr("src",a.href);f(a.wrap).bind("onReset",function(){try{f(this).find("iframe").hide().attr("src","//about:blank").end().empty()}catch(a){}});a.iframe.preload&&(b.showLoading(),d.one("load",function(){f(this).data("ready",1);s||f(this).bind("load.fb",b.update);f(this).parents(".fancybox-wrap").width("100%").removeClass("fancybox-tmp").show();b._afterLoad()}));a.content=d.appendTo(a.inner);a.iframe.preload||b._afterLoad()},_preloadImages:function(){var a= -b.group,d=b.current,e=a.length,c=d.preload?Math.min(d.preload,e-1):0,f,g;for(g=1;g<=c;g+=1)f=a[(d.index+g)%e],"image"===f.type&&f.href&&((new Image).src=f.href)},_afterLoad:function(){var a=b.coming,d=b.current,e,c,k,g,h;b.hideLoading();if(a&&!1!==b.isActive)if(!1===b.trigger("afterLoad",a,d))a.wrap.stop(!0).trigger("onReset").remove(),b.coming=null;else{d&&(b.trigger("beforeChange",d),d.wrap.stop(!0).removeClass("fancybox-opened").find(".fancybox-item, .fancybox-nav").remove());b.unbindEvents(); -e=a.content;c=a.type;k=a.scrolling;f.extend(b,{wrap:a.wrap,skin:a.skin,outer:a.outer,inner:a.inner,current:a,previous:d});g=a.href;switch(c){case "inline":case "ajax":case "html":a.selector?e=f("
").html(e).find(a.selector):t(e)&&(e.data("fancybox-placeholder")||e.data("fancybox-placeholder",f('
').insertAfter(e).hide()),e=e.show().detach(),a.wrap.bind("onReset",function(){f(this).find(e).length&&e.hide().replaceAll(e.data("fancybox-placeholder")).data("fancybox-placeholder", -!1)}));break;case "image":e=a.tpl.image.replace("{href}",g);break;case "swf":e='',h="",f.each(a.swf,function(a,b){e+='';h+=" "+a+'="'+b+'"'}),e+='"}(!t(e)||!e.parent().is(a.inner))&&a.inner.append(e);b.trigger("beforeShow"); -a.inner.css("overflow","yes"===k?"scroll":"no"===k?"hidden":k);b._setDimension();b.reposition();b.isOpen=!1;b.coming=null;b.bindEvents();if(b.isOpened){if(d.prevMethod)b.transitions[d.prevMethod]()}else f(".fancybox-wrap").not(a.wrap).stop(!0).trigger("onReset").remove();b.transitions[b.isOpened?a.nextMethod:a.openMethod]();b._preloadImages()}},_setDimension:function(){var a=b.getViewport(),d=0,e=!1,c=!1,e=b.wrap,k=b.skin,g=b.inner,h=b.current,c=h.width,j=h.height,m=h.minWidth,u=h.minHeight,n=h.maxWidth, -v=h.maxHeight,s=h.scrolling,q=h.scrollOutside?h.scrollbarWidth:0,y=h.margin,p=l(y[1]+y[3]),r=l(y[0]+y[2]),z,A,t,D,B,G,C,E,w;e.add(k).add(g).width("auto").height("auto").removeClass("fancybox-tmp");y=l(k.outerWidth(!0)-k.width());z=l(k.outerHeight(!0)-k.height());A=p+y;t=r+z;D=F(c)?(a.w-A)*l(c)/100:c;B=F(j)?(a.h-t)*l(j)/100:j;if("iframe"===h.type){if(w=h.content,h.autoHeight&&1===w.data("ready"))try{w[0].contentWindow.document.location&&(g.width(D).height(9999),G=w.contents().find("body"),q&&G.css("overflow-x", -"hidden"),B=G.height())}catch(H){}}else if(h.autoWidth||h.autoHeight)g.addClass("fancybox-tmp"),h.autoWidth||g.width(D),h.autoHeight||g.height(B),h.autoWidth&&(D=g.width()),h.autoHeight&&(B=g.height()),g.removeClass("fancybox-tmp");c=l(D);j=l(B);E=D/B;m=l(F(m)?l(m,"w")-A:m);n=l(F(n)?l(n,"w")-A:n);u=l(F(u)?l(u,"h")-t:u);v=l(F(v)?l(v,"h")-t:v);G=n;C=v;h.fitToView&&(n=Math.min(a.w-A,n),v=Math.min(a.h-t,v));A=a.w-p;r=a.h-r;h.aspectRatio?(c>n&&(c=n,j=l(c/E)),j>v&&(j=v,c=l(j*E)),cA||p>r)&&(c>m&&j>u)&&!(19n&&(c=n,j=l(c/E)),g.width(c).height(j),e.width(c+y),a=e.width(),p=e.height();else c=Math.max(m,Math.min(c,c-(a-A))),j=Math.max(u,Math.min(j,j-(p-r)));q&&("auto"===s&&jA||p>r)&&c>m&&j>u;c=h.aspectRatio?cu&&j
').appendTo("body"); -this.fixed=!1;a.fixed&&b.defaults.fixed&&(this.overlay.addClass("fancybox-overlay-fixed"),this.fixed=!0)},open:function(a){var d=this;a=f.extend({},this.defaults,a);this.overlay?this.overlay.unbind(".overlay").width("auto").height("auto"):this.create(a);this.fixed||(q.bind("resize.overlay",f.proxy(this.update,this)),this.update());a.closeClick&&this.overlay.bind("click.overlay",function(a){f(a.target).hasClass("fancybox-overlay")&&(b.isActive?b.close():d.close())});this.overlay.css(a.css).show()}, -close:function(){f(".fancybox-overlay").remove();q.unbind("resize.overlay");this.overlay=null;!1!==this.margin&&(f("body").css("margin-right",this.margin),this.margin=!1);this.el&&this.el.removeClass("fancybox-lock")},update:function(){var a="100%",b;this.overlay.width(a).height("100%");H?(b=Math.max(z.documentElement.offsetWidth,z.body.offsetWidth),n.width()>b&&(a=n.width())):n.width()>q.width()&&(a=n.width());this.overlay.width(a).height(n.height())},onReady:function(a,b){f(".fancybox-overlay").stop(!0, -!0);this.overlay||(this.margin=n.height()>q.height()||"scroll"===f("body").css("overflow-y")?f("body").css("margin-right"):!1,this.el=z.all&&!z.querySelector?f("html"):f("body"),this.create(a));a.locked&&this.fixed&&(b.locked=this.overlay.append(b.wrap),b.fixed=!1);!0===a.showEarly&&this.beforeShow.apply(this,arguments)},beforeShow:function(a,b){b.locked&&(this.el.addClass("fancybox-lock"),!1!==this.margin&&f("body").css("margin-right",l(this.margin)+b.scrollbarWidth));this.open(a)},onUpdate:function(){this.fixed|| -this.update()},afterClose:function(a){this.overlay&&!b.isActive&&this.overlay.fadeOut(a.speedOut,f.proxy(this.close,this))}};b.helpers.title={defaults:{type:"float",position:"bottom"},beforeShow:function(a){var d=b.current,e=d.title,c=a.type;f.isFunction(e)&&(e=e.call(d.element,d));if(p(e)&&""!==f.trim(e)){d=f('
'+e+"
");switch(c){case "inside":c=b.skin;break;case "outside":c=b.wrap;break;case "over":c=b.inner;break;default:c=b.skin,d.appendTo("body"), -H&&d.width(d.width()),d.wrapInner(''),b.current.margin[2]+=Math.abs(l(d.css("margin-bottom")))}d["top"===a.position?"prependTo":"appendTo"](c)}}};f.fn.fancybox=function(a){var d,e=f(this),c=this.selector||"",k=function(g){var h=f(this).blur(),j=d,k,l;!g.ctrlKey&&(!g.altKey&&!g.shiftKey&&!g.metaKey)&&!h.is(".fancybox-wrap")&&(k=a.groupAttr||"data-fancybox-group",l=h.attr(k),l||(k="rel",l=h.get(0)[k]),l&&(""!==l&&"nofollow"!==l)&&(h=c.length?f(c):e,h=h.filter("["+k+'="'+l+ -'"]'),j=h.index(this)),a.index=j,!1!==b.open(h,a)&&g.preventDefault())};a=a||{};d=a.index||0;!c||!1===a.live?e.unbind("click.fb-start").bind("click.fb-start",k):n.undelegate(c,"click.fb-start").delegate(c+":not('.fancybox-item, .fancybox-nav')","click.fb-start",k);this.filter("[data-fancybox-start=1]").trigger("click");return this};n.ready(function(){f.scrollbarWidth===r&&(f.scrollbarWidth=function(){var a=f('
').appendTo("body"),b=a.children(), -b=b.innerWidth()-b.height(99).innerWidth();a.remove();return b});if(f.support.fixedPosition===r){var a=f.support,d=f('
').appendTo("body"),e=20===d[0].offsetTop||15===d[0].offsetTop;d.remove();a.fixedPosition=e}f.extend(b.defaults,{scrollbarWidth:f.scrollbarWidth(),fixed:f.support.fixedPosition,parent:f("body")})})})(window,document,jQuery); \ No newline at end of file +(function(l,o,i,e){var d=i(l),a=i(o),p=i.fancybox=function(){p.open.apply(this,arguments)},k=navigator.userAgent.match(/msie/i),c=null,f=o.createTouch!==e,j=function(q){return q&&q.hasOwnProperty&&q instanceof i},b=function(q){return q&&i.type(q)==="string"},m=function(q){return b(q)&&q.indexOf("%")>0},h=function(q){return(q&&!(q.style.overflow&&q.style.overflow==="hidden")&&((q.clientWidth&&q.scrollWidth>q.clientWidth)||(q.clientHeight&&q.scrollHeight>q.clientHeight)))},n=function(s,r){var q=parseInt(s,10)||0;if(r&&m(s)){q=p.getViewport()[r]/100*q}return Math.ceil(q)},g=function(q,r){return n(q,r)+"px"};i.extend(p,{version:"2.1.4",defaults:{padding:15,margin:20,width:800,height:600,minWidth:100,minHeight:100,maxWidth:9999,maxHeight:9999,autoSize:true,autoHeight:false,autoWidth:false,autoResize:true,autoCenter:!f,fitToView:true,aspectRatio:false,topRatio:0.5,leftRatio:0.5,scrolling:"auto",wrapCSS:"",arrows:true,closeBtn:true,closeClick:false,nextClick:false,mouseWheel:true,autoPlay:false,playSpeed:3000,preload:3,modal:false,loop:true,ajax:{dataType:"html",headers:{"X-fancyBox":true}},iframe:{scrolling:"auto",preload:true},swf:{wmode:"transparent",allowfullscreen:"true",allowscriptaccess:"always"},keys:{next:{13:"left",34:"up",39:"left",40:"up"},prev:{8:"right",33:"down",37:"right",38:"down"},close:[27],play:[32],toggle:[70]},direction:{next:"left",prev:"right"},scrollOutside:true,index:0,type:null,href:null,content:null,title:null,tpl:{wrap:'
',image:'',iframe:'",error:'

The requested content cannot be loaded.
Please try again later.

',closeBtn:'',next:'',prev:''},openEffect:"fade",openSpeed:250,openEasing:"swing",openOpacity:true,openMethod:"zoomIn",closeEffect:"fade",closeSpeed:250,closeEasing:"swing",closeOpacity:true,closeMethod:"zoomOut",nextEffect:"elastic",nextSpeed:250,nextEasing:"swing",nextMethod:"changeIn",prevEffect:"elastic",prevSpeed:250,prevEasing:"swing",prevMethod:"changeOut",helpers:{overlay:true,title:true},onCancel:i.noop,beforeLoad:i.noop,afterLoad:i.noop,beforeShow:i.noop,afterShow:i.noop,beforeChange:i.noop,beforeClose:i.noop,afterClose:i.noop},group:{},opts:{},previous:null,coming:null,current:null,isActive:false,isOpen:false,isOpened:false,wrap:null,skin:null,outer:null,inner:null,player:{timer:null,isActive:false},ajaxLoad:null,imgPreload:null,transitions:{},helpers:{},open:function(r,q){if(!r){return}if(!i.isPlainObject(q)){q={}}if(false===p.close(true)){return}if(!i.isArray(r)){r=j(r)?i(r).get():[r]}i.each(r,function(w,x){var v={},s,A,y,z,u,B,t;if(i.type(x)==="object"){if(x.nodeType){x=i(x)}if(j(x)){v={href:x.data("fancybox-href")||x.attr("href"),title:x.data("fancybox-title")||x.attr("title"),isDom:true,element:x};if(i.metadata){i.extend(true,v,x.metadata())}}else{v=x}}s=q.href||v.href||(b(x)?x:null);A=q.title!==e?q.title:v.title||"";y=q.content||v.content;z=y?"html":(q.type||v.type);if(!z&&v.isDom){z=x.data("fancybox-type");if(!z){u=x.prop("class").match(/fancybox\.(\w+)/);z=u?u[1]:null}}if(b(s)){if(!z){if(p.isImage(s)){z="image"}else{if(p.isSWF(s)){z="swf"}else{if(s.charAt(0)==="#"){z="inline"}else{if(b(x)){z="html";y=x}}}}}if(z==="ajax"){B=s.split(/\s+/,2);s=B.shift();t=B.shift()}}if(!y){if(z==="inline"){if(s){y=i(b(s)?s.replace(/.*(?=#[^\s]+$)/,""):s)}else{if(v.isDom){y=x}}}else{if(z==="html"){y=s}else{if(!z&&!s&&v.isDom){z="inline";y=x}}}}i.extend(v,{href:s,type:z,content:y,title:A,selector:t});r[w]=v});p.opts=i.extend(true,{},p.defaults,q);if(q.keys!==e){p.opts.keys=q.keys?i.extend({},p.defaults.keys,q.keys):false}p.group=r;return p._start(p.opts.index)},cancel:function(){var q=p.coming;if(!q||false===p.trigger("onCancel")){return}p.hideLoading();if(p.ajaxLoad){p.ajaxLoad.abort()}p.ajaxLoad=null;if(p.imgPreload){p.imgPreload.onload=p.imgPreload.onerror=null}if(q.wrap){q.wrap.stop(true,true).trigger("onReset").remove()}p.coming=null;if(!p.current){p._afterZoomOut(q)}},close:function(q){p.cancel();if(false===p.trigger("beforeClose")){return}p.unbindEvents();if(!p.isActive){return}if(!p.isOpen||q===true){i(".fancybox-wrap").stop(true).trigger("onReset").remove();p._afterZoomOut()}else{p.isOpen=p.isOpened=false;p.isClosing=true;i(".fancybox-item, .fancybox-nav").remove();p.wrap.stop(true,true).removeClass("fancybox-opened");p.transitions[p.current.closeMethod]()}},play:function(s){var q=function(){clearTimeout(p.player.timer)},u=function(){q();if(p.current&&p.player.isActive){p.player.timer=setTimeout(p.next,p.current.playSpeed)}},r=function(){q();a.unbind(".player");p.player.isActive=false;p.trigger("onPlayEnd")},t=function(){if(p.current&&(p.current.loop||p.current.index=s.index?"next":"prev")];p.router=q||"jumpto";if(s.loop){if(r<0){r=s.group.length+(r%s.group.length)}r=r%s.group.length}if(s.group[r]!==e){p.cancel();p._start(r)}},reposition:function(t,q){var s=p.current,r=s?s.wrap:null,u;if(r){u=p._getPosition(q);if(t&&t.type==="scroll"){delete u.position;r.stop(true,true).animate(u,200)}else{r.css(u);s.pos=i.extend({},s.dim,u)}}},update:function(s){var q=(s&&s.type),r=!q||q==="orientationchange";if(r){clearTimeout(c);c=null}if(!p.isOpen||c){return}c=setTimeout(function(){var t=p.current;if(!t||p.isClosing){return}p.wrap.removeClass("fancybox-tmp");if(r||q==="load"||(q==="resize"&&t.autoResize)){p._setDimension()}if(!(q==="scroll"&&t.canShrink)){p.reposition(s)}p.trigger("onUpdate");c=null},(r&&!f?0:300))},toggle:function(q){if(p.isOpen){p.current.fitToView=i.type(q)==="boolean"?q:!p.current.fitToView;if(f){p.wrap.removeAttr("style").addClass("fancybox-tmp");p.trigger("onUpdate")}p.update()}},hideLoading:function(){a.unbind(".loading");i("#fancybox-loading").remove()},showLoading:function(){var r,q;p.hideLoading();r=i('
').click(p.cancel).appendTo("body");a.bind("keydown.loading",function(s){if((s.which||s.keyCode)===27){s.preventDefault();p.cancel()}});if(!p.defaults.fixed){q=p.getViewport();r.css({position:"absolute",top:(q.h*0.5)+q.y,left:(q.w*0.5)+q.x})}},getViewport:function(){var q=(p.current&&p.current.locked)||false,r={x:d.scrollLeft(),y:d.scrollTop()};if(q){r.w=q[0].clientWidth;r.h=q[0].clientHeight}else{r.w=f&&l.innerWidth?l.innerWidth:d.width();r.h=f&&l.innerHeight?l.innerHeight:d.height()}return r},unbindEvents:function(){if(p.wrap&&j(p.wrap)){p.wrap.unbind(".fb")}a.unbind(".fb");d.unbind(".fb")},bindEvents:function(){var r=p.current,q;if(!r){return}d.bind("orientationchange.fb"+(f?"":" resize.fb")+(r.autoCenter&&!r.locked?" scroll.fb":""),p.update);q=r.keys;if(q){a.bind("keydown.fb",function(u){var s=u.which||u.keyCode,t=u.target||u.srcElement;if(s===27&&p.coming){return false}if(!u.ctrlKey&&!u.altKey&&!u.shiftKey&&!u.metaKey&&!(t&&(t.type||i(t).is("[contenteditable]")))){i.each(q,function(v,w){if(r.group.length>1&&w[s]!==e){p[v](w[s]);u.preventDefault();return false}if(i.inArray(s,w)>-1){p[v]();u.preventDefault();return false}})}})}if(i.fn.mousewheel&&r.mouseWheel){p.wrap.bind("mousewheel.fb",function(x,y,t,s){var w=x.target||null,u=i(w),v=false;while(u.length){if(v||u.is(".fancybox-skin")||u.is(".fancybox-wrap")){break}v=h(u[0]);u=i(u).parent()}if(y!==0&&!v){if(p.group.length>1&&!r.canShrink){if(s>0||t>0){p.prev(s>0?"down":"left")}else{if(s<0||t<0){p.next(s<0?"up":"right")}}x.preventDefault()}}})}},trigger:function(r,t){var q,s=t||p.coming||p.current;if(!s){return}if(i.isFunction(s[r])){q=s[r].apply(s,Array.prototype.slice.call(arguments,1))}if(q===false){return false}if(s.helpers){i.each(s.helpers,function(v,u){if(u&&p.helpers[v]&&i.isFunction(p.helpers[v][r])){u=i.extend(true,{},p.helpers[v].defaults,u);p.helpers[v][r](u,s)}})}a.trigger(r)},isImage:function(q){return b(q)&&q.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp)((\?|#).*)?$)/i)},isSWF:function(q){return b(q)&&q.match(/\.(swf)((\?|#).*)?$/i)},_start:function(r){var s={},w,q,t,u,v;r=n(r);w=p.group[r]||null;if(!w){return false}s=i.extend(true,{},p.opts,w);u=s.margin;v=s.padding;if(i.type(u)==="number"){s.margin=[u,u,u,u]}if(i.type(v)==="number"){s.padding=[v,v,v,v]}if(s.modal){i.extend(true,s,{closeBtn:false,closeClick:false,nextClick:false,arrows:false,mouseWheel:false,keys:null,helpers:{overlay:{closeClick:false}}})}if(s.autoSize){s.autoWidth=s.autoHeight=true}if(s.width==="auto"){s.autoWidth=true}if(s.height==="auto"){s.autoHeight=true}s.group=p.group;s.index=r;p.coming=s;if(false===p.trigger("beforeLoad")){p.coming=null;return}t=s.type;q=s.href;if(!t){p.coming=null;if(p.current&&p.router&&p.router!=="jumpto"){p.current.index=r;return p[p.router](p.direction)}return false}p.isActive=true;if(t==="image"||t==="swf"){s.autoHeight=s.autoWidth=false;s.scrolling="visible"}if(t==="image"){s.aspectRatio=true}if(t==="iframe"&&f){s.scrolling="scroll"}s.wrap=i(s.tpl.wrap).addClass("fancybox-"+(f?"mobile":"desktop")+" fancybox-type-"+t+" fancybox-tmp "+s.wrapCSS).appendTo(s.parent||"body");i.extend(s,{skin:i(".fancybox-skin",s.wrap),outer:i(".fancybox-outer",s.wrap),inner:i(".fancybox-inner",s.wrap)});i.each(["Top","Right","Bottom","Left"],function(y,x){s.skin.css("padding"+x,g(s.padding[y]))});p.trigger("onReady");if(t==="inline"||t==="html"){if(!s.content||!s.content.length){return p._error("content")}}else{if(!q){return p._error("href")}}if(t==="image"){p._loadImage()}else{if(t==="ajax"){p._loadAjax()}else{if(t==="iframe"){p._loadIframe()}else{p._afterLoad()}}}},_error:function(q){i.extend(p.coming,{type:"html",autoWidth:true,autoHeight:true,minWidth:0,minHeight:0,scrolling:"no",hasError:q,content:p.coming.tpl.error});p._afterLoad()},_loadImage:function(){var q=p.imgPreload=new Image();q.onload=function(){this.onload=this.onerror=null;p.coming.width=this.width;p.coming.height=this.height;p._afterLoad()};q.onerror=function(){this.onload=this.onerror=null;p._error("image")};q.src=p.coming.href;if(q.complete!==true){p.showLoading()}},_loadAjax:function(){var q=p.coming;p.showLoading();p.ajaxLoad=i.ajax(i.extend({},q.ajax,{url:q.href,error:function(r,s){if(p.coming&&s!=="abort"){p._error("ajax",r)}else{p.hideLoading()}},success:function(r,s){if(s==="success"){q.content=r;p._afterLoad()}}}))},_loadIframe:function(){var q=p.coming,r=i(q.tpl.iframe.replace(/\{rnd\}/g,new Date().getTime())).attr("scrolling",f?"auto":q.iframe.scrolling).attr("src",q.href);i(q.wrap).bind("onReset",function(){try{i(this).find("iframe").hide().attr("src","//about:blank").end().empty()}catch(s){}});if(q.iframe.preload){p.showLoading();r.one("load",function(){i(this).data("ready",1);if(!f){i(this).bind("load.fb",p.update)}i(this).parents(".fancybox-wrap").width("100%").removeClass("fancybox-tmp").show();p._afterLoad()})}q.content=r.appendTo(q.inner);if(!q.iframe.preload){p._afterLoad()}},_preloadImages:function(){var v=p.group,u=p.current,q=v.length,s=u.preload?Math.min(u.preload,q-1):0,t,r;for(r=1;r<=s;r+=1){t=v[(u.index+r)%q];if(t.type==="image"&&t.href){new Image().src=t.href}}},_afterLoad:function(){var r=p.coming,t=p.current,y="fancybox-placeholder",v,w,x,s,q,u;p.hideLoading();if(!r||p.isActive===false){return}if(false===p.trigger("afterLoad",r,t)){r.wrap.stop(true).trigger("onReset").remove();p.coming=null;return}if(t){p.trigger("beforeChange",t);t.wrap.stop(true).removeClass("fancybox-opened").find(".fancybox-item, .fancybox-nav").remove()}p.unbindEvents();v=r;w=r.content;x=r.type;s=r.scrolling;i.extend(p,{wrap:v.wrap,skin:v.skin,outer:v.outer,inner:v.inner,current:v,previous:t});q=v.href;switch(x){case"inline":case"ajax":case"html":if(v.selector){w=i("
").html(w).find(v.selector)}else{if(j(w)){if(!w.data(y)){w.data(y,i('
').insertAfter(w).hide())}w=w.show().detach();v.wrap.bind("onReset",function(){if(i(this).find(w).length){w.hide().replaceAll(w.data(y)).data(y,false)}})}}break;case"image":w=v.tpl.image.replace("{href}",q).replace("&","&");break;case"swf":w='';u="";i.each(v.swf,function(z,A){w+='';u+=" "+z+'="'+A+'"'});w+='";break}if(!(j(w)&&w.parent().is(v.inner))){v.inner.append(w)}p.trigger("beforeShow");v.inner.css("overflow",s==="yes"?"scroll":(s==="no"?"hidden":s));p._setDimension();p.reposition();p.isOpen=false;p.coming=null;p.bindEvents();if(!p.isOpened){i(".fancybox-wrap").not(v.wrap).stop(true).trigger("onReset").remove()}else{if(t.prevMethod){p.transitions[t.prevMethod]()}}p.transitions[p.isOpened?v.nextMethod:v.openMethod]();p._preloadImages()},_setDimension:function(){var T=p.getViewport(),P=0,V=false,X=false,B=p.wrap,N=p.skin,Y=p.inner,K=p.current,L=K.width,I=K.height,E=K.minWidth,x=K.minHeight,R=K.maxWidth,J=K.maxHeight,D=K.scrolling,v=K.scrollOutside?K.scrollbarWidth:0,H=K.margin,w=n(H[1]+H[3]),u=n(H[0]+H[2]),s,r,O,Q,G,F,M,z,y,U,t,W,q,A,C;B.add(N).add(Y).width("auto").height("auto").removeClass("fancybox-tmp");s=n(N.outerWidth(true)-N.width());r=n(N.outerHeight(true)-N.height());O=w+s;Q=u+r;G=m(L)?(T.w-O)*n(L)/100:L;F=m(I)?(T.h-Q)*n(I)/100:I;if(K.type==="iframe"){A=K.content;if(K.autoHeight&&A.data("ready")===1){try{if(A[0].contentWindow.document.location){Y.width(G).height(9999);C=A.contents().find("body");if(v){C.css("overflow-x","hidden")}F=C.height()}}catch(S){}}}else{if(K.autoWidth||K.autoHeight){Y.addClass("fancybox-tmp");if(!K.autoWidth){Y.width(G)}if(!K.autoHeight){Y.height(F)}if(K.autoWidth){G=Y.width()}if(K.autoHeight){F=Y.height()}Y.removeClass("fancybox-tmp")}}L=n(G);I=n(F);y=G/F;E=n(m(E)?n(E,"w")-O:E);R=n(m(R)?n(R,"w")-O:R);x=n(m(x)?n(x,"h")-Q:x);J=n(m(J)?n(J,"h")-Q:J);M=R;z=J;if(K.fitToView){R=Math.min(T.w-O,R);J=Math.min(T.h-Q,J)}W=T.w-w;q=T.h-u;if(K.aspectRatio){if(L>R){L=R;I=n(L/y)}if(I>J){I=J;L=n(I*y)}if(LW||t>q)&&L>E&&I>x){if(P++>19){break}I=Math.max(x,Math.min(J,I-10));L=n(I*y);if(LR){L=R;I=n(L/y)}Y.width(L).height(I);B.width(L+s);U=B.width();t=B.height()}}else{L=Math.max(E,Math.min(L,L-(U-W)));I=Math.max(x,Math.min(I,I-(t-q)))}}if(v&&D==="auto"&&IW||t>q)&&L>E&&I>x;X=K.aspectRatio?(Lx&&I1)){p.inner.css("cursor","pointer").bind("click.fb",function(r){if(!i(r.target).is("a")&&!i(r.target).parent().is("a")){r.preventDefault();p[q.closeClick?"close":"next"]()}})}if(q.closeBtn){i(q.tpl.closeBtn).appendTo(p.skin).bind("click.fb",function(r){r.preventDefault();p.close()})}if(q.arrows&&p.group.length>1){if(q.loop||q.index>0){i(q.tpl.prev).appendTo(p.outer).bind("click.fb",p.prev)}if(q.loop||q.index
').appendTo("body");this.fixed=false;if(q.fixed&&p.defaults.fixed){this.overlay.addClass("fancybox-overlay-fixed");this.fixed=true}},open:function(r){var q=this;r=i.extend({},this.defaults,r);if(this.overlay){this.overlay.unbind(".overlay").width("auto").height("auto")}else{this.create(r)}if(!this.fixed){d.bind("resize.overlay",i.proxy(this.update,this));this.update()}if(r.closeClick){this.overlay.bind("click.overlay",function(s){if(i(s.target).hasClass("fancybox-overlay")){if(p.isActive){p.close()}else{q.close()}}})}this.overlay.css(r.css).show()},close:function(){i(".fancybox-overlay").remove();d.unbind("resize.overlay");this.overlay=null;if(this.margin!==false){i("body").css("margin-right",this.margin);this.margin=false}if(this.el){this.el.removeClass("fancybox-lock")}},update:function(){var r="100%",q;this.overlay.width(r).height("100%");if(k){q=Math.max(o.documentElement.offsetWidth,o.body.offsetWidth);if(a.width()>q){r=a.width()}}else{if(a.width()>d.width()){r=a.width()}}this.overlay.width(r).height(a.height())},onReady:function(q,r){i(".fancybox-overlay").stop(true,true);if(!this.overlay){this.margin=a.height()>d.height()||i("body").css("overflow-y")==="scroll"?i("body").css("margin-right"):false;this.el=o.all&&!o.querySelector?i("html"):i("body");this.create(q)}if(q.locked&&this.fixed){r.locked=this.overlay.append(r.wrap);r.fixed=false}if(q.showEarly===true){this.beforeShow.apply(this,arguments)}},beforeShow:function(q,r){if(r.locked){this.el.addClass("fancybox-lock");if(this.margin!==false){i("body").css("margin-right",n(this.margin)+r.scrollbarWidth)}}this.open(q)},onUpdate:function(){if(!this.fixed){this.update()}},afterClose:function(q){if(this.overlay&&!p.isActive){this.overlay.fadeOut(q.speedOut,i.proxy(this.close,this))}}};p.helpers.title={defaults:{type:"float",position:"bottom"},beforeShow:function(r){var t=p.current,v=t.title,q=r.type,u,s;if(i.isFunction(v)){v=v.call(t.element,t)}if(!b(v)||i.trim(v)===""){return}u=i('
'+v+"
");switch(q){case"inside":s=p.skin;break;case"outside":s=p.wrap;break;case"over":s=p.inner;break;default:s=p.skin;u.appendTo("body");if(k){u.width(u.width())}u.wrapInner('');p.current.margin[2]+=Math.abs(n(u.css("margin-bottom")));break}u[(r.position==="top"?"prependTo":"appendTo")](s)}};i.fn.fancybox=function(s){var r,t=i(this),q=this.selector||"",u=function(y){var x=i(this).blur(),v=r,w,z;if(!(y.ctrlKey||y.altKey||y.shiftKey||y.metaKey)&&!x.is(".fancybox-wrap")){w=s.groupAttr||"data-fancybox-group";z=x.attr(w);if(!z){w="rel";z=x.get(0)[w]}if(z&&z!==""&&z!=="nofollow"){x=q.length?i(q):t;x=x.filter("["+w+'="'+z+'"]');v=x.index(this)}s.index=v;if(p.open(x,s)!==false){y.preventDefault()}}};s=s||{};r=s.index||0;if(!q||s.live===false){t.unbind("click.fb-start").bind("click.fb-start",u)}else{a.undelegate(q,"click.fb-start").delegate(q+":not('.fancybox-item, .fancybox-nav')","click.fb-start",u)}this.filter("[data-fancybox-start=1]").trigger("click");return this};a.ready(function(){if(i.scrollbarWidth===e){i.scrollbarWidth=function(){var r=i('
').appendTo("body"),s=r.children(),q=s.innerWidth()-s.height(99).innerWidth();r.remove();return q}}if(i.support.fixedPosition===e){i.support.fixedPosition=(function(){var r=i('
').appendTo("body"),q=(r[0].offsetTop===20||r[0].offsetTop===15);r.remove();return q}())}i.extend(p.defaults,{scrollbarWidth:i.scrollbarWidth(),fixed:i.support.fixedPosition,parent:i("body")})})}(window,document,jQuery)); \ No newline at end of file From f766e3abf1879524eb2b21056afeeb4a30fd8d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Mon, 6 May 2013 17:45:27 +0200 Subject: [PATCH 0004/1200] Remove the unneeded option to use CDN. it's too confusing and is not really needed. --- CHANGELOG | 2 +- config_default.php | 7 ------- index.php | 13 ++----------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6d24387c1..d885a273f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ 0.3.5 - 201305?? * Add multiple database support. Check the documentation of $config['calibre_directory'] in config-default.php to see how ot enable it. - * Include jquery library in COPS's repository to be sure that COPS will work on LAN (without Internet access). You can enable CDN resources with $config['cops_use_local_resources']. + * Include jquery library in COPS's repository to be sure that COPS will work on LAN (without Internet access). * Prepare the switch to HTML5. Thanks to Thomas Severinsen for most of the code. * Update the locale strings to be more strict with plurals. Thanks to Tobias Ausländer for the code. * If Fancybox is not enabled ($config['cops_use_fancyapps'] = "0") then it's not used at all (even in the about box). diff --git a/config_default.php b/config_default.php index 340827c31..ac5e721d3 100644 --- a/config_default.php +++ b/config_default.php @@ -166,12 +166,5 @@ * 0 : No */ $config['cops_provide_kepub'] = "0"; - - /* - * Use local JS resources instead if external resources hosted on CDN - * 1 : Yes (enable) - * 0 : No - */ - $config['cops_use_local_resources'] = "1"; ?> \ No newline at end of file diff --git a/index.php b/index.php index 7b9c8f950..e535a0b56 100644 --- a/index.php +++ b/index.php @@ -54,21 +54,12 @@ <?php echo htmlspecialchars ($currentPage->title) ?> - - - - - - - - - + - - + From 1414cedd36e7477a75b5308c320639c3efe642db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Mon, 6 May 2013 17:49:22 +0200 Subject: [PATCH 0005/1200] Move Fancybox to the resources directory. --HG-- rename : fancybox/blank.gif => resources/fancybox/blank.gif rename : fancybox/fancybox_loading.gif => resources/fancybox/fancybox_loading.gif rename : fancybox/fancybox_overlay.png => resources/fancybox/fancybox_overlay.png rename : fancybox/fancybox_sprite.png => resources/fancybox/fancybox_sprite.png rename : fancybox/helpers/fancybox_buttons.png => resources/fancybox/helpers/fancybox_buttons.png rename : fancybox/helpers/jquery.fancybox-buttons.css => resources/fancybox/helpers/jquery.fancybox-buttons.css rename : fancybox/helpers/jquery.fancybox-buttons.js => resources/fancybox/helpers/jquery.fancybox-buttons.js rename : fancybox/helpers/jquery.fancybox-media.js => resources/fancybox/helpers/jquery.fancybox-media.js rename : fancybox/helpers/jquery.fancybox-thumbs.css => resources/fancybox/helpers/jquery.fancybox-thumbs.css rename : fancybox/helpers/jquery.fancybox-thumbs.js => resources/fancybox/helpers/jquery.fancybox-thumbs.js rename : fancybox/jquery.fancybox.css => resources/fancybox/jquery.fancybox.css rename : fancybox/jquery.fancybox.pack.js => resources/fancybox/jquery.fancybox.pack.js --- index.php | 4 ++-- {fancybox => resources/fancybox}/blank.gif | Bin .../fancybox}/fancybox_loading.gif | Bin .../fancybox}/fancybox_overlay.png | Bin .../fancybox}/fancybox_sprite.png | Bin .../fancybox}/helpers/fancybox_buttons.png | Bin .../fancybox}/helpers/jquery.fancybox-buttons.css | 0 .../fancybox}/helpers/jquery.fancybox-buttons.js | 0 .../fancybox}/helpers/jquery.fancybox-media.js | 0 .../fancybox}/helpers/jquery.fancybox-thumbs.css | 0 .../fancybox}/helpers/jquery.fancybox-thumbs.js | 0 .../fancybox}/jquery.fancybox.css | 0 .../fancybox}/jquery.fancybox.pack.js | 0 13 files changed, 2 insertions(+), 2 deletions(-) rename {fancybox => resources/fancybox}/blank.gif (100%) rename {fancybox => resources/fancybox}/fancybox_loading.gif (100%) rename {fancybox => resources/fancybox}/fancybox_overlay.png (100%) rename {fancybox => resources/fancybox}/fancybox_sprite.png (100%) rename {fancybox => resources/fancybox}/helpers/fancybox_buttons.png (100%) rename {fancybox => resources/fancybox}/helpers/jquery.fancybox-buttons.css (100%) rename {fancybox => resources/fancybox}/helpers/jquery.fancybox-buttons.js (100%) rename {fancybox => resources/fancybox}/helpers/jquery.fancybox-media.js (100%) rename {fancybox => resources/fancybox}/helpers/jquery.fancybox-thumbs.css (100%) rename {fancybox => resources/fancybox}/helpers/jquery.fancybox-thumbs.js (100%) rename {fancybox => resources/fancybox}/jquery.fancybox.css (100%) rename {fancybox => resources/fancybox}/jquery.fancybox.pack.js (100%) diff --git a/index.php b/index.php index e535a0b56..b42b07e71 100644 --- a/index.php +++ b/index.php @@ -57,8 +57,8 @@ - - + + " media="screen" /> diff --git a/fancybox/blank.gif b/resources/fancybox/blank.gif similarity index 100% rename from fancybox/blank.gif rename to resources/fancybox/blank.gif diff --git a/fancybox/fancybox_loading.gif b/resources/fancybox/fancybox_loading.gif similarity index 100% rename from fancybox/fancybox_loading.gif rename to resources/fancybox/fancybox_loading.gif diff --git a/fancybox/fancybox_overlay.png b/resources/fancybox/fancybox_overlay.png similarity index 100% rename from fancybox/fancybox_overlay.png rename to resources/fancybox/fancybox_overlay.png diff --git a/fancybox/fancybox_sprite.png b/resources/fancybox/fancybox_sprite.png similarity index 100% rename from fancybox/fancybox_sprite.png rename to resources/fancybox/fancybox_sprite.png diff --git a/fancybox/helpers/fancybox_buttons.png b/resources/fancybox/helpers/fancybox_buttons.png similarity index 100% rename from fancybox/helpers/fancybox_buttons.png rename to resources/fancybox/helpers/fancybox_buttons.png diff --git a/fancybox/helpers/jquery.fancybox-buttons.css b/resources/fancybox/helpers/jquery.fancybox-buttons.css similarity index 100% rename from fancybox/helpers/jquery.fancybox-buttons.css rename to resources/fancybox/helpers/jquery.fancybox-buttons.css diff --git a/fancybox/helpers/jquery.fancybox-buttons.js b/resources/fancybox/helpers/jquery.fancybox-buttons.js similarity index 100% rename from fancybox/helpers/jquery.fancybox-buttons.js rename to resources/fancybox/helpers/jquery.fancybox-buttons.js diff --git a/fancybox/helpers/jquery.fancybox-media.js b/resources/fancybox/helpers/jquery.fancybox-media.js similarity index 100% rename from fancybox/helpers/jquery.fancybox-media.js rename to resources/fancybox/helpers/jquery.fancybox-media.js diff --git a/fancybox/helpers/jquery.fancybox-thumbs.css b/resources/fancybox/helpers/jquery.fancybox-thumbs.css similarity index 100% rename from fancybox/helpers/jquery.fancybox-thumbs.css rename to resources/fancybox/helpers/jquery.fancybox-thumbs.css diff --git a/fancybox/helpers/jquery.fancybox-thumbs.js b/resources/fancybox/helpers/jquery.fancybox-thumbs.js similarity index 100% rename from fancybox/helpers/jquery.fancybox-thumbs.js rename to resources/fancybox/helpers/jquery.fancybox-thumbs.js diff --git a/fancybox/jquery.fancybox.css b/resources/fancybox/jquery.fancybox.css similarity index 100% rename from fancybox/jquery.fancybox.css rename to resources/fancybox/jquery.fancybox.css diff --git a/fancybox/jquery.fancybox.pack.js b/resources/fancybox/jquery.fancybox.pack.js similarity index 100% rename from fancybox/jquery.fancybox.pack.js rename to resources/fancybox/jquery.fancybox.pack.js From 516740b44cff3578a887992492c67dd012b81031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Mon, 6 May 2013 18:30:04 +0200 Subject: [PATCH 0006/1200] Next version will be 0.4.0 --- CHANGELOG | 3 ++- base.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d885a273f..d8220c22e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -0.3.5 - 201305?? +0.4.0 - 201305?? * Add multiple database support. Check the documentation of $config['calibre_directory'] in config-default.php to see how ot enable it. * Include jquery library in COPS's repository to be sure that COPS will work on LAN (without Internet access). * Prepare the switch to HTML5. Thanks to Thomas Severinsen for most of the code. @@ -6,6 +6,7 @@ * If Fancybox is not enabled ($config['cops_use_fancyapps'] = "0") then it's not used at all (even in the about box). * Fix book comments if it contains UTF8 characters. Reported by Alain. * Link to the book permalink was not working correctly in some cases. Reported by celta. + * Moved some external resources to a resources directory. 0.3.4 - 20130327 * Hopefully fix metadata update. Beware you should remove the directory php-epub-meta if you have one. Thanks to Mario for his time. diff --git a/base.php b/base.php index 874569d48..8e88f955d 100644 --- a/base.php +++ b/base.php @@ -6,7 +6,7 @@ * @author Sébastien Lucas */ -define ("VERSION", "0.3.5"); +define ("VERSION", "0.4.0"); define ("DB", "db"); date_default_timezone_set($config['default_timezone']); From 8260c9b5a45fde5a8dbebf7e469557bbc42e473f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Mon, 6 May 2013 20:47:39 +0200 Subject: [PATCH 0007/1200] Add some credits. --- about.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/about.xml b/about.xml index 50f25071b..35043edf5 100644 --- a/about.xml +++ b/about.xml @@ -19,4 +19,11 @@

You can also check COPS's topic on MobileRead forum.

+
Thanks
+
+

Thanks a lot to Kovid Goyal for Calibre.

+ +

And many thanks to all those who helped test COPS.

+
+
\ No newline at end of file From 377b89e328332c1339acac02c22260a284502cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 7 May 2013 14:26:11 +0200 Subject: [PATCH 0008/1200] Update changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d8220c22e..6623f18df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ * Fix book comments if it contains UTF8 characters. Reported by Alain. * Link to the book permalink was not working correctly in some cases. Reported by celta. * Moved some external resources to a resources directory. + * Add chinese translation. Thanks to wogong for the pull request. 0.3.4 - 20130327 * Hopefully fix metadata update. Beware you should remove the directory php-epub-meta if you have one. Thanks to Mario for his time. From 03f13da334f615e5f2d78b017ed755c131b9b809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 7 May 2013 20:20:06 +0200 Subject: [PATCH 0009/1200] Change date of release --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 6623f18df..f1a11c70b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -0.4.0 - 201305?? +0.4.0 - 20130507 * Add multiple database support. Check the documentation of $config['calibre_directory'] in config-default.php to see how ot enable it. * Include jquery library in COPS's repository to be sure that COPS will work on LAN (without Internet access). * Prepare the switch to HTML5. Thanks to Thomas Severinsen for most of the code. From d924f406f6fa0db3962efacc026a2e6fa2f53b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 7 May 2013 20:31:04 +0200 Subject: [PATCH 0010/1200] =?UTF-8?q?=C3=89tiquette=200.4.0=20ajout=C3=A9e?= =?UTF-8?q?=20=C3=A0=20la=20r=C3=A9vision=2089ed9654ac9c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 10b71ee6f..a28b7a9cd 100644 --- a/.hgtags +++ b/.hgtags @@ -13,3 +13,4 @@ aca483636af460c93f9817e083e85d1976aa1b7d 0.3.1 5888006bc559842de0364ec3e67f641aa1653d0e 0.3.2 2ff58ed42cecf00b24d981426dff507fa1e86c20 0.3.3 3cdee8daedf28e6611203ce90c90bb8906003e22 0.3.4 +89ed9654ac9c5de1695f63992aa92d55ef82f2b9 0.4.0 From db6cffaef397be53161cf789abc0f1f80444a4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 8 May 2013 08:25:56 +0200 Subject: [PATCH 0011/1200] typo --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f1a11c70b..e4e35c370 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,5 @@ 0.4.0 - 20130507 - * Add multiple database support. Check the documentation of $config['calibre_directory'] in config-default.php to see how ot enable it. + * Add multiple database support. Check the documentation of $config['calibre_directory'] in config-default.php to see how to enable it. * Include jquery library in COPS's repository to be sure that COPS will work on LAN (without Internet access). * Prepare the switch to HTML5. Thanks to Thomas Severinsen for most of the code. * Update the locale strings to be more strict with plurals. Thanks to Tobias Ausländer for the code. From 1641f4f165a94e661ce222d03601080be710a7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 9 May 2013 02:51:26 +0200 Subject: [PATCH 0012/1200] Prepare for next release. --- base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base.php b/base.php index a78c5e9c5..55fa0f72a 100644 --- a/base.php +++ b/base.php @@ -6,7 +6,7 @@ * @author Sébastien Lucas */ -define ("VERSION", "0.4.0"); +define ("VERSION", "0.4.1"); define ("DB", "db"); date_default_timezone_set($config['default_timezone']); From c8cf80064b8e3e31db4abdb12d3450b229fcd289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 14 May 2013 21:35:43 +0200 Subject: [PATCH 0013/1200] Add an handler to access epub internal files. --HG-- extra : rebase_source : 31f7f77fba14f09117d08dd1228da0a8221f8747 --- epubfs.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 epubfs.php diff --git a/epubfs.php b/epubfs.php new file mode 100644 index 000000000..9a5ea3cad --- /dev/null +++ b/epubfs.php @@ -0,0 +1,37 @@ +initSpineComponent (); + +$component = $_GET["comp"]; + +if (empty ($component)) { + notFound (); +} + +try { + $data = $book->component ($component); + $directory = dirname ($component); + + $data = preg_replace ("/src=[\"']([^:]*?)[\"']/", "src='epubfs.php?comp=$1'", $data); + $data = preg_replace ("/href=[\"']([^:]*?)[\"']/", "href='epubfs.php?comp=$1'", $data); + $data = preg_replace ("/\@import\s+[\"'](.*?)[\"'];/", "@import 'epubfs.php?comp={$directory}/$1';", $data); + + header ("Content-Type: " . $book->componentContentType($component)); + echo $data; +} +catch (Exception $e) { + notFound (); +} + + +?> \ No newline at end of file From a00d2e7476b929ab39bd31634eaf7aa18c738796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 09:21:59 +0200 Subject: [PATCH 0014/1200] Add the epub reader. early stage ;) --HG-- extra : rebase_source : 4e74b8db133f69feca6787063c05b45667c8f64d --- epubreader.php | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 epubreader.php diff --git a/epubreader.php b/epubreader.php new file mode 100644 index 000000000..d9aa339be --- /dev/null +++ b/epubreader.php @@ -0,0 +1,67 @@ +initSpineComponent (); + +?> + + + + + + + COPS's Epub Reader + + + " media="screen" /> + + + + + \ No newline at end of file From 25622229be90e12dc9721b104ec9d25a941e685e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 12:11:47 +0200 Subject: [PATCH 0015/1200] Complete working example --HG-- extra : rebase_source : d0f38d1aa980ddec8e60dbe8ddd7689509257886 --- epubreader.php | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/epubreader.php b/epubreader.php index d9aa339be..771f949b2 100644 --- a/epubreader.php +++ b/epubreader.php @@ -18,39 +18,32 @@ COPS's Epub Reader - - " media="screen" /> + + " media="screen" /> + +
+
+ \ No newline at end of file From 8bbaa83b51be3f07bf6be26e4415a3499a83d554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 13:41:08 +0200 Subject: [PATCH 0016/1200] simplify --HG-- extra : rebase_source : 4bf414bce486565a29ca928e77b55f6d2a55718d --- epubreader.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/epubreader.php b/epubreader.php index 771f949b2..c48fd3338 100644 --- a/epubreader.php +++ b/epubreader.php @@ -35,14 +35,10 @@ getComponent: function (componentId, callback) { $.ajax({ url: "epubfs.php?comp=" + componentId, - type: 'get' - , dataType: 'text' - , error: function () {alert ("error");} - , success: function (data, textStatus, jqXHR ) { - //alert (textStatus); - //alert (data); - callback (data); - } + type: 'get', + dataType: 'text', + error: function () {alert ("error");}, + success: callback }); }, getMetaData: function(key) { From a559aa065c029e14cd0960e30ecc193ef4fbbf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 14:16:09 +0200 Subject: [PATCH 0017/1200] Borrow Monocle sample style --HG-- extra : rebase_source : 87c21882bc820ec0d253b67e14d3cb1868324cc1 --- cops-monocle.css | 273 +++++++++++++++++++++++++++++++++++++++++++++++ cops-monocle.js | 162 ++++++++++++++++++++++++++++ epubreader.php | 20 +++- 3 files changed, 450 insertions(+), 5 deletions(-) create mode 100644 cops-monocle.css create mode 100644 cops-monocle.js diff --git a/cops-monocle.css b/cops-monocle.css new file mode 100644 index 000000000..7c8c5b6ed --- /dev/null +++ b/cops-monocle.css @@ -0,0 +1,273 @@ +body { + margin: 0; + padding: 0; + background: #000; + -webkit-user-select: none; + -webkit-text-size-adjust: none; +} + +#components { + display: none; +} + +#reader, #readerBg { + position: absolute; + width: 100%; + height: 100%; +} + +#reader div pre { + white-space: normal; + font: normal 100% serif; +} + +/* from smallest and outermost to largest and innermost */ +.dummyPage { + position: absolute; + left: 0; + top: 20px; + bottom: 20px; + right: 15px; + background-color: #FCF6F0; + -webkit-box-shadow: 2px 2px 4px #754; + -moz-box-shadow: 2px 2px 4px #754; + box-shadow: 2px 2px 4px #754; + -webkit-border-top-left-radius: 26px 6px; + -webkit-border-bottom-left-radius: 26px 6px; + -moz-border-top-left-radius: 26px 6px; + -moz-border-bottom-left-radius: 26px 6px; + border-top-left-radius: 26px 6px; + border-bottom-left-radius: 26px 6px; +} +.dummyPage + .dummyPage { + top: 16px; + bottom: 16px; + right: 16px; + -webkit-box-shadow: 1px 0 2px #A99; + -moz-box-shadow: 1px 0 2px #A99; + box-shadow: 1px 0 2px #A99; +} +.dummyPage + .dummyPage + .dummyPage { + top: 13px; + bottom: 13px; + right: 18px; + background-color: #FFF9F4; +} +.dummyPage + .dummyPage + .dummyPage + .dummyPage { + top: 10px; + bottom: 10px; + right: 21px; +} +.dummyPage + .dummyPage + .dummyPage + .dummyPage + .dummyPage { + top: 8px; + bottom: 8px; + right: 25px; +} +.jacket { + position: absolute; + top: 1px; + bottom: 1px; + right: 3px; + left: 65%; + -webkit-box-shadow: -3px 0 3px #311; + -moz-box-shadow: -3px 0 3px #311; + box-shadow: -3px 0 3px #311; + -webkit-border-top-right-radius: 3px; + -webkit-border-bottom-right-radius: 3px; + -moz-border-top-right-radius: 3px; + -moz-border-bottom-right-radius: 3px; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + background-color: #F7F7F7; + background: -webkit-linear-gradient(0deg, #DDD, #FFF); + background: -moz-linear-gradient(0deg , #DDD, #FFF); + background: linear-gradient(90deg, #DDD, #FFF); +} +.board { + position: absolute; + top: 1px; + bottom: 1px; + width: 90%; + background-color: #974; + border: 1px solid #852; +} + +.runner { + color: #542; + text-transform: uppercase; + font-size: 82%; +} + +.pageNumber, .bookTitle, .chapterTitle { + padding: 3% 2%; +} + +.bookTitle, .chapterTitle { + position: absolute; + top: 1%; + left: 6%; + cursor: pointer; + padding-left: 0; +} + +.chapterTitle { + top: auto; + bottom: 1%; + right: 20%; +} + +.pageNumber { + position: absolute; + bottom: 1%; + right: 8%; + padding-right: 0; +} + +#readerBg { + background-color: #000; +} + +#toc ul.root { + position: absolute; + top: 50px; + left: 8%; + max-height: 75%; + max-width: 80%; + background: #E0D3C0; + -webkit-box-shadow: 1px 2px 2px #652; + -moz-box-shadow: 1px 2px 2px #652; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + overflow-y: auto; + color: #432; + font: 11pt Georgia, serif; + text-shadow: 1px 1px #EEE6D0; + border: 1px solid #EED; + z-index: 9; +} + +.tocArrow { + position: absolute; + top: 40px; + left: 16%; + width: 20px; + height: 20px; + -webkit-transform: rotateZ(45deg); + background: #E0D3C0; + z-index: 8; +} + +#toc li { + list-style: none; + line-height: 220%; + padding-left: 1em; + padding-right: 2em; + border-bottom: 2px groove #FFF6E9; + cursor: pointer; +} + +#toc li span { + display: block; +} + +#toc ul li:last-child { + border-bottom: none; +} + +#toc ul { + margin: 0; + padding: 0; +} +#toc ul.root { + border-top: none; +} + + + +/* Overrides to core elements */ + +div.monelem_container { + background: none; + width: auto; +} + +div.monelem_page { + top: 6px; + bottom: 6px; + right: 4px; + border-color: #CBA; + outline: none; + -webkit-box-shadow: 1px 0 1px #CBA; + -moz-box-shadow: 1px 0 1px #CBA; + box-shadow: 1px 0 2px #CBA; + -webkit-border-top-left-radius: 26px 4px; + -webkit-border-bottom-left-radius: 26px 4px; + -moz-border-top-left-radius: 26px 4px; + -moz-border-bottom-left-radius: 26px 4px; + border-top-left-radius: 26px 4px; + border-bottom-left-radius: 26px 4px; + background-color: #FFFFFE; + background-image: -webkit-linear-gradient(0deg, #EDEAE8 0px, #FFFFFE 24px); + background-image: -moz-linear-gradient(0deg, #EDEAE8 0px, #FFFFFE 24px); + background-image: linear-gradient(90deg, #EDEAE8 0px, #FFFFFE 24px); +} + +div.monelem_sheaf { + left: 6%; + right: 8%; + top: 8%; + bottom: 8%; +} + + +/* Overriding magnifier button display */ + +div.monelem_controls_magnifier_button { + color: #632; + padding: 2%; + top: 1%; + right: 6%; +} + + +/* Overriding table of contents display */ + +div.monelem_controls_contents_container { + background: #E0D3C0; + border: 1px solid #EED; + font: 11pt Georgia, serif; + color: #432; + text-shadow: 1px 1px #FFF6E0; +} + +div.monelem_controls_contents_chapter { + border-bottom: 2px groove #FFF6E9; +} + +li.monelem_controls_contents_chapter_active { + text-shadow: -1px -1px #876; + background: #BA9; +} + + +/* Overriding the scrubber display */ +div.monelem_controls_scrubber_container { + left: 5.5%; + right: 9%; + bottom: 2%; + background: #FFFEFC; +} + +div.monelem_controls_scrubber_track { + border-color: #432; +} + +div.monelem_controls_scrubber_needle { + border-color: #432; + background: #E0D3C0; +} + +div.monelem_controls_scrubber_trail { + background: #E0D3C0; +} diff --git a/cops-monocle.js b/cops-monocle.js new file mode 100644 index 000000000..222187f93 --- /dev/null +++ b/cops-monocle.js @@ -0,0 +1,162 @@ +Monocle.DEBUG = true; + +(function () { + + Monocle.Styles.container.right = "24px"; + + // Initialize the reader element. + Monocle.Events.listen( + window, + 'load', + function () { + var readerOptions = {}; + + /* PLACE SAVER */ + var bkTitle = bookData.getMetaData('title'); + var placeSaver = new Monocle.Controls.PlaceSaver(bkTitle); + readerOptions.place = placeSaver.savedPlace(); + readerOptions.panels = Monocle.Panels.Marginal; + readerOptions.stylesheet = "body { " + + "color: #210;" + + "font-family: Palatino, Georgia, serif;" + + "}"; + + /* Initialize the reader */ + window.reader = Monocle.Reader( + 'reader', + bookData, + readerOptions, + function(reader) { + reader.addControl(placeSaver, 'invisible'); + + /* SPINNER */ + var spinner = Monocle.Controls.Spinner(reader); + reader.addControl(spinner, 'page', { hidden: true }); + spinner.listenForUsualDelays('reader'); + + /* Because the 'reader' element changes size on window resize, + * we should notify it of this event. */ + Monocle.Events.listen( + window, + 'resize', + function () { window.reader.resized() } + ); + + /* MAGNIFIER CONTROL */ + var magnifier = new Monocle.Controls.Magnifier(reader); + reader.addControl(magnifier, 'page'); + + /* The stencil activates internal links */ + var stencil = new Monocle.Controls.Stencil(reader); + reader.addControl(stencil); + //stencil.toggleHighlights(); + + /* BOOK TITLE RUNNING HEAD */ + var bookTitle = {} + bookTitle.contentsMenu = Monocle.Controls.Contents(reader); + reader.addControl(bookTitle.contentsMenu, 'popover', { hidden: true }); + bookTitle.createControlElements = function () { + var cntr = document.createElement('div'); + cntr.className = "bookTitle"; + var runner = document.createElement('div'); + runner.className = "runner"; + runner.innerHTML = reader.getBook().getMetaData('title'); + cntr.appendChild(runner); + + Monocle.Events.listenForContact( + cntr, + { + start: function (evt) { + if (evt.preventDefault) { + evt.stopPropagation(); + evt.preventDefault(); + } else { + evt.returnValue = false; + } + reader.showControl(bookTitle.contentsMenu); + } + } + ); + + return cntr; + } + reader.addControl(bookTitle, 'page'); + + + /* CHAPTER TITLE RUNNING HEAD */ + var chapterTitle = { + runners: [], + createControlElements: function (page) { + var cntr = document.createElement('div'); + cntr.className = "chapterTitle"; + var runner = document.createElement('div'); + runner.className = "runner"; + cntr.appendChild(runner); + this.runners.push(runner); + this.update(page); + return cntr; + }, + update: function (page) { + var place = reader.getPlace(page); + if (place) { + this.runners[page.m.pageIndex].innerHTML = place.chapterTitle(); + } + } + } + reader.addControl(chapterTitle, 'page'); + reader.listen( + 'monocle:pagechange', + function (evt) { chapterTitle.update(evt.m.page); } + ); + + + /* PAGE NUMBER RUNNING HEAD */ + var pageNumber = { + runners: [], + createControlElements: function (page) { + var cntr = document.createElement('div'); + cntr.className = "pageNumber"; + var runner = document.createElement('div'); + runner.className = "runner"; + cntr.appendChild(runner); + this.runners.push(runner); + this.update(page, page.m.place.pageNumber()); + return cntr; + }, + update: function (page, pageNumber) { + if (pageNumber) { + this.runners[page.m.pageIndex].innerHTML = pageNumber; + } + } + } + reader.addControl(pageNumber, 'page'); + reader.listen( + 'monocle:pagechange', + function (evt) { + pageNumber.update(evt.m.page, evt.m.pageNumber); + } + ); + + /* Scrubber */ + var scrubber = new Monocle.Controls.Scrubber(reader); + reader.addControl(scrubber, 'popover', { hidden: true }); + var showFn = function (evt) { + evt.stopPropagation(); + reader.showControl(scrubber); + scrubber.updateNeedles(); + } + for (var i = 0; i < chapterTitle.runners.length; ++i) { + Monocle.Events.listenForContact( + chapterTitle.runners[i].parentNode, + { start: showFn } + ); + Monocle.Events.listenForContact( + pageNumber.runners[i].parentNode, + { start: showFn } + ); + } + } + ); + } + ); +})(); diff --git a/epubreader.php b/epubreader.php index c48fd3338..ef8dcc1f3 100644 --- a/epubreader.php +++ b/epubreader.php @@ -19,10 +19,9 @@ COPS's Epub Reader + " media="screen" /> - + " media="screen" /> + + " media="screen" /> -
+
+
+
+
+
+
+
+
+
+
+
- \ No newline at end of file From 46c8e040ed5b98f6308e6a0ad518f9ca9c74bc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 19:32:18 +0200 Subject: [PATCH 0018/1200] Prepare a feature to send the book by email (Send to Kindle). re #53 --- book.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/book.php b/book.php index a7d434c85..009843975 100644 --- a/book.php +++ b/book.php @@ -199,6 +199,21 @@ public function getDatas () } return $this->datas; } + + public function GetMostInterestingDataToSendToKindle () + { + $bestFormatForKindle = array ("PDF", "MOBI"); + $bestRank = -1; + $bestData = NULL; + foreach ($this->getDatas () as $data) { + $key = array_search ($data->format, $bestFormatForKindle); + if ($key !== false && $key > $bestRank) { + $bestRank = $key; + $bestData = $data; + } + } + return $bestData; + } public function getDataById ($idData) { From 45ca67f25bb26e7c196dd0c80e81c8d109bd4906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 19:33:15 +0200 Subject: [PATCH 0019/1200] Add a new external resource to send mail (PHPMailer). re #53 --- resources/PHPMailer/LICENSE | 504 ++++ resources/PHPMailer/README.md | 134 ++ resources/PHPMailer/class.phpmailer.php | 2810 +++++++++++++++++++++++ resources/PHPMailer/class.pop3.php | 418 ++++ resources/PHPMailer/class.smtp.php | 1088 +++++++++ 5 files changed, 4954 insertions(+) create mode 100644 resources/PHPMailer/LICENSE create mode 100644 resources/PHPMailer/README.md create mode 100644 resources/PHPMailer/class.phpmailer.php create mode 100644 resources/PHPMailer/class.pop3.php create mode 100644 resources/PHPMailer/class.smtp.php diff --git a/resources/PHPMailer/LICENSE b/resources/PHPMailer/LICENSE new file mode 100644 index 000000000..f3f1b3b65 --- /dev/null +++ b/resources/PHPMailer/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/resources/PHPMailer/README.md b/resources/PHPMailer/README.md new file mode 100644 index 000000000..671141da7 --- /dev/null +++ b/resources/PHPMailer/README.md @@ -0,0 +1,134 @@ +# PHPMailer - A full-featured email creation and transfer class for PHP + +Build status: [![Build Status](https://travis-ci.org/Synchro/PHPMailer.png)](https://travis-ci.org/Synchro/PHPMailer) + +## Class Features + +- Probably the world's most popular code for sending email from PHP! +- Used by many open-source projects: Drupal, SugarCRM, Yii, Joomla! and many more +- Integrated SMTP support - send without a local mail server +- Send emails with multiple TOs, CCs, BCCs and REPLY-TOs +- Multipart/alternative emails for mail clients that do not read HTML email +- Support for 8bit, base64, binary, and quoted-printable encoding +- SMTP authentication with LOGIN, PLAIN, NTLM and CRAM-MD5 mechanisms +- Native language support +- Compatible with PHP 5.0 and later +- Much more! + +## Why you might need it + +Many PHP developers utilize email in their code. The only PHP function that supports this is the mail() function. However, it does not provide any assistance for making use of popular features such as HTML-based emails and attachments. + +Formatting email correctly is surprisingly difficult. There are myriad overlapping RFCs, requiring tight adherence to horribly complicated formatting and encoding rules - the vast majority of code that you'll find online that uses the mail() function directly is just plain wrong! +*Please* don't be tempted to do it yourself - if you don't use PHPMailer, there are many other excellent libraries that you should look at before rolling your own - try SwiftMailer, Zend_Mail, eZcomponents etc. + +The PHP mail() function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD and OS X platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP implementation allows email sending on Windows platforms without a local mail server. + +## License + +This software is licenced under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html). Please read LICENSE for information on the +software availability and distribution. + +## Installation + +PHPMailer is available via [Composer/Packagist](https://packagist.org/packages/phpmailer/phpmailer). Alternatively, just copy the contents of the PHPMailer folder into somewhere that's in your PHP `include_path` setting. If you don't speak git or just want a tarball, click the 'zip' button at the top of the page in GitHub. + + +## A Simple Example + +```php +IsSMTP(); // Set mailer to use SMTP +$mail->Host = 'smtp1.example.com;smtp2.example.com'; // Specify main and backup server +$mail->SMTPAuth = true; // Enable SMTP authentication +$mail->Username = 'jswan'; // SMTP username +$mail->Password = 'secret'; // SMTP password +$mail->SMTPSecure = 'tls'; // Enable encryption, 'ssl' also accepted + +$mail->From = 'from@example.com'; +$mail->FromName = 'Mailer'; +$mail->AddAddress('josh@example.net', 'Josh Adams'); // Add a recipient +$mail->AddAddress('ellen@example.com'); // Name is optional +$mail->AddReplyTo('info@example.com', 'Information'); +$mail->AddCC('cc@example.com'); +$mail->AddBCC('bcc@example.com'); + +$mail->WordWrap = 50; // Set word wrap to 50 characters +$mail->AddAttachment('/var/tmp/file.tar.gz'); // Add attachments +$mail->AddAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name +$mail->IsHTML(true); // Set email format to HTML + +$mail->Subject = 'Here is the subject'; +$mail->Body = 'This is the HTML message body in bold!'; +$mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + +if(!$mail->Send()) { + echo 'Message could not be sent.'; + echo 'Mailer Error: ' . $mail->ErrorInfo; + exit; +} + +echo 'Message has been sent'; +``` + +You'll find plenty more to play with in the `examples` folder. + +That's it. You should now be ready to use PHPMailer! + +## Localization +PHPMailer defaults to English, but in the `languages` folder you'll find numerous translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: + +```php +// To load the French version +$mail->SetLanguage('fr', '/optional/path/to/language/directory/'); +``` + +## Documentation + +You'll find some basic user-level docs in the docs folder, and you can generate complete API-level documentation using the `generatedocs.sh` shell script in the docs folder, though you'll need to install [PHPDocumentor](http://www.phpdoc.org) first. + +## Tests + +You'll find a PHPUnit test script in the `test` folder. + +Build status: [![Build Status](https://travis-ci.org/PHPMailer/PHPMailer.png)](https://travis-ci.org/PHPMailer/PHPMailer) + +If this isn't passing, is there something you can do to help? + +## Contributing + +Please submit bug reports, suggestions and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). + +We're particularly interested in fixing edge-cases, expanding test coverage and updating translations. + +With the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone: + +git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git + +Please *don't* use the SourceForge or Google Code projects any more. + +## Changelog + +See changelog.md + +## History +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- Marcus Bointon (coolbru on SF) and Andy Prevost (codeworxtech) took over the project in 2004. +- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. +- Marcus created his fork on [GitHub](https://github.com/Synchro/PHPMailer). +- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer. +- PHPMailer moves to the [PHPMailer organisation](https://github.com/PHPMailer) on GitHub. + +### What's changed since moving from SourceForge? +- Official successor to the SourceForge and Google Code projects. +- Test suite. +- Continuous integration with Travis-CI. +- Composer support. +- Rolling releases. +- Additional languages and language strings. +- CRAM-MD5 authentication support. +- Preserves full repo history of authors, commits and branches from the original SourceForge project. diff --git a/resources/PHPMailer/class.phpmailer.php b/resources/PHPMailer/class.phpmailer.php new file mode 100644 index 000000000..667f18788 --- /dev/null +++ b/resources/PHPMailer/class.phpmailer.php @@ -0,0 +1,2810 @@ +UseSendmailOptions) ) { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header); + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header, $params); + } + return $rt; + } + + /** + * Outputs debugging info via user-defined method + * @param string $str + */ + private function edebug($str) { + if ($this->Debugoutput == "error_log") { + error_log($str); + } else { + echo $str; + } + } + + /** + * Constructor + * @param boolean $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = false) { + $this->exceptions = ($exceptions == true); + } + + /** + * Destructor + */ + public function __destruct() { + if ($this->Mailer == 'smtp') { //Close any open SMTP connection nicely + $this->SmtpClose(); + } + } + + /** + * Sets message type to HTML. + * @param bool $ishtml + * @return void + */ + public function IsHTML($ishtml = true) { + if ($ishtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Sets Mailer to send message using SMTP. + * @return void + */ + public function IsSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * @return void + */ + public function IsMail() { + $this->Mailer = 'mail'; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * @return void + */ + public function IsSendmail() { + if (!stristr(ini_get('sendmail_path'), 'sendmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + /** + * Sets Mailer to send message using the qmail MTA. + * @return void + */ + public function IsQmail() { + if (stristr(ini_get('sendmail_path'), 'qmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + ///////////////////////////////////////////////// + // METHODS, RECIPIENTS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddAddress($address, $name = '') { + return $this->AddAnAddress('to', $address, $name); + } + + /** + * Adds a "Cc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddCC($address, $name = '') { + return $this->AddAnAddress('cc', $address, $name); + } + + /** + * Adds a "Bcc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddBCC($address, $name = '') { + return $this->AddAnAddress('bcc', $address, $name); + } + + /** + * Adds a "Reply-to" address. + * @param string $address + * @param string $name + * @return boolean + */ + public function AddReplyTo($address, $name = '') { + return $this->AddAnAddress('Reply-To', $address, $name); + } + + /** + * Adds an address to one of the recipient arrays + * Addresses that have been added already return false, but do not throw exceptions + * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' + * @param string $address The email address to send to + * @param string $name + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function AddAnAddress($kind, $address, $name = '') { + if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { + $this->SetError($this->Lang('Invalid recipient array').': '.$kind); + if ($this->exceptions) { + throw new phpmailerException('Invalid recipient array: ' . $kind); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('Invalid recipient array').': '.$kind); + } + return false; + } + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!$this->ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('invalid_address').': '.$address); + } + return false; + } + if ($kind != 'Reply-To') { + if (!isset($this->all_recipients[strtolower($address)])) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; +} + + /** + * Set the From and FromName properties + * @param string $address + * @param string $name + * @param int $auto Also set Reply-To and Sender + * @throws phpmailerException + * @return boolean + */ + public function SetFrom($address, $name = '', $auto = 1) { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!$this->ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('invalid_address').': '.$address); + } + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->ReplyTo)) { + $this->AddAnAddress('Reply-To', $address, $name); + } + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Check that a string looks roughly like an email address should + * Static so it can be used without instantiation, public so people can overload + * Conforms to RFC5322: Uses *correct* regex on which FILTER_VALIDATE_EMAIL is + * based; So why not use FILTER_VALIDATE_EMAIL? Because it was broken to + * not allow a@b type valid addresses :( + * @link http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright regex Copyright Michael Rushton 2009-10 | http://squiloople.com/ | Feel free to use and redistribute this code. But please keep this copyright notice. + * @param string $address The email address to check + * @return boolean + * @static + * @access public + */ + public static function ValidateAddress($address) { + if (defined('PCRE_VERSION')) { //Check this instead of extension_loaded so it works when that function is disabled + if (version_compare(PCRE_VERSION, '8.0') >= 0) { + return (boolean)preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address); + } else { + //Fall back to an older regex that doesn't need a recent PCRE + return (boolean)preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', $address); + } + } else { + //No PCRE! Do something _very_ approximate! + //Check the address is 3 chars or longer and contains an @ that's not the first or last char + return (strlen($address) >= 3 and strpos($address, '@') >= 1 and strpos($address, '@') != strlen($address) - 1); + } + } + + ///////////////////////////////////////////////// + // METHODS, MAIL SENDING + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. + * @throws phpmailerException + * @return bool + */ + public function Send() { + try { + if(!$this->PreSend()) return false; + return $this->PostSend(); + } catch (phpmailerException $e) { + $this->mailHeader = ''; + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Prep mail by constructing all message entities + * @throws phpmailerException + * @return bool + */ + public function PreSend() { + try { + $this->mailHeader = ""; + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL); + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) { + $this->ContentType = 'multipart/alternative'; + } + + $this->error_count = 0; // reset errors + $this->SetMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty and empty($this->Body)) { + throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL); + } + + $this->MIMEHeader = $this->CreateHeader(); + $this->MIMEBody = $this->CreateBody(); + + // To capture the complete message when using mail(), create + // an extra header list which CreateHeader() doesn't fold in + if ($this->Mailer == 'mail') { + if (count($this->to) > 0) { + $this->mailHeader .= $this->AddrAppend("To", $this->to); + } else { + $this->mailHeader .= $this->HeaderLine("To", "undisclosed-recipients:;"); + } + $this->mailHeader .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader(trim($this->Subject)))); + } + + // digitally sign with DKIM if enabled + if (!empty($this->DKIM_domain) && !empty($this->DKIM_private) && !empty($this->DKIM_selector) && !empty($this->DKIM_domain) && file_exists($this->DKIM_private)) { + $header_dkim = $this->DKIM_Add($this->MIMEHeader . $this->mailHeader, $this->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody); + $this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader; + } + + return true; + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Actual Email transport function + * Send the email via the selected mechanism + * @throws phpmailerException + * @return bool + */ + public function PostSend() { + try { + // Choose the mailer and send through it + switch($this->Mailer) { + case 'sendmail': + return $this->SendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->SmtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + default: + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + if ($this->SMTPDebug) { + $this->edebug($e->getMessage()."\n"); + } + } + return false; + } + + /** + * Sends mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @access protected + * @return bool + */ + protected function SendmailSend($header, $body) { + if ($this->Sender != '') { + $sendmail = sprintf("%s -oi -f%s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); + } + if ($this->SingleTo === true) { + foreach ($this->SingleToArray as $val) { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, "To: " . $val . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Sends mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @access protected + * @return bool + */ + protected function MailSend($header, $body) { + $toArr = array(); + foreach($this->to as $t) { + $toArr[] = $this->AddrFormat($t); + } + $to = implode(', ', $toArr); + + if (empty($this->Sender)) { + $params = " "; + } else { + $params = sprintf("-f%s", $this->Sender); + } + if ($this->Sender != '' and !ini_get('safe_mode')) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $rt = false; + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $val) { + $rt = $this->mail_passthru($val, $this->Subject, $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = $this->mail_passthru($to, $this->Subject, $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if(!$rt) { + throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @uses SMTP + * @access protected + * @return bool + */ + protected function SmtpSend($header, $body) { + require_once $this->PluginDir . 'class.smtp.php'; + $bad_rcpt = array(); + + if(!$this->SmtpConnect()) { + throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; + if(!$this->smtp->Mail($smtp_from)) { + $this->SetError($this->Lang('from_failed') . $smtp_from . ' : ' .implode(',', $this->smtp->getError())); + throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); + } + + // Attempt to send attach all recipients + foreach($this->to as $to) { + if (!$this->smtp->Recipient($to[0])) { + $bad_rcpt[] = $to[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } + } + foreach($this->cc as $cc) { + if (!$this->smtp->Recipient($cc[0])) { + $bad_rcpt[] = $cc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } + } + foreach($this->bcc as $bcc) { + if (!$this->smtp->Recipient($bcc[0])) { + $bad_rcpt[] = $bcc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } + } + + + if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses + $badaddresses = implode(', ', $bad_rcpt); + throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses); + } + if(!$this->smtp->Data($header . $body)) { + throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL); + } + if($this->SMTPKeepAlive == true) { + $this->smtp->Reset(); + } else { + $this->smtp->Quit(); + $this->smtp->Close(); + } + return true; + } + + /** + * Initiates a connection to an SMTP server. + * Returns false if the operation failed. + * @uses SMTP + * @access public + * @throws phpmailerException + * @return bool + */ + public function SmtpConnect() { + if(is_null($this->smtp)) { + $this->smtp = new SMTP; + } + + $this->smtp->Timeout = $this->Timeout; + $this->smtp->do_debug = $this->SMTPDebug; + $hosts = explode(';', $this->Host); + $index = 0; + $connection = $this->smtp->Connected(); + + // Retry while there is no connection + try { + while($index < count($hosts) && !$connection) { + $hostinfo = array(); + if (preg_match('/^(.+):([0-9]+)$/', $hosts[$index], $hostinfo)) { + $host = $hostinfo[1]; + $port = $hostinfo[2]; + } else { + $host = $hosts[$index]; + $port = $this->Port; + } + + $tls = ($this->SMTPSecure == 'tls'); + $ssl = ($this->SMTPSecure == 'ssl'); + + if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) { + + $hello = ($this->Helo != '' ? $this->Helo : $this->ServerHostname()); + $this->smtp->Hello($hello); + + if ($tls) { + if (!$this->smtp->StartTLS()) { + throw new phpmailerException($this->Lang('connect_host')); + } + + //We must resend HELO after tls negotiation + $this->smtp->Hello($hello); + } + + $connection = true; + if ($this->SMTPAuth) { + if (!$this->smtp->Authenticate($this->Username, $this->Password, $this->AuthType, $this->Realm, $this->Workstation)) { + throw new phpmailerException($this->Lang('authenticate')); + } + } + } + $index++; + if (!$connection) { + throw new phpmailerException($this->Lang('connect_host')); + } + } + } catch (phpmailerException $e) { + $this->smtp->Reset(); + if ($this->exceptions) { + throw $e; + } + } + return true; + } + + /** + * Closes the active SMTP session if one exists. + * @return void + */ + public function SmtpClose() { + if ($this->smtp !== null) { + if($this->smtp->Connected()) { + $this->smtp->Quit(); + $this->smtp->Close(); + } + } + } + + /** + * Sets the language for all class error messages. + * Returns false if it cannot load the language file. The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br") + * @param string $lang_path Path to the language file directory + * @return bool + * @access public + */ + function SetLanguage($langcode = 'en', $lang_path = 'language/') { + //Define full set of translatable strings + $PHPMAILER_LANG = array( + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: Data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP Connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ' + ); + //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"! + $l = true; + if ($langcode != 'en') { //There is no English translation file + $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php'; + } + $this->language = $PHPMAILER_LANG; + return ($l == true); //Returns false if language not found + } + + /** + * Return the current array of language strings + * @return array + */ + public function GetTranslations() { + return $this->language; + } + + ///////////////////////////////////////////////// + // METHODS, MESSAGE CREATION + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. + * @access public + * @param string $type + * @param array $addr + * @return string + */ + public function AddrAppend($type, $addr) { + $addr_str = $type . ': '; + $addresses = array(); + foreach ($addr as $a) { + $addresses[] = $this->AddrFormat($a); + } + $addr_str .= implode(', ', $addresses); + $addr_str .= $this->LE; + + return $addr_str; + } + + /** + * Formats an address correctly. + * @access public + * @param string $addr + * @return string + */ + public function AddrFormat($addr) { + if (empty($addr[1])) { + return $this->SecureHeader($addr[0]); + } else { + return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">"; + } + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == "utf-8"); + $lelen = strlen($this->LE); + $crlflen = strlen(self::CRLF); + + $message = $this->FixEOL($message); + if (substr($message, -$lelen) == $this->LE) { + $message = substr($message, 0, -$lelen); + } + + $line = explode($this->LE, $message); // Magic. We know FixEOL uses $LE + $message = ''; + for ($i = 0 ;$i < count($line); $i++) { + $line_part = explode(' ', $line[$i]); + $buf = ''; + for ($e = 0; $e $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if ($e != 0) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf("=%s", self::CRLF); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf("=%s", self::CRLF); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (' ' . $word); + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . self::CRLF; + } + + return $message; + } + + /** + * Finds last character boundary prior to maxLength in a utf-8 + * quoted (printable) encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param int $maxLength find last character boundary prior to this length + * @return int + */ + public function UTF8CharBoundary($encodedText, $maxLength) { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, "="); + if ($encodedCharPos !== false) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + $maxLength = ($encodedCharPos == 0) ? $maxLength : + $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec >= 192) { // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + + /** + * Set the body wrapping. + * @access public + * @return void + */ + public function SetWordWrap() { + if($this->WordWrap < 1) { + return; + } + + switch($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->WrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assembles message header. + * @access public + * @return string The assembled header + */ + public function CreateHeader() { + $result = ''; + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $uniq_id; + $this->boundary[2] = 'b2_' . $uniq_id; + $this->boundary[3] = 'b3_' . $uniq_id; + + if ($this->MessageDate == '') { + $result .= $this->HeaderLine('Date', self::RFCDate()); + } else { + $result .= $this->HeaderLine('Date', $this->MessageDate); + } + + if ($this->ReturnPath) { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->ReturnPath).'>'); + } elseif ($this->Sender == '') { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->From).'>'); + } else { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->Sender).'>'); + } + + // To be created automatically by mail() + if($this->Mailer != 'mail') { + if ($this->SingleTo === true) { + foreach($this->to as $t) { + $this->SingleToArray[] = $this->AddrFormat($t); + } + } else { + if(count($this->to) > 0) { + $result .= $this->AddrAppend('To', $this->to); + } elseif (count($this->cc) == 0) { + $result .= $this->HeaderLine('To', 'undisclosed-recipients:;'); + } + } + } + + $from = array(); + $from[0][0] = trim($this->From); + $from[0][1] = $this->FromName; + $result .= $this->AddrAppend('From', $from); + + // sendmail and mail() extract Cc from the header before sending + if(count($this->cc) > 0) { + $result .= $this->AddrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { + $result .= $this->AddrAppend('Bcc', $this->bcc); + } + + if(count($this->ReplyTo) > 0) { + $result .= $this->AddrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if($this->Mailer != 'mail') { + $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject))); + } + + if($this->MessageID != '') { + $result .= $this->HeaderLine('Message-ID', $this->MessageID); + } else { + $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE); + } + $result .= $this->HeaderLine('X-Priority', $this->Priority); + if ($this->XMailer == '') { + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (https://github.com/PHPMailer/PHPMailer/)'); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->HeaderLine('X-Mailer', $myXmailer); + } + } + + if($this->ConfirmReadingTo != '') { + $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + } + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) { + $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1]))); + } + if (!$this->sign_key_file) { + $result .= $this->HeaderLine('MIME-Version', '1.0'); + $result .= $this->GetMailMIME(); + } + + return $result; + } + + /** + * Returns the message MIME. + * @access public + * @return string + */ + public function GetMailMIME() { + $result = ''; + switch($this->message_type) { + case 'inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); + $result .= $this->TextLine('Content-Type: '.$this->ContentType.'; charset='.$this->CharSet); + break; + } + + if($this->Mailer != 'mail') { + $result .= $this->LE; + } + + return $result; + } + + /** + * Returns the MIME message (headers and body). Only really valid post PreSend(). + * @access public + * @return string + */ + public function GetSentMIMEMessage() { + return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody; + } + + + /** + * Assembles the message body. Returns an empty string on failure. + * @access public + * @throws phpmailerException + * @return string The assembled message body + */ + public function CreateBody() { + $body = ''; + + if ($this->sign_key_file) { + $body .= $this->GetMailMIME().$this->LE; + } + + $this->SetWordWrap(); + + switch($this->message_type) { + case 'inline': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine('--' . $this->boundary[2]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[3], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[3]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + default: + // catch case 'plain' and case '' + $body .= $this->EncodeString($this->Body, $this->Encoding); + break; + } + + if ($this->IsError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new phpmailerException($this->Lang('signing').' OpenSSL extension missing.'); + } + $file = tempnam(sys_get_temp_dir(), 'mail'); + file_put_contents($file, $body); //TODO check this worked + $signed = tempnam(sys_get_temp_dir(), 'signed'); + if (@openssl_pkcs7_sign($file, $signed, 'file://'.realpath($this->sign_cert_file), array('file://'.realpath($this->sign_key_file), $this->sign_key_pass), null)) { + @unlink($file); + $body = file_get_contents($signed); + @unlink($signed); + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->Lang('signing').openssl_error_string()); + } + } catch (phpmailerException $e) { + $body = ''; + if ($this->exceptions) { + throw $e; + } + } + } + return $body; + } + + /** + * Returns the start of a message boundary. + * @access protected + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * @return string + */ + protected function GetBoundary($boundary, $charSet, $contentType, $encoding) { + $result = ''; + if($charSet == '') { + $charSet = $this->CharSet; + } + if($contentType == '') { + $contentType = $this->ContentType; + } + if($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->TextLine('--' . $boundary); + $result .= sprintf("Content-Type: %s; charset=%s", $contentType, $charSet); + $result .= $this->LE; + $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding); + $result .= $this->LE; + + return $result; + } + + /** + * Returns the end of a message boundary. + * @access protected + * @param string $boundary + * @return string + */ + protected function EndBoundary($boundary) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Sets the message type. + * @access protected + * @return void + */ + protected function SetMessageType() { + $this->message_type = array(); + if($this->AlternativeExists()) $this->message_type[] = "alt"; + if($this->InlineImageExists()) $this->message_type[] = "inline"; + if($this->AttachmentExists()) $this->message_type[] = "attach"; + $this->message_type = implode("_", $this->message_type); + if($this->message_type == "") $this->message_type = "plain"; + } + + /** + * Returns a formatted header line. + * @access public + * @param string $name + * @param string $value + * @return string + */ + public function HeaderLine($name, $value) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Returns a formatted mail line. + * @access public + * @param string $value + * @return string + */ + public function TextLine($value) { + return $value . $this->LE; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, ATTACHMENTS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @throws phpmailerException + * @return bool + */ + public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + try { + if ( !@is_file($path) ) { + throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE); + } + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + if ($this->SMTPDebug) { + $this->edebug($e->getMessage()."\n"); + } + if ( $e->getCode() == self::STOP_CRITICAL ) { + return false; + } + } + return true; + } + + /** + * Return the current array of attachments + * @return array + */ + public function GetAttachments() { + return $this->attachment; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * @param string $disposition_type + * @param string $boundary + * @return string + */ + protected function AttachAll($disposition_type, $boundary) { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // CHECK IF IT IS A VALID DISPOSITION_FILTER + if($attachment[6] == $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5(serialize($attachment)); + if (in_array($inclhash, $incl)) { continue; } + $incl[] = $inclhash; + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; } + $cidUniq[$cid] = true; + + $mime[] = sprintf("--%s%s", $boundary, $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == 'inline') { + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + } + + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + + // Encode as string attachment + if($bString) { + $mime[] = $this->EncodeString($string, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } else { + $mime[] = $this->EncodeFile($path, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } + } + } + + $mime[] = sprintf("--%s--%s", $boundary, $this->LE); + + return implode("", $mime); + } + + /** + * Encodes attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @throws phpmailerException + * @see EncodeFile() + * @access protected + * @return string + */ + protected function EncodeFile($path, $encoding = 'base64') { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); + } + $magic_quotes = get_magic_quotes_runtime(); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime(0); + } else { + ini_set('magic_quotes_runtime', 0); + } + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->EncodeString($file_buffer, $encoding); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime($magic_quotes); + } else { + ini_set('magic_quotes_runtime', $magic_quotes); + } + } + return $file_buffer; + } catch (Exception $e) { + $this->SetError($e->getMessage()); + return ''; + } + } + + /** + * Encodes string to requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function EncodeString($str, $encoding = 'base64') { + $encoded = ''; + switch(strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->FixEOL($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->EncodeQP($str); + break; + default: + $this->SetError($this->Lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string to best (shortest) of Q, B, quoted or none. + * @access public + * @param string $str + * @param string $position + * @return string + */ + public function EncodeHeader($str, $position = 'text') { + $x = 0; + + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know what value has magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + case 'comment': + $x = preg_match_all('/[()"]/', $str, $matches); + // Fall-through + case 'text': + default: + $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($x == 0) { //There are no chars that need encoding + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if ($x > strlen($str)/3) { //More than a third of the content will need encoding, so B encoding will be most efficient + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->Base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->EncodeQ($str, $position); + $encoded = $this->WrapText($encoded, $maxlen, true); + $encoded = str_replace('='.self::CRLF, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Checks if a string contains multibyte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return bool + */ + public function HasMultiBytes($str) { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Correctly encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php + * @access public + * @param string $str multi-byte text to wrap encode + * @param string $lf string to use as linefeed/end-of-line + * @return string + */ + public function Base64EncodeWrapMB($str, $lf=null) { + $start = "=?".$this->CharSet."?B?"; + $end = "?="; + $encoded = ""; + if ($lf === null) { + $lf = $this->LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $offset = $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } + while (strlen($chunk) > $length); + + $encoded .= $chunk . $lf; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($lf)); + return $encoded; + } + + /** + * Encode string to RFC2045 (6.7) quoted-printable format + * @access public + * @param string $string The text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + * @link PHP version adapted from http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 + */ + public function EncodeQP($string, $line_max = 76) { + if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + return quoted_printable_encode($string); + } + //Fall back to a pure PHP implementation + $string = str_replace(array('%20', '%0D%0A.', '%0D%0A', '%'), array(' ', "\r\n=2E", "\r\n", '='), rawurlencode($string)); + $string = preg_replace('/[^\r\n]{'.($line_max - 3).'}[^=\r\n]{2}/', "$0=\r\n", $string); + return $string; + } + + /** + * Wrapper to preserve BC for old QP encoding function that was removed + * @see EncodeQP() + * @access public + * @param string $string + * @param integer $line_max + * @param bool $space_conv + * @return string + */ + public function EncodeQPphp($string, $line_max = 76, $space_conv = false) { + return $this->EncodeQP($string, $line_max); + } + + /** + * Encode string to q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function EncodeQ($str, $position = 'text') { + //There should not be any EOL in the string + $pattern=""; + $encoded = str_replace(array("\r", "\n"), '', $str); + switch (strtolower($position)) { + case 'phrase': + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + + case 'comment': + $pattern = '\(\)"'; + //note that we don't break here! + //for this reason we build the $pattern without including delimiters and [] + + case 'text': + default: + //Replace every high ascii, control =, ? and _ characters + //We put \075 (=) as first value to make sure it's the first one in being converted, preventing double encode + $pattern = '\075\000-\011\013\014\016-\037\077\137\177-\377' . $pattern; + break; + } + + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + + //Replace every spaces to _ (more readable than =20) + return str_replace(' ', '_', $encoded); +} + + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + } + + /** + * Add an embedded attachment from a file. + * This can include images, sounds, and just about any other document type. + * Be sure to set the $type to an image type for images: + * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File MIME type. + * @return bool True on successfully adding an attachment + */ + public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + + if ( !@is_file($path) ) { + $this->SetError($this->Lang('file_access') . $path); + return false; + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + return true; + } + + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * Be sure to set the $type to an image type for images: + * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. + * @param string $string The attachment binary data. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name + * @param string $encoding File encoding (see $Encoding). + * @param string $type MIME type. + * @return bool True on successfully adding an attachment + */ + public function AddStringEmbeddedImage($string, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + return true; + } + + /** + * Returns true if an inline attachment is present. + * @access public + * @return bool + */ + public function InlineImageExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + /** + * Returns true if an attachment (non-inline) is present. + * @return bool + */ + public function AttachmentExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'attachment') { + return true; + } + } + return false; + } + + /** + * Does this message have an alternative body set? + * @return bool + */ + public function AlternativeExists() { + return !empty($this->AltBody); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MESSAGE RESET + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @return void + */ + public function ClearAddresses() { + foreach($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @return void + */ + public function ClearCCs() { + foreach($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @return void + */ + public function ClearBCCs() { + foreach($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @return void + */ + public function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @return void + */ + public function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @return void + */ + public function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @return void + */ + public function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MISCELLANEOUS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * @access protected + * @param string $msg + * @return void + */ + protected function SetError($msg) { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { + $msg .= '

' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "

\n"; + } + } + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function RFCDate() { + //Set the time zone to whatever the default is to avoid 500 errors + //Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + return date('D, j M Y H:i:s O'); + } + + /** + * Returns the server hostname or 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function ServerHostname() { + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } else { + $result = 'localhost.localdomain'; + } + + return $result; + } + + /** + * Returns a message in the appropriate language. + * @access protected + * @param string $key + * @return string + */ + protected function Lang($key) { + if(count($this->language) < 1) { + $this->SetLanguage('en'); // set the default language + } + + if(isset($this->language[$key])) { + return $this->language[$key]; + } else { + return 'Language string failed to load: ' . $key; + } + } + + /** + * Returns true if an error occurred. + * @access public + * @return bool + */ + public function IsError() { + return ($this->error_count > 0); + } + + /** + * Changes every end of line from CRLF, CR or LF to $this->LE. + * @access public + * @param string $str String to FixEOL + * @return string + */ + public function FixEOL($str) { + // condense down to \n + $nstr = str_replace(array("\r\n", "\r"), "\n", $str); + // Now convert LE as needed + if ($this->LE !== "\n") { + $nstr = str_replace("\n", $this->LE, $nstr); + } + return $nstr; + } + + /** + * Adds a custom header. $name value can be overloaded to contain + * both header name and value (name:value) + * @access public + * @param string $name custom header name + * @param string $value header value + * @return void + */ + public function AddCustomHeader($name, $value=null) { + if ($value === null) { + // Value passed in as name:value + $this->CustomHeader[] = explode(':', $name, 2); + } else { + $this->CustomHeader[] = array($name, $value); + } + } + + /** + * Creates a message from an HTML string, making modifications for inline images and backgrounds + * and creates a plain-text version by converting the HTML + * Overwrites any existing values in $this->Body and $this->AltBody + * @access public + * @param string $message HTML message string + * @param string $basedir baseline directory for path + * @param bool $advanced Whether to use the advanced HTML to text converter + * @return string $message + */ + public function MsgHTML($message, $basedir = '', $advanced = false) { + preg_match_all("/(src|background)=[\"'](.*)[\"']/Ui", $message, $images); + if(isset($images[2])) { + foreach($images[2] as $i => $url) { + // do not change urls for absolute images (thanks to corvuscorax) + if (!preg_match('#^[A-z]+://#', $url)) { + $filename = basename($url); + $directory = dirname($url); + if ($directory == '.') { + $directory = ''; + } + $cid = 'cid:' . md5($url); + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $mimeType = self::_mime_types($ext); + if ( strlen($basedir) > 1 && substr($basedir, -1) != '/') { $basedir .= '/'; } + if ( strlen($directory) > 1 && substr($directory, -1) != '/') { $directory .= '/'; } + if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($url), $filename, 'base64', $mimeType) ) { + $message = preg_replace("/".$images[1][$i]."=[\"']".preg_quote($url, '/')."[\"']/Ui", $images[1][$i]."=\"".$cid."\"", $message); + } + } + } + } + $this->IsHTML(true); + $this->Body = $message; + $this->AltBody = $this->html2text($message, $advanced); + if (empty($this->AltBody)) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; + } + return $message; + } + + /** + * Convert an HTML string into a plain text version + * @param string $html The HTML text to convert + * @param bool $advanced Should this use the more complex html2text converter or just a simple one? + * @return string + */ + public function html2text($html, $advanced = false) { + if ($advanced) { + require_once 'extras/class.html2text.php'; + $h = new html2text($html); + return $h->get_text(); + } + return html_entity_decode(trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $html))), ENT_QUOTES, $this->CharSet); + } + + /** + * Gets the MIME type of the embedded or inline image + * @param string $ext File extension + * @access public + * @return string MIME type of ext + * @static + */ + public static function _mime_types($ext = '') { + $mimes = array( + 'xl' => 'application/excel', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie' + ); + return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)]; + } + + /** + * Set (or reset) Class Objects (variables) + * + * Usage Example: + * $page->set('X-Priority', '3'); + * + * @access public + * @param string $name + * @param mixed $value + * NOTE: will not work with arrays, there are no arrays to set/reset + * @throws phpmailerException + * @return bool + * @todo Should this not be using __set() magic function? + */ + public function set($name, $value = '') { + try { + if (isset($this->$name) ) { + $this->$name = $value; + } else { + throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL); + } + } catch (Exception $e) { + $this->SetError($e->getMessage()); + if ($e->getCode() == self::STOP_CRITICAL) { + return false; + } + } + return true; + } + + /** + * Strips newlines to prevent header injection. + * @access public + * @param string $str + * @return string + */ + public function SecureHeader($str) { + return trim(str_replace(array("\r", "\n"), '', $str)); + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + */ + public function Sign($cert_filename, $key_filename, $key_pass) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $txt + * @return string + */ + public function DKIM_QP($txt) { + $line = ''; + for ($i = 0; $i < strlen($txt); $i++) { + $ord = ord($txt[$i]); + if ( ((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E)) ) { + $line .= $txt[$i]; + } else { + $line .= "=".sprintf("%02X", $ord); + } + } + return $line; + } + + /** + * Generate DKIM signature + * + * @access public + * @param string $s Header + * @throws phpmailerException + * @return string + */ + public function DKIM_Sign($s) { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new phpmailerException($this->Lang("signing").' OpenSSL extension missing.'); + } + return ''; + } + $privKeyStr = file_get_contents($this->DKIM_private); + if ($this->DKIM_passphrase != '') { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($s, $signature, $privKey)) { + return base64_encode($signature); + } + return ''; + } + + /** + * Generate DKIM Canonicalization Header + * + * @access public + * @param string $s Header + * @return string + */ + public function DKIM_HeaderC($s) { + $s = preg_replace("/\r\n\s+/", " ", $s); + $lines = explode("\r\n", $s); + foreach ($lines as $key => $line) { + list($heading, $value) = explode(":", $line, 2); + $heading = strtolower($heading); + $value = preg_replace("/\s+/", " ", $value) ; // Compress useless spaces + $lines[$key] = $heading.":".trim($value) ; // Don't forget to remove WSP around the value + } + $s = implode("\r\n", $lines); + return $s; + } + + /** + * Generate DKIM Canonicalization Body + * + * @access public + * @param string $body Message Body + * @return string + */ + public function DKIM_BodyC($body) { + if ($body == '') return "\r\n"; + // stabilize line endings + $body = str_replace("\r\n", "\n", $body); + $body = str_replace("\n", "\r\n", $body); + // END stabilize line endings + while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { + $body = substr($body, 0, strlen($body) - 2); + } + return $body; + } + + /** + * Create the DKIM header, body, as new header + * + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode($this->LE, $headers_line); + $from_header = ''; + $to_header = ''; + $current = ''; + foreach($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + $current = 'from_header'; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + $current = 'to_header'; + } else { + if($current && strpos($header, ' =?') === 0){ + $$current .= $header; + } else { + $current = ''; + } + } + } + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body) ; // Length of body + $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body + $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";"; + $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n". + "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n". + "\th=From:To:Subject;\r\n". + "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n". + "\tz=$from\r\n". + "\t|$to\r\n". + "\t|$subject;\r\n". + "\tbh=" . $DKIMb64 . ";\r\n". + "\tb="; + $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); + $signed = $this->DKIM_Sign($toSign); + return $dkimhdrs.$signed."\r\n"; + } + + /** + * Perform callback + * @param boolean $isSent + * @param string $to + * @param string $cc + * @param string $bcc + * @param string $subject + * @param string $body + * @param string $from + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from = null) { + if (!empty($this->action_function) && is_callable($this->action_function)) { + $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); + call_user_func_array($this->action_function, $params); + } + } +} + +/** + * Exception handler for PHPMailer + * @package PHPMailer + */ +class phpmailerException extends Exception { + /** + * Prettify error message output + * @return string + */ + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; + return $errorMsg; + } +} diff --git a/resources/PHPMailer/class.pop3.php b/resources/PHPMailer/class.pop3.php new file mode 100644 index 000000000..17bb6756f --- /dev/null +++ b/resources/PHPMailer/class.pop3.php @@ -0,0 +1,418 @@ + + * @author Andy Prevost + * @author Jim Jagielski + */ + +class POP3 { + /** + * Default POP3 port + * @var int + */ + public $POP3_PORT = 110; + + /** + * Default Timeout + * @var int + */ + public $POP3_TIMEOUT = 30; + + /** + * POP3 Carriage Return + Line Feed + * @var string + */ + public $CRLF = "\r\n"; + + /** + * Displaying Debug warnings? (0 = now, 1+ = yes) + * @var int + */ + public $do_debug = 2; + + /** + * POP3 Mail Server + * @var string + */ + public $host; + + /** + * POP3 Port + * @var int + */ + public $port; + + /** + * POP3 Timeout Value + * @var int + */ + public $tval; + + /** + * POP3 Username + * @var string + */ + public $username; + + /** + * POP3 Password + * @var string + */ + public $password; + + /** + * Sets the POP3 PHPMailer Version number + * @var string + */ + public $Version = '5.2.6'; + + ///////////////////////////////////////////////// + // PROPERTIES, PRIVATE AND PROTECTED + ///////////////////////////////////////////////// + + /** + * @var resource Resource handle for the POP connection socket + */ + private $pop_conn; + /** + * @var boolean Are we connected? + */ + private $connected; + /** + * @var array Error container + */ + private $error; // Error log array + + /** + * Constructor, sets the initial values + * @access public + * @return POP3 + */ + public function __construct() { + $this->pop_conn = 0; + $this->connected = false; + $this->error = null; + } + + /** + * Combination of public events - connect, login, disconnect + * @access public + * @param string $host + * @param bool|int $port + * @param bool|int $tval + * @param string $username + * @param string $password + * @param int $debug_level + * @return bool + */ + public function Authorise ($host, $port = false, $tval = false, $username, $password, $debug_level = 0) { + $this->host = $host; + + // If no port value is passed, retrieve it + if ($port == false) { + $this->port = $this->POP3_PORT; + } else { + $this->port = $port; + } + + // If no port value is passed, retrieve it + if ($tval == false) { + $this->tval = $this->POP3_TIMEOUT; + } else { + $this->tval = $tval; + } + + $this->do_debug = $debug_level; + $this->username = $username; + $this->password = $password; + + // Refresh the error log + $this->error = null; + + // Connect + $result = $this->Connect($this->host, $this->port, $this->tval); + + if ($result) { + $login_result = $this->Login($this->username, $this->password); + + if ($login_result) { + $this->Disconnect(); + + return true; + } + + } + + // We need to disconnect regardless if the login succeeded + $this->Disconnect(); + + return false; + } + + /** + * Connect to the POP3 server + * @access public + * @param string $host + * @param bool|int $port + * @param integer $tval + * @return boolean + */ + public function Connect ($host, $port = false, $tval = 30) { + // Are we already connected? + if ($this->connected) { + return true; + } + + /* + On Windows this will raise a PHP Warning error if the hostname doesn't exist. + Rather than supress it with @fsockopen, let's capture it cleanly instead + */ + + set_error_handler(array(&$this, 'catchWarning')); + + // Connect to the POP3 server + $this->pop_conn = fsockopen($host, // POP3 Host + $port, // Port # + $errno, // Error Number + $errstr, // Error Message + $tval); // Timeout (seconds) + + // Restore the error handler + restore_error_handler(); + + // Does the Error Log now contain anything? + if ($this->error && $this->do_debug >= 1) { + $this->displayErrors(); + } + + // Did we connect? + if ($this->pop_conn == false) { + // It would appear not... + $this->error = array( + 'error' => "Failed to connect to server $host on port $port", + 'errno' => $errno, + 'errstr' => $errstr + ); + + if ($this->do_debug >= 1) { + $this->displayErrors(); + } + + return false; + } + + // Increase the stream time-out + + // Check for PHP 4.3.0 or later + if (version_compare(phpversion(), '5.0.0', 'ge')) { + stream_set_timeout($this->pop_conn, $tval, 0); + } else { + // Does not work on Windows + if (substr(PHP_OS, 0, 3) !== 'WIN') { + socket_set_timeout($this->pop_conn, $tval, 0); + } + } + + // Get the POP3 server response + $pop3_response = $this->getResponse(); + + // Check for the +OK + if ($this->checkResponse($pop3_response)) { + // The connection is established and the POP3 server is talking + $this->connected = true; + return true; + } + return false; + } + + /** + * Login to the POP3 server (does not support APOP yet) + * @access public + * @param string $username + * @param string $password + * @return boolean + */ + public function Login ($username = '', $password = '') { + if ($this->connected == false) { + $this->error = 'Not connected to POP3 server'; + + if ($this->do_debug >= 1) { + $this->displayErrors(); + } + } + + if (empty($username)) { + $username = $this->username; + } + + if (empty($password)) { + $password = $this->password; + } + + $pop_username = "USER $username" . $this->CRLF; + $pop_password = "PASS $password" . $this->CRLF; + + // Send the Username + $this->sendString($pop_username); + $pop3_response = $this->getResponse(); + + if ($this->checkResponse($pop3_response)) { + // Send the Password + $this->sendString($pop_password); + $pop3_response = $this->getResponse(); + + if ($this->checkResponse($pop3_response)) { + return true; + } + } + return false; + } + + /** + * Disconnect from the POP3 server + * @access public + */ + public function Disconnect () { + $this->sendString('QUIT'); + + fclose($this->pop_conn); + } + + ///////////////////////////////////////////////// + // Private Methods + ///////////////////////////////////////////////// + + /** + * Get the socket response back. + * $size is the maximum number of bytes to retrieve + * @access private + * @param integer $size + * @return string + */ + private function getResponse ($size = 128) { + $pop3_response = fgets($this->pop_conn, $size); + + return $pop3_response; + } + + /** + * Send a string down the open socket connection to the POP3 server + * @access private + * @param string $string + * @return integer + */ + private function sendString ($string) { + $bytes_sent = fwrite($this->pop_conn, $string, strlen($string)); + + return $bytes_sent; + } + + /** + * Checks the POP3 server response for +OK or -ERR + * @access private + * @param string $string + * @return boolean + */ + private function checkResponse ($string) { + if (substr($string, 0, 3) !== '+OK') { + $this->error = array( + 'error' => "Server reported an error: $string", + 'errno' => 0, + 'errstr' => '' + ); + + if ($this->do_debug >= 1) { + $this->displayErrors(); + } + + return false; + } else { + return true; + } + + } + + /** + * If debug is enabled, display the error message array + * @access private + */ + private function displayErrors () { + echo '
';
+
+    foreach ($this->error as $single_error) {
+      print_r($single_error);
+    }
+
+    echo '
'; + } + + /** + * Takes over from PHP for the socket warning handler + * @access private + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + */ + private function catchWarning ($errno, $errstr, $errfile, $errline) { + $this->error[] = array( + 'error' => "Connecting to the POP3 server raised a PHP warning: ", + 'errno' => $errno, + 'errstr' => $errstr + ); + } + + // End of class +} diff --git a/resources/PHPMailer/class.smtp.php b/resources/PHPMailer/class.smtp.php new file mode 100644 index 000000000..8444eb895 --- /dev/null +++ b/resources/PHPMailer/class.smtp.php @@ -0,0 +1,1088 @@ +Debugoutput == 'error_log') { + error_log($str); + } else { + echo $str; + } + } + + /** + * Initialize the class so that the data is in a known state. + * @access public + * @return SMTP + */ + public function __construct() { + $this->smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to the server specified on the port specified. + * If the port is not specified use the default SMTP_PORT. + * If tval is specified then a connection will try and be + * established with the server for that number of seconds. + * If tval is not specified the default is 30 seconds to + * try on the connection. + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @param string $host + * @param int $port + * @param int $tval + * @return bool + */ + public function Connect($host, $port = 0, $tval = 30) { + // set the error val to null so there is no confusion + $this->error = null; + + // make sure we are __not__ connected + if($this->connected()) { + // already connected, generate error + $this->error = array('error' => 'Already connected to a server'); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // connect to the smtp server + $this->smtp_conn = @fsockopen($host, // the host of the server + $port, // the port to use + $errno, // error number if any + $errstr, // error message if any + $tval); // give up after ? secs + // verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array('error' => 'Failed to connect to server', + 'errno' => $errno, + 'errstr' => $errstr); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ": $errstr ($errno)" . $this->CRLF . '
'); + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != 'WIN') { + $max = ini_get('max_execution_time'); + if ($max != 0 && $tval > $max) { // don't bother if unlimited + @set_time_limit($tval); + } + stream_set_timeout($this->smtp_conn, $tval, 0); + } + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $announce . $this->CRLF . '
'); + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array('error' => 'Called StartTLS() without being connected'); + return false; + } + + $this->client_send('STARTTLS' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 220) { + $this->error = + array('error' => 'STARTTLS not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @param string $username + * @param string $password + * @param string $authtype + * @param string $realm + * @param string $workstation + * @return bool + */ + public function Authenticate($username, $password, $authtype='LOGIN', $realm='', $workstation='') { + if (empty($authtype)) { + $authtype = 'LOGIN'; + } + + switch ($authtype) { + case 'PLAIN': + // Start authentication + $this->client_send('AUTH PLAIN' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + // Send encoded username and password + $this->client_send(base64_encode("\0".$username."\0".$password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Authentication not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + break; + case 'LOGIN': + // Start authentication + $this->client_send('AUTH LOGIN' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + // Send encoded username + $this->client_send(base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'Username not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + // Send encoded password + $this->client_send(base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Password not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + break; + case 'NTLM': + /* + * ntlm_sasl_client.php + ** Bundled with Permission + ** + ** How to telnet in windows: http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx + ** PROTOCOL Documentation http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication + */ + require_once 'extras/ntlm_sasl_client.php'; + $temp = new stdClass(); + $ntlm_client = new ntlm_sasl_client_class; + if(! $ntlm_client->Initialize($temp)){//let's test if every function its available + $this->error = array('error' => $temp->error); + if($this->do_debug >= 1) { + $this->edebug('You need to enable some modules in your php.ini file: ' . $this->error['error'] . $this->CRLF); + } + return false; + } + $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);//msg1 + + $this->client_send('AUTH NTLM ' . base64_encode($msg1) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF); + } + return false; + } + + $challenge = substr($rply, 3);//though 0 based, there is a white space after the 3 digit number....//msg2 + $challenge = base64_decode($challenge); + $ntlm_res = $ntlm_client->NTLMResponse(substr($challenge, 24, 8), $password); + $msg3 = $ntlm_client->TypeMsg3($ntlm_res, $username, $realm, $workstation);//msg3 + // Send encoded username + $this->client_send(base64_encode($msg3) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Could not authenticate', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF); + } + return false; + } + break; + case 'CRAM-MD5': + // Start authentication + $this->client_send('AUTH CRAM-MD5' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + // Get the challenge + $challenge = base64_decode(substr($rply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // Send encoded credentials + $this->client_send(base64_encode($response) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'Credentials not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + break; + } + return true; + } + + /** + * Works like hash_hmac('md5', $data, $key) in case that function is not available + * @access private + * @param string $data + * @param string $key + * @return string + */ + private function hmac($data, $key) { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // Hacked by Lance Rushing + + $b = 64; // byte length for md5 + if (strlen($key) > $b) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $b, chr(0x00)); + $ipad = str_pad('', $b, chr(0x36)); + $opad = str_pad('', $b, chr(0x5c)); + $k_ipad = $key ^ $ipad ; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if($sock_status['eof']) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + $this->edebug('SMTP -> NOTICE:' . $this->CRLF . 'EOF caught while checking if connected'); + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being seperated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552, 554, 451, 452 + * SMTP CODE FAILURE: 451, 554 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @access public + * @param string $msg_data + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Data() without being connected'); + return false; + } + + $this->client_send('DATA' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 354) { + $this->error = + array('error' => 'DATA command not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n", "\n", $msg_data); + $msg_data = str_replace("\r", "\n", $msg_data); + $lines = explode("\n", $msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if(!empty($field) && !strstr($field, ' ')) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(, $line) = @each($lines)) { + $lines_out = null; + if($line == '' && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line, 0, $max_line_length), ' '); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(, $line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == '.') { + $line_out = '.' . $line_out; + } + } + $this->client_send($line_out . $this->CRLF); + } + } + + // message data has been sent + $this->client_send($this->CRLF . '.' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 250) { + $this->error = + array('error' => 'DATA not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @param string $host + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Hello() without being connected'); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = 'localhost'; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello('EHLO', $host)) { + if(!$this->SendHello('HELO', $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access private + * @param string $hello + * @param string $host + * @return bool + */ + private function SendHello($hello, $host) { + $this->client_send($hello . ' ' . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER: ' . $rply . $this->CRLF . '
'); + } + + if($code != 250) { + $this->error = + array('error' => $hello . ' not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 421 + * @access public + * @param string $from + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Mail() without being connected'); + return false; + } + + $useVerp = ($this->do_verp ? ' XVERP' : ''); + $this->client_send('MAIL FROM:<' . $from . '>' . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 250) { + $this->error = + array('error' => 'MAIL not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @param bool $close_on_error + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Quit() without being connected'); + return false; + } + + // send the quit command to the server + $this->client_send('quit' . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $byemsg . $this->CRLF . '
'); + } + + $rval = true; + $e = null; + + $code = substr($byemsg, 0, 3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array('error' => 'SMTP server rejected quit command', + 'smtp_code' => $code, + 'smtp_rply' => substr($byemsg, 4)); + $rval = false; + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $e['error'] . ': ' . $byemsg . $this->CRLF . '
'); + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250, 251 + * SMTP CODE FAILURE: 550, 551, 552, 553, 450, 451, 452 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @access public + * @param string $to + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Recipient() without being connected'); + return false; + } + + $this->client_send('RCPT TO:<' . $to . '>' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 250 && $code != 251) { + $this->error = + array('error' => 'RCPT not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array('error' => 'Called Reset() without being connected'); + return false; + } + + $this->client_send('RSET' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 250) { + $this->error = + array('error' => 'RSET failed', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 502, 421 + * @access public + * @param string $from + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called SendAndMail() without being connected'); + return false; + } + + $this->client_send('SAML FROM:' . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply . $this->CRLF . '
'); + } + + if($code != 250) { + $this->error = + array('error' => 'SAML not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply . $this->CRLF . '
'); + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array('error' => 'This method, TURN, of the SMTP '. + 'is not implemented'); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> NOTICE: ' . $this->error['error'] . $this->CRLF . '
'); + } + return false; + } + + /** + * Sends data to the server + * @param string $data + * @access public + * @return Integer number of bytes sent to the server or FALSE on error + */ + public function client_send($data) { + if ($this->do_debug >= 1) { + $this->edebug("CLIENT -> SMTP: $data" . $this->CRLF . '
'); + } + return fwrite($this->smtp_conn, $data); + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access private + * @return string + */ + private function get_lines() { + $data = ''; + $endtime = 0; + /* If for some reason the fp is bad, don't inf loop */ + if (!is_resource($this->smtp_conn)) { + return $data; + } + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + while(is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + $str = @fgets($this->smtp_conn, 515); + if($this->do_debug >= 4) { + $this->edebug("SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
'); + $this->edebug("SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
'); + } + $data .= $str; + if($this->do_debug >= 4) { + $this->edebug("SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '
'); + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str, 3, 1) == ' ') { break; } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + if($this->do_debug >= 4) { + $this->edebug('SMTP -> get_lines(): timed-out (' . $this->Timeout . ' seconds)
'); + } + break; + } + // Now check if reads took too long + if ($endtime) { + if (time() > $endtime) { + if($this->do_debug >= 4) { + $this->edebug('SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' seconds)
'); + } + break; + } + } + } + return $data; + } + +} From 7181ed4f1c18ed8e4d3fe9dc983a9e3ff241d905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 19:34:37 +0200 Subject: [PATCH 0020/1200] Add a new configuration item to handle mail sending. re #53 --- config_default.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/config_default.php b/config_default.php index ac5e721d3..10b1400c7 100644 --- a/config_default.php +++ b/config_default.php @@ -167,4 +167,32 @@ */ $config['cops_provide_kepub'] = "0"; + /* + * Enable and configure Send To Kindle (or Email) feature. + * + * Don't forget to authorize the sender email you configured in your Kindle's Approved Personal Document E-mail List. + * + * If you want to use a simple smtp server (provided by your ISP for example), you can configure it like that : + * $config['cops_mail_configuration'] = array( "smtp.host" => "smtp.free.fr", + * "smtp.username" => "", + * "smtp.password" => "", + * "smtp.secure" => "", + * "address.from" => "cops@slucas.fr" + * ); + * + * For Gmail : + * $config['cops_mail_configuration'] = array( "smtp.host" => "smtp.gmail.com", + * "smtp.username" => "YOUR GMAIL ADRESS", + * "smtp.password" => "YOUR GMAIL PASSWORD", + * "smtp.secure" => "ssl", + * "address.from" => "cops@slucas.fr" + * ); + */ + $config['cops_mail_configuration'] = array( "smtp.host" => "smtp.free.fr", + "smtp.username" => "", + "smtp.password" => "", + "smtp.secure" => "", + "address.from" => "cops@slucas.fr" + ); + ?> \ No newline at end of file From 326c17676eb0841ab25b52fd0fa3ded5570f3a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 19:36:48 +0200 Subject: [PATCH 0021/1200] Add a new file to handle mail sending. re #53 --- sendtomail.php | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 sendtomail.php diff --git a/sendtomail.php b/sendtomail.php new file mode 100644 index 000000000..782fcccf5 --- /dev/null +++ b/sendtomail.php @@ -0,0 +1,68 @@ +getDataById ($idData); + +if (filesize ($data->getLocalPath ()) > 10 * 1024 * 1024) { + echo 'Attachement too big'; + exit; +} + +$mail = new PHPMailer; + +$mail->IsSMTP(); +$mail->Timeout = 30; // 30 seconds as some files can be big +$mail->Host = $config['cops_mail_configuration']["smtp.host"]; +if (!empty ($config['cops_mail_configuration']["smtp.secure"])) { + $mail->SMTPSecure = $config['cops_mail_configuration']["smtp.secure"]; + $mail->Port = 465; +} +$mail->SMTPAuth = !empty ($config['cops_mail_configuration']["smtp.username"]); +if (!empty ($config['cops_mail_configuration']["smtp.username"])) $mail->Username = $config['cops_mail_configuration']["smtp.username"]; +if (!empty ($config['cops_mail_configuration']["smtp.password"])) $mail->Password = $config['cops_mail_configuration']["smtp.password"]; +if (!empty ($config['cops_mail_configuration']["smtp.secure"])) $mail->SMTPSecure = $config['cops_mail_configuration']["smtp.secure"]; + +$mail->From = $config['cops_mail_configuration']["address.from"]; +$mail->FromName = $config['cops_title_default']; + +$mail->AddAddress($emailDest); + +$mail->AddAttachment($data->getLocalPath ()); + +$mail->IsHTML(false); +$mail->Subject = 'Sent by COPS'; +$mail->Body = 'Sent by COPS'; + +if(!$mail->Send()) { + echo 'Message could not be sent.'; + echo 'Mailer Error: ' . $mail->ErrorInfo; + exit; +} + +echo 'Message has been sent'; + + +?> \ No newline at end of file From a9a8487edb6e38147864e25fb379ae73af466da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 21:58:27 +0200 Subject: [PATCH 0022/1200] Seems to be working again. --- index.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/index.php b/index.php index 8236aceab..b03e69d97 100644 --- a/index.php +++ b/index.php @@ -277,9 +277,7 @@ ?>
- " alt="" /> - - + ">" alt="" /> isPaginated ()) { ?> From a3f7aab3085a06c1562f0a929620eca55f8db83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 15 May 2013 22:09:28 +0200 Subject: [PATCH 0023/1200] Fix some stuff again. Lots of work remaining. --- bookdetail.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookdetail.php b/bookdetail.php index 8bce647ca..57e30904a 100644 --- a/bookdetail.php +++ b/bookdetail.php @@ -20,7 +20,7 @@ -
+
@@ -47,7 +47,7 @@ -

" alt="permalink" />title) ?>

+

" alt="" />title) ?>

:

From feb6876b4a6d3d4eaf8be333a83bc21b84bb0d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 06:37:30 +0200 Subject: [PATCH 0024/1200] Fix about box with fancybox. --- about.xml | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/about.xml b/about.xml index 35043edf5..7b2acf3fc 100644 --- a/about.xml +++ b/about.xml @@ -1,29 +1,21 @@ -

-
Authors
-
+
+

About COPS

+

Authors

COPS is developped and maintained by Sébastien Lucas.

See full history on Github to check all authors.

COPS use some external librairies, check README for the details.

-
-
Copyright
-
+

Copyright

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.

The complete content of license is provided in file COPYING within distribution and also available online.

-
-
Contact
-
+

Contact

For more info please visit COPS Home Page

You can also check COPS's topic on MobileRead forum.

-
-
Thanks
-
+

Thanks

Thanks a lot to Kovid Goyal for Calibre.

And many thanks to all those who helped test COPS.

-
-
\ No newline at end of file From b97aff10cbbbac5ce9d7f987c5ecf313e68b8e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 06:52:22 +0200 Subject: [PATCH 0025/1200] Somehow fix bookdetail --- bookdetail.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookdetail.php b/bookdetail.php index 57e30904a..7d0d9bd64 100644 --- a/bookdetail.php +++ b/bookdetail.php @@ -105,6 +105,6 @@ } ?>
-

-getComment (false) ?>

+

+
>getComment (false) ?>
\ No newline at end of file From 366a48967ede421ba4a40b64de852b3edff7ce83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 06:59:34 +0200 Subject: [PATCH 0026/1200] Fix the toolbox. --- index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index b03e69d97..168f4d983 100644 --- a/index.php +++ b/index.php @@ -160,8 +160,7 @@ " alt="Settings and menu" />

title) ?>

- - +
+
Date: Thu, 16 May 2013 07:00:21 +0200 Subject: [PATCH 0027/1200] reindent --- index.php | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/index.php b/index.php index 168f4d983..611b0251e 100644 --- a/index.php +++ b/index.php @@ -160,42 +160,42 @@ " alt="Settings and menu" />

title) ?>

-
> -
- +
> +
+ +
+ " /> +
+
+ + + + + + +
+ +
+ containsBook ()) { ?> +
- " /> + <?php echo localize (" />
- - - - - - + +
- -
-containsBook ()) { ?> -
-
- <?php echo localize (" /> -
-
- -
+
- -
From 00e597c069b236435ee0473bed70240d257afc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 08:29:53 +0200 Subject: [PATCH 0028/1200] Fix a validation error --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 611b0251e..6f816f342 100644 --- a/index.php +++ b/index.php @@ -226,7 +226,7 @@ book->hasCover) { ?> - <?php echo localize(" /> + <?php echo localize(" /> From 443a5866157605cbb60f14dff14d232b48935ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 12:26:41 +0200 Subject: [PATCH 0029/1200] Fix header so that it's always vertically centered --- index.php | 4 +++- style.css | 21 ++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/index.php b/index.php index 6f816f342..fd37c9221 100644 --- a/index.php +++ b/index.php @@ -159,7 +159,9 @@ " alt="" /> " alt="Settings and menu" /> -

title) ?>

+
+

title) ?>

+
>
diff --git a/style.css b/style.css index ec8347bc6..a1a2df9fb 100644 --- a/style.css +++ b/style.css @@ -79,12 +79,23 @@ header { float: left; } +.headcenter +{ +float:none; +margin:auto; +text-align:center; +height:70px; +display:table; +} + header h1{ - float:none; - padding-top:25px; -text-shadow: 0px 4px 3px rgba(0,0,0,0.4), - 0px 8px 13px rgba(0,0,0,0.1), - 0px 18px 23px rgba(0,0,0,0.1); + display: table-cell; + vertical-align: middle; + text-align: center; + line-height: 100%; + text-shadow: 0px 4px 3px rgba(0,0,0,0.4), + 0px 8px 13px rgba(0,0,0,0.1), + 0px 18px 23px rgba(0,0,0,0.1); } .headright { From 4649f711155d092ca47d9f9cc5e664ef12d71fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 12:30:29 +0200 Subject: [PATCH 0030/1200] Tab to spaces. --- style.css | 130 +++++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/style.css b/style.css index a1a2df9fb..25378fc57 100644 --- a/style.css +++ b/style.css @@ -1,30 +1,30 @@ -/* Global Box Sizing and Font-Smoothing */ +/* Global Box Sizing and Font-Smoothing */ *, *:after, *:before { - box-sizing:border-box; - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - -webkit-font-smoothing:antialiased; - -moz-font-smoothing:antialiased; - -o-font-smoothing:antialiased; - font-smoothing:antialiased; - text-rendering:optimizeLegibility; + box-sizing:border-box; + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-font-smoothing:antialiased; + -moz-font-smoothing:antialiased; + -o-font-smoothing:antialiased; + font-smoothing:antialiased; + text-rendering:optimizeLegibility; } html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { - font-family: 'Open Sans', sans-serif; - line-height:18px; - color: #1c1c1c; /* Lighter on the eyes than #000 Black */ - margin: 0px; background: #cccccc; background-size: cover; + font-family: 'Open Sans', sans-serif; + line-height:18px; + color: #1c1c1c; /* Lighter on the eyes than #000 Black */ + margin: 0px; background: #cccccc; background-size: cover; } img { - border: 0px; - max-width: 100%; - height: auto; - width: auto\9; /* ie8 */ + border: 0px; + max-width: 100%; + height: auto; + width: auto\9; /* ie8 */ } /* ========================================================================== @@ -57,26 +57,26 @@ border:0; ========================================================================== */ .container{ background: #414141; border:1px solid #000; border-radius:10px; - max-width:800px;width:95%;margin:0 auto;position:relative; } - + max-width:800px;width:95%;margin:0 auto;position:relative; } + .fancyabout { font-weight:400; text-decoration: none; } /* ============================================================================= Header stuff goes here ========================================================================== */ header { - clear:both; - color:white; - text-align:center; - text-transform:uppercase; - display:block; - box-shadow:inset 0px -5px 8px #000000; - min-height:70px; - border-radius: 10px 10px 0px 0px; + clear:both; + color:white; + text-align:center; + text-transform:uppercase; + display:block; + box-shadow:inset 0px -5px 8px #000000; + min-height:70px; + border-radius: 10px 10px 0px 0px; } .headleft { - float: left; + float: left; } .headcenter @@ -99,7 +99,7 @@ header h1{ } .headright { - float: right; + float: right; } /* ============================================================================= @@ -118,70 +118,70 @@ max-width:800px; /*-------------frontpage article-------------*/ .frontpage h2 { - padding: 5px 0 0 5px; + padding: 5px 0 0 5px; } .frontpage h4 { - padding: 5px 0 5px 20px; - font-style: italic; + padding: 5px 0 5px 20px; + font-style: italic; } /*-------------books article-------------*/ .books { - clear: both; - padding: 10px; - min-height: 90px; + clear: both; + padding: 10px; + min-height: 90px; } .cover { - float:left; - margin: 0 10px 0 0; - min-width: 56px; - min-height: 70px; + float:left; + margin: 0 10px 0 0; + min-width: 56px; + min-height: 70px; } .download { - float: right; - line-height:40px; - text-align: right; + float: right; + line-height:40px; + text-align: right; } .download a { - border-radius: 6px; - box-shadow:0 0 10px #000; - background: darkgray; - background: radial-gradient(#666, black); - color: #EEE; - text-decoration : none; - font-weight: bold; - padding: 5px 10px 5px 10px; + border-radius: 6px; + box-shadow:0 0 10px #000; + background: darkgray; + background: radial-gradient(#666, black); + color: #EEE; + text-decoration : none; + font-weight: bold; + padding: 5px 10px 5px 10px; } .books h4{ - display: inline; - font-style: italic; + display: inline; + font-style: italic; } /*-------------books popup article-------------*/ .bookpopup h2{ - margin: 15px 15px; + margin: 15px 15px; } .bookpopup h3{ - display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; + display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; } .bookpopup p{ - display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; + display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; } .bookpopup h4{ - border-top: 1px solid black; + border-top: 1px solid black; } .fullclickpopup{ - display: block; + display: block; } /* ============================================================================= Footer stuff goes here @@ -195,7 +195,7 @@ height:32px; } footer a{ - float: right; + float: right; } /* ============================================================================= Aside stuff goes here @@ -252,17 +252,17 @@ h1 {font-size: 1.2em;} /* 480px and greater */ @media only screen and (min-width: 480px) { h1 {font-size: 1.5em;} -body { font-size: 1em;/*12px/16px */ - font-weight:450; /* Better supported than 'lighter' attribute */ - } +body { font-size: 1em;/*12px/16px */ + font-weight:450; /* Better supported than 'lighter' attribute */ + } } /* 810px and greater */ @media only screen and (min-width: 768px) { h1 {font-size: 2em;} .container { box-shadow:0 0 20px #000; } -body { margin: 5px; - font-size: 0.85em;/*12px/16px */ - font-weight:400; /* Better supported than 'lighter' attribute */ - } +body { margin: 5px; + font-size: 0.85em;/*12px/16px */ + font-weight:400; /* Better supported than 'lighter' attribute */ + } } \ No newline at end of file From 7640fc5e50f4f5aceaa7c247c09cae3ba4d91820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 12:30:43 +0200 Subject: [PATCH 0031/1200] Fix   --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index fd37c9221..39478a8ce 100644 --- a/index.php +++ b/index.php @@ -292,7 +292,7 @@ -

n . " / " . $currentPage->getMaxPage () . " " ?>

+

n . " / " . $currentPage->getMaxPage () . " " ?>

From 54b3a86c987c736c4418e7aea2a04202399e4068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 12:41:22 +0200 Subject: [PATCH 0032/1200] Somehow fix paging ui. --- index.php | 2 ++ style.css | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index 39478a8ce..ee8659519 100644 --- a/index.php +++ b/index.php @@ -279,7 +279,9 @@ ?>
+ isPaginated ()) { ?> diff --git a/style.css b/style.css index 25378fc57..69c224844 100644 --- a/style.css +++ b/style.css @@ -189,14 +189,34 @@ max-width:800px; footer { clear:both; +color:white; box-shadow:inset 0px 5px 8px #000000; border-radius: 0px 0px 10px 10px; height:32px; } -footer a{ - float: right; +.footright +{ +float:right; +height:32px; +} + +.footcenter +{ +margin:auto; +text-align:center; +height:32px; +display:table; +} + +.footcenter p, .footcenter a +{ +display: table-cell; +vertical-align: middle; +text-align: center; +line-height: 100%; } + /* ============================================================================= Aside stuff goes here ========================================================================== */ From 5f240d2de0aecdec1055cc4e3320a52274964fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 13:32:02 +0200 Subject: [PATCH 0033/1200] Fix the toolbox color --- style.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/style.css b/style.css index 69c224844..d1aa6062f 100644 --- a/style.css +++ b/style.css @@ -222,8 +222,6 @@ line-height: 100%; ========================================================================== */ #tool { -color:white; -background: #414141; width:100%; height:32px; overflow: hidden; From 9c1e08b9ca3456e6ef6898eb17d5d059c8ad7af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 13:35:24 +0200 Subject: [PATCH 0034/1200] Fix display when no cover is found --- index.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index ee8659519..d5715eb28 100644 --- a/index.php +++ b/index.php @@ -225,13 +225,15 @@ { ?>
+ book->hasCover) { ?> - <?php echo localize(" /> + <?php echo localize(" /> +

Date: Thu, 16 May 2013 13:40:44 +0200 Subject: [PATCH 0035/1200] Remove visited color. Don't like that --- style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/style.css b/style.css index d1aa6062f..985b12c50 100644 --- a/style.css +++ b/style.css @@ -40,7 +40,6 @@ h6 {font-size: 0.75em;} a:hover, a:active { outline: none; } a { color: #000066; font-weight: 800; text-decoration: none;} -a:visited { color:#414141 ; } a:hover { color:#000; text-decoration: none; } .frontpage a:hover { display:inline-block; width: 100%; background-color: #778899; /*Dirty IE Hack*/ zoom: 1; *display: inline;} .link a:hover { display:inline-block; width: 100%; background-color: #778899; /*Dirty IE Hack*/ zoom: 1; *display: inline;} From f2d0fb37267f6adb74b425ac0145b711f10825e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 13:59:53 +0200 Subject: [PATCH 0036/1200] Fix bookpopup. --- bookdetail.php | 23 +++++++++++++---------- style.css | 3 --- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bookdetail.php b/bookdetail.php index 7d0d9bd64..db29bca83 100644 --- a/bookdetail.php +++ b/bookdetail.php @@ -48,9 +48,9 @@ } ?>

" alt="" />title) ?>

- -

:

+

:

+ -


+

0) { ?> -

:

-

+

+

:

+ -


+

+

:

seriesIndex, htmlspecialchars ($serie->name)) ?> -
+

getPubDate() != "") { ?> - +

:

getPubDate() ?> - +

getLanguages () != "") { ?> -
+

:

getLanguages () ?> +

diff --git a/style.css b/style.css index 985b12c50..117823251 100644 --- a/style.css +++ b/style.css @@ -171,9 +171,6 @@ max-width:800px; display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; } -.bookpopup p{ - display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; -} .bookpopup h4{ border-top: 1px solid black; From da103d57db05cf5d594ade3f12dea74de0c68781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 14:00:28 +0200 Subject: [PATCH 0037/1200] Tab to space again --- bookdetail.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bookdetail.php b/bookdetail.php index db29bca83..66dbc1de5 100644 --- a/bookdetail.php +++ b/bookdetail.php @@ -28,7 +28,7 @@ - + hasCover) { ?> @@ -38,19 +38,19 @@ - + getDatas() as $data) { ?> -

format ?>

+

format ?>

-

" alt="" />title) ?>

-

-

:

- +

" alt="" />title) ?>

+

+

:

+

-

:

+

:

seriesIndex, htmlspecialchars ($serie->name)) ?>

Date: Thu, 16 May 2013 14:10:48 +0200 Subject: [PATCH 0038/1200] Bookdetail is better when fancybox is disabled. --- bookdetail.php | 10 ---------- style.css | 6 +++++- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/bookdetail.php b/bookdetail.php index 66dbc1de5..f8e367698 100644 --- a/bookdetail.php +++ b/bookdetail.php @@ -16,18 +16,8 @@ $serie = $book->getSerie (); $book->getLinkArray (); -?> - -
-
- hasCover) { diff --git a/style.css b/style.css index 117823251..3c6bc5b1c 100644 --- a/style.css +++ b/style.css @@ -171,7 +171,6 @@ max-width:800px; display:inline; /*Dirty IE Hack*/ zoom: 1; *display: inline; } - .bookpopup h4{ border-top: 1px solid black; } @@ -179,6 +178,11 @@ max-width:800px; .fullclickpopup{ display: block; } + +section .bookpopup{ + padding: 8px 8px; +} + /* ============================================================================= Footer stuff goes here ========================================================================== */ From 66e8b3ddfad2c05967d8622d62036641a1a5fa4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 14:26:16 +0200 Subject: [PATCH 0039/1200] Fix about box when Fancybox is disabled. --- about.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/about.xml b/about.xml index 7b2acf3fc..5c867d07c 100644 --- a/about.xml +++ b/about.xml @@ -1,4 +1,4 @@ -
+

About COPS

Authors

COPS is developped and maintained by Sébastien Lucas.

From e412515a4f2f9ca55f7bc9f57581de0888a2632e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 14:41:24 +0200 Subject: [PATCH 0040/1200] Almost fixed checkconfig. --- checkconfig.php | 85 +++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/checkconfig.php b/checkconfig.php index 8a36162f5..5713922cc 100644 --- a/checkconfig.php +++ b/checkconfig.php @@ -10,7 +10,7 @@ require_once ("config.php"); require_once ("base.php"); - header ("Content-Type:application/xhtml+xml"); + header ("Content-Type:text/html; charset=UTF-8"); $err = getURLParam ("err", -1); $error = NULL; @@ -21,38 +21,35 @@ } ?> - - + + - - COPS Configuration Check " media="screen" />
-
+
-

COPS Configuration Check

+

COPS Configuration Check

-
-
+ -
+
-
-
You've been redirected because COPS is not configured properly
-
-
+
+

You've been redirected because COPS is not configured properly

+

+
-
-
Check if GD is properly installed and loaded
-
+
+

Check if GD is properly installed and loaded

+

-

-
-
-
Check if Sqlite is properly installed and loaded
-
+

+
+
+

Check if Sqlite is properly installed and loaded

+

-

-
-
-
Check if libxml is properly installed and loaded
-
+ + +
+

Check if libxml is properly installed and loaded

+

-

-
+ + $database) { ?> -
-
Check if Calibre database file exists and is readable
-
+
+

Check if Calibre database file exists and is readable

"; } ?> -
-
-
-
Check if Calibre database file can be opened with PHP
-
+ +
+

Check if Calibre database file can be opened with PHP

+

-

-
-
-
Check if Calibre database file contains at least some of the needed tables
-
+ + +
+

Check if Calibre database file contains at least some of the needed tables

+

-

-
+ + -
+ From c629603056dcbf8aae7aa556dddd53e4f612b6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 14:42:56 +0200 Subject: [PATCH 0041/1200] Add a footer. --- checkconfig.php | 1 + 1 file changed, 1 insertion(+) diff --git a/checkconfig.php b/checkconfig.php index 5713922cc..681975d09 100644 --- a/checkconfig.php +++ b/checkconfig.php @@ -137,6 +137,7 @@ +
From 4a08c8df0711e7d68d8f165f0ed1143223b5552c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 17:09:29 +0200 Subject: [PATCH 0042/1200] Change the way to handle smartphones --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index d5715eb28..875b9bab1 100644 --- a/index.php +++ b/index.php @@ -51,7 +51,7 @@ - + <?php echo htmlspecialchars ($currentPage->title) ?> From 6e9654ad34be47ecc173e05b4f2eb58a671e5b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 17:12:19 +0200 Subject: [PATCH 0043/1200] Typo --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 875b9bab1..3fc48f95a 100644 --- a/index.php +++ b/index.php @@ -51,7 +51,7 @@ - + <?php echo htmlspecialchars ($currentPage->title) ?> From 20e88d7ecc83af2f36b1f510199edd6f8dc371cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 17:22:21 +0200 Subject: [PATCH 0044/1200] Small fixes to UI again --- checkconfig.php | 2 +- index.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/checkconfig.php b/checkconfig.php index 681975d09..4b81e4fba 100644 --- a/checkconfig.php +++ b/checkconfig.php @@ -24,7 +24,7 @@ - + COPS Configuration Check " media="screen" /> diff --git a/index.php b/index.php index 3fc48f95a..fc0ace311 100644 --- a/index.php +++ b/index.php @@ -50,7 +50,6 @@ - <?php echo htmlspecialchars ($currentPage->title) ?> From a2d59357d5d7ad032c4e01517858b7f52a708605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 16 May 2013 17:51:09 +0200 Subject: [PATCH 0045/1200] Fix book sorting --- index.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/index.php b/index.php index fc0ace311..ad558005a 100644 --- a/index.php +++ b/index.php @@ -73,7 +73,7 @@ }); $("#sort").click(function(){ - $('.book').sortElements(function(a, b){ + $('.books').sortElements(function(a, b){ var test = 1; if ($("#sortorder").val() == "desc") { @@ -81,7 +81,6 @@ } return $(a).find ("." + $("#sortchoice").val()).text() > $(b).find ("." + $("#sortchoice").val()).text() ? test : -test; }); - $("#search").slideUp(); }); @@ -251,23 +250,23 @@
-

title) ?> +

title) ?> book->getPubDate() != "") { ?> - (book->getPubDate() ?>) + (book->getPubDate() ?>) - book->getRating () ?>

-

" . htmlspecialchars ($entry->book->getAuthorsName ()) ?>
+ book->getRating () ?>

+

book->getAuthorsName ()) ?>

" . htmlspecialchars ($entry->book->getTagsName ()) ?>
book->getSerie (); if (!is_null ($serie)) { ?> -

" . htmlspecialchars ($serie->name) . " (" . $entry->book->seriesIndex . ")" ?>
+

name) . " (" . $entry->book->seriesIndex . ")" ?>
From 7f797e25d158c1c6ddcfe22a332275cfc12df35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Fri, 17 May 2013 06:36:38 +0200 Subject: [PATCH 0046/1200] Include a local version of normalize. --- index.php | 32 +-- resources/normalize/normalize.css | 396 ++++++++++++++++++++++++++++++ 2 files changed, 405 insertions(+), 23 deletions(-) create mode 100644 resources/normalize/normalize.css diff --git a/index.php b/index.php index ad558005a..157f1d119 100644 --- a/index.php +++ b/index.php @@ -62,15 +62,11 @@ " media="screen" /> - + " /> + " media="screen" /> " /> - + + + + " /> + + + +
+
+ + " alt="" /> + + " alt="Settings and menu" /> +
+

Customize COPS UI

+
+
+ +
+
+

Style

+

+
+
+

Use Fancybox

+

/>

+
+
+ +
+
+
+ + diff --git a/images/theme.png b/images/theme.png new file mode 100644 index 0000000000000000000000000000000000000000..e8f4e8e9bda5dcb7f4b0ac041d6b1a0a3836f5a1 GIT binary patch literal 714 zcmV;*0yX`KP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv0006xNkl7{~EBHY#ecOLgfI zQ7|Y5br}Th7Qs5CqM`>6il!h6!o;a4&M8dtFR3}0ps28+f~K2jinHyqLt}KTTZhO? z5FH|#Pmk~nMBa|i4t?RntMBtWic|kphlhvHkB*K~_*9`;dVMh+92`7B!+yUHBl!8G z*Xv!SGk@J~SC4)oIM?ZP#B}DZ*=#N#S-ai7-fFd+=qm!WNE5#5_4-Vs(Kz1Q+XH-l z!tVxr&hPKCM1H^jOCS({2~C>t=Jk3P*Vor!!Y`lCcb6u-xm>OpkH@p@cDvuA zAp+#3Gktitle) ?> - + " media="screen" /> @@ -80,7 +80,7 @@ }); }); - + $(".fancycover").fancybox({ 'type' : 'image', prevEffect : 'none', @@ -279,8 +279,11 @@ ?>