From: Roman Bazalevsky Date: Thu, 19 May 2016 17:02:10 +0000 (+0300) Subject: 1) Исправления в связи со сменой API MySQL X-Git-Url: https://git.rvb.name/openlib.git/commitdiff_plain/6b3a07a008979ee27733a2deae2ff4fc42f4a535 1) Исправления в связи со сменой API MySQL 2) В схему добавлены все хранимые процедуры 3) Добавлен веб-интерфейс --- diff --git a/db.py b/db.py index a39c160..d3a4912 100644 --- a/db.py +++ b/db.py @@ -6,7 +6,7 @@ import ConfigParser def SortName(name): if database: c = database.cursor() - c.execute('SELECT metadata.SortStr(%s)', (name)) + c.execute('SELECT metadata.SortStr(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -15,7 +15,7 @@ def SortName(name): def SortAuthorName(name): if database: c = database.cursor() - c.execute('SELECT metadata.SortAuthor(%s)', (name)) + c.execute('SELECT metadata.SortAuthor(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -24,7 +24,7 @@ def SortAuthorName(name): def GetOrCreateAuthor(name): if database: c = database.cursor() - c.execute('SELECT metadata.GetOrCreateAuthor(%s)', (name)) + c.execute('SELECT metadata.GetOrCreateAuthor(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -33,7 +33,7 @@ def GetOrCreateAuthor(name): def GetOrCreateLang(name): if database: c = database.cursor() - c.execute('SELECT metadata.GetOrCreateLang(%s)', (name)) + c.execute('SELECT metadata.GetOrCreateLang(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -42,7 +42,7 @@ def GetOrCreateLang(name): def GetOrCreatePublisher(name): if database: c = database.cursor() - c.execute('SELECT metadata.GetOrCreatePublisher(%s)', (name)) + c.execute('SELECT metadata.GetOrCreatePublisher(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -51,7 +51,7 @@ def GetOrCreatePublisher(name): def GetOrCreateSeries(name): if database: c = database.cursor() - c.execute('SELECT metadata.GetOrCreateSeries(%s)', (name)) + c.execute('SELECT metadata.GetOrCreateSeries(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -60,7 +60,7 @@ def GetOrCreateSeries(name): def GetOrCreateTag(name): if database: c = database.cursor() - c.execute('SELECT metadata.GetOrCreateTag(%s)', (name)) + c.execute('SELECT metadata.GetOrCreateTag(%s)', [name]) return c.fetchone()[0] else: print "No connection to DB" @@ -69,7 +69,7 @@ def GetOrCreateTag(name): def CreateBook(title,pubdate,series_index,isbn): if database: c = database.cursor() - c.execute('SELECT metadata.CreateBook(%s,%s,%s,%s)', (title,pubdate,series_index,isbn)) + c.execute('SELECT metadata.CreateBook(%s,%s,%s,%s)', [title,pubdate,series_index,isbn]) return c.fetchone()[0] else: print "No connection to DB" @@ -79,7 +79,7 @@ def LinkBookToAuthors(book_id,author_ids): if database: c = database.cursor() for author_id in author_ids: - c.execute('INSERT INTO metadata.books_authors_link(book,author) VALUES (%s,%s)', (book_id,author_id)) + c.execute('INSERT INTO metadata.books_authors_link(book,author) VALUES (%s,%s)', [book_id,author_id]) else: print "No connection to DB" exit() @@ -90,7 +90,7 @@ def LinkBookToLangs(book_id,lang_ids): io = 0 for lang_id in lang_ids: io = io + 1 - c.execute('INSERT INTO metadata.books_languages_link(book,lang_code,item_order) VALUES (%s,%s,%s)', (book_id,lang_id,io)) + c.execute('INSERT INTO metadata.books_languages_link(book,lang_code,item_order) VALUES (%s,%s,%s)', [book_id,lang_id,io]) else: print "No connection to DB" exit() @@ -98,7 +98,7 @@ def LinkBookToLangs(book_id,lang_ids): def LinkBookToPublishers(book_id,pub_id): if database: c = database.cursor() - c.execute('INSERT INTO metadata.books_publishers_link(book,publisher) VALUES (%s,%s)', (book_id,pub_id)) + c.execute('INSERT INTO metadata.books_publishers_link(book,publisher) VALUES (%s,%s)', [book_id,pub_id]) else: print "No connection to DB" exit() @@ -106,7 +106,7 @@ def LinkBookToPublishers(book_id,pub_id): def LinkBookToSeries(book_id,ser_id): if database: c = database.cursor() - c.execute('INSERT INTO metadata.books_series_link(book,series) VALUES (%s,%s)', (book_id,ser_id)) + c.execute('INSERT INTO metadata.books_series_link(book,series) VALUES (%s,%s)', [book_id,ser_id]) else: print "No connection to DB" exit() @@ -115,7 +115,7 @@ def LinkBookToTags(book_id,tag_ids): if database: c = database.cursor() for tag_id in tag_ids: - c.execute('INSERT INTO metadata.books_tags_link(book,tag) VALUES (%s,%s)', (book_id,tag_id)) + c.execute('INSERT INTO metadata.books_tags_link(book,tag) VALUES (%s,%s)', [book_id,tag_id]) else: print "No connection to DB" exit() @@ -123,8 +123,8 @@ def LinkBookToTags(book_id,tag_ids): def SetPath(book_id,path,dataname,filesize,cover): if database: c = database.cursor() - c.execute('UPDATE metadata.books SET path=%s, has_cover=%s WHERE id=%s', (path,cover,book_id)) - c.execute('INSERT INTO metadata.data(book,format,uncompressed_size,name) values (%s,%s,%s,%s)',(book_id,'FB2',filesize,dataname)) + c.execute('UPDATE metadata.books SET path=%s, has_cover=%s WHERE id=%s', [path,cover,book_id]) + c.execute('INSERT INTO metadata.data(book,format,uncompressed_size,name) values (%s,%s,%s,%s)',[book_id,'FB2',filesize,dataname]) else: print "No connection to DB" exit() @@ -132,7 +132,7 @@ def SetPath(book_id,path,dataname,filesize,cover): def StoreComment(book_id,comment): if database: c = database.cursor() - c.execute('INSERT INTO metadata.comments(book,text) values (%s,%s)',(book_id,comment)) + c.execute('INSERT INTO metadata.comments(book,text) values (%s,%s)',[book_id,comment]) else: print "No connection to DB" exit() @@ -140,7 +140,7 @@ def StoreComment(book_id,comment): def PathByID(book_id): if database: c = database.cursor() - c.execute('SELECT path FROM metadata.books WHERE id=%s',(book_id)) + c.execute('SELECT path FROM metadata.books WHERE id=%s',[book_id]) return c.fetchone()[0] else: print "No connection to DB" @@ -149,7 +149,7 @@ def PathByID(book_id): def DataByID(book_id,format): if database: c = database.cursor() - c.execute('SELECT name FROM metadata.data WHERE book=%s and format=%s',(book_id,format)) + c.execute('SELECT name FROM metadata.data WHERE book=%s and format=%s',[book_id,format]) return c.fetchone()[0]+'.'+format.lower() else: print "No connection to DB" @@ -158,7 +158,7 @@ def DataByID(book_id,format): def DelBook(book_id): if database: c = database.cursor() - c.execute('DELETE FROM metadata.books WHERE id=%s',(book_id)) + c.execute('DELETE FROM metadata.books WHERE id=%s',[book_id]) else: print "No connection to DB" exit() @@ -166,7 +166,7 @@ def DelBook(book_id): def ChangeBookFormat(book_id,old_format,new_format): if database: c = database.cursor() - c.execute('UPDATE metadata.data SET format=%s WHERE book=%s and format=%s',(new_format,book_id,old_format)) + c.execute('UPDATE metadata.data SET format=%s WHERE book=%s and format=%s',[new_format,book_id,old_format]) else: print "No connection to DB" exit() @@ -174,7 +174,7 @@ def ChangeBookFormat(book_id,old_format,new_format): def TestArchive(name): if database: c = database.cursor() - c.execute('SELECT count(*) from metadata.processed_archives WHERE filename=%s',(name)) + c.execute('SELECT count(*) from metadata.processed_archives WHERE filename=%s',[name]) return c.fetchone()[0] else: print "No connection to DB" @@ -183,7 +183,7 @@ def TestArchive(name): def MarkArchive(name): if database: c = database.cursor() - c.execute('insert into metadata.processed_archives(filename) values (%s)',(name)) + c.execute('insert into metadata.processed_archives(filename) values (%s)',[name]) else: print "No connection to DB" exit() @@ -191,7 +191,7 @@ def MarkArchive(name): def ListByFormat(format,limit=100): if database: c = database.cursor() - c.execute('SELECT DISTINCT book FROM metadata.data WHERE format=%s ORDER BY book LIMIT 0,%s',(format,limit)) + c.execute('SELECT DISTINCT book FROM metadata.data WHERE format=%s ORDER BY book LIMIT 0,%s',[format,limit]) return c.fetchall() else: print "No connection to DB" @@ -200,7 +200,7 @@ def ListByFormat(format,limit=100): def ListDups(limit=100): if database: c = database.cursor() - c.execute('SELECT b.title,l.author,max(b.id) id FROM metadata.books b,metadata.books_authors_link l where b.id=l.book group by b.title,l.author having count(*)>%s',(limit)) + c.execute('SELECT b.title,l.author,max(b.id) id FROM metadata.books b,metadata.books_authors_link l where b.id=l.book group by b.title,l.author having count(*)>%s',[limit]) return c.fetchall() else: print "No connection to DB" @@ -209,7 +209,7 @@ def ListDups(limit=100): def ListByTitleAndAuthor(title,author,id=0): if database: c = database.cursor() - c.execute('SELECT b.id FROM metadata.books b,metadata.books_authors_link l where b.id=l.book and b.title=%s and l.author=%s and b.id<>%s',(title,author,id)) + c.execute('SELECT b.id FROM metadata.books b,metadata.books_authors_link l where b.id=l.book and b.title=%s and l.author=%s and b.id<>%s',[title,author,id]) return c.fetchall() else: print "No connection to DB" diff --git a/get_flibusta.py b/get_flibusta.py index 13768c4..6409847 100755 --- a/get_flibusta.py +++ b/get_flibusta.py @@ -12,7 +12,7 @@ os.environ['no_proxy']='localhost,127.0.0.1' proxies = {'http': 'http://localhost:3128'} -for host in ['flibustahezeous3.onion','flibusta.i2p','flibusta.net']: +for host in ['flibusta.lib','flibustahezeous3.onion','flibusta.i2p']: try: print "Trying %s" % (host) @@ -20,13 +20,13 @@ for host in ['flibustahezeous3.onion','flibusta.i2p','flibusta.net']: html = BeautifulSoup(html_page) os_command = "wget -c -q -P \"%s\" http://%s/daily/%s" % (db.upload_files,host,'%s') - print os_command matched = False for link in html.findAll('a'): file = link.get("href") print file if pattern.match(file): + print "Pattern matched" matched = True if not db.TestArchive(file): print "Processing %s" % file diff --git a/metadata.sql b/metadata.sql index 3e078ad..113734d 100644 --- a/metadata.sql +++ b/metadata.sql @@ -1,8 +1,8 @@ --- MySQL dump 10.13 Distrib 5.5.34, for debian-linux-gnu (x86_64) +-- MySQL dump 10.13 Distrib 5.7.12, for Linux (x86_64) -- -- Host: localhost Database: metadata -- ------------------------------------------------------ --- Server version 5.5.34-0ubuntu0.13.10.1 +-- Server version 5.7.12-0ubuntu1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -29,7 +29,7 @@ CREATE TABLE `authors` ( PRIMARY KEY (`id`), UNIQUE KEY `NAMEIDX` (`name`) USING BTREE, KEY `SORTIDX` (`sort`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=118680 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=140889 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -45,7 +45,7 @@ CREATE TABLE `books` ( `sort` varchar(512) DEFAULT NULL, `timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `pubdate` timestamp NULL DEFAULT NULL, - `series_index` decimal(4,2) DEFAULT '1.00', + `series_index` decimal(10,2) DEFAULT '1.00', `isbn` varchar(45) DEFAULT NULL, `path` varchar(2000) DEFAULT NULL, `uuid` varchar(128) DEFAULT NULL, @@ -56,7 +56,7 @@ CREATE TABLE `books` ( KEY `TITLEIDX` (`title`(255)), KEY `TIMESTAMPIDX` (`timestamp`), KEY `ISBNIDX` (`isbn`) -) ENGINE=InnoDB AUTO_INCREMENT=728948 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=1892665 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -75,8 +75,8 @@ CREATE TABLE `books_authors_link` ( KEY `books_authors_link_aidx` (`author`), KEY `books_authors_link_bidx` (`book`), CONSTRAINT `fk_books_authors_link_auth` FOREIGN KEY (`author`) REFERENCES `authors` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, - CONSTRAINT `fk_books_authors_link_books` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=523239 DEFAULT CHARSET=utf8; + CONSTRAINT `fk_books_authors_link_books` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=2017231 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -95,9 +95,36 @@ CREATE TABLE `books_languages_link` ( UNIQUE KEY `book` (`book`,`lang_code`), KEY `books_languages_link_aidx` (`lang_code`), KEY `books_languages_link_bidx` (`book`), - CONSTRAINT `fk_books_languages_link_book` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `fk_books_languages_link_book` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT `fk_books_languages_link_lang` FOREIGN KEY (`lang_code`) REFERENCES `languages` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=488274 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=1667797 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `books_old` +-- + +DROP TABLE IF EXISTS `books_old`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `books_old` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(512) DEFAULT NULL, + `sort` varchar(512) DEFAULT NULL, + `timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `pubdate` timestamp NULL DEFAULT NULL, + `series_index` decimal(4,2) DEFAULT '1.00', + `isbn` varchar(45) DEFAULT NULL, + `path` varchar(2000) DEFAULT NULL, + `uuid` varchar(128) DEFAULT NULL, + `has_cover` int(11) DEFAULT '0', + `last_modified` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `SORTIDX` (`sort`(255)), + KEY `TITLEIDX` (`title`(255)), + KEY `TIMESTAMPIDX` (`timestamp`), + KEY `ISBNIDX` (`isbn`) +) ENGINE=InnoDB AUTO_INCREMENT=1892532 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -114,9 +141,9 @@ CREATE TABLE `books_publishers_link` ( PRIMARY KEY (`id`), KEY `books_publishers_link_aidx` (`publisher`), KEY `books_publishers_link_bidx` (`book`), - CONSTRAINT `fk_books_publishers_link_book` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `fk_books_publishers_link_book` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT `fk_books_publishers_link_pub` FOREIGN KEY (`publisher`) REFERENCES `publishers` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=204595 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=981343 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -133,9 +160,9 @@ CREATE TABLE `books_series_link` ( PRIMARY KEY (`id`), KEY `books_series_link_aidx` (`series`), KEY `books_series_link_bidx` (`book`), - CONSTRAINT `fk_books_series_link_book` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `fk_books_series_link_book` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT `fk_books_series_link_ser` FOREIGN KEY (`series`) REFERENCES `series` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=179786 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=741933 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -153,9 +180,9 @@ CREATE TABLE `books_tags_link` ( UNIQUE KEY `book` (`book`,`tag`), KEY `books_tags_link_aidx` (`tag`), KEY `books_tags_link_bidx` (`book`), - CONSTRAINT `fk_books_tags_link_book` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `fk_books_tags_link_book` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT `fk_books_tags_link_tag` FOREIGN KEY (`tag`) REFERENCES `tags` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=544808 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=2015931 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -171,8 +198,8 @@ CREATE TABLE `comments` ( `text` varchar(4000) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `book_UNIQUE` (`book`), - CONSTRAINT `fk_comments_book` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=414407 DEFAULT CHARSET=utf8; + CONSTRAINT `fk_comments_book` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=1457254 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -191,8 +218,23 @@ CREATE TABLE `data` ( PRIMARY KEY (`id`), KEY `fk_data_book_idx` (`book`), KEY `format_idx` (`format`), - CONSTRAINT `fk_data_book` FOREIGN KEY (`book`) REFERENCES `books` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION -) ENGINE=InnoDB AUTO_INCREMENT=728543 DEFAULT CHARSET=utf8; + CONSTRAINT `fk_data_book` FOREIGN KEY (`book`) REFERENCES `books_old` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=1892071 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `directories` +-- + +DROP TABLE IF EXISTS `directories`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `directories` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `path` varchar(256) NOT NULL, + `descr` varchar(2000) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -223,7 +265,7 @@ CREATE TABLE `languages` ( `lang_code` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `lang_code_UNIQUE` (`lang_code`) -) ENGINE=InnoDB AUTO_INCREMENT=204 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=217 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -238,7 +280,7 @@ CREATE TABLE `processed_archives` ( `filename` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `filename` (`filename`) -) ENGINE=InnoDB AUTO_INCREMENT=115 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=1035 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -253,7 +295,7 @@ CREATE TABLE `publishers` ( `name` longtext NOT NULL, `sort` longtext, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=26395 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=31291 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -268,7 +310,7 @@ CREATE TABLE `series` ( `name` longtext NOT NULL, `sort` longtext, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=39266 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=46699 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -283,7 +325,7 @@ CREATE TABLE `tags` ( `name` longtext NOT NULL, PRIMARY KEY (`id`), KEY `nameidx` (`name`(32)) -) ENGINE=InnoDB AUTO_INCREMENT=478 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=548 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -298,8 +340,632 @@ CREATE TABLE `tags_mapping` ( `tag_mask` varchar(45) NOT NULL, `tag_result` varchar(45) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=316 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=347 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping events for database 'metadata' +-- + +-- +-- Dumping routines for database 'metadata' +-- +/*!50003 DROP FUNCTION IF EXISTS `CreateBook` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `CreateBook`(title varchar(2000),pubdate datetime,series_index decimal(10,2),isbn varchar(45)) RETURNS int(11) +begin + DECLARE lID INTEGER; + INSERT INTO books(title,sort,pubdate,series_index,isbn) VALUES (title,SortStr(title),pubdate,series_index,isbn); + select last_insert_id() into lID; + RETURN lID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `DeleteDoubleSpaces` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `DeleteDoubleSpaces`( title VARCHAR(250) ) RETURNS varchar(250) CHARSET utf8 + DETERMINISTIC +BEGIN + DECLARE result VARCHAR(250); + SET result = REPLACE( title, ' ', ' ' ); + WHILE (result <> title) DO + SET title = result; + SET result = REPLACE( title, ' ', ' ' ); + END WHILE; + RETURN result; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `GetOrCreateAuthor` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `GetOrCreateAuthor`(pAuthor varchar(255)) RETURNS int(11) +BEGIN + declare lID integer; + select min(ID) into lID from authors where name=pAuthor; + if lID is null then + insert into authors(name,sort) + values(pAuthor,SortAuthor(pAuthor)); + SET lID = last_insert_id(); + end if; +RETURN lID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `GetOrCreateLang` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `GetOrCreateLang`(pLang varchar(255)) RETURNS int(11) +BEGIN + declare lID integer; + declare lLang varchar(255); + select min(lang_code) into lLang from lang_alias where alias=pLang; + if lLang is null then + SET lLang = pLang; + end if; + select min(ID) into lID from languages where lang_code=lLang; + if lID is null then + insert into languages(lang_code) + values(lLang); + SET lID = last_insert_id(); + end if; +RETURN lID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `GetOrCreatePublisher` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `GetOrCreatePublisher`(pPub varchar(255)) RETURNS int(11) +BEGIN + declare lID integer; + select min(ID) into lID from publishers where name=pPub; + if lID is null then + insert into publishers(name,sort) + values(pPub,SortStr(pPub)); + SET lID = last_insert_id(); + end if; +RETURN lID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `GetOrCreateSeries` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `GetOrCreateSeries`(pSeries varchar(255)) RETURNS int(11) +BEGIN + declare lID integer; + select min(ID) into lID from series where name=pSeries; + if lID is null then + insert into series(name,sort) + values(pSeries,SortStr(pSeries)); + SET lID = last_insert_id(); + end if; +RETURN lID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `GetOrCreateTag` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `GetOrCreateTag`(pTag varchar(255)) RETURNS int(11) +BEGIN + declare lID integer; + select min(ID) into lID from tags where name=pTag; + if lID is null then + insert into tags(name) + values(pTag); + SET lID = last_insert_id(); + end if; +RETURN lID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `ProcessLang` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `ProcessLang`(pOldLang varchar(64),pNewLang varchar(64)) RETURNS varchar(2000) CHARSET utf8 +BEGIN + declare Res varchar(2000); + declare lOldID integer; + declare lNewID integer; + select id into lOldID from languages where lang_code=pOldLang; + select id into lNewID from languages where lang_code=pNewLang; + SET Res = CONCAT(lOldID ,' ',lNewID); + insert into metadata.books_languages_link(book,lang_code) + select book,lNewID from metadata.books_languages_link + where lang_code=lOldID + and book not in + (select book from metadata.books_languages_link + where lang_code=lNewID); + delete from metadata.books_languages_link + where lang_code=lOldID; + delete from metadata.languages where id=lOldID; + return Res; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `ProcessMapping` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `ProcessMapping`() RETURNS int(11) +BEGIN + DECLARE counter INTEGER; + DECLARE done INT DEFAULT FALSE; + DECLARE old_tag, new_tag,dummystr VARCHAR(255); + DECLARE cur CURSOR FOR + SELECT t1.name,t2.name FROM metadata.tags t1,metadata.tags t2,metadata.tags_mapping m + where m.tag_result=t2.name and t1.name REGEXP m.tag_mask and t1.id<>t2.id; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; + OPEN cur; + SET counter=0; + tags: loop + FETCH cur into old_tag,new_tag; + IF done THEN + LEAVE tags; + END IF; + SELECT ProcessTags(old_tag,new_tag) INTo dummystr; + SET counter = counter+1; + END loop; + CLOSE cur; + RETURN counter; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `ProcessTags` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `ProcessTags`(pOldTag varchar(64),pNewTag varchar(64)) RETURNS varchar(2000) CHARSET utf8 +BEGIN + declare Res varchar(2000); + declare lOldID integer; + declare lNewID integer; + select id into lOldID from tags where name=pOldTag; + select id into lNewID from tags where name=pNewTag; + SET Res = CONCAT(lOldID ,' ',lNewID); + insert into metadata.books_tags_link(book,tag) + select book,lNewID from metadata.books_tags_link + where tag=lOldID + and book not in + (select book from metadata.books_tags_link + where tag=lNewID); + delete from metadata.books_tags_link + where tag=lOldID; + delete from metadata.tags where id=lOldID; + return Res; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `SortAuthor` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `SortAuthor`(pStr varchar(2000)) RETURNS varchar(2000) CHARSET utf8 + DETERMINISTIC +BEGIN + RETURN REPLACE(SplitAuthor(SortStr(pStr)),'NO ORIGINAL NAME FOR ',''); +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `SortStr` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `SortStr`(pStr varchar(2000)) RETURNS varchar(2000) CHARSET utf8 + DETERMINISTIC +BEGIN + DECLARE i INT DEFAULT 1; + DECLARE v_char VARCHAR(1); + DECLARE v_parseStr VARCHAR(2000) DEFAULT ' '; + +SET pStr = UPPER(pStr); + +IF pSTR LIKE 'A %' THEN + SET pSTR=SUBSTR(pStr,3); +ELSEIF pSTR LIKE 'THE %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'EL %' THEN + SET pSTR=SUBSTR(pStr,4); +ELSEIF pSTR LIKE 'IL %' THEN + SET pSTR=SUBSTR(pStr,4); +ELSEIF pSTR LIKE 'L\'%' THEN + SET pSTR=SUBSTR(pStr,3); +ELSEIF pSTR LIKE 'LES %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'DIE %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'DER %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'DAS %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'LA %' THEN + SET pSTR=SUBSTR(pStr,4); +ELSEIF pSTR LIKE 'LOS %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'LÀ %' THEN + SET pSTR=SUBSTR(pStr,4); +ELSEIF pSTR LIKE 'LE %' THEN + SET pSTR=SUBSTR(pStr,4); +ELSEIF pSTR LIKE 'LAS %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'UN %' THEN + SET pSTR=SUBSTR(pStr,4); +ELSEIF pSTR LIKE 'UNA %' THEN + SET pSTR=SUBSTR(pStr,5); +ELSEIF pSTR LIKE 'UNO %' THEN + SET pSTR=SUBSTR(pStr,5); +END IF; + +WHILE (i <= LENGTH(pStr) ) DO + + SET v_char = SUBSTR(pStr,i,1); + +IF v_char REGEXP '^[A-Z0-9АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ ]+$' OR v_char = '-' THEN #alphanumeric + SET v_parseStr = CONCAT(v_parseStr,v_char); + SET v_char=''; + ELSEIF v_char='Å ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'S'); + SET v_char=''; + ELSEIF v_char='Ð' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Dj'); + SET v_char=''; + ELSEIF v_char='Ž' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Z'); + SET v_char=''; + ELSEIF v_char='À' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Á' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Â' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Ã' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Ä' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Å' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Æ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'A'); + SET v_char=''; + ELSEIF v_char='Ç' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'C'); + SET v_char=''; + ELSEIF v_char='È' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'E'); + SET v_char=''; + ELSEIF v_char='É' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'E'); + SET v_char=''; + ELSEIF v_char='Ê' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'E'); + SET v_char=''; + ELSEIF v_char='Ë' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'E'); + SET v_char=''; + ELSEIF v_char='Ì' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'I'); + SET v_char=''; + ELSEIF v_char='Í' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'I'); + SET v_char=''; + ELSEIF v_char='Î' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'I'); + SET v_char=''; + ELSEIF v_char='Ï' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'I'); + SET v_char=''; + ELSEIF v_char='Ñ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'N'); + SET v_char=''; + ELSEIF v_char='Ò' THEN + SET v_parseStr = CONCAT(v_parseStr,'O'); + SET v_char=''; + ELSEIF v_char='Ó' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'O'); + SET v_char=''; + ELSEIF v_char='Ô' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'O'); + SET v_char=''; + ELSEIF v_char='Õ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'O'); + SET v_char=''; + ELSEIF v_char='Ö' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'O'); + SET v_char=''; + ELSEIF v_char='Ø' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'O'); + SET v_char=''; + ELSEIF v_char='Ù' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'U'); + SET v_char=''; + ELSEIF v_char='Ú' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'U'); + SET v_char=''; + ELSEIF v_char='Û' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'U'); + SET v_char=''; + ELSEIF v_char='Ü' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'U'); + SET v_char=''; + ELSEIF v_char='Ý' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Y'); + SET v_char=''; + ELSEIF v_char='Þ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'B'); + SET v_char=''; + ELSEIF v_char='ß' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'SS'); + SET v_char=''; + ELSEIF v_char='Ł' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'L'); + SET v_char=''; + ELSEIF v_char='Ĥ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'H'); + SET v_char=''; + ELSEIF v_char='Č' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'C'); + SET v_char=''; + END IF; + + IF v_char='Ґ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Г'); + SET v_char=''; + ELSEIF v_char='І' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'И'); + SET v_char=''; + ELSEIF v_char='Є' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Е'); + SET v_char=''; + ELSEIF v_char='Ї' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Й'); + SET v_char=''; + ELSEIF v_char='Ў' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'У'); + SET v_char=''; + ELSEIF v_char='Ђ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'ДЖ'); + SET v_char=''; + ELSEIF v_char='Џ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'ДЖ'); + SET v_char=''; + ELSEIF v_char='Ѓ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Г'); + SET v_char=''; + ELSEIF v_char='Љ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'ЛЬ'); + SET v_char=''; + ELSEIF v_char='Њ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'HЬ'); + SET v_char=''; + ELSEIF v_char='Ћ' collate 'utf8_bin' THEN + SET v_parseStr = CONCAT(v_parseStr,'Ч'); + SET v_char=''; + END IF; + + IF v_char='$' THEN + SET v_parseStr = CONCAT(v_parseStr,'S'); + SET v_char=''; + END IF; + + SET i = i + 1; +END WHILE; +SET v_parseStr=trim(DeleteDoubleSpaces(v_parseStr)); +RETURN v_parseStr; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP FUNCTION IF EXISTS `SplitAuthor` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` FUNCTION `SplitAuthor`(pStr VARCHAR(2000)) RETURNS varchar(2000) CHARSET utf8 +BEGIN + DECLARE lLastName VARCHAR(2000); + DECLARE lStr VARCHAR(2000); + DECLARE lLastNameLen INTEGER; + DECLARE lFirstName VARCHAR(2000); + SET lStr=trim(pStr); + SET lLastName=substring_index(lStr,' ',-1); + IF lLastName<>lStr THEN + SET lLastNameLen=char_length(lLastName); + SET lFirstName=trim(substring(pStr,1,char_length(lStr)-lLastNameLen-1)); + RETURN concat(lLastName,' ',lFirstName); + ELSE + RETURN lStr; + END IF; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP PROCEDURE IF EXISTS `DropTag` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` PROCEDURE `DropTag`(pOldTag varchar(64)) +BEGIN + declare lOldID integer; + select id into lOldID from tags where name=pOldTag; + delete from metadata.books_tags_link + where tag=lOldID; + delete from metadata.tags where id=lOldID; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 DROP PROCEDURE IF EXISTS `ProcessLangAliases` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +DELIMITER ;; +CREATE DEFINER=`admin`@`%` PROCEDURE `ProcessLangAliases`() +BEGIN + DECLARE done INT DEFAULT FALSE; + DECLARE alias,lang,dummystr VARCHAR(255); + DECLARE cur CURSOR FOR + SELECT a.alias,a.lang_code FROM metadata.languages l,metadata.lang_alias a where l.lang_code=a.alias; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; + OPEN cur; + langs: loop + FETCH cur into alias,lang; + IF done THEN + LEAVE langs; + END IF; + SELECT ProcessLang(alias,lang) INTO dummystr; + END loop; + CLOSE cur; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; @@ -310,4 +976,4 @@ CREATE TABLE `tags_mapping` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2013-11-30 18:05:09 +-- Dump completed on 2016-05-19 19:39:15 diff --git a/www/COPYING b/www/COPYING new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/www/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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 program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; 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. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/www/OPDS_renderer.php b/www/OPDS_renderer.php new file mode 100644 index 0000000..10156ff --- /dev/null +++ b/www/OPDS_renderer.php @@ -0,0 +1,271 @@ + + */ + +require_once ("base.php"); + +class OPDSRenderer +{ + const PAGE_OPENSEARCH = "8"; + const PAGE_OPENSEARCH_QUERY = "9"; + + private $xmlStream = NULL; + private $updated = NULL; + + private function getUpdatedTime () { + if (is_null ($this->updated)) { + $this->updated = time(); + } + return date (DATE_ATOM, $this->updated); + } + + private function getXmlStream () { + if (is_null ($this->xmlStream)) { + $this->xmlStream = new XMLWriter(); + $this->xmlStream->openMemory(); + $this->xmlStream->setIndent (true); + } + return $this->xmlStream; + } + + public function getOpenSearch () { + global $config; + $xml = new XMLWriter (); + $xml->openMemory (); + $xml->setIndent (true); + $xml->startDocument('1.0','UTF-8'); + $xml->startElement ("OpenSearchDescription"); + $xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/"); + $xml->startElement ("ShortName"); + $xml->text ("My catalog"); + $xml->endElement (); + $xml->startElement ("Description"); + $xml->text ("Search for ebooks"); + $xml->endElement (); + $xml->startElement ("InputEncoding"); + $xml->text ("UTF-8"); + $xml->endElement (); + $xml->startElement ("OutputEncoding"); + $xml->text ("UTF-8"); + $xml->endElement (); + $xml->startElement ("Image"); + $xml->writeAttribute ("type", "image/x-icon"); + $xml->writeAttribute ("width", "16"); + $xml->writeAttribute ("height", "16"); + $xml->text ($config['cops_icon']); + $xml->endElement (); + $xml->startElement ("Url"); + $xml->writeAttribute ("type", 'application/atom+xml'); + $urlparam = "?query={searchTerms}"; + if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB)); + $urlparam = str_replace ("%7B", "{", $urlparam); + $urlparam = str_replace ("%7D", "}", $urlparam); + $xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php' . $urlparam); + $xml->endElement (); + $xml->startElement ("Query"); + $xml->writeAttribute ("role", "example"); + $xml->writeAttribute ("searchTerms", "robot"); + $xml->endElement (); + $xml->endElement (); + $xml->endDocument(); + return $xml->outputMemory(true); + } + + private function startXmlDocument ($page) { + global $config; + self::getXmlStream ()->startDocument('1.0','UTF-8'); + self::getXmlStream ()->startElement ("feed"); + self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom"); + self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml"); + self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog"); + self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/"); + self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/"); + self::getXmlStream ()->startElement ("title"); + self::getXmlStream ()->text ($page->title); + self::getXmlStream ()->endElement (); + if ($page->subtitle != "") + { + self::getXmlStream ()->startElement ("subtitle"); + self::getXmlStream ()->text ($page->subtitle); + self::getXmlStream ()->endElement (); + } + self::getXmlStream ()->startElement ("id"); + if ($page->idPage) + { + $idPage = $page->idPage; + if (!is_null (GetUrlParam (DB))) $idPage = str_replace ("cops:", "cops:" . GetUrlParam (DB) . ":", $idPage); + self::getXmlStream ()->text ($idPage); + } + else + { + self::getXmlStream ()->text ($_SERVER['REQUEST_URI']); + } + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("updated"); + self::getXmlStream ()->text (self::getUpdatedTime ()); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("icon"); + self::getXmlStream ()->text ($page->favicon); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("author"); + self::getXmlStream ()->startElement ("name"); + self::getXmlStream ()->text ($page->authorName); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("uri"); + self::getXmlStream ()->text ($page->authorUri); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("email"); + self::getXmlStream ()->text ($page->authorEmail); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->endElement (); + $link = new LinkNavigation ("", "start", "Home"); + self::renderLink ($link); + $link = new LinkNavigation ("?" . $_SERVER['QUERY_STRING'], "self"); + self::renderLink ($link); + $urlparam = "?"; + if (!is_null (GetUrlParam (DB))) $urlparam = addURLParameter ($urlparam, DB, GetUrlParam (DB)); + if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) { + // Good and compliant way of handling search + $urlparam = addURLParameter ($urlparam, "page", self::PAGE_OPENSEARCH); + $link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here"); + } + else + { + // Bad way, will be removed when OPDS client are fixed + $urlparam = addURLParameter ($urlparam, "query", "{searchTerms}"); + $urlparam = str_replace ("%7B", "{", $urlparam); + $urlparam = str_replace ("%7D", "}", $urlparam); + $link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here"); + } + self::renderLink ($link); + if ($page->containsBook () && !is_null ($config['cops_books_filter']) && count ($config['cops_books_filter']) > 0) { + $Urlfilter = getURLParam ("tag", ""); + foreach ($config['cops_books_filter'] as $lib => $filter) { + $link = new LinkFacet ("?" . addURLParameter ($_SERVER['QUERY_STRING'], "tag", $filter), $lib, localize ("tagword.title"), $filter == $Urlfilter); + self::renderLink ($link); + } + } + } + + private function endXmlDocument () { + self::getXmlStream ()->endElement (); + self::getXmlStream ()->endDocument (); + return self::getXmlStream ()->outputMemory(true); + } + + private function renderLink ($link) { + self::getXmlStream ()->startElement ("link"); + self::getXmlStream ()->writeAttribute ("href", $link->href); + self::getXmlStream ()->writeAttribute ("type", $link->type); + if (!is_null ($link->rel)) { + self::getXmlStream ()->writeAttribute ("rel", $link->rel); + } + if (!is_null ($link->title)) { + self::getXmlStream ()->writeAttribute ("title", $link->title); + } + if (!is_null ($link->facetGroup)) { + self::getXmlStream ()->writeAttribute ("opds:facetGroup", $link->facetGroup); + } + if ($link->activeFacet) { + self::getXmlStream ()->writeAttribute ("opds:activeFacet", "true"); + } + self::getXmlStream ()->endElement (); + } + + + private function renderEntry ($entry) { + self::getXmlStream ()->startElement ("title"); + self::getXmlStream ()->text ($entry->title); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("updated"); + self::getXmlStream ()->text (self::getUpdatedTime ()); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("id"); + self::getXmlStream ()->text ($entry->id); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("content"); + self::getXmlStream ()->writeAttribute ("type", $entry->contentType); + if ($entry->contentType == "text") { + self::getXmlStream ()->text ($entry->content); + } else { + self::getXmlStream ()->writeRaw ($entry->content); + } + self::getXmlStream ()->endElement (); + foreach ($entry->linkArray as $link) { + self::renderLink ($link); + } + + if (get_class ($entry) != "EntryBook") { + return; + } + + foreach ($entry->book->getAuthors () as $author) { + self::getXmlStream ()->startElement ("author"); + self::getXmlStream ()->startElement ("name"); + self::getXmlStream ()->text ($author->name); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("uri"); + self::getXmlStream ()->text ("feed.php" . $author->getUri ()); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->endElement (); + } + foreach ($entry->book->getTags () as $category) { + self::getXmlStream ()->startElement ("category"); + self::getXmlStream ()->writeAttribute ("term", $category->name); + self::getXmlStream ()->writeAttribute ("label", $category->name); + self::getXmlStream ()->endElement (); + } + if ($entry->book->getPubDate () != "") { + self::getXmlStream ()->startElement ("dcterms:issued"); + self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate)); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("published"); + self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate) . "T08:08:08Z"); + self::getXmlStream ()->endElement (); + } + + $lang = $entry->book->getLanguages (); + if (!empty ($lang)) { + self::getXmlStream ()->startElement ("dcterms:language"); + self::getXmlStream ()->text ($lang); + self::getXmlStream ()->endElement (); + } + + } + + public function render ($page) { + global $config; + self::startXmlDocument ($page); + if ($page->isPaginated ()) + { + self::getXmlStream ()->startElement ("opensearch:totalResults"); + self::getXmlStream ()->text ($page->totalNumber); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("opensearch:itemsPerPage"); + self::getXmlStream ()->text ($config['cops_max_item_per_page']); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("opensearch:startIndex"); + self::getXmlStream ()->text (($page->n - 1) * $config['cops_max_item_per_page'] + 1); + self::getXmlStream ()->endElement (); + $prevLink = $page->getPrevLink (); + $nextLink = $page->getNextLink (); + if (!is_null ($prevLink)) { + self::renderLink ($prevLink); + } + if (!is_null ($nextLink)) { + self::renderLink ($nextLink); + } + } + foreach ($page->entryArray as $entry) { + self::getXmlStream ()->startElement ("entry"); + self::renderEntry ($entry); + self::getXmlStream ()->endElement (); + } + return self::endXmlDocument (); + } +} + diff --git a/www/OPDS_renderer.php.bak b/www/OPDS_renderer.php.bak new file mode 100644 index 0000000..6795147 --- /dev/null +++ b/www/OPDS_renderer.php.bak @@ -0,0 +1,264 @@ + + */ + +require_once ("base.php"); + +class OPDSRenderer +{ + const PAGE_OPENSEARCH = "8"; + const PAGE_OPENSEARCH_QUERY = "9"; + + private $xmlStream = NULL; + private $updated = NULL; + + private function getUpdatedTime () { + if (is_null ($this->updated)) { + $this->updated = time(); + } + return date (DATE_ATOM, $this->updated); + } + + private function getXmlStream () { + if (is_null ($this->xmlStream)) { + $this->xmlStream = new XMLWriter(); + $this->xmlStream->openMemory(); + $this->xmlStream->setIndent (true); + } + return $this->xmlStream; + } + + public function getOpenSearch () { + global $config; + $xml = new XMLWriter (); + $xml->openMemory (); + $xml->setIndent (true); + $xml->startDocument('1.0','UTF-8'); + $xml->startElement ("OpenSearchDescription"); + $xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/"); + $xml->startElement ("ShortName"); + $xml->text ("My catalog"); + $xml->endElement (); + $xml->startElement ("Description"); + $xml->text ("Search for ebooks"); + $xml->endElement (); + $xml->startElement ("InputEncoding"); + $xml->text ("UTF-8"); + $xml->endElement (); + $xml->startElement ("OutputEncoding"); + $xml->text ("UTF-8"); + $xml->endElement (); + $xml->startElement ("Image"); + $xml->writeAttribute ("type", "image/x-icon"); + $xml->writeAttribute ("width", "16"); + $xml->writeAttribute ("height", "16"); + $xml->text ($config['cops_icon']); + $xml->endElement (); + $xml->startElement ("Url"); + $xml->writeAttribute ("type", 'application/atom+xml'); + $urlparam = "?query={searchTerms}"; + $urlparam = str_replace ("%7B", "{", $urlparam); + $urlparam = str_replace ("%7D", "}", $urlparam); + $xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php' . $urlparam); + $xml->endElement (); + $xml->startElement ("Query"); + $xml->writeAttribute ("role", "example"); + $xml->writeAttribute ("searchTerms", "robot"); + $xml->endElement (); + $xml->endElement (); + $xml->endDocument(); + return $xml->outputMemory(true); + } + + private function startXmlDocument ($page) { + global $config; + self::getXmlStream ()->startDocument('1.0','UTF-8'); + self::getXmlStream ()->startElement ("feed"); + self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom"); + self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml"); + self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog"); + self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/"); + self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/"); + self::getXmlStream ()->startElement ("title"); + self::getXmlStream ()->text ($page->title); + self::getXmlStream ()->endElement (); + if ($page->subtitle != "") + { + self::getXmlStream ()->startElement ("subtitle"); + self::getXmlStream ()->text ($page->subtitle); + self::getXmlStream ()->endElement (); + } + self::getXmlStream ()->startElement ("id"); + if ($page->idPage) + { + $idPage = $page->idPage; + self::getXmlStream ()->text ($idPage); + } + else + { + self::getXmlStream ()->text ($_SERVER['REQUEST_URI']); + } + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("updated"); + self::getXmlStream ()->text (self::getUpdatedTime ()); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("icon"); + self::getXmlStream ()->text ($page->favicon); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("author"); + self::getXmlStream ()->startElement ("name"); + self::getXmlStream ()->text (utf8_encode ("Sébastien Lucas")); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("uri"); + self::getXmlStream ()->text ("http://blog.slucas.fr"); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("email"); + self::getXmlStream ()->text ("sebastien@slucas.fr"); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->endElement (); + $link = new LinkNavigation ("", "start", "Home"); + self::renderLink ($link); + $link = new LinkNavigation ("?" . $_SERVER['QUERY_STRING'], "self"); + self::renderLink ($link); + $urlparam = "?page=" . self::PAGE_OPENSEARCH; + if ($config['cops_generate_invalid_opds_stream'] == 0 || preg_match("/(MantanoReader|FBReader)/", $_SERVER['HTTP_USER_AGENT'])) { + // Good and compliant way of handling search + $link = new Link ("feed.php" . $urlparam, "application/opensearchdescription+xml", "search", "Search here"); + } + else + { + // Bad way, will be removed when OPDS client are fixed + $link = new Link ($config['cops_full_url'] . 'feed.php' . $urlparam, "application/atom+xml", "search", "Search here"); + } + self::renderLink ($link); + if ($page->containsBook () && !is_null ($config['cops_books_filter']) && count ($config['cops_books_filter']) > 0) { + $Urlfilter = getURLParam ("tag", ""); + foreach ($config['cops_books_filter'] as $lib => $filter) { + $link = new LinkFacet ("?" . addURLParameter ($_SERVER['QUERY_STRING'], "tag", $filter), $lib, localize ("tagword.title"), $filter == $Urlfilter); + self::renderLink ($link); + } + } + } + + private function endXmlDocument () { + self::getXmlStream ()->endElement (); + self::getXmlStream ()->endDocument (); + return self::getXmlStream ()->outputMemory(true); + } + + private function renderLink ($link) { + self::getXmlStream ()->startElement ("link"); + self::getXmlStream ()->writeAttribute ("href", $link->href); + self::getXmlStream ()->writeAttribute ("type", $link->type); + if (!is_null ($link->rel)) { + self::getXmlStream ()->writeAttribute ("rel", $link->rel); + } + if (!is_null ($link->title)) { + self::getXmlStream ()->writeAttribute ("title", $link->title); + } + if (!is_null ($link->facetGroup)) { + self::getXmlStream ()->writeAttribute ("opds:facetGroup", $link->facetGroup); + } + if ($link->activeFacet) { + self::getXmlStream ()->writeAttribute ("opds:activeFacet", "true"); + } + self::getXmlStream ()->endElement (); + } + + + private function renderEntry ($entry) { + self::getXmlStream ()->startElement ("title"); + self::getXmlStream ()->text ($entry->title); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("updated"); + self::getXmlStream ()->text (self::getUpdatedTime ()); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("id"); + self::getXmlStream ()->text ($entry->id); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("content"); + self::getXmlStream ()->writeAttribute ("type", $entry->contentType); + if ($entry->contentType == "text") { + self::getXmlStream ()->text ($entry->content); + } else { + self::getXmlStream ()->writeRaw ($entry->content); + } + self::getXmlStream ()->endElement (); + foreach ($entry->linkArray as $link) { + self::renderLink ($link); + } + + if (get_class ($entry) != "EntryBook") { + return; + } + + foreach ($entry->book->getAuthors () as $author) { + self::getXmlStream ()->startElement ("author"); + self::getXmlStream ()->startElement ("name"); + self::getXmlStream ()->text ($author->name); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("uri"); + self::getXmlStream ()->text ("feed.php" . $author->getUri ()); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->endElement (); + } + foreach ($entry->book->getTags () as $category) { + self::getXmlStream ()->startElement ("category"); + self::getXmlStream ()->writeAttribute ("term", $category->name); + self::getXmlStream ()->writeAttribute ("label", $category->name); + self::getXmlStream ()->endElement (); + } + if ($entry->book->getPubDate () != "") { + self::getXmlStream ()->startElement ("dcterms:issued"); + self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate)); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("published"); + self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate) . "T08:08:08Z"); + self::getXmlStream ()->endElement (); + } + + $lang = $entry->book->getLanguages (); + if (!empty ($lang)) { + self::getXmlStream ()->startElement ("dcterms:language"); + self::getXmlStream ()->text ($lang); + self::getXmlStream ()->endElement (); + } + + } + + public function render ($page) { + global $config; + self::startXmlDocument ($page); + if ($page->isPaginated ()) + { + self::getXmlStream ()->startElement ("opensearch:totalResults"); + self::getXmlStream ()->text ($page->totalNumber); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("opensearch:itemsPerPage"); + self::getXmlStream ()->text ($config['cops_max_item_per_page']); + self::getXmlStream ()->endElement (); + self::getXmlStream ()->startElement ("opensearch:startIndex"); + self::getXmlStream ()->text (($page->n - 1) * $config['cops_max_item_per_page'] + 1); + self::getXmlStream ()->endElement (); + $prevLink = $page->getPrevLink (); + $nextLink = $page->getNextLink (); + if (!is_null ($prevLink)) { + self::renderLink ($prevLink); + } + if (!is_null ($nextLink)) { + self::renderLink ($nextLink); + } + } + foreach ($page->entryArray as $entry) { + self::getXmlStream ()->startElement ("entry"); + self::renderEntry ($entry); + self::getXmlStream ()->endElement (); + } + return self::endXmlDocument (); + } +} + diff --git a/www/README b/www/README new file mode 100644 index 0000000..268e548 --- /dev/null +++ b/www/README @@ -0,0 +1,89 @@ += COPS = + +COPS stands for Calibre OPDS (and HTML) Php Server. + +COPS output is valid the unofficial OPDS validator : +http://opds-validator.appspot.com/ + += Why ? = + +In my opinion Calibre is a marvelous tool but is too big and has too much +dependencies to be used for its content server. + +That's the main reason why I coded this OPDS server. I needed a simple +tool to be installed on a small server (Seagate Dockstar in my case). + +I initially thought of Calibre2OPDS but as it generate static file no +search was possible. + +Later I added an simple HTML catalog that should be usable on my Kobo. + +So COPS's main advantages are : + * No need for many dependencies. + * No need for a lot of CPU or RAM. + * Not much code. + * Search is available. + * With Dropbox / owncloud it's very easy to have an up to date OPDS server. + * It was fun to code. + +If you want to use the OPDS feed don't forget to specify feed.php at the end of your URL. + += Prerequisites = + +1. PHP 5.3 or 5.4 with GD image processing & SQLite3 support. +2. A web server with PHP support. I only tested with various version of Nginx. + Other people reported it working with Apache and Cherokee. +3. The path to a calibre library (metadata.db, format, & cover files). + +On any Debian base Linux you can use : + aptitude install php5-gd php5-sqlite + += Install = + +1. Extract the zip file to a folder in web space (visible to the web server). +2. If a first-time install, copy config_local.php.example to config_local.php +3. Edit config_local.php to match your config. +4. If needed add other configuration item from config_default.php + +If you choose to put your Calibre directory inside your web directory then you +will have to edit /etc/nginx/mime.types to add this line : +application/epub+zip epub; + += Known problems = + +Not a lot ;) + +Please see https://github.com/seblucas/cops/issues for open issues + += Need help = + +Please read https://github.com/seblucas/cops/wiki + += Disclaimer = + +It's tested by me and many other users but there's still some little bugs around ;) + += Credits = + + * All localization informations come from Calibre2OPDS (http://calibre2opds.com/) + * Locale message handling is inspired of http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/ + * str_format function come from http://tmont.com/blargh/2010/1/string-format-in-php + * All icons come from Font Awesome : http://fortawesome.github.io/Font-Awesome/ + * Thanks to all testers + +External libraries used : + * JQuery : http://jquery.com/ + * Magnific Popup : http://dimsemenov.com/plugins/magnific-popup/ + * Php-epub-meta : https://github.com/splitbrain/php-epub-meta with some modification by me + https://github.com/seblucas/php-epub-meta + * TbsZip : http://www.tinybutstrong.com/apps/tbszip/tbszip_help.html + * DoT.js : http://olado.github.io/doT/index.html + * PHPMailer : https://github.com/PHPMailer/PHPMailer + * js-lru : https://github.com/rsms/js-lru + += Copyright & License = + +COPS - 2012-2013 (c) Sébastien Lucas + +See COPYING and file headers for license info + diff --git a/www/README.md b/www/README.md new file mode 100644 index 0000000..8f30b32 --- /dev/null +++ b/www/README.md @@ -0,0 +1,6 @@ +cops +==== + +Calibre OPDS (and HTML) PHP Server : light alternative to Calibre content server / Calibre2OPDS + +See : http://blog.slucas.fr/en/oss/calibre-opds-php-server diff --git a/www/about.html b/www/about.html new file mode 100644 index 0000000..5c867d0 --- /dev/null +++ b/www/about.html @@ -0,0 +1,21 @@ +
+

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

+

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

+

For more info please visit COPS Home Page

+ +

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 diff --git a/www/author.php b/www/author.php new file mode 100644 index 0000000..9d5f0d8 --- /dev/null +++ b/www/author.php @@ -0,0 +1,121 @@ + + */ + +require_once('base.php'); + +class Author extends Base { + const ALL_AUTHORS_ID = "calibre:authors"; + + const AUTHOR_COLUMNS = "authors.id as id, authors.name as name, authors.sort as sort, link.count as count"; + const SQL_AUTHORS_BY_FIRST_LETTER = "select {0} from authors, (select author,count(*) as count from books_authors_link group by author) link where link.author = authors.id and authors.sort like ? order by authors.sort"; + const SQL_ALL_AUTHORS = "select {0} from authors, (select author,count(*) as count from books_authors_link group by author) link where link.author = authors.id order by authors.sort "; + + public $id; + public $name; + public $sort; + + public function __construct($pid, $pname) { + $this->id = $pid; + $this->name = $pname; + } + + public function getUri () { + return "?page=".parent::PAGE_AUTHOR_DETAIL."&id=$this->id"; + } + + public function getEntryId () { + return self::ALL_AUTHORS_ID.":".$this->id; + } + + public static function getEntryIdByLetter ($startingLetter) { + return self::ALL_AUTHORS_ID.":letter:".$startingLetter; + } + + public static function getCount() { + $nAuthors = parent::getDb ()->query('select count(*) from authors')->fetchColumn(0); + $entry = new Entry (localize("authors.title"), self::ALL_AUTHORS_ID, + str_format (localize("authors.alphabetical", $nAuthors), $nAuthors), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_AUTHORS_FIRST_LETTER))); + return $entry; + } + + public static function getAuthorsByFirstLetter($letter) { + if (!$letter) { $letter = ""; } + $len = mb_strlen($letter,'UTF-8')+1; + $result = parent::getDb ()->query('select substring(sort, 1, '.$len.') as title, count(distinct sort) sort_cnt,count(*) as count,min(id) min_id,min(sort) min_sort +from authors +where sort like "'.$letter.'%" +group by substring(sort, 1, '.$len.') +order by substring(sort, 1, '.$len.')'); + $entryArray = array(); + while ($post = $result->fetchObject ()) + { + if ($post->count==1) { + $author = new Author ($post->min_id, $post->min_sort); + $title = $post->min_sort; + $entryid = $author->getEntryId(); + $link = array ( new LinkNavigation ($author->getUri ())); + } elseif ($post->count > parent::maxItemsPerPage() && $post->sort_cnt>1) { + $page = parent::PAGE_AUTHORS_FIRST_LETTER; + $title= $post->title.'...'; + $entryid = Author::getEntryIdByLetter ($post->title); + $link = array ( new LinkNavigation ("?page=".$page."&id=". rawurlencode ($post->title))); + } else { + $page = parent::PAGE_AUTHORS_STARTING_LETTERS; + $title = $post->title.'...'; + $entryid = Author::getEntryIdByLetter ($post->title); + $link = array ( new LinkNavigation ("?page=".$page."&id=". rawurlencode ($post->title))); + } + array_push ($entryArray, new Entry ($title, $entryid, + str_format (localize("authorword", $post->count), $post->count), "text", + $link)); + } + return $entryArray; + } + + + public static function getAuthorsByStartingLetter($letter) { + return self::getEntryArray (self::SQL_AUTHORS_BY_FIRST_LETTER, array ($letter . "%")); + } + + public static function getAllAuthors() { + return self::getEntryArray (self::SQL_ALL_AUTHORS, array ()); + } + + public static function getEntryArray ($query, $params) { + list ($totalNumber, $result) = parent::executeQuery ($query, self::AUTHOR_COLUMNS, "", $params, -1); + $entryArray = array(); + while ($post = $result->fetchObject ()) + { + $author = new Author ($post->id, $post->sort); + array_push ($entryArray, new Entry ($post->sort, $author->getEntryId (), + str_format (localize("bookword", $post->count), $post->count), "text", + array ( new LinkNavigation ($author->getUri ())))); + } + return $entryArray; + } + + public static function getAuthorById ($authorId) { + $result = parent::getDb ()->prepare('select sort from authors where id = ?'); + $result->execute (array ($authorId)); + return new Author ($authorId, $result->fetchColumn (0)); + } + + public static function getAuthorByBookId ($bookId) { + $result = parent::getDb ()->prepare('select authors.id as id, authors.sort as sort +from authors, books_authors_link +where author = authors.id +and book = ?'); + $result->execute (array ($bookId)); + $authorArray = array (); + while ($post = $result->fetchObject ()) { + array_push ($authorArray, new Author ($post->id, $post->sort)); + } + return $authorArray; + } +} diff --git a/www/base.php b/www/base.php new file mode 100644 index 0000000..32f593f --- /dev/null +++ b/www/base.php @@ -0,0 +1,839 @@ + + */ + +define ("VERSION", "0.6.1"); +date_default_timezone_set($config['default_timezone']); + +function getURLParam ($name, $default = NULL) { + if (!empty ($_GET) && isset($_GET[$name]) && $_GET[$name] != "") { + return $_GET[$name]; + } + return $default; +} + +function getCurrentOption ($option) { + global $config; + if (isset($_COOKIE[$option])) { + return $_COOKIE[$option]; + } + if ($option == "style") { + return "default"; + } + + if (isset($config ["cops_" . $option])) { + return $config ["cops_" . $option]; + } + + return ""; +} + +function getCurrentCss () { + return "styles/style-" . getCurrentOption ("style") . ".css"; +} + +function getUrlWithVersion ($url) { + return $url . "?v=" . VERSION; +} + +function xml2xhtml($xml) { + return preg_replace_callback('#<(\w+)([^>]*)\s*/>#s', create_function('$m', ' + $xhtml_tags = array("br", "hr", "input", "frame", "img", "area", "link", "col", "base", "basefont", "param"); + return in_array($m[1], $xhtml_tags) ? "<$m[1]$m[2] />" : "<$m[1]$m[2]>"; + '), $xml); +} + +function display_xml_error($error) +{ + $return .= str_repeat('-', $error->column) . "^\n"; + + switch ($error->level) { + case LIBXML_ERR_WARNING: + $return .= "Warning $error->code: "; + break; + case LIBXML_ERR_ERROR: + $return .= "Error $error->code: "; + break; + case LIBXML_ERR_FATAL: + $return .= "Fatal Error $error->code: "; + break; + } + + $return .= trim($error->message) . + "\n Line: $error->line" . + "\n Column: $error->column"; + + if ($error->file) { + $return .= "\n File: $error->file"; + } + + return "$return\n\n--------------------------------------------\n\n"; +} + +function are_libxml_errors_ok () +{ + $errors = libxml_get_errors(); + + foreach ($errors as $error) { + if ($error->code == 801) return false; + } + return true; +} + +function html2xhtml ($html) { + $doc = new DOMDocument(); + libxml_use_internal_errors(true); + + $doc->loadHTML('' . + $html . ''); // Load the HTML + $output = $doc->saveXML($doc->documentElement); // Transform to an Ansi xml stream + $output = xml2xhtml($output); + if (preg_match ('#(.*)#ms', $output, $matches)) { + $output = $matches [1]; // Remove + } + /* + // In case of error with summary, use it to debug + $errors = libxml_get_errors(); + + foreach ($errors as $error) { + $output .= display_xml_error($error); + } + */ + + if (!are_libxml_errors_ok ()) $output = "HTML code not valid."; + + libxml_use_internal_errors(false); + return $output; +} + +/** + * This method is a direct copy-paste from + * http://tmont.com/blargh/2010/1/string-format-in-php + */ +function str_format($format) { + $args = func_get_args(); + $format = array_shift($args); + + preg_match_all('/(?=\{)\{(\d+)\}(?!\})/', $format, $matches, PREG_OFFSET_CAPTURE); + $offset = 0; + foreach ($matches[1] as $data) { + $i = $data[0]; + $format = substr_replace($format, @$args[$i], $offset + $data[1] - 1, 2 + strlen($i)); + $offset += strlen(@$args[$i]) - 2 - strlen($i); + } + + return $format; +} + +/** + * This method is based on this page + * http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/ + */ +function localize($phrase, $count=-1) { + + if ($count == 0) + $phrase .= ".none"; + if ($count == 1) + $phrase .= ".one"; + if ($count > 1) + $phrase .= ".many"; + + /* Static keyword is used to ensure the file is loaded only once */ + static $translations = NULL; + /* If no instance of $translations has occured load the language file */ + if (is_null($translations)) { + $lang = "en"; + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + { + $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); + } + $lang_file_en = NULL; + $lang_file = 'lang/Localization_' . $lang . '.json'; + if (!file_exists($lang_file)) { + $lang_file = 'lang/' . 'Localization_en.json'; + } + elseif ($lang != "en") { + $lang_file_en = 'lang/' . 'Localization_en.json'; + } + $lang_file_content = file_get_contents($lang_file); + /* Load the language file as a JSON object and transform it into an associative array */ + $translations = json_decode($lang_file_content, true); + if ($lang_file_en) + { + $lang_file_content = file_get_contents($lang_file_en); + $translations_en = json_decode($lang_file_content, true); + $translations = array_merge ($translations_en, $translations); + } + } + if (array_key_exists ($phrase, $translations)) { + return $translations[$phrase]; + } + return $phrase; +} + +function addURLParameter($urlParams, $paramName, $paramValue) { + $start = ""; + if (preg_match ("#^\?(.*)#", $urlParams, $matches)) { + $start = "?"; + $urlParams = $matches[1]; + } + $params = array(); + parse_str($urlParams, $params); + if (empty ($paramValue) && $paramValue != 0) { + unset ($params[$paramName]); + } else { + $params[$paramName] = $paramValue; + } + return $start . http_build_query($params); +} + +class Link +{ + const OPDS_THUMBNAIL_TYPE = "http://opds-spec.org/image/thumbnail"; + const OPDS_IMAGE_TYPE = "http://opds-spec.org/image"; + const OPDS_ACQUISITION_TYPE = "http://opds-spec.org/acquisition/open-access"; + const OPDS_NAVIGATION_TYPE = "application/atom+xml;profile=opds-catalog;kind=navigation"; + const OPDS_PAGING_TYPE = "application/atom+xml;profile=opds-catalog;kind=acquisition"; + + public $href; + public $type; + public $rel; + public $title; + public $facetGroup; + public $activeFacet; + + public function __construct($phref, $ptype, $prel = NULL, $ptitle = NULL, $pfacetGroup = NULL, $pactiveFacet = FALSE) { + $this->href = $phref; + $this->type = $ptype; + $this->rel = $prel; + $this->title = $ptitle; + $this->facetGroup = $pfacetGroup; + $this->activeFacet = $pactiveFacet; + } + + public function hrefXhtml () { + return $this->href; + } +} + +class LinkNavigation extends Link +{ + public function __construct($phref, $prel = NULL, $ptitle = NULL) { + parent::__construct ($phref, Link::OPDS_NAVIGATION_TYPE, $prel, $ptitle); + if (!preg_match ("#^\?(.*)#", $this->href) && !empty ($this->href)) $this->href = "?" . $this->href; + if (preg_match ("/(bookdetail|getJSON).php/", $_SERVER["SCRIPT_NAME"])) { + $this->href = "index.php" . $this->href; + } else { + $this->href = $_SERVER["SCRIPT_NAME"] . $this->href; + } + } +} + +class LinkFacet extends Link +{ + public function __construct($phref, $ptitle = NULL, $pfacetGroup = NULL, $pactiveFacet = FALSE) { + parent::__construct ($phref, Link::OPDS_PAGING_TYPE, "http://opds-spec.org/facet", $ptitle, $pfacetGroup, $pactiveFacet); + $this->href = $_SERVER["SCRIPT_NAME"] . $this->href; + } +} + +class Entry +{ + public $title; + public $id; + public $content; + public $contentType; + public $linkArray; + public $localUpdated; + private static $updated = NULL; + + public static $icons = array( + Author::ALL_AUTHORS_ID => 'images/author.png', + Serie::ALL_SERIES_ID => 'images/serie.png', + Book::ALL_RECENT_BOOKS_ID => 'images/recent.png', + Tag::ALL_TAGS_ID => 'images/tag.png', + Language::ALL_LANGUAGES_ID => 'images/language.png', + "calibre:books$" => 'images/allbook.png', + "calibre:books:letter" => 'images/allbook.png' + ); + + public function getUpdatedTime () { + if (!is_null ($this->localUpdated)) { + return date (DATE_ATOM, $this->localUpdated); + } + if (is_null (self::$updated)) { + self::$updated = time(); + } + return date (DATE_ATOM, self::$updated); + } + + public function getContentArray () { + $navlink = "#"; + foreach ($this->linkArray as $link) { + if ($link->type != Link::OPDS_NAVIGATION_TYPE) { continue; } + + $navlink = $link->hrefXhtml (); + } + return array ( "title" => $this->title, "content" => $this->content, "navlink" => $navlink ); + } + + public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray) { + global $config; + $this->title = $ptitle; + $this->id = $pid; + $this->content = $pcontent; + $this->contentType = $pcontentType; + $this->linkArray = $plinkArray; + + if ($config['cops_show_icons'] == 1) + { + foreach (self::$icons as $reg => $image) + { + if (preg_match ("/" . $reg . "/", $pid)) { + array_push ($this->linkArray, new Link (getUrlWithVersion ($image), "image/png", Link::OPDS_THUMBNAIL_TYPE)); + break; + } + } + } + + } +} + +class EntryBook extends Entry +{ + public $book; + + public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pbook) { + parent::__construct ($ptitle, $pid, $pcontent, $pcontentType, $plinkArray); + $this->book = $pbook; + $this->localUpdated = $pbook->timestamp; + } + + public function getContentArray () { + $entry = array ( "title" => $this->title); + $entry ["book"] = $this->book->getContentArray (); + return $entry; + } + + public function getCoverThumbnail () { + foreach ($this->linkArray as $link) { + if ($link->rel == Link::OPDS_THUMBNAIL_TYPE) + return $link->hrefXhtml (); + } + return null; + } + + public function getCover () { + foreach ($this->linkArray as $link) { + if ($link->rel == Link::OPDS_IMAGE_TYPE) + return $link->hrefXhtml (); + } + return null; + } +} + +class Page +{ + public $title; + public $subtitle = ""; + public $idPage; + public $idGet; + public $query; + public $favicon; + public $n; + public $book; + public $totalNumber = -1; + public $entryArray = array(); + + public static function getPage ($pageId, $id, $query, $n) + { + switch ($pageId) { + case Base::PAGE_AUTHORS_FIRST_LETTER : + return new PageAllAuthorsLetter ($id, $query, $n); + case Base::PAGE_AUTHORS_STARTING_LETTERS : + return new PageAuthorsLetter ($id, $query, $n); + case Base::PAGE_AUTHOR_DETAIL : + return new PageAuthorDetail ($id, $query, $n); + case Base::PAGE_ALL_TAGS : + return new PageAllTags ($id, $query, $n); + case Base::PAGE_TAG_DETAIL : + return new PageTagDetail ($id, $query, $n); + case Base::PAGE_ALL_LANGUAGES : + return new PageAllLanguages ($id, $query, $n); + case Base::PAGE_LANGUAGE_DETAIL : + return new PageLanguageDetail ($id, $query, $n); + case Base::PAGE_ALL_SERIES : + return new PageAllSeries ($id, $query, $n); + case Base::PAGE_SERIES_STARTING_LETTERS : + return new PageAllSeriesStartingLetters ($id, $query, $n); + case Base::PAGE_ALL_BOOKS : + return new PageAllBooks ($id, $query, $n); + case Base::PAGE_ALL_BOOKS_LETTER: + return new PageAllBooksLetter ($id, $query, $n); + case Base::PAGE_ALL_RECENT_BOOKS : + return new PageRecentBooks ($id, $query, $n); + case Base::PAGE_SERIE_DETAIL : + return new PageSerieDetail ($id, $query, $n); + case Base::PAGE_OPENSEARCH_QUERY : + return new PageQueryResult ($id, $query, $n); + case Base::PAGE_BOOK_DETAIL : + return new PageBookDetail ($id, $query, $n); + case Base::PAGE_ABOUT : + return new PageAbout ($id, $query, $n); + case Base::PAGE_CUSTOMIZE : + return new PageCustomize ($id, $query, $n); + default: + $page = new Page ($id, $query, $n); + $page->idPage = "cops:catalog"; + return $page; + } + } + + public function __construct($pid, $pquery, $pn) { + global $config; + + $this->idGet = $pid; + $this->query = $pquery; + $this->n = $pn; + $this->favicon = $config['cops_icon']; + } + + public function InitializeContent () + { + global $config; + $this->title = $config['cops_title_default']; + $this->subtitle = $config['cops_subtitle_default']; + array_push ($this->entryArray, Author::getCount()); + $series = Serie::getCount(); + if (!is_null ($series)) array_push ($this->entryArray, $series); + $tags = Tag::getCount(); + if (!is_null ($tags)) array_push ($this->entryArray, $tags); + $languages = Language::getCount(); + if (!is_null ($languages)) array_push ($this->entryArray, $languages); + $this->entryArray = array_merge ($this->entryArray, Book::getCount()); + } + + public function isPaginated () + { + global $config; + return (getCurrentOption ("max_item_per_page") != -1 && + $this->totalNumber != -1 && + $this->totalNumber > getCurrentOption ("max_item_per_page")); + } + + public function getNextLink () + { + global $config; + $currentUrl = $_SERVER['QUERY_STRING']; + $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . $_SERVER['QUERY_STRING']); + if (($this->n) * getCurrentOption ("max_item_per_page") < $this->totalNumber) { + return new LinkNavigation ($currentUrl . "&n=" . ($this->n + 1), "next", "Page suivante"); + } + return NULL; + } + + public function getPrevLink () + { + global $config; + $currentUrl = $_SERVER['QUERY_STRING']; + $currentUrl = preg_replace ("/\&n=.*?$/", "", "?" . $_SERVER['QUERY_STRING']); + if ($this->n > 1) { + return new LinkNavigation ($currentUrl . "&n=" . ($this->n - 1), "previous", "Page precedente"); + } + return NULL; + } + + public function getMaxPage () + { + global $config; + return ceil ($this->totalNumber / getCurrentOption ("max_item_per_page")); + } + + public function containsBook () + { + if (count ($this->entryArray) == 0) return false; + if (get_class ($this->entryArray [0]) == "EntryBook") return true; + return false; + } + +} + +class PageAllAuthorsLetter extends Page +{ + public function InitializeContent () + { + global $config; + + $this->idPage = Author::getEntryIdByLetter ($this->idGet); + $this->entryArray = Author::getAuthorsByFirstLetter ($this->idGet); + if ($this->idGet) { + $this->title = localize("authors.starting").' '.$this->idGet; + } else { + $this->title = localize("authors.title"); + } + } +} + +class PageAuthorsLetter extends Page +{ + public function InitializeContent () + { + global $config; + + $this->idPage = Author::getEntryIdByLetter ($this->idGet); + $this->entryArray = Author::getAuthorsByStartingLetter ($this->idGet); + $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("authorword", count ($this->entryArray)), count ($this->entryArray)), $this->idGet); + } +} + +class PageAuthorDetail extends Page +{ + public function InitializeContent () + { + $author = Author::getAuthorById ($this->idGet); + $this->idPage = $author->getEntryId (); + $this->title = $author->name; + list ($this->entryArray, $this->totalNumber) = Book::getBooksByAuthor ($this->idGet, $this->n); + } +} + +class PageAllTags extends Page +{ + public function InitializeContent () + { + $this->title = localize("tags.title"); + $array = Tag::getAllTags(); + asort($array); + $this->entryArray = $array; + $this->idPage = Tag::ALL_TAGS_ID; + } +} + +class PageAllLanguages extends Page +{ + public function InitializeContent () + { + $this->title = localize("languages.title"); + $array = Language::getAllLanguages(); + asort($array); + $this->entryArray = $array; + $this->idPage = Language::ALL_LANGUAGES_ID; + } +} + +class PageTagDetail extends Page +{ + public function InitializeContent () + { + $tag = Tag::getTagById ($this->idGet); + $this->idPage = $tag->getEntryId (); + $this->title = $tag->name; + list ($this->entryArray, $this->totalNumber) = Book::getBooksByTag ($this->idGet, $this->n); + } +} + +class PageLanguageDetail extends Page +{ + public function InitializeContent () + { + $language = Language::getLanguageById ($this->idGet); + $this->idPage = $language->getEntryId (); + $this->title = $language->lang_code; + list ($this->entryArray, $this->totalNumber) = Book::getBooksByLanguage ($this->idGet, $this->n); + } +} + +class PageAllSeries extends Page +{ + public function InitializeContent () + { + if ($this->idGet) { + $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("series.title")), $this->idGet); + } else { + $this->title = localize("series.title"); + } + $this->entryArray = Serie::getAllSeries($this->idGet); + $this->idPage = Serie::getEntryIdByLetters($this->idGet); + } +} + +class PageAllSeriesStartingLetters extends Page +{ + public function InitializeContent () + { + $this->entryArray = Serie::getAllSeriesStartingLetters($this->idGet); + $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("series.title")), $this->idGet); + $this->idPage = Serie::getEntryIdByLetters($this->idGet); + } +} + +class PageSerieDetail extends Page +{ + public function InitializeContent () + { + $serie = Serie::getSerieById ($this->idGet); + $this->title = $serie->name; + list ($this->entryArray, $this->totalNumber) = Book::getBooksBySeries ($this->idGet, $this->n); + $this->idPage = $serie->getEntryId (); + } +} + +class PageAllBooks extends Page +{ + public function InitializeContent () + { + $this->entryArray = Book::getAllBooks ($this->idGet); + if ($this->entryArray->count==1) { + $this->title = $this->idGet; + } elseif ($this->idGet) { + $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("bookword.title")), $this->idGet); + } else { + $this->title = localize ("allbooks.title"); + } + $this->idPage = Book::ALL_BOOKS_ID; + } +} + +class PageAllBooksLetter extends Page +{ + public function InitializeContent () + { + list ($this->entryArray, $this->totalNumber) = Book::getBooksByStartingLetter ($this->idGet, $this->n); + $this->idPage = Book::getEntryIdByLetter ($this->idGet); + + $count = $this->totalNumber; + if ($count == -1) + $count = count ($this->entryArray); + + if ($count==1) { + $this->title = $this->idGet; + } else { + $this->title = str_format (localize ("splitByLetter.letter"), str_format (localize ("bookword", $count), $count), $this->idGet); + } + } +} + +class PageRecentBooks extends Page +{ + public function InitializeContent () + { + $this->title = localize ("recent.title"); + $this->entryArray = Book::getAllRecentBooks (); + $this->idPage = Book::ALL_RECENT_BOOKS_ID; + } +} + +class PageQueryResult extends Page +{ + public function InitializeContent () + { + global $config; + $this->title = str_format (localize ("search.result"), $this->query); + $currentPage = getURLParam ("current", NULL); + + switch ($currentPage) { + case Base::PAGE_AUTHORS_FIRST_LETTER : + $this->entryArray = Author::getAuthorsByStartingLetter ('%' . $this->query); + break; + default: + list ($this->entryArray, $this->totalNumber) = Book::getBooksByQuery ($this->query, $this->n); + } + } +} + +class PageBookDetail extends Page +{ + public function InitializeContent () + { + $this->book = Book::getBookById ($this->idGet); + $this->title = $this->book->title; + } +} + +class PageAbout extends Page +{ + public function InitializeContent () + { + $this->title = localize ("about.title"); + } +} + +class PageCustomize extends Page +{ + public function InitializeContent () + { + global $config; + $this->title = localize ("customize.title"); + $this->entryArray = array (); + + $use_fancybox = ""; + if (getCurrentOption ("use_fancyapps") == 1) { + $use_fancybox = "checked='checked'"; + } + $html_tag_filter = ""; + if (getCurrentOption ("html_tag_filter") == 1) { + $html_tag_filter = "checked='checked'"; + } + + + $content = ""; + if (!preg_match("/(Kobo|Kindle\/3.0|EBRD1101)/", $_SERVER['HTTP_USER_AGENT'])) { + $content .= '"; + } + } + if (!preg_match("/(Kobo|Kindle\/3.0|EBRD1101)/", $_SERVER['HTTP_USER_AGENT'])) { + $content .= ''; + } + array_push ($this->entryArray, new Entry (localize ("customize.style"), "", + $content, "text", + array ())); + + $content = ''; + array_push ($this->entryArray, new Entry (localize ("customize.fancybox"), "", + $content, "text", + array ())); + $content = ''; + array_push ($this->entryArray, new Entry (localize ("customize.paging"), "", + $content, "text", + array ())); + $content = ''; + array_push ($this->entryArray, new Entry (localize ("customize.pages"), "", + $content, "text", + array ())); + $content = ''; + array_push ($this->entryArray, new Entry (localize ("customize.email"), "", + $content, "text", + array ())); + $content = ''; + array_push ($this->entryArray, new Entry (localize ("customize.filter"), "", + $content, "text", + array ())); + } +} + + +abstract class Base +{ + const PAGE_INDEX = "index"; + const PAGE_AUTHORS_FIRST_LETTER = "AUTH"; + const PAGE_AUTHORS_STARTING_LETTERS = "AUTHST"; + const PAGE_AUTHOR_DETAIL = "AUTHDET"; + const PAGE_ALL_BOOKS = "BOOK"; + const PAGE_ALL_BOOKS_LETTER = "BOOKST"; + const PAGE_ALL_SERIES = "SER"; + const PAGE_SERIES_STARTING_LETTERS = "SERST"; + const PAGE_SERIE_DETAIL = "SERDET"; + const PAGE_OPENSEARCH = "OPEN"; + const PAGE_OPENSEARCH_QUERY = "OPENQ"; + const PAGE_ALL_RECENT_BOOKS = "RECENT"; + const PAGE_ALL_TAGS = "GENR"; + const PAGE_TAG_DETAIL = "GENRDET"; + const PAGE_BOOK_DETAIL = "BOOKDET"; + const PAGE_ABOUT = "ABOUT"; + const PAGE_ALL_LANGUAGES = "LANG"; + const PAGE_LANGUAGE_DETAIL = "LANGDET"; + const PAGE_CUSTOMIZE = "OPTS"; + + const COMPATIBILITY_XML_ALDIKO = "aldiko"; + + private static $db = NULL; + + public static function getFileDirectory () { + global $config; + return $config['file_directory']; + } + + public static function getDbHost () { + global $config; + return $config['db_host']; + } + + public static function getDbUser () { + global $config; + return $config['db_user']; + } + + public static function getDbPasswd () { + global $config; + return $config['db_passwd']; + } + + public static function getDb () { + global $config; + if (is_null (self::$db)) { + try { + self::$db = new PDO('mysql:host='. self::getDbHost (), self::getDbUser(), self::getDbPasswd()); + self::$db -> exec("set names utf8"); + } catch (Exception $e) { + header("location: checkconfig.php?err=1"); + exit(); + } + } + return self::$db; + } + + public static function clearDb () { + self::$db = NULL; + } + + public static function executeQuery($query, $columns, $filter, $params, $n) { + global $config; + $totalResult = -1; + + if (getCurrentOption ("max_item_per_page") != -1 && $n != -1) + { + // First check total number of results + $query_str = str_format ($query, "count(*)", $filter); + $result = self::getDb ()->prepare ($query_str); + $result->execute ($params); + $totalResult = $result->fetchColumn (0); + + // Next modify the query and params + $query .= " limit ".(($n - 1) * getCurrentOption ("max_item_per_page")).", ".getCurrentOption ("max_item_per_page"); + } + + $query_str = str_format($query, $columns, $filter); + $result = self::getDb ()->prepare($query_str); + $result->execute ($params); + return array ($totalResult, $result); + } + + public static function booksPages () + { + global $config; + $result = getCurrentOption("max_books_pages"); + if ($result=="") { $result = 10; } + return $result; + } + + public static function maxItemsPerPage() + { + global $config; + $result = getCurrentOption("max_item_per_page"); + if ($result=="") { $result = 25; } + return $result; + } + + +} diff --git a/www/book.php b/www/book.php new file mode 100644 index 0000000..4736e6a --- /dev/null +++ b/www/book.php @@ -0,0 +1,550 @@ + + */ + +require_once('base.php'); +require_once('serie.php'); +require_once('author.php'); +require_once('tag.php'); +require_once('language.php'); +require_once('data.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"); +define ('SQL_BOOKS_BY_FIRST_LETTER', "select {0} from books " . SQL_BOOKS_LEFT_JOIN . " + where books.sort like ? order by books.sort"); +define ('SQL_BOOKS_BY_AUTHOR', "select {0} from books_authors_link, books " . SQL_BOOKS_LEFT_JOIN . " + where books_authors_link.book = books.id and author = ? order by pubdate"); +define ('SQL_BOOKS_BY_SERIE', "select {0} from books_series_link, books " . SQL_BOOKS_LEFT_JOIN . " + where books_series_link.book = books.id and series = ? order by series_index"); +define ('SQL_BOOKS_BY_TAG', "select {0} from books_tags_link, books " . SQL_BOOKS_LEFT_JOIN . " + where books_tags_link.book = books.id and tag = ? order by sort"); +define ('SQL_BOOKS_BY_LANGUAGE', "select {0} from books_languages_link, books " . SQL_BOOKS_LEFT_JOIN . " + where books_languages_link.book = books.id and lang_code = ? order by sort"); +define ('SQL_BOOKS_QUERY', "select {0} from books " . SQL_BOOKS_LEFT_JOIN . " + where (exists (select null from authors, books_authors_link where book = books.id and author = authors.id and authors.name like ?) or title like ?) order by books.sort"); +define ('SQL_BOOKS_RECENT', "select {0} from books " . SQL_BOOKS_LEFT_JOIN . " + where 1=1 order by timestamp desc limit "); + +class Book extends Base { + const ALL_BOOKS_UUID = "tag:book"; + const ALL_BOOKS_ID = "calibre:books"; + const ALL_RECENT_BOOKS_ID = "calibre:recentbooks"; + const BOOK_COLUMNS = "books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index, uuid, has_cover"; + + const SQL_BOOKS_LEFT_JOIN = SQL_BOOKS_LEFT_JOIN; + const SQL_BOOKS_BY_FIRST_LETTER = SQL_BOOKS_BY_FIRST_LETTER; + const SQL_BOOKS_BY_AUTHOR = SQL_BOOKS_BY_AUTHOR; + const SQL_BOOKS_BY_SERIE = SQL_BOOKS_BY_SERIE; + const SQL_BOOKS_BY_TAG = SQL_BOOKS_BY_TAG; + const SQL_BOOKS_BY_LANGUAGE = SQL_BOOKS_BY_LANGUAGE; + const SQL_BOOKS_QUERY = SQL_BOOKS_QUERY; + const SQL_BOOKS_RECENT = SQL_BOOKS_RECENT; + + public $id; + public $title; + public $timestamp; + public $pubdate; + public $path; + public $uuid; + public $hasCover; + public $relativePath; + public $seriesIndex; + public $comment; + public $datas = NULL; + public $authors = NULL; + public $serie = NULL; + public $tags = NULL; + public $languages = NULL; + public $format = array (); + + + public function __construct($line) { + global $config; + $this->id = $line->id; + $this->title = $line->title; + $this->timestamp = strtotime ($line->timestamp); + $this->pubdate = strtotime ($line->pubdate); + $this->path = Base::getFileDirectory () . $line->path; + $this->relativePath = $line->path; + $this->seriesIndex = $line->series_index; + $this->comment = $line->comment; + $this->uuid = $line->uuid; + $this->hasCover = $line->has_cover; + if (!file_exists ($this->getFilePath ("jpg"))) { + // double check + $this->hasCover = 0; + } + } + + public function getEntryId () { + return self::ALL_BOOKS_UUID.":".$this->id; + } + + public static function getEntryIdByLetter ($startingLetter) { + return self::ALL_BOOKS_ID.":letter:".$startingLetter; + } + + public function getUri () { + return "?page=".parent::PAGE_BOOK_DETAIL."&id=$this->id"; + } + + public function getContentArray () { + global $config; + $i = 0; + $preferedData = array (); + foreach ($config['cops_prefered_format'] as $format) + { + if ($i == 2) { break; } + if ($data = $this->getDataFormat ($format)) { + $i++; + array_push ($preferedData, array ("url" => $data->getHtmlLink (), "name" => $format)); + } + } + $serie = $this->getSerie (); + if (is_null ($serie)) { + $sn = ""; + $scn = ""; + $su = ""; + } else { + $sn = $serie->name; + $scn = str_format (localize ("content.series.data"), $this->seriesIndex, $serie->name); + $link = new LinkNavigation ($serie->getUri ()); + $su = $link->hrefXhtml (); + } + + return array ("id" => $this->id, + "hasCover" => $this->hasCover, + "preferedData" => $preferedData, + "pubDate" => $this->getPubDate (), + "languagesName" => $this->getLanguages (), + "authorsName" => $this->getAuthorsName (), + "tagsName" => $this->getTagsName (), + "seriesName" => $sn, + "seriesIndex" => $this->seriesIndex, + "seriesCompleteName" => $scn, + "seriesurl" => $su); + + } + public function getFullContentArray () { + global $config; + $out = $this->getContentArray (); + + $out ["coverurl"] = Data::getLink ($this, "jpg", "image/jpeg", Link::OPDS_IMAGE_TYPE, "cover.jpg", NULL)->hrefXhtml (); + $out ["thumbnailurl"] = Data::getLink ($this, "jpg", "image/jpeg", Link::OPDS_THUMBNAIL_TYPE, "cover.jpg", NULL, NULL, $config['cops_html_thumbnail_height'] * 2)->hrefXhtml (); + $out ["content"] = $this->getComment (false); + $out ["datas"] = array (); + $dataKindle = $this->GetMostInterestingDataToSendToKindle (); + foreach ($this->getDatas() as $data) { + if ($data->format=="FB2.ZIP") { $dataformat = "FB2*"; } else { $dataformat = $data->format; } + $tab = array ("id" => $data->id, "format" => $dataformat, "url" => $data->getHtmlLink (), "mail" => 0); + if (!empty ($config['cops_mail_configuration']) && !is_null ($dataKindle) && $data->id == $dataKindle->id) { + $tab ["mail"] = 1; + } + array_push ($out ["datas"], $tab); + } + $out ["authors"] = array (); + foreach ($this->getAuthors () as $author) { + $link = new LinkNavigation ($author->getUri ()); + array_push ($out ["authors"], array ("name" => $author->name, "url" => $link->hrefXhtml ())); + } + $out ["tags"] = array (); + foreach ($this->getTags () as $tag) { + $link = new LinkNavigation ($tag->getUri ()); + array_push ($out ["tags"], array ("name" => $tag->name, "url" => $link->hrefXhtml ())); + } + ; + return $out; + } + + public function getDetailUrl ($permalink = false) { + global $config; + $urlParam = $this->getUri (); + return 'index.php' . $urlParam; + } + + public function getTitle () { + return $this->title; + } + + public function getAuthors () { + if (is_null ($this->authors)) { + $this->authors = Author::getAuthorByBookId ($this->id); + } + return $this->authors; + } + + public static function getFilterString () { + $filter = getURLParam ("tag", NULL); + if (empty ($filter)) return ""; + + $exists = true; + if (preg_match ("/^!(.*)$/", $filter, $matches)) { + $exists = false; + $filter = $matches[1]; + } + + $result = "exists (select null from books_tags_link, tags where books_tags_link.book = books.id and books_tags_link.tag = tags.id and tags.name = '" . $filter . "')"; + + if (!$exists) { + $result = "not " . $result; + } + + return "and " . $result; + } + + public function getAuthorsName () { + return implode (", ", array_map (function ($author) { return $author->name; }, $this->getAuthors ())); + } + + public function getSerie () { + if (is_null ($this->serie)) { + $this->serie = Serie::getSerieByBookId ($this->id); + } + return $this->serie; + } + + public function getLanguages () { + $lang = array (); + $result = parent::getDb ()->prepare('select languages.lang_code + from books_languages_link, languages + where books_languages_link.lang_code = languages.id + and book = ? + order by item_order'); + $result->execute (array ($this->id)); + while ($post = $result->fetchObject ()) + { + array_push ($lang, Language::getLanguageString($post->lang_code)); + } + return implode (", ", $lang); + } + + public function getTags () { + if (is_null ($this->tags)) { + $this->tags = array (); + + $result = parent::getDb ()->prepare('select tags.id as id, name + from books_tags_link, tags + where tag = tags.id + and book = ? + order by name'); + $result->execute (array ($this->id)); + while ($post = $result->fetchObject ()) + { + array_push ($this->tags, new Tag ($post->id, Tag::getTagString ($post->name))); + } + } + return $this->tags; + } + + public function getDatas () + { + if (is_null ($this->datas)) { + $this->datas = array (); + + $result = parent::getDb ()->prepare('select id, format, name + from data where book = ?'); + $result->execute (array ($this->id)); + + while ($post = $result->fetchObject ()) + { + array_push ($this->datas, new Data ($post, $this)); + } + } + return $this->datas; + } + + public function GetMostInterestingDataToSendToKindle () + { + $bestFormatForKindle = array ("EPUB", "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) + { + foreach ($this->getDatas () as $data) { + if ($data->id == $idData) { + return $data; + } + } + return NULL; + } + + + public function getTagsName () { + return implode (", ", array_map (function ($tag) { return $tag->name; }, $this->getTags ())); + } + + public function getPubDate () { + if (is_null ($this->pubdate) || ($this->pubdate <= -58979923200)) { + return ""; + } + else { + return date ("Y", $this->pubdate); + } + } + + public function getComment ($withSerie = true) { + $addition = ""; + $se = $this->getSerie (); + if (!is_null ($se) && $withSerie) { + $addition = $addition . "" . localize("content.series") . "" . str_format (localize ("content.series.data"), $this->seriesIndex, htmlspecialchars ($se->name)) . "
\n"; + } + if (preg_match ("/<\/(div|p|a|span)>/", $this->comment)) + { + return $addition . html2xhtml ($this->comment); + } + else + { + return $addition . htmlspecialchars ($this->comment); + } + } + + public function getDataFormat ($format) { + foreach ($this->getDatas () as $data) + { + if ($data->format == $format) + { + return $data; + } + } + return NULL; + } + + public function getFilePath ($extension, $idData = NULL, $relative = false) + { + $file = NULL; + if ($extension == "jpg") + { + $file = "cover.jpg"; + } + else + { + $data = $this->getDataById ($idData); + if (!$data) return NULL; + $file = $data->name . "." . strtolower ($data->format); + } + + if ($relative) + { + return $this->relativePath."/".$file; + } + else + { + return $this->path."/".$file; + } + } + + public function getUpdatedEpub ($idData) + { + global $config; + $data = $this->getDataById ($idData); + + try + { + $epub = new EPub ($data->getLocalPath ()); + + $epub->Title ($this->title); + $authorArray = array (); + foreach ($this->getAuthors() as $author) { + $authorArray [$author->sort] = $author->name; + } + $epub->Authors ($authorArray); + $epub->Language ($this->getLanguages ()); + $epub->Description ($this->getComment (false)); + $epub->Subjects ($this->getTagsName ()); + $epub->Cover2 ($this->getFilePath ("jpg"), "image/jpeg"); + $epub->Calibre ($this->uuid); + $se = $this->getSerie (); + if (!is_null ($se)) { + $epub->Serie ($se->name); + $epub->SerieIndex ($this->seriesIndex); + } + if ($config['cops_provide_kepub'] == "1" && preg_match("/Kobo/", $_SERVER['HTTP_USER_AGENT'])) { + $epub->updateForKepub (); + } + $epub->download ($data->getUpdatedFilenameEpub ()); + } + catch (Exception $e) + { + echo "Exception : " . $e->getMessage(); + } + } + + public function getLinkArray () + { + global $config; + $linkArray = array(); + + if ($this->hasCover) + { + array_push ($linkArray, Data::getLink ($this, "jpg", "image/jpeg", Link::OPDS_IMAGE_TYPE, "cover.jpg", NULL)); + + array_push ($linkArray, Data::getLink ($this, "jpg", "image/jpeg", Link::OPDS_THUMBNAIL_TYPE, "cover.jpg", NULL)); + } + + foreach ($this->getDatas () as $data) + { + if ($data->isKnownType ()) + { + array_push ($linkArray, $data->getDataLink (Link::OPDS_ACQUISITION_TYPE, "Download")); + } + } + + foreach ($this->getAuthors () as $author) { + array_push ($linkArray, new LinkNavigation ($author->getUri (), "related", str_format (localize ("bookentry.author"), localize ("splitByLetter.book.other"), $author->name))); + } + + $serie = $this->getSerie (); + if (!is_null ($serie)) { + array_push ($linkArray, new LinkNavigation ($serie->getUri (), "related", str_format (localize ("content.series.data"), $this->seriesIndex, $serie->name))); + } + + return $linkArray; + } + + + public function getEntry () { + return new EntryBook ($this->getTitle (), $this->getEntryId (), + $this->getComment (), "text/html", + $this->getLinkArray (), $this); + } + + public static function getBookCount() { + global $config; + $nBooks = parent::getDb ()->query('select count(*) from books')->fetchColumn(0); + return $nBooks; + } + + public static function getCount() { + global $config; + $nBooks = parent::getDb ()->query('select count(*) from books')->fetchColumn(0); + $result = array(); + $entry = new Entry (localize ("allbooks.title"), + self::ALL_BOOKS_ID, + str_format (localize ("allbooks.alphabetical", $nBooks), $nBooks), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_ALL_BOOKS))); + array_push ($result, $entry); + $entry = new Entry (localize ("recent.title"), + self::ALL_RECENT_BOOKS_ID, + str_format (localize ("recent.list"), $config['cops_recentbooks_limit']), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_ALL_RECENT_BOOKS))); + array_push ($result, $entry); + return $result; + } + + public static function getBooksByAuthor($authorId, $n) { + return self::getEntryArray (self::SQL_BOOKS_BY_AUTHOR, array ($authorId), $n); + } + + + public static function getBooksBySeries($serieId, $n) { + return self::getEntryArray (self::SQL_BOOKS_BY_SERIE, array ($serieId), $n); + } + + public static function getBooksByTag($tagId, $n) { + return self::getEntryArray (self::SQL_BOOKS_BY_TAG, array ($tagId), $n); + } + + public static function getBooksByLanguage($languageId, $n) { + return self::getEntryArray (self::SQL_BOOKS_BY_LANGUAGE, array ($languageId), $n); + } + + public static function getBookById($bookId) { + $result = parent::getDb ()->prepare('select ' . self::BOOK_COLUMNS . ' +from books ' . self::SQL_BOOKS_LEFT_JOIN . ' +where books.id = ?'); + $result->execute (array($bookId)); + while ($post = $result->fetchObject ()) + { + $book = new Book ($post); + return $book; + } + return NULL; + } + + public static function getBookByDataId($dataId) { + $result = parent::getDb ()->prepare('select ' . self::BOOK_COLUMNS . ', data.name, data.format +from data, books ' . self::SQL_BOOKS_LEFT_JOIN . ' +where data.book = books.id and data.id = ?'); + $result->execute (array ($dataId)); + while ($post = $result->fetchObject ()) + { + $book = new Book ($post); + $data = new Data ($post, $book); + $data->id = $dataId; + $book->datas = array ($data); + return $book; + } + return NULL; + } + + public static function getBooksByQuery($query, $n) { + return self::getEntryArray (self::SQL_BOOKS_QUERY, array ("%" . $query . "%", "%" . $query . "%"), $n); + } + + public static function getAllBooks($letters) { + if (!$letters) { $letters=''; } + $len = mb_strlen($letters,'UTF-8')+1; + $result = parent::getDb ()->query('select substr(sort, 1, '.$len.') as title, count(distinct sort) sort_cnt,count(*) as count, min(id) min_id, min(sort) min_sort +from books +where sort like "'.$letters.'%" +group by substr(sort, 1, '.$len.') +order by substr(sort, 1, '.$len.')'); + $entryArray = array(); + while ($post = $result->fetchObject ()) + { + if ($post->count == 1) { + array_push ($entryArray, new Entry ($post->min_sort, Book::getEntryIdByLetter ($post->min_sort), + str_format (localize("bookword", $post->count), $post->count), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_BOOK_DETAIL."&id=". rawurlencode ($post->min_id))))); + } elseif ($post->count>parent::booksPages()*parent::maxItemsPerPage() && $post->sort_cnt>1) { + array_push ($entryArray, new Entry ($post->title.'...', Book::getEntryIdByLetter ($post->title), + str_format (localize("bookword", $post->count), $post->count), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_ALL_BOOKS."&id=". rawurlencode ($post->title))))); + } else { + array_push ($entryArray, new Entry ($post->title.'...', Book::getEntryIdByLetter ($post->title), + str_format (localize("bookword", $post->count), $post->count), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_ALL_BOOKS_LETTER."&id=". rawurlencode ($post->title))))); + } + } + return $entryArray; + } + + public static function getBooksByStartingLetter($letter, $n) { + return self::getEntryArray (self::SQL_BOOKS_BY_FIRST_LETTER, array ($letter . "%"), $n); + } + + public static function getEntryArray ($query, $params, $n) { + list ($totalNumber, $result) = parent::executeQuery ($query, self::BOOK_COLUMNS, self::getFilterString (), $params, $n); + $entryArray = array(); + while ($post = $result->fetchObject ()) + { + $book = new Book ($post); + array_push ($entryArray, $book->getEntry ()); + } + return array ($entryArray, $totalNumber); + } + + + public static function getAllRecentBooks() { + global $config; + list ($entryArray, $totalNumber) = self::getEntryArray (self::SQL_BOOKS_RECENT . $config['cops_recentbooks_limit'], array (), -1); + return $entryArray; + } + +} diff --git a/www/checkconfig.php b/www/checkconfig.php new file mode 100644 index 0000000..bd92258 --- /dev/null +++ b/www/checkconfig.php @@ -0,0 +1,103 @@ + + * + */ + + require_once ("config.php"); + require_once ("base.php"); + + header ("Content-Type:text/html; charset=UTF-8"); + + $err = getURLParam ("err", -1); + $error = NULL; + switch ($err) { + case 1 : + $error = "Database error"; + break; + } + +?> + + + + + COPS Configuration Check + + + +
+
+
+

COPS Configuration Check

+
+
+ +
+ +
+

You've been redirected because COPS is not configured properly

+

+
+ +
+

Check if GD is properly installed and loaded

+

+ +

+
+
+

Check if MySQL is properly installed and loaded

+

+ +

+
+
+

Check if libxml is properly installed and loaded

+

+ +

+
+
+

Check if database can be opened with PHP

+

+ +

+
+
+
+
+ + \ No newline at end of file diff --git a/www/config.php b/www/config.php new file mode 100644 index 0000000..ba70f9b --- /dev/null +++ b/www/config.php @@ -0,0 +1,11 @@ + + */ + + require_once 'config_default.php'; + if (file_exists('config_local.php')) + require_once 'config_local.php'; diff --git a/www/config_default.php b/www/config_default.php new file mode 100644 index 0000000..7005178 --- /dev/null +++ b/www/config_default.php @@ -0,0 +1,198 @@ + + */ + + if (!isset($config)) + $config = array(); + + /* + * The directory containing calibre's metadata.db file, with sub-directories + * containing all the formats. + * BEWARE : it has to end with a / + * You can enable multiple database with this notation instead of a simple string : + * $config['calibre_directory'] = array ("My database name" => "/home/directory/calibre1/", "My other database name" => "/home/directory/calibre2/"); + */ + $config['file_directory'] = './'; + + /* + * SPECIFIC TO NGINX + * The internal directory set in nginx config file + * Leave empty if you don't know what you're doing + */ + $config['calibre_internal_directory'] = ''; + + /* + * Full URL prefix (with trailing /) + * usefull especially for Opensearch where a full URL is sometimes required + * For example Mantano requires it. + */ + $config['cops_full_url'] = ''; + + /* + * Number of recent books to show + */ + $config['cops_recentbooks_limit'] = '50'; + + /* + * Catalog's title + */ + $config['cops_title_default'] = "COPS"; + + /* + * Catalog's subtitle + */ + $config['cops_subtitle_default'] = ""; + + /* + * Wich header to use when downloading books outside the web directory + * Possible values are : + * X-Accel-Redirect : For Nginx + * X-Sendfile : For Lightttpd or Apache (with mod_xsendfile) + * No value (default) : Let PHP handle the download + */ + $config['cops_x_accel_redirect'] = ""; + + /* + * Height of thumbnail image for OPDS + */ + $config['cops_opds_thumbnail_height'] = "70"; + + /* + * Height of thumbnail image for HTML + */ + $config['cops_html_thumbnail_height'] = "164"; + + /* + * Icon for both OPDS and HTML catalog + * Note that this has to be a real icon (.ico) + */ + $config['cops_icon'] = "favicon.ico"; + + /* + * Show icon for authors, series, tags and books on OPDS feed + * 1 : enable + * 0 : disable + */ + $config['cops_show_icons'] = "1"; + + /* + * Default timezone + * Check following link for other timezones : + * http://www.php.net/manual/en/timezones.php + */ + $config['default_timezone'] = "Europe/Moscow"; + + /* + * Prefered format for HTML catalog + * The two first will be displayed in book entries + * The other only appear in book detail + */ + $config['cops_prefered_format'] = array ("EPUB", "PDF", "AZW3", "AZW", "MOBI", "CBR", "CBZ"); + + /* + * use URL rewriting for downloading of ebook in HTML catalog + * See Github wiki for more information + * 1 : enable + * 0 : disable + */ + $config['cops_use_url_rewriting'] = "0"; + + /* + * generate a invalid OPDS stream to allow bad OPDS client to use search + * Example of non compliant OPDS client : FBReader (was working in May 2012), Moon+ Reader + * Example of good OPDS client : Mantano + * 1 : enable support for non compliant OPDS client + * 0 : always generate valid OPDS code + */ + $config['cops_generate_invalid_opds_stream'] = "0"; + + /* + * Max number of items per page + * -1 unlimited + */ + $config['cops_max_item_per_page'] = "-1"; + + /* + * split authors by first letter + * 1 : Yes + * 0 : No + */ + $config['cops_author_split_first_letter'] = "1"; + + /* + * Enable the Lightboxes (for popups) + * 1 : Yes (enable) + * 0 : No + */ + $config['cops_use_fancyapps'] = "1"; + + /* + * Update Epub metadata before download + * 1 : Yes (enable) + * 0 : No + */ + $config['cops_update_epub-metadata'] = "0"; + + /* + * Filter on tags to book list + * Only works with the OPDS catalog + * Usage : array ("I only want to see books using the tag : Tag1" => "Tag1", + * "I only want to see books not using the tag : Tag1" => "!Tag1", + * "I want to see every books" => "", + * + * Example : array ("All" => "", "Unread" => "!Read", "Read" => "Read") + */ + $config['cops_books_filter'] = array (); + + /* + * Custom Columns to add as an array containing the lookup names + * configured in Calibre + * + * For example : array ("genre", "mycolumn"); + * + * Note that for now only the first, second and forth type of custom columns are supported + */ + $config['cops_calibre_custom_column'] = array (); + + /* + * Rename .epub to .kepub.epub if downloaded from a Kobo eReader + * The ebook will then be recognized a Kepub so with chaptered paging, statistics, ... + * You have to enable URL rewriting if you want to enable kepup.epub download + * 1 : Yes (enable) + * 0 : No + */ + $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 (ssl is mandatory) : + * $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'] = NULL; + + /* + * Use filter in HTML catalog + * 1 : Yes (enable) + * 0 : No + */ + $config['cops_html_tag_filter'] = "0"; diff --git a/www/config_local.php b/www/config_local.php new file mode 100644 index 0000000..642ceca --- /dev/null +++ b/www/config_local.php @@ -0,0 +1,30 @@ + diff --git a/www/data.php b/www/data.php new file mode 100644 index 0000000..2219bc8 --- /dev/null +++ b/www/data.php @@ -0,0 +1,139 @@ + + */ + +require_once('base.php'); + +class Data extends Base { + public $id; + public $name; + public $format; + public $realFormat; + public $extension; + public $book; + + public static $mimetypes = array( + 'azw' => 'application/x-mobipocket-ebook', + 'azw3' => 'application/x-mobipocket-ebook', + 'cbz' => 'application/x-cbz', + 'cbr' => 'application/x-cbr', + 'doc' => 'application/msword', + 'epub' => 'application/epub+zip', + 'fb2' => 'text/fb2+xml', + 'kobo' => 'application/x-koboreader-ebook', + 'mobi' => 'application/x-mobipocket-ebook', + 'lit' => 'application/x-ms-reader', + 'lrs' => 'text/x-sony-bbeb+xml', + 'lrf' => 'application/x-sony-bbeb', + 'lrx' => 'application/x-sony-bbeb', + 'ncx' => 'application/x-dtbncx+xml', + 'opf' => 'application/oebps-package+xml', + 'otf' => 'application/x-font-opentype', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'prc' => 'application/x-mobipocket-ebook', + 'rtf' => 'application/rtf', + 'svg' => 'image/svg+xml', + 'ttf' => 'application/x-font-truetype', + 'wmf' => 'image/wmf', + 'xhtml' => 'application/xhtml+xml', + 'xpgt' => 'application/adobe-page-template+xml', + 'zip' => 'application/zip', + 'fb2.zip' => 'application/fb2+zip' + ); + + public function __construct($post, $book = null) { + $this->id = $post->id; + $this->name = $post->name; + $this->format = $post->format; + $this->realFormat = str_replace ("ORIGINAL_", "", $post->format); + $this->extension = strtolower ($this->realFormat); + $this->book = $book; + } + + public function isKnownType () { + return array_key_exists ($this->extension, self::$mimetypes); + } + + public function getMimeType () { + if ($this->isKnownType ()) { + return self::$mimetypes [$this->extension]; + } else { + return "application/octet-stream"; + } + } + + public function getFilename () { + return $this->name . "." . strtolower ($this->format); + } + + public function getUpdatedFilename () { + return $this->book->getAuthorsName () . " - " . $this->book->title; + } + + public function getUpdatedFilenameEpub () { + return $this->getUpdatedFilename () . ".epub"; + } + + public function getUpdatedFilenameKepub () { + return $this->getUpdatedFilename () . ".kepub.epub"; + } + + public function getDataLink ($rel, $title = NULL) { + return self::getBookLink ($this->book, $this->extension, $this->getMimeType (), $rel, $this->getFilename (), $this->id, $title); + } + + public function getLocalPath () { + return $this->book->path . "/" . $this->getFilename (); + } + + public function getHtmlLink () { + global $config; + return self::getBookLink ($this->book, $this->extension, $this->getMimeType (), NULL, $this->getFilename (), $this->id, $this->getFilename())->href; + } + + public static function getBookLink ($book, $type, $mime, $rel, $filename, $idData, $title) + { + $id = $book->id; + $encoded_name=rawurlencode($filename); + $url = "fetch/$idData/$type/$id/$encoded_name"; + return new Link ($url,$mime,$rel,$filename); + } + + public static function getLink ($book, $type, $mime, $rel, $filename, $idData, $title = NULL, $height = NULL) + { + global $config; + + $urlParam = addURLParameter("", "data", $idData); + + if (preg_match ('/^\//', Base::getFileDirectory ()) || // Linux / + preg_match ('/^\w\:/', Base::getFileDirectory ()) || // Windows X: + $rel == Link::OPDS_THUMBNAIL_TYPE || + ($type == "epub" && $config['cops_update_epub-metadata'])) + { + if ($type != "jpg") $urlParam = addURLParameter($urlParam, "type", $type); + if ($rel == Link::OPDS_THUMBNAIL_TYPE) { + if (is_null ($height)) { + if (preg_match ('/feed.php/', $_SERVER["SCRIPT_NAME"])) { + $height = $config['cops_opds_thumbnail_height']; + } + else + { + $height = $config['cops_html_thumbnail_height']; + } + } + $urlParam = addURLParameter($urlParam, "height", $height); + } + $urlParam = addURLParameter($urlParam, "id", $book->id); + return new Link ("fetch.php?" . $urlParam, $mime, $rel, $title); + } + else + { + return new Link (str_replace('%2F','/',rawurlencode ($book->path."/".$filename)), $mime, $rel, $title); + } + } +} diff --git a/www/favicon.ico b/www/favicon.ico new file mode 100644 index 0000000..9be986b Binary files /dev/null and b/www/favicon.ico differ diff --git a/www/feed.php b/www/feed.php new file mode 100644 index 0000000..7949990 --- /dev/null +++ b/www/feed.php @@ -0,0 +1,38 @@ + + * + */ + + require_once ("config.php"); + require_once ("base.php"); + require_once ("author.php"); + require_once ("serie.php"); + require_once ("tag.php"); + require_once ("book.php"); + require_once ("OPDS_renderer.php"); + + header ("Content-Type:application/xml"); + $page = getURLParam ("page", Base::PAGE_INDEX); + $query = getURLParam ("query"); + $n = getURLParam ("n", "1"); + if ($query) + $page = Base::PAGE_OPENSEARCH_QUERY; + $qid = getURLParam ("id"); + + $OPDSRender = new OPDSRenderer (); + + switch ($page) { + case Base::PAGE_OPENSEARCH : + echo $OPDSRender->getOpenSearch (); + return; + default: + $currentPage = Page::getPage ($page, $qid, $query, $n); + $currentPage->InitializeContent (); + echo $OPDSRender->render ($currentPage); + return; + break; + } diff --git a/www/fetch.php b/www/fetch.php new file mode 100644 index 0000000..4b51f87 --- /dev/null +++ b/www/fetch.php @@ -0,0 +1,148 @@ + with integration/modification by Sébastien Lucas + */ + + require_once ("config.php"); + require_once ("book.php"); + require_once ("data.php"); + +function mb_basename($filepath, $suffix = NULL) { + $splited = preg_split ( '/\//', rtrim ( $filepath, '/ ' ) ); + return substr ( basename ( 'X' . $splited [count ( $splited ) - 1], + $suffix ), 1 ); + } + +function notFound () { + header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); + header("Status: 404 Not Found"); + + $_SERVER['REDIRECT_STATUS'] = 404; +} + + global $config; + $expires = 60*60*24*14; + header("Pragma: public"); + header("Cache-Control: maxage=".$expires); + header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT'); + $bookId = getURLParam ("id", NULL); + $type = getURLParam ("type", "jpg"); + $idData = getURLParam ("data", NULL); + + if (is_null ($bookId)) + { + $book = Book::getBookByDataId($idData); + } + else + { + $book = Book::getBookById($bookId); + } + + if (!$book) { + notFound (); + return; + } + + + if ($book && ($type == "jpg" || empty ($config['calibre_internal_directory']))) { + if ($type == "jpg") { + $file = $book->getFilePath ($type); + } else { + $file = $book->getFilePath ($type, $idData); + } + if (!$file || !file_exists ($file)) { + notFound (); + return; + } + } + + if ($type == "jpg") { + header("Content-Type: image/jpeg"); + if (isset($_GET["width"])) + { + $file = $book->getFilePath ($type); + // get image size + if($size = GetImageSize($file)){ + $w = $size[0]; + $h = $size[1]; + //set new size + $nw = $_GET["width"]; + $nh = ($nw*$h)/$w; + } + else{ + //set new size + $nw = "160"; + $nh = "120"; + } + //draw the image + $src_img = imagecreatefromjpeg($file); + $dst_img = imagecreatetruecolor($nw,$nh); + imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $nw, $nh, $w, $h);//resizing the image + imagejpeg($dst_img,null,80); + imagedestroy($src_img); + imagedestroy($dst_img); + return; + } + if (isset($_GET["height"])) + { + $file = $book->getFilePath ($type); + // get image size + if($size = GetImageSize($file)){ + $w = $size[0]; + $h = $size[1]; + //set new size + $nh = $_GET["height"]; + $nw = ($nh*$w)/$h; + } + else{ + //set new size + $nw = "160"; + $nh = "120"; + } + //draw the image + $src_img = imagecreatefromjpeg($file); + $dst_img = imagecreatetruecolor($nw,$nh); + imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $nw, $nh, $w, $h);//resizing the image + imagejpeg($dst_img,null,80); + imagedestroy($src_img); + imagedestroy($dst_img); + return; + } + } + + $file = $book->getFilePath ($type, $idData, false); + + $filename = $dir . $file; + + if ($type == "jpg") { + header('Content-Type: image/jpg'); + header('Content-Disposition: filename="' . mb_basename ($file) . '"'); +# } elseif ($type == "fb2.zip") { +# $z = new ZipArchive(); +# $z->open($filename); +# $zname = $z->getNameIndex(0); +# $zstat = $z->statIndex(0); +# header('Content-Type: text/fb2+xml'); +# header('Content-Disposition: filename="' . $zname . '"'); + } else { + header("Content-Type: " . Data::$mimetypes[$type]); + header('Content-Disposition: attachment; filename="' . mb_basename ($file) . '"'); + } + + if (empty ($config['cops_x_accel_redirect'])) { + $filename = $dir . $file; +# if ($type == "fb2.zip") { +# $fp = $z->getStream($zname); +# header("Content-Length: " . $zstat['size']); +# } else { + $fp = fopen($filename, 'rb'); + header("Content-Length: " . filesize($filename)); +# } + fpassthru($fp); + } + else { + header ($config['cops_x_accel_redirect'] . ": " . $dir . $file); + } diff --git a/www/getJSON.php b/www/getJSON.php new file mode 100644 index 0000000..e599f53 --- /dev/null +++ b/www/getJSON.php @@ -0,0 +1,99 @@ + + * + */ + + require_once ("config.php"); + require_once ("base.php"); + require_once ("author.php"); + require_once ("serie.php"); + require_once ("tag.php"); + require_once ("language.php"); + require_once ("book.php"); + + header ("Content-Type:application/json;charset=utf-8"); + $page = getURLParam ("page", Base::PAGE_INDEX); + $query = getURLParam ("query"); + $qid = getURLParam ("id"); + $n = getURLParam ("n", "1"); + + $currentPage = Page::getPage ($page, $qid, $query, $n); + $currentPage->InitializeContent (); + + $out = array ( "title" => $currentPage->title); + $entries = array (); + foreach ($currentPage->entryArray as $entry) { + array_push ($entries, $entry->getContentArray ()); + } + if (!is_null ($currentPage->book)) { + $out ["book"] = $currentPage->book->getFullContentArray (); + } + $out ["page"] = $page; + $out ["entries"] = $entries; + $out ["isPaginated"] = 0; + if ($currentPage->isPaginated ()) { + $prevLink = $currentPage->getPrevLink (); + $nextLink = $currentPage->getNextLink (); + $out ["isPaginated"] = 1; + $out ["prevLink"] = ""; + if (!is_null ($prevLink)) { + $out ["prevLink"] = $prevLink->hrefXhtml (); + } + $out ["nextLink"] = ""; + if (!is_null ($nextLink)) { + $out ["nextLink"] = $nextLink->hrefXhtml (); + } + $out ["maxPage"] = $currentPage->getMaxPage (); + $out ["currentPage"] = $currentPage->n; + } + if (!is_null (getURLParam ("complete"))) { + $out ["const"] = array ("version" => VERSION, "i18n" => array ( + "coverAlt" => localize("i18n.coversection"), + "authorsTitle" => localize("authors.title"), + "bookwordTitle" => localize("bookword.title"), + "tagsTitle" => localize("tags.title"), + "seriesTitle" => localize("series.title"), + "customizeTitle" => localize ("customize.title"), + "aboutTitle" => localize ("about.title"), + "previousAlt" => localize ("paging.previous.alternate"), + "nextAlt" => localize ("paging.next.alternate"), + "searchAlt" => localize ("search.alternate"), + "sortAlt" => localize ("sort.alternate"), + "homeAlt" => localize ("home.alternate"), + "settingsAlt" => localize ("settings.alternate"), + "permalinkAlt" => localize ("permalink.alternate"), + "pubdateTitle" => localize("pubdate.title"), + "languagesTitle" => localize("language.title"), + "contentTitle" => localize("content.summary"), + "sortorderAsc" => localize("search.sortorder.asc"), + "sortorderDesc" => localize("search.sortorder.desc"), + "customizeEmail" => localize("customize.email")), + "url" => array ( + "detailUrl" => "index.php?page=".Base::PAGE_BOOK_DETAIL."&id={0}", + "coverUrl" => "fetch.php?id={0}", + "thumbnailUrl" => "fetch.php?height=" . $config['cops_html_thumbnail_height'] . "&id={0}"), + "config" => array ( + "use_fancyapps" => $config ["cops_use_fancyapps"], + "max_item_per_page" => $config['cops_max_item_per_page'], + "html_tag_filter" => $config['cops_html_tag_filter'])); + } + + $out ["containsBook"] = 0; + if ($currentPage->containsBook ()) { + $out ["containsBook"] = 1; + } + + $out["abouturl"] = "index.php?page=".Base::PAGE_ABOUT; + + if ($page == Base::PAGE_ABOUT) { + $out ["fullhtml"] = file_get_contents('about.html'); + } + + $out ["homeurl"] = "index.php"; + + echo json_encode ($out); + diff --git a/www/images/allbook.png b/www/images/allbook.png new file mode 100644 index 0000000..7d863f9 Binary files /dev/null and b/www/images/allbook.png differ diff --git a/www/images/author.png b/www/images/author.png new file mode 100644 index 0000000..79f35cc Binary files /dev/null and b/www/images/author.png differ diff --git a/www/images/language.png b/www/images/language.png new file mode 100644 index 0000000..9757fc6 Binary files /dev/null and b/www/images/language.png differ diff --git a/www/images/recent.png b/www/images/recent.png new file mode 100644 index 0000000..783c833 Binary files /dev/null and b/www/images/recent.png differ diff --git a/www/images/serie.png b/www/images/serie.png new file mode 100644 index 0000000..0f9ed4d Binary files /dev/null and b/www/images/serie.png differ diff --git a/www/images/tag.png b/www/images/tag.png new file mode 100644 index 0000000..9757fc6 Binary files /dev/null and b/www/images/tag.png differ diff --git a/www/index.php b/www/index.php new file mode 100644 index 0000000..b64caa0 --- /dev/null +++ b/www/index.php @@ -0,0 +1,94 @@ + + * + */ + + require_once ("config.php"); + require_once ("base.php"); + require_once ("author.php"); + require_once ("serie.php"); + require_once ("tag.php"); + require_once ("language.php"); + require_once ("book.php"); + + // If we detect that an OPDS reader try to connect try to redirect to feed.php + if (preg_match("/(PocketBook|MantanoReader|FBReader|Stanza|Aldiko|Moon+ Reader|CoolReader)/", $_SERVER['HTTP_USER_AGENT'])) { + header("location: feed.php"); + exit (); + } + + header ("Content-Type:text/html;charset=utf-8"); + $page = getURLParam ("page", Base::PAGE_INDEX); + $query = getURLParam ("query"); + $qid = getURLParam ("id"); + $n = getURLParam ("n", "1"); +?> + + + + + COPS + + + + " media="screen" /> + + + + + + + + " /> + " media="screen" /> + + + + + + \ No newline at end of file diff --git a/www/js/jquery.sortElements.js b/www/js/jquery.sortElements.js new file mode 100644 index 0000000..bf1902d --- /dev/null +++ b/www/js/jquery.sortElements.js @@ -0,0 +1,69 @@ +/** + * jQuery.fn.sortElements + * -------------- + * @author James Padolsey (http://james.padolsey.com) + * @version 0.11 + * @updated 18-MAR-2010 + * -------------- + * @param Function comparator: + * Exactly the same behaviour as [1,2,3].sort(comparator) + * + * @param Function getSortable + * A function that should return the element that is + * to be sorted. The comparator will run on the + * current collection, but you may want the actual + * resulting sort to occur on a parent or another + * associated element. + * + * E.g. $('td').sortElements(comparator, function(){ + * return this.parentNode; + * }) + * + * The 's parent () will be sorted instead + * of the itself. + */ +jQuery.fn.sortElements = (function(){ + + var sort = [].sort; + + return function(comparator, getSortable) { + + getSortable = getSortable || function(){return this;}; + + var placements = this.map(function(){ + + var sortElement = getSortable.call(this), + parentNode = sortElement.parentNode, + + // Since the element itself will change position, we have + // to have some way of storing it's original position in + // the DOM. The easiest way is to have a 'flag' node: + nextSibling = parentNode.insertBefore( + document.createTextNode(''), + sortElement.nextSibling + ); + + return function() { + + if (parentNode === this) { + throw new Error( + "You can't sort elements if any one is a descendant of another." + ); + } + + // Insert before flag: + parentNode.insertBefore(this, nextSibling); + // Remove flag: + parentNode.removeChild(nextSibling); + + }; + + }); + + return sort.call(this, comparator).each(function(i){ + placements[i].call(getSortable.call(this)); + }); + + }; + +})(); \ No newline at end of file diff --git a/www/lang/Localization_ca.json b/www/lang/Localization_ca.json new file mode 100644 index 0000000..9d99966 --- /dev/null +++ b/www/lang/Localization_ca.json @@ -0,0 +1,100 @@ +{ +"boolean.yes":"si", +"splitByLetter.letter":"{0} ({1})", +"home.title":"Catàleg principal", +"link.fullentry":"Entrada sencera", +"title.nextpage":"propera pàg. ({0} de {1})", +"title.lastpage":"pàg. següent (darrera)", +"title.numberOfPages":"{0} ({1} pàgines)", +"bookword.title":"Llibres", +"bookword.none":"Cap llibre", +"bookword.one":"1 llibre", +"bookword.many":"{0} llibres", +"authorword.title":"Autors", +"authorword.none":"Cap autor", +"authorword.one":"1 autor", +"authorword.many":"{0} autors", +"taglevelword.title":"Nivells d'etiquetes", +"taglevelword.none":"Cap nivell d'etiquetes", +"taglevelword.one":"1 nivell d'etiqueta", +"taglevelword.many":"{0} nivells d'etiquetes", +"seriesword.none":"Cap sèrie", +"seriesword.one":"1 sèrie", +"seriesword.many":"sèries", +"tagword.title":"Etiquetes", +"tagword.none":"Cap etiqueta", +"tagword.one":"1 etiqueta", +"tagword.many":"etiquetes", +"content.tags":"Etiquetes:", +"content.series.data":"Llibre {0} de la sèrie {1}", +"content.publisher":"Editorial:", +"content.publisher.data":"Publicat el {1} por {0}", +"content.summary":"Sinopsi:", +"bookentry.series":"Llibre {0} de la sèrie {1}", +"bookentry.author":"{0} de {1}", +"bookentry.tags":"{0} en {1}", +"bookentry.ratings":"{0} puntuats {1}", +"bookentry.goodreads":"El llibre a Goodreads", +"bookentry.goodreads.review":"Comenta'l a Goodreads", +"bookentry.goodreads.author":"{0} a Goodreads", +"bookentry.wikipedia":"El llibre a la Wikipedia", +"bookentry.wikipedia.author":"{0} a Wikipedia", +"bookentry.librarything":"El llibre a LibraryThing", +"bookentry.librarything.author":"{0} a LibraryThing", +"bookentry.amazon":"El llibre a Amazon", +"bookentry.amazon.author":"{0} a Amazon", +"bookentry.isfdb.author":"{0} a ISFDB", +"bookentry.download":"Descarrega el llibre com a {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Entrada sencera", +"tags.title":"Etiquetes", +"tags.categorized":"Llistat per categories de les {0} etiquetes", +"tags.categorized.single":"Llistat per categories de la única etiqueta", +"tags.alphabetical.many":"{0} etiquetes ordenades alfabèticament", +"tags.alphabetical.one":"Llistat alfabètic de la única etiqueta", +"splitByLetter.tag.other":"Altres etiquetes", +"authors.series.title":"Sèries", +"authors.title":"Autors", +"authors.alphabetical.many":"{0} autors ordenats alfabèticament", +"authors.alphabetical.one":"Índex s'un sol autor", +"splitByLetter.author.other":"Altres autors", +"series.title":"Sèries", +"series.alphabetical.many":"{0} sèries ordenades alfabèticament", +"series.alphabetical.one":"Índex alfabètic d'una sola sèrie", +"splitByLetter.series.other":"Altres sèries", +"recent.title":"Els més recents", +"recent.list":"{0} darrers títols incorporats", +"recent.list.single":"El llibre més recent", +"rating.title":"Valoració", +"rating.summary":"{0}, agrupats per valoració", +"allbooks.title":"Tots els llibres", +"allbooks.alphabetical.many":"{0} llibres ordenats alfabèticament", +"allbooks.alphabetical.one":"Índex d'un sol llibre", +"splitByLetter.book.other":"Altres llibres", +"main.title":"Biblioteca generada per Calibre", +"main.summary":"{0} ha catalogat {1}", +"i18n.downloads":"Descàrregues, enllaços i altres catàlegs", +"i18n.links":"Enllaços i altres catàlegs", +"i18n.downloadfile":"Descarregar arxiu", +"i18n.downloadsection":"Descàrregues, enllaços i més", +"i18n.relatedsection":"Catàlegs relacionats", +"i18n.linksection":"Enllaços externs", +"i18n.backToMain":"Tornar a la pàgina d'inici del catàleg", +"i18n.summarysection":"Resum", +"i18n.dateGenerated":"Catàleg creat el {0}", +"deeplevel.summary":"{0} per autors, etiquetes, etc.", +"about.summary":"Notes d'ús", +"usage.intro":"Les opcions provenen de l'arxiu de configuració present a: {0}", +"language.title":"Idioma", +"config.Language.description":"Aquesta opció canvia l'idioma del programa; fes servir un codi ISO estàndard, p.e: EN, FR, DE, ES...)", +"config.Language.possible":"Valors posibles: {0}", +"intro.goal":"Genear catàleg OPDS i HTML des de la biblioteca del Calibre", +"intro.team.title":"L'equip de Calibre2Opds:", +"intro.team.list1":"David Pierron - Programador principal", +"intro.team.list2":"Dave Walker - gurú, opcions, gestió i tester extraordinari", +"intro.team.list4":"Douglas Steele - programador", +"intro.team.list5":"Jane Litte - beta tester i recolzament moral", +"intro.thanks.1":"Gràcies especials a Kb Sriram, que no només ha programat Trook, un gestor de biblioteques OPDS excel·lent i compatible", +"intro.thanks.2":"amb el Nook, sinó que, a més, ha estat tan amable de donar un Nook", +"fin":"fi" +} diff --git a/www/lang/Localization_de.json b/www/lang/Localization_de.json new file mode 100644 index 0000000..2ecc1e4 --- /dev/null +++ b/www/lang/Localization_de.json @@ -0,0 +1,123 @@ +{ +"boolean.no":"nein", +"boolean.yes":"ja", +"splitByLetter.letter":"{0} unter {1}", +"home.title":"Hauptkatalog", +"link.fullentry":"Vollständiger Eintrag", +"title.nextpage":"nächste Seite ({0} von {1})", +"title.lastpage":"nächste Seite (letzte)", +"title.numberOfPages":"{0} ({1} Seiten)", +"bookword.title":"Bücher", +"bookword.none":"Kein Buch", +"bookword.one":"1 Buch", +"bookword.many":"{0} Bücher", +"authorword.title":"Autoren", +"authorword.none":"Kein Autor", +"authorword.one":"1 Autor", +"authorword.many":"{0} Autoren", +"taglevelword.title":"Schlagwortebenen", +"taglevelword.none":"Keine Schlagwortebene", +"taglevelword.one":"1 Schlagwortebene", +"taglevelword.many":"{0} Schlagwortebenen", +"seriesword.title":"Serien", +"seriesword.none":"Keine Serie", +"seriesword.one":"1 Serie", +"seriesword.many":"{0} Serien", +"tagword.title":"Schlagwörter", +"tagword.none":"Kein Schlagwort", +"tagword.one":"1 Schlagwort", +"tagword.many":"{0} Schlagwörter", +"content.tags":"Schlagwörter: ", +"content.series":"Serien: ", +"content.series.data":"Buch {0} der {1} - Reihe", +"content.publisher":"Verlag: ", +"content.published":"Veröffentlicht:", +"content.publisher.data":"Veröffentlicht {1} von {0}", +"content.summary":"Inhalt: ", +"bookentry.series":"Buch {0} der {1} - Reihe", +"bookentry.author":"{0} von {1}", +"bookentry.tags":"{0} unter {1}", +"bookentry.ratings":"{0} bewertet mit {1}", +"bookentry.goodreads":"Dieses Buch bei Goodreads", +"bookentry.goodreads.review":"Rezensiere dieses Buch bei Goodreads", +"bookentry.goodreads.author":"{0} bei Goodreads", +"bookentry.wikipedia":"Dieses Buch bei Wikipedia", +"bookentry.wikipedia.author":"{0} bei Wikipedia", +"bookentry.librarything":"Dieses Buch bei LibraryThing", +"bookentry.librarything.author":"{0} bei LibraryThing", +"bookentry.amazon":"Dieses Buch bei Amazon", +"bookentry.amazon.author":"{0} bei Amazon", +"bookentry.isfdb.author":"{0} bei ISFDB", +"bookentry.download":"Lade dieses eBook als {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Vollständiger Eintrag", +"tags.title":"Schlagwörter", +"tags.categorized":"Index der {0} Schlagwörter nach Kategorien", +"tags.categorized.single":"Index des Schlagworts nach Kategorien", +"tags.alphabetical.many":"Alphabetischer Index der {0} Schlagwörter", +"tags.alphabetical.one":"Alphabetischer Index des Schlagworts", +"splitByLetter.tag.other":"Andere Schlagwörter", +"authors.series.title":"Serien: {0}", +"authors.title":"Autoren", +"authors.alphabetical.many":"Alphabetischer Index der {0} Autoren", +"authors.alphabetical.one":"Alphabetischer Index des Autors", +"splitByLetter.author.other":"Andere Autoren", +"series.title":"Serien", +"series.alphabetical.many":"Alphabetischer Index der {0} Serien", +"series.alphabetical.one":"Alphabetischer Index der Serie", +"splitByLetter.series.other":"Andere Serien", +"recent.title":"Neuzugänge", +"recent.list":"{0} neue Bücher", +"recent.list.single":"Das neueste einzige Buch", +"rating.title":"Bewertung", +"rating.summary":"{0}, gruppiert nach Bewertung", +"allbooks.title":"Alle Bücher", +"allbooks.alphabetical.many":"Alphabetischer Index der {0} Bücher", +"allbooks.alphabetical.one":"Alphabetischer Index des einzigen Buchs", +"splitByLetter.book.other":"Andere Bücher", +"main.title":"Calibre Bibliothek", +"main.summary":"{0} hat {1} katalogisierte Bücher", +"startup.newhome":"Standard-Konfigurationsverzeichnis wurde in {0} umgeleitet", +"startup.redirectfound":".redirect-Datei in {0} gefunden", +"startup.redirectreadfail":"… konnte .redirect-Datei nicht lesen", +"startup.redirectnotfound":"… Verzeichnis {0} für Umleitung nicht gefunden", +"startup.redirectabandoned":"… Umleitung nicht durchgeführt", +"startup.redirecting":"Home-Verzeichnis in {0} umgeleitet", +"startup.configusing":"Benutze Konfigurations-Verzeichnis {0}", +"startup.folderuserhome":"Suche Konfigurations-Verzeichnis in Benutzer-Verzeichnis {0}", +"startup.foldertilde":"Suche Konfigurations-Verzeichnis in ~ („Tilde-Verzeichnis“) {0}", +"startup.folderjar":"Suche Konfigurations-Verzeichnis ab der .jar-Datei {0}", +"startup.foldernotexist":"… angegebener Verzeichnis existiert nicht", +"i18n.and":"und", +"i18n.downloads":"Downloads, Links und andere Kataloge", +"i18n.links":"Links und andere Kataloge", +"i18n.coversection":"Umschlag", +"i18n.downloadfile":"Download Datei", +"i18n.downloadsection":"Downloads", +"i18n.relatedsection":"Verwandte Kataloge", +"i18n.linksection":"Externe Links", +"i18n.backToMain":"Zurück zur Hauptseite des Katalogs", +"i18n.summarysection":"Zusammenfassung", +"i18n.dateGenerated":"Katalog erstellt am {0}", +"deeplevel.summary":"{0} aufgeschlüsselt nach Autoren, Schlagwörter, etc.", +"about.summary":"Anmerkungen zur Verwendung von Calibre2Opds", +"usage.intro":"Die Optionen werden der Konfigurationsdatei in {0} entnommen", +"language.title":"Sprache", +"config.Language.description":"Diese Einstellung ändert die verwendete Sprache des Programms; verwende den Standard ISO Sprachcode (z.B. EN, FR, DE …)", +"config.Language.possible":"Mögliche Werte : {0}", +"intro.goal":"Erstelle OPDS und HTML Kataloge der Calibre eBooks Datenbank", +"intro.wiki.title":"Calibre2opds Projektseite:", +"intro.wiki.url":"http://calibre2opds.com", +"intro.team.title":"Das Calibre2Opds Team:", +"intro.team.list1":"David Pierron - Hauptprogrammer", +"intro.team.list2":"Dave Walker - Guru, Manager der Features und Tester der Extraklasse", +"intro.team.list3":"Farid Soussi - CSS und HTML Guru", +"intro.team.list4":"Douglas Steele - Programmierung", +"intro.team.list5":"Jane Litte - Beta Tests und moralische Unterstützung", +"intro.thanks.1":"Speziellen Dank an Kb Sriram, der nicht nur Trook, eine exzellente und OPDS kompatible ", +"intro.thanks.2":"Bibliotheksverwaltung programmierte, sondern auch einen Nook spendete!", +"search.result":"Suchergebnis für", +"search.sortorder.asc":"Auf", +"search.sortorder.desc":"Ab", +"fin":"fin" +} diff --git a/www/lang/Localization_en.json b/www/lang/Localization_en.json new file mode 100644 index 0000000..298e2f6 --- /dev/null +++ b/www/lang/Localization_en.json @@ -0,0 +1,338 @@ +{ +"boolean.no":"no", +"boolean.yes":"yes", +"splitByLetter.letter":"{0} starting with {1}", +"home.title":"Main catalog", +"link.fullentry":"Full entry", +"title.nextpage":"next page ({0} of {1})", +"title.lastpage":"next page (last)", +"title.numberOfPages":"{0} ({1} pages)", +"pubdate.title":"Publication year", +"bookword.title":"Books", +"bookword.none":"No book", +"bookword.one":"1 book", +"bookword.many":"{0} books", +"authorword.title":"Authors", +"authorword.none":"No author", +"authorword.one":"1 author", +"authorword.many":"{0} authors", +"taglevelword.title":"Tag levels", +"taglevelword.none":"No tag level", +"taglevelword.one":"1 tag level", +"taglevelword.many":"{0} tag levels", +"seriesword.title":"Series", +"seriesword.none":"No series", +"seriesword.one":"1 series", +"seriesword.many":"{0} series", +"tagword.title":"Tags", +"tagword.none":"No tag", +"tagword.one":"1 tag", +"tagword.many":"{0} tags", +"content.tags":"Tags:", +"content.series":"Series:", +"content.series.data":"Book {0} in the {1} series", +"content.publisher":"Publisher:", +"content.published":"Published:", +"content.added":"Added: ", +"content.modified":"Modified:", +"content.publisher.data":"Published {1} by {0}", +"content.summary":"Summary", +"bookentry.series":"Book {0} in the {1} series", +"bookentry.author":"{0} by {1}", +"bookentry.tags":"{0} in {1}", +"bookentry.ratings":"{0} rated {1}", +"bookentry.goodreads":"This book on Goodreads", +"bookentry.goodreads.review":"Review this book on Goodreads", +"bookentry.goodreads.author":"{0} on Goodreads", +"bookentry.wikipedia":"This book on Wikipedia", +"bookentry.wikipedia.author":"{0} on Wikipedia", +"bookentry.librarything":"This book on LibraryThing", +"bookentry.librarything.author":"{0} on LibraryThing", +"bookentry.amazon":"This book on Amazon", +"bookentry.amazon.author":"{0} on Amazon", +"bookentry.isfdb.author":"{0} on ISFDB", +"bookentry.download":"Download this ebook as {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Full entry", +"languages.title":"Languages", +"languages.categorized":"Categorized index of the {0} languages", +"languages.categorized.single":"Categorized index of the single language", +"languages.alphabetical.many":"Alphabetical index of the {0} languages", +"languages.alphabetical.one":"Alphabetical index of the single language", +"languages.alphabetical.none":"Alphabetical index of absolutely no language", +"languages.abk":"Abkhaz", +"languages.aaf":"Afar", +"languages.afr":"Afrikaans", +"languages.aka":"Akan", +"languages.sqi":"Albanian", +"languages.amh":"Amharic", +"languages.ara":"Arabic", +"languages.arg":"Aragonese", +"languages.hye":"Armenian", +"languages.asm":"Assamese", +"languages.ava":"Avaric", +"languages.ave":"Avestan", +"languages.aym":"Aymara", +"languages.aze":"Azerbaijani", +"languages.bam":"Bambara", +"languages.bak":"Bashkir", +"languages.eus":"Basque", +"languages.bel":"Belarusian", +"languages.ben":"Bengali", +"languages.bih":"Bihari", +"languages.bis":"Bislama", +"languages.bos":"Bosnian", +"languages.bre":"Breton", +"languages.bul":"Bulgarian", +"languages.mya":"Burmese", +"languages.cat":"Catalan", +"languages.cha":"Chamorro", +"languages.che":"Chechen", +"languages.nya":"Chichewa", +"languages.zho":"Chinese", +"languages.chv":"Chuvash", +"languages.cor":"Cornish", +"languages.cos":"Corsican", +"languages.cre":"Cree", +"languages.hrv":"Croatian", +"languages.ces":"Czech", +"languages.dan":"Danish", +"languages.div":"Divehi", +"languages.nld":"Dutch", +"languages.dzo":"Dzongkha", +"languages.eng":"English", +"languages.epo":"Esperanto", +"languages.est":"Estonian", +"languages.ewe":"Ewe", +"languages.fao":"Faroese", +"languages.fij":"Fijian", +"languages.fin":"Finnish", +"languages.fra":"French", +"languages.ful":"Fula", +"languages.glg":"Galician", +"languages.kat":"Georgian", +"languages.deu":"German", +"languages.ell":"Greek", +"languages.grn":"Guaraní", +"languages.guj":"Gujarati", +"languages.hat":"Haitian", +"languages.hau":"Hausa", +"languages.hed":"Hebrew", +"languages.her":"Herero", +"languages.hin":"Hindi", +"languages.hmo":"Hiri Motu", +"languages.hun":"Hungarian", +"languages.ina":"Interlingua", +"languages.ind":"Indonesian", +"languages.ile":"Interlingue", +"languages.gle":"Irish", +"languages.ibo":"Igbo", +"languages.ipk":"Inupiaq", +"languages.ido":"Ido", +"languages.isl":"Icelandic", +"languages.ita":"Italian", +"languages.iku":"Inuktitut", +"languages.jpn":"Japanese", +"languages.jav":"Javanese", +"languages.kal":"Kalaallisut", +"languages.kan":"Kannada", +"languages.kau":"Kanuri", +"languages.kas":"Kashmiri", +"languages.kaz":"Kazakh", +"languages.khm":"Khmer", +"languages.kik":"Kikuyu", +"languages.kin":"Kinyarwanda", +"languages.kir":"Kyrgyz", +"languages.kom":"Komi", +"languages.kon":"Kongo", +"languages.kor":"Korean", +"languages.kur":"Kurdish", +"languages.kua":"Kwanyama", +"languages.lat":"Latin", +"languages.ltz":"Luxembourgish", +"languages.lug":"Ganda", +"languages.lim":"Limburgish", +"languages.lin":"Lingala", +"languages.lao":"Lao", +"languages.lit":"Lithuanian", +"languages.lub":"Luba-Katanga", +"languages.lav":"Latvian", +"languages.glv":"Manx", +"languages.mkd":"Macedonian", +"languages.mlg":"Malagasy", +"languages.msa":"Malay", +"languages.mal":"Malayalam", +"languages.mlt":"Maltese", +"languages.mri":"Māori", +"languages.mar":"Marathi", +"languages.mah":"Marshallese", +"languages.mon":"Mongolian", +"languages.nau":"Nauru", +"languages.nav":"Navajo", +"languages.nob":"Norwegian BokmÃ¥l", +"languages.nde":"North Ndebele", +"languages.nep":"Nepali", +"languages.ndo":"Ndonga", +"languages.nno":"Norwegian Nynorsk", +"languages.nor":"Norwegian", +"languages.iii":"Nuosu", +"languages.nbl":"South Ndebele", +"languages.oci":"Occitan", +"languages.oji":"Ojibwe", +"languages.chu":"Old Church Slavonic", +"languages.orm":"Oromo", +"languages.ori":"Oriya", +"languages.oss":"Ossetian", +"languages.pan":"Panjabi", +"languages.pli":"Pāli", +"languages.fas":"Persian", +"languages.pol":"Polish", +"languages.pus":"Pashto", +"languages.por":"Portuguese", +"languages.que":"Quechua", +"languages.roh":"Romansh", +"languages.run":"Kirundi", +"languages.ron":"Romanian", +"languages.rus":"Russian", +"languages.san":"Sanskrit", +"languages.srd":"Sardinian", +"languages.snd":"Sindhi", +"languages.sme":"Northern Sami", +"languages.smo":"Samoan", +"languages.sag":"Sango", +"languages.srp":"Serbian", +"languages.gla":"Scottish Gaelic", +"languages.sna":"Shona", +"languages.sin":"Sinhala", +"languages.slk":"Slovak", +"languages.slv":"Slovene", +"languages.som":"Somali", +"languages.sot":"Southern Sotho", +"languages.spa":"Spanish", +"languages.sun":"Sundanese", +"languages.swa":"Swahili", +"languages.ssw":"Swati", +"languages.swe":"Swedish", +"languages.tam":"Tamil", +"languages.tel":"Telugu", +"languages.tgk":"Tajik", +"languages.tha":"Thai", +"languages.tir":"Tigrinya", +"languages.bod":"Tibetan Standard", +"languages.tuk":"Turkmen", +"languages.tgl":"Tagalog", +"languages.tsn":"Tswana", +"languages.ton":"Tonga", +"languages.tur":"Turkish", +"languages.tso":"Tsonga", +"languages.tat":"Tatar", +"languages.twi":"Twi", +"languages.tah":"Tahitian", +"languages.uig":"Uighur", +"languages.ukr":"Ukrainian", +"languages.urd":"Urdu", +"languages.uzb":"Uzbek", +"languages.ven":"Venda", +"languages.vie":"Vietnamese", +"languages.vol":"Volapük", +"languages.win":"Walloon", +"languages.cym":"Welsh", +"languages.wol":"Wolof", +"languages.fry":"Western Frisian", +"languages.xho":"Xhosa", +"languages.yid":"Yiddish", +"languages.yor":"Yoruba", +"languages.zha":"Zhuang", +"languages.zul":"Zulu", +"tags.title":"Tags", +"tags.categorized":"Categorized index of the {0} tags", +"tags.categorized.single":"Categorized index of the single tag", +"tags.alphabetical.many":"Alphabetical index of the {0} tags", +"tags.alphabetical.one":"Alphabetical index of the single tag", +"tags.alphabetical.none":"Alphabetical index of absolutely no tag", +"splitByLetter.tag.other":"Other tags", +"authors.series.title":"Series: {0}", +"authors.title":"Authors", +"authors.alphabetical.many":"Alphabetical index of the {0} authors", +"authors.alphabetical.one":"Alphabetical index of the single author", +"authors.alphabetical.none":"Alphabetical index of absolutely no author", +"splitByLetter.author.other":"Other authors", +"series.title":"Series", +"series.alphabetical.many":"Alphabetical index of the {0} series", +"series.alphabetical.one":"Alphabetical index of the single series", +"series.alphabetical.none":"Alphabetical index of absolutely no series", +"splitByLetter.series.other":"Other series", +"recent.title":"Recent additions", +"recent.list":"{0} most recent books", +"recent.list.single":"Most recent single book", +"rating.title":"Rating", +"rating.summary":"{0}, grouped by rating", +"allbooks.title":"All books", +"allbooks.alphabetical.many":"Alphabetical index of the {0} books", +"allbooks.alphabetical.one":"Alphabetical index of the single book", +"allbooks.alphabetical.none":"Alphabetical index of absolutely no book", +"splitByLetter.book.other":"Other books", +"main.title":"Calibre library", +"main.summary":"{0} has catalogued {1}", +"startup.newhome":"Default configuration folder home redirected to {0}", +"startup.redirectfound":".redirect file found in {0}", +"startup.redirectreadfail":"... failure reading .redirect file", +"startup.redirectnotfound":"... unable to find redirect folder {0}", +"startup.redirectabandoned":"... so redirect abandoned", +"startup.redirecting":"redirecting home folder to {0}", +"startup.configusing":"Using configuration folder {0}", +"startup.folderuserhome":"Try configuration folder in user home folder {0}", +"startup.foldertilde":"Try configuration folder from tilde folder {0}", +"startup.folderjar":"Try configuration folder from .jar location {0}", +"startup.foldernotexist":"... but specified folder does not exist", +"i18n.and":"and", +"i18n.downloads":"Downloads, links and other catalogs", +"i18n.links":"Links and other catalogs", +"i18n.coversection":"Cover", +"i18n.downloadfile":"Download file", +"i18n.downloadsection":"Downloads", +"i18n.relatedsection":"Related catalogs", +"i18n.linksection":"External links", +"i18n.backToMain":"Back to the main page of the catalog", +"i18n.summarysection":"Description", +"i18n.dateGenerated":"Catalog generated on {0}", +"deeplevel.summary":"{0} broken up by authors, tags, etc. ", +"about.title":"About COPS", +"customize.title":"Customize COPS", +"about.summary":"Notes on using Calibre2Opds", +"usage.intro":"The options are taken from the configuration file located at {0}", +"language.title":"Language", +"config.Language.description":"This setting changes the language used by the program ; use the standard ISO language code (e.g. EN, FR, DE...)", +"config.Language.possible":"Possible values : {0}", +"intro.goal":"Generate OPDS and HTML catalogs from your Calibre ebooks database", +"intro.wiki.title":"The project''s home : ", +"intro.wiki.url":"http://calibre2opds.com", +"intro.team.title":"The Calibre2Opds team :", +"intro.team.list1":"David Pierron - main programmer", +"intro.team.list2":"Dave Walker - guru, features manager and tester extraordinaire", +"intro.team.list3":"Farid Soussi - css and html guru", +"intro.team.list4":"Douglas Steele - programmer", +"intro.team.list5":"Jane Litte - beta tester and moral support", +"intro.thanks.1":"Special thanks to Kb Sriram, who not only programmed Trook, an excellent and OPDS compatible ", +"intro.thanks.2":"library manager for the Nook, but also was kind enough to donate a Nook !", +"search.result":"Search result for *{0}*", +"search.sortorder.asc":"Asc", +"search.sortorder.desc":"Desc", +"permalink.alternate":"Permalink", +"home.alternate":"Home", +"search.alternate":"Search", +"sort.alternate":"Sort", +"settings.alternate":"Settings and Menu", +"paging.next.alternate":"Next", +"paging.previous.alternate":"Previous", +"customize.title":"Customize COPS UI", +"customize.style":"Theme", +"customize.fancybox":"Use a Lightbox", +"customize.paging":"Max number of books per page (-1 to disable)", +"customize.pages":"Max number of pages at one level", +"customize.email":"Set your email (to allow book emailing)", +"customize.filter":"Enable tag filtering", +"mail.messagenotsent":"Message could not be sent.", +"mail.messagesent":"Message has been sent", +"fin":"fin" +} diff --git a/www/lang/Localization_es.json b/www/lang/Localization_es.json new file mode 100644 index 0000000..83f3049 --- /dev/null +++ b/www/lang/Localization_es.json @@ -0,0 +1,100 @@ +{ +"boolean.yes":"si", +"splitByLetter.letter":"{0} que empiezan por {1}", +"home.title":"Catalogo principal", +"link.fullentry":"Entrada completa", +"title.nextpage":"next page ({0} de {1})", +"title.lastpage":"pagina siguiente (ultima)", +"title.numberOfPages":"{0} ({1} paginas)", +"bookword.title":"Libros", +"bookword.none":"Sin libros", +"bookword.one":"1 libro", +"bookword.many":"{0} libros", +"authorword.title":"Autores", +"authorword.none":"Sin autor", +"authorword.one":"1 autor", +"authorword.many":"{0} autores", +"taglevelword.title":"Niveles de Etiquetas", +"taglevelword.none":"Sin nivel de etiquetas", +"taglevelword.one":"1 nivel de etiqueta", +"taglevelword.many":"{0} niveles de etiquetas", +"seriesword.none":"Sin series", +"seriesword.one":"1 serie", +"seriesword.many":"series", +"tagword.title":"Etiquetas", +"tagword.none":"Sin etiquetas", +"tagword.one":"1 etiqueta", +"tagword.many":"etiquetas", +"content.tags":"Etiquetas:", +"content.series.data":"Libro {0} en la {1} serie", +"content.publisher":"Editorial:", +"content.publisher.data":"Publicado {1} por {0}", +"content.summary":"Sumario:", +"bookentry.series":"Libro {0} en la {1} serie", +"bookentry.author":"{0} de {1}", +"bookentry.tags":"{0} en {1}", +"bookentry.ratings":"{0} puntuados {1}", +"bookentry.goodreads":"Este libro en Goodreads", +"bookentry.goodreads.review":"Comenta este libro en Goodreads", +"bookentry.goodreads.author":"{0} en Goodreads", +"bookentry.wikipedia":"Este libro en Wikipedia", +"bookentry.wikipedia.author":"{0} en Wikipedia", +"bookentry.librarything":"Este libro en LibraryThing", +"bookentry.librarything.author":"{0} en LibraryThing", +"bookentry.amazon":"Este libro en Amazon", +"bookentry.amazon.author":"{0} en Amazon", +"bookentry.isfdb.author":"{0} en ISFDB", +"bookentry.download":"Descargar este libro como {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Entrada completa", +"tags.title":"etiquetas", +"tags.categorized":"Listado por categorias de las {0} etiquetas", +"tags.categorized.single":"Listado por categorias de la unica etiqueta", +"tags.alphabetical.many":"Listado alfabético de las {0} etiquetas", +"tags.alphabetical.one":"Listado Alfabético de la unica etiqueta", +"splitByLetter.tag.other":"Otras etiquetas", +"authors.series.title":"Series", +"authors.title":"Autores", +"authors.alphabetical.many":"Indice alfabético de {0}", +"authors.alphabetical.one":"Indice de un solo autor", +"splitByLetter.author.other":"Otros Autores", +"series.title":"Series:", +"series.alphabetical.many":"Indice alfabético de {0}", +"series.alphabetical.one":"Indice de una sola serie", +"splitByLetter.series.other":"Otras series", +"recent.title":"Añadidos recientemente", +"recent.list":"{0} libros más recientes", +"recent.list.single":"El libro más reciente", +"rating.title":"Valoración", +"rating.summary":"{0}, agrupados por valoración", +"allbooks.title":"Todos los libros", +"allbooks.alphabetical.many":"Indice alfabético de {0} libros", +"allbooks.alphabetical.one":"Indice de un solo ", +"splitByLetter.book.other":"Otros libros", +"main.title":"Biblioteca generada de calibre", +"main.summary":"{0} ha catalogado {1}", +"i18n.downloads":"Descargas, enlaces y otros catálogos", +"i18n.links":"Enlaces y otros catálogos", +"i18n.downloadfile":"Descargar fichero", +"i18n.downloadsection":"Descargas, enlaces y otros", +"i18n.relatedsection":"Catálogos relacionados", +"i18n.linksection":"Enlaces externos", +"i18n.backToMain":"Volver a la página de inicio del catálogo", +"i18n.summarysection":"Resumen", +"i18n.dateGenerated":"Catálogo creado el {0}", +"deeplevel.summary":"{0} por autores, etiquetas, etc.", +"about.summary":"Notas de Uso", +"usage.intro":"Las opciones provienen del fichero de configuración presente en: {0}", +"language.title":"Idioma", +"config.Language.description":"Esta opción cambia el idioma del programa; use un código ISO estándar (ej: EN, FR, DE, ES...)", +"config.Language.possible":"Valores posibles: {0}", +"intro.goal":"Genear catálogos OPDS y HTML desde la biblioteca de calibre", +"intro.team.title":"El equipo de Calibre2Opds:", +"intro.team.list1":"David Pierron - Programador principal", +"intro.team.list2":"Dave Walker - guru, opciones, gestión y tester extraordinaire", +"intro.team.list4":"Douglas Steele - programador", +"intro.team.list5":"Jane Litte - beta tester y apoyo moral", +"intro.thanks.1":"Gracias especiales a Kb Sriram, que no solo ha programado Trook, un gestor de bibliotecas OPDS excelente y compatible", +"intro.thanks.2":"con el Nook, sino que además ha sido tan amable de donar un Nook", +"fin":"fin" +} diff --git a/www/lang/Localization_fr.json b/www/lang/Localization_fr.json new file mode 100644 index 0000000..840e38d --- /dev/null +++ b/www/lang/Localization_fr.json @@ -0,0 +1,131 @@ +{ +"boolean.no":"non", +"boolean.yes":"oui", +"splitByLetter.letter":"{0} débutant par {1}", +"home.title":"Catalogue principal", +"link.fullentry":"Entrée complète", +"title.nextpage":"page suivante ({0} sur {1})", +"title.lastpage":"page suivante (dernière)", +"title.numberOfPages":"{0} ({1} pages)", +"pubdate.title":"Année de publication", +"bookword.title":"Livres", +"bookword.none":"Aucun livre", +"bookword.one":"1 livre", +"bookword.many":"{0} livres", +"authorword.title":"Auteurs", +"authorword.none":"Pas d'auteur", +"authorword.one":"1 auteur", +"authorword.many":"{0} auteurs", +"taglevelword.title":"Niveaux", +"taglevelword.none":"Pas de niveau", +"taglevelword.one":"1 niveau", +"taglevelword.many":"{0} niveaux", +"seriesword.title":"Collections", +"seriesword.none":"Pas de collection", +"seriesword.one":"1 collection", +"seriesword.many":"{0} collections", +"tagword.title":"Étiquettes", +"tagword.none":"Sans étiquette", +"tagword.one":"1 étiquette", +"tagword.many":"{0} étiquettes", +"content.tags":"Étiquettes:", +"content.series":"Collection:", +"content.series.data":"Livre {0} dans la collection {1}", +"content.publisher":"Editeur:", +"content.published":"Publié:", +"content.publisher.data":"Publié {0} le {1}", +"content.summary":"Résumé:", +"bookentry.series":"Livre {0} dans la collection {1}", +"bookentry.author":"{0} de {1}", +"bookentry.tags":"{0} dans {1}", +"bookentry.ratings":"{0} notés {1}", +"bookentry.goodreads":"Ce livre sur Goodreads", +"bookentry.goodreads.review":"Critique du livre sur Goodreads", +"bookentry.goodreads.author":"{0} sur Goodreads", +"bookentry.wikipedia":"Ce livre sur Wikipedia", +"bookentry.wikipedia.author":"{0} sur Wikipedia", +"bookentry.librarything":"Ce livre sur LibraryThing", +"bookentry.librarything.author":"{0} sur LibraryThing", +"bookentry.amazon":"Ce livre sur Amazon", +"bookentry.amazon.author":"{0} sur Amazon", +"bookentry.isfdb.author":"{0} sur ISFDB", +"bookentry.download":"Télécharger ce livre au format {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Entrée complète", +"languages.title":"Langues", +"languages.alphabetical.many":"Index alphabétique des {0} langues", +"languages.alphabetical.one":"Index alphabétique de la seule langue", +"tags.title":"Étiquettes", +"tags.categorized":"Index ordonné des {0} étiquettes", +"tags.categorized.single":"Index ordonné de la seule étiquette disponible", +"tags.alphabetical.many":"Index alphabétique des {0} étiquettes", +"tags.alphabetical.one":"Index alphabétique de la seule étiquette", +"splitByLetter.tag.other":"Autres étiquettes", +"authors.series.title":"Collection: {0}", +"authors.title":"Auteurs", +"authors.alphabetical.many":"Index alphabétique des {0} auteurs", +"authors.alphabetical.one":"Index alphabétique du seul auteur", +"splitByLetter.author.other":"Autres auteurs", +"series.title":"Collections", +"series.alphabetical.many":"Index alphabétique de {0} collections", +"series.alphabetical.one":"Index alphabétique de la seule collection", +"splitByLetter.series.other":"Autres collections", +"recent.title":"Ajouts récents", +"recent.list":"{0} livres les plus récents", +"recent.list.single":"Ajouté récemment", +"rating.title":"Appréciations", +"rating.summary":"{0}, par appréciation", +"allbooks.title":"Tous les livres", +"allbooks.alphabetical.many":"Index alphabétique des {0} livres", +"allbooks.alphabetical.one":"Index alphabétique du seul livre", +"splitByLetter.book.other":"Autres livres", +"main.title":"Bibliothèque gérée par Calibre", +"main.summary":"{0} a catalogué {1}", +"i18n.downloads":"Téléchargements, liens et autres catalogues", +"i18n.links":"Liens et autres catalogues", +"i18n.coversection":"Couverture", +"i18n.downloadfile":"Télécharger fichier", +"i18n.downloadsection":"Téléchargements", +"i18n.relatedsection":"Catalogues similaires", +"i18n.linksection":"Liens externes", +"i18n.backToMain":"Page principale du catalogue", +"i18n.summarysection":"Résumé", +"i18n.dateGenerated":"Catalogue généré le {0}", +"deeplevel.summary":"{0} par auteur, étiquette, etc.", +"about.title":"A propos de COPS", +"about.summary":"Notes d'utilisation", +"usage.intro":"Les options sont tirées du fichier de configuration qui se trouve ici : {0}", +"language.title":"Langue", +"config.Language.description":"Fixe la langue utilisée par le programme ; Code ISO Standard (ex: EN, FR, DE...)", +"config.Language.possible":"Valeurs proposées : {0}", +"intro.goal":"Génère des catalogues OPDS et HTML à partir de votre base de données de Calibre", +"intro.wiki.title":"Le site du projet :", +"intro.team.title":"L'équipe Calibre2Opds :", +"intro.team.list1":"David Pierron - programmeur principal", +"intro.team.list2":"Dave Walker - guru, responsable des fonctionnalités et testeur extraordinaire", +"intro.team.list3":"Farid Soussi - guru HTML et CSS", +"intro.team.list4":"Douglas Steele - programmeur", +"intro.team.list5":"Jane Litte - beta testeuse et soutien moral", +"intro.thanks.1":"Remerciements particuliers à Kb Sriram, qui est l'auteur de Trook, un excellent", +"intro.thanks.2":"gestionnaire de bibliothèque pour Nook, et a m'a fait cadeau d'un Nook !", +"search.result":"Résultats pour *{0}*", +"search.sortorder.asc":"Crois.", +"search.sortorder.desc":"Décrois.", +"permalink.alternate":"Permalien", +"home.alternate":"Accueil", +"search.alternate":"Rechercher", +"sort.alternate":"Trier", +"paging.next.alternate":"Suivant", +"paging.previous.alternate":"Précédent", +"customize.title":"Paramétrage de COPS", +"customize.style":"Thème", +"customize.fancybox":"Utiliser une Lightbox", +"customize.paging":"Nombre de livres par page (-1 pour désactiver)", +"customize.email":"Adresse email (pour l'envoi automatique de livres)", +"customize.filter":"Filtrage via les étiquettes", +"languages.eng":"Anglais", +"languages.fra":"Français", +"mail.messagenotsent":"Le message n'a pas pu être envoyé.", +"mail.messagesent":"Le message a été envoyé", +"fin":"fin" +} diff --git a/www/lang/Localization_it.json b/www/lang/Localization_it.json new file mode 100644 index 0000000..ee5b369 --- /dev/null +++ b/www/lang/Localization_it.json @@ -0,0 +1,102 @@ +{ +"boolean.yes":"sì", +"splitByLetter.letter":"{0} che iniziano per {1}", +"home.title":"Catalogo principale", +"link.fullentry":"Scheda completa", +"title.nextpage":"pagina seguente ({0} di {1})", +"title.lastpage":"pagina seguente (ultima)", +"title.numberOfPages":"{0} ({1} pagine)", +"bookword.title":"Libri", +"bookword.none":"Nessun libro", +"bookword.one":"1 libro", +"bookword.many":"{0} libri", +"authorword.title":"Autori", +"authorword.none":"Senza autore", +"authorword.one":"1 autore", +"authorword.many":"{0} autori", +"taglevelword.title":"Livelli", +"taglevelword.none":"Nessun livello", +"taglevelword.one":"1 livello", +"taglevelword.many":"{0} livelli", +"seriesword.title":"Collane", +"seriesword.none":"Nessuna collana", +"seriesword.one":"1 collana", +"seriesword.many":"{0} collane", +"tagword.title":"Argomenti", +"tagword.none":"Senza argomento", +"tagword.one":"1 argomento", +"tagword.many":"{0} argomenti", +"content.tags":"Argomenti:", +"content.series":"Collana:", +"content.series.data":"Libro {0} nella collana {1}", +"content.publisher":"Editore:", +"content.publisher.data":"Pubblicato {0} il {1}", +"content.summary":"Riassunto:", +"bookentry.series":"Libro {0} nella collana {1}", +"bookentry.author":"{0} di {1}", +"bookentry.ratings":"{0} annotati {1}", +"bookentry.goodreads":"Questo libro su Goodreads", +"bookentry.goodreads.review":"Commenta questo libro su Goodreads", +"bookentry.goodreads.author":"{0} su Goodreads", +"bookentry.wikipedia":"Questo libro su Wikipedia", +"bookentry.wikipedia.author":"{0} su Wikipedia", +"bookentry.librarything":"Questo libro su LibraryThing", +"bookentry.librarything.author":"{0} su LibraryThing", +"bookentry.amazon":"Questo libro su Amazon", +"bookentry.amazon.author":"{0} su Amazon", +"bookentry.isfdb.author":"{0} su ISFDB", +"bookentry.download":"Scarica questo libro nel formato {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Scheda completa", +"tags.title":"Argomenti", +"tags.categorized":"Indice ordinato di {0} argomenti", +"tags.categorized.single":"Indice ordinato del solo argomento disponibile", +"tags.alphabetical.many":"Indice alfabetico di {0} argomenti", +"tags.alphabetical.one":"Indice alfabetico del solo argomento", +"splitByLetter.tag.other":"Altri argomenti", +"authors.series.title":"Collane: {0}", +"authors.title":"Autori", +"authors.alphabetical.many":"Indice alfabetico di {0} autori", +"authors.alphabetical.one":"Indice alfabetico di un solo autore", +"splitByLetter.author.other":"Altri autori", +"series.title":"Collane", +"series.alphabetical.many":"Indice alfabetico di {0} collane", +"series.alphabetical.one":"Indice alfabetico di una sola collana", +"splitByLetter.series.other":"Altre collane", +"recent.title":"Ultime aggiunte", +"recent.list":" I {0} libri più recenti", +"recent.list.single":"Il libro più recente", +"rating.title":"Valutazioni", +"rating.summary":"{0}, per valutazioni", +"allbooks.title":"Tutti i libri", +"allbooks.alphabetical.many":"Indice alfabetico di {0} libri", +"allbooks.alphabetical.one":"Indice alfabetico di un solo libro", +"splitByLetter.book.other":"Altri libri", +"main.title":"Biblioteca generata da Calibre", +"main.summary":"{0} ha catalogato {1}", +"i18n.downloads":"Scarica, collegamenti e altri cataloghi", +"i18n.links":"Collegamenti e altri cataloghi", +"i18n.downloadfile":"Scarica il documento", +"i18n.downloadsection":"Scaricati", +"i18n.relatedsection":"Cataloghi simili", +"i18n.linksection":"Collegamenti esterni", +"i18n.backToMain":"Pagina principale del catalogo", +"i18n.summarysection":"Riassunto", +"i18n.dateGenerated":"Catalogo generato il {0}", +"deeplevel.summary":"{0} par autore, etichetta, ecc.", +"about.summary":"Note d'utilizzo", +"usage.intro":"Le opzioni sono prese dal file di configuraziones che si trova qui: {0}", +"language.title":"Lingua", +"config.Language.description":"Scegli la lingua da utilizzare ; usa il codice ISO standard (es: EN, FR, DE...)", +"config.Language.possible":"Valori proposti: {0}", +"intro.goal":"Crea cataloghi OPDS e HTML a partire dalla tua banca dati di Calibre", +"intro.wiki.title":"Il sito del progetto: ", +"intro.team.title":"Il team Calibre2Opds:", +"intro.team.list1":"David Pierron - programmatore principale", +"intro.team.list2":"Dave Walker - guru, responsabile delle funzionalità e collaudatore straordinario", +"intro.team.list4":"Douglas Steele - programmatore", +"intro.team.list5":"Jane Litte - collaudatrice beta e sostegno morale", +"intro.thanks.1":"Un ringraziamento particolare a Kb Sriram, autore di Trook, un eccellente ", +"intro.thanks.2":"gestore di biblioteche per Nook, che mi ha regalato un Nook!", +"fin":"fin" +} diff --git a/www/lang/Localization_nb.json b/www/lang/Localization_nb.json new file mode 100644 index 0000000..6b0ce37 --- /dev/null +++ b/www/lang/Localization_nb.json @@ -0,0 +1,144 @@ +{ +"boolean.no":"nei", +"boolean.yes":"ja", +"splitByLetter.letter":"{0} begynner med {1}", +"home.title":"Hovedkatalog", +"link.fullentry":"Full oppføring", +"title.nextpage":"neste side ({0} av {1})", +"title.lastpage":"neste side (siste)", +"title.numberOfPages":"{0} ({1} sider)", +"pubdate.title":"PublikasjonsÃ¥r", +"bookword.title":"Bøker", +"bookword.none":"Ingen bøker", +"bookword.one":"1 bok", +"bookword.many":"{0} bøker", +"authorword.title":"Forfattere", +"authorword.none":"Ingen forfattere", +"authorword.one":"1 forfatter", +"authorword.many":"{0} forfattere", +"taglevelword.title":"StikkordnivÃ¥er", +"taglevelword.none":"Ingen stikkordnivÃ¥er", +"taglevelword.one":"1 stikkordnivÃ¥", +"taglevelword.many":"{0} stikkordnivÃ¥", +"seriesword.title":"Serier", +"seriesword.none":"Ingen serier", +"seriesword.one":"1 serie", +"seriesword.many":"{0} serier", +"tagword.title":"Stikkord", +"tagword.none":"Ingen stikkord", +"tagword.one":"1 stikkord", +"tagword.many":"{0} stikkord", +"content.tags":"Stikkord:", +"content.series":"Serier:", +"content.series.data":"Bok {0} i serien {1}", +"content.publisher":"Utgiver:", +"content.published":"Utgitt:", +"content.added":"Lagt til: ", +"content.modified":"Endret:", +"content.publisher.data":"Utgitt {1} av {0}", +"content.summary":"Sammendrag", +"bookentry.series":"Bok {0} i serien {1}", +"bookentry.author":"{0} av {1}", +"bookentry.tags":"{0} i {1}", +"bookentry.ratings":"{0} rated {1}", +"bookentry.goodreads":"Denne boka pÃ¥ Goodreads", +"bookentry.goodreads.review":"Anmeld denne boka pÃ¥ Goodreads", +"bookentry.goodreads.author":"{0} pÃ¥ Goodreads", +"bookentry.wikipedia":"Denne boka pÃ¥ Wikipedia", +"bookentry.wikipedia.author":"{0} pÃ¥ Wikipedia", +"bookentry.librarything":"Denne boka pÃ¥ LibraryThing", +"bookentry.librarything.author":"{0} pÃ¥ LibraryThing", +"bookentry.amazon":"Denne boka pÃ¥ Amazon", +"bookentry.amazon.author":"{0} pÃ¥ Amazon", +"bookentry.isfdb.author":"{0} pÃ¥ ISFDB", +"bookentry.download":"Last ned denne eboka som {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Full oppføring", +"languages.title":"SprÃ¥k", +"languages.categorized":"Kategorisert indeks for de {0} sprÃ¥kene", +"languages.categorized.single":"Kategorisert indeks for ett enkelt sprÃ¥k", +"languages.alphabetical.many":"Alfabetisk indek for de {0} sprÃ¥kene", +"languages.alphabetical.one":"Alfabetisk indeks for ett enkelt sprÃ¥k", +"languages.alphabetical.none":"Alfabetisk indeks for absolutt ingen sprÃ¥k", +"languages.nob":"Norsk bokmÃ¥l", +"tags.title":"Stikkord", +"tags.categorized":"Kategorisert indeks for {0} stikkord", +"tags.categorized.single":"Kategorisert indeks for ett enkelt stikkord", +"tags.alphabetical.many":"Alfabetisk indeks for {0} stikkord", +"tags.alphabetical.one":"Alfabetisk indeks for ett enkelt stikkord", +"tags.alphabetical.none":"Alfabetisk indeks for ingen stikkord", +"splitByLetter.tag.other":"Andre stikkord", +"authors.series.title":"Serier: {0}", +"authors.title":"Forfattere", +"authors.alphabetical.many":"Alfabetisk indeks for {0} forfattere", +"authors.alphabetical.one":"Alfabetisk indeks for èn forfatter", +"authors.alphabetical.none":"Alfabetisk indeks for ingen forfattere", +"splitByLetter.author.other":"Andre forfattere", +"series.title":"Serier", +"series.alphabetical.many":"Alfabetisk indeks for {0} serier", +"series.alphabetical.one":"Alfabetisk indeks for èn enkelt serie", +"series.alphabetical.none":"Alfabetisk indeks for ingen serier", +"splitByLetter.series.other":"Andre serier", +"recent.title":"Nylig lagt til", +"recent.list":"{0} nyeste bøker", +"recent.list.single":"Nyeste enkeltbok", +"rating.title":"Vurdering", +"rating.summary":"{0}, gruppert etter vurdering", +"allbooks.title":"Alle bøker", +"allbooks.alphabetical.many":"Alfabetisk indeks for {0} bøker", +"allbooks.alphabetical.one":"Alfabetisk indeks for èn enkelt bok", +"allbooks.alphabetical.none":"Alfabetisk indeks for absolutt ingen bøker", +"splitByLetter.book.other":"Andre bøker", +"main.title":"Calibre-bibliotek", +"main.summary":"{0} har katalogisert {1}", +"startup.newhome":"Standard konfigurasjonsmappe omdirigert til {0}", +"startup.redirectfound":".redirect fil finnet i {0}", +"startup.redirectreadfail":"... feil ved innlesing av .redirect fil", +"startup.redirectnotfound":"... kunne ikke finne omdirigeringsmappe {0}", +"startup.redirectabandoned":"... sÃ¥ omdirigering er avbrutt", +"startup.redirecting":"omdirigerer hjemmemappe til {0}", +"startup.configusing":"Bruker konfigurasjonsmappe {0}", +"startup.folderuserhome":"Prøv konfigurasjonsmappe i brukers hjemmemappe {0}", +"startup.foldertilde":"Prøv konfigurasjonsmappe fra tilde-mappe {0}", +"startup.folderjar":"Prøv konfigurasjonsmappe fra .jar plassering {0}", +"startup.foldernotexist":"... men spesifisert mappe eksisterer ikke", +"i18n.and":"og", +"i18n.downloads":"Nedlastinger, lenker og andre kataloger", +"i18n.links":"Lenker og andre kataloger", +"i18n.coversection":"Omslag", +"i18n.downloadfile":"Last ned fil", +"i18n.downloadsection":"Nedlastinger", +"i18n.relatedsection":"Relaterte kataloger", +"i18n.linksection":"Eksterne lenker", +"i18n.backToMain":"Tilbake til hovedside for katalogen", +"i18n.summarysection":"Beskrivelse", +"i18n.dateGenerated":"Katalog generert den {0}", +"deeplevel.summary":"{0} brutt ned pÃ¥ forfattere, stikkord, osv. ", +"about.title":"Om COPS", +"about.summary":"Merknader om bruk av Calibre2Opds", +"usage.intro":"Innstillinger er tatt fra konfigurasjonsfila som ligger i {0}", +"language.title":"SprÃ¥k", +"config.Language.description":"Denne innstillingen endrer sprÃ¥ket som benyttes av programmet; bruk standard ISO sprÃ¥kkoder (e.g. EN, FR, DE...)", +"config.Language.possible":"Mulige verdier : {0}", +"intro.goal":"Generer OPDS- og HTML-kataloger fra din Calibre ebokdatabase", +"intro.wiki.title":"Prosjektets hjemmeside : ", +"intro.wiki.url":"http://calibre2opds.com", +"intro.team.title":"Calibre2Opds-teamet :", +"intro.team.list1":"David Pierron - main programmer", +"intro.team.list2":"Dave Walker - guru, features manager and tester extraordinaire", +"intro.team.list3":"Farid Soussi - css and html guru", +"intro.team.list4":"Douglas Steele - programmer", +"intro.team.list5":"Jane Litte - beta tester and moral support", +"intro.thanks.1":"Special thanks to Kb Sriram, who not only programmed Trook, an excellent and OPDS compatible ", +"intro.thanks.2":"library manager for the Nook, but also was kind enough to donate a Nook !", +"search.result":"Søkeresultat for *{0}*", +"search.sortorder.asc":"Stigende", +"search.sortorder.desc":"Synkende", +"permalink.alternate":"Permalenke", +"home.alternate":"Hjem", +"search.alternate":"Søk", +"sort.alternate":"Sorter", +"paging.next.alternate":"Neste", +"paging.previous.alternate":"Forrige", +"fin":"fin" +} diff --git a/www/lang/Localization_nl.json b/www/lang/Localization_nl.json new file mode 100644 index 0000000..6b9efae --- /dev/null +++ b/www/lang/Localization_nl.json @@ -0,0 +1,122 @@ +{ +"boolean.no":"nee", +"boolean.yes":"ja", +"splitByLetter.letter":"{0} beginnend met {1}", +"home.title":"Hoofd catalogus", +"link.fullentry":"Volledige invoer", +"title.nextpage":"Volgende pagina ({0} van {1})", +"title.lastpage":"Volgende pagina (last)", +"title.numberOfPages":"{0} ({1} paginas)", +"bookword.title":"Boeken", +"bookword.none":"Geen boek", +"bookword.one":"1 boek", +"bookword.many":"{0} boeken", +"authorword.title":"Auteurs", +"authorword.none":"Geen auteur", +"authorword.one":"1 auteur", +"authorword.many":"{0} auteurs", +"taglevelword.title":"Tag niveaus", +"taglevelword.none":"Geen tag niveau", +"taglevelword.one":"1 tag niveau", +"taglevelword.many":"{0} tag niveaus", +"seriesword.title":"Series", +"seriesword.none":"Geen series", +"seriesword.one":"1 serie", +"seriesword.many":"{0} series", +"tagword.title":"Tags", +"tagword.none":"Geen tag", +"tagword.one":"1 tag", +"tagword.many":"{0} tags", +"content.tags":"Tags:", +"content.series":"Series:", +"content.series.data":"Boek {0} in de {1} serie", +"content.publisher":"Uitgever:", +"content.published":"Uitgegeven:", +"content.added":"Toegevoegd: ", +"content.modified":"Gewijzigd:", +"content.publisher.data":"Uitgegeven {1} door {0}", +"content.summary":"Samenvatting", +"bookentry.series":"Boek {0} in de {1} serie", +"bookentry.author":"{0} door {1}", +"bookentry.tags":"{0} in {1}", +"bookentry.ratings":"{0} rated {1}", +"bookentry.goodreads":"Dit boek op Goodreads", +"bookentry.goodreads.review":"Review dit boek op Goodreads", +"bookentry.goodreads.author":"{0} op Goodreads", +"bookentry.wikipedia":"Dit boek op Wikipedia", +"bookentry.wikipedia.author":"{0} op Wikipedia", +"bookentry.librarything":"Dit boek op LibraryThing", +"bookentry.librarything.author":"{0} op LibraryThing", +"bookentry.amazon":"Dit boek op Amazon", +"bookentry.amazon.author":"{0} op Amazon", +"bookentry.isfdb.author":"{0} op ISFDB", +"bookentry.download":"Download dit eboek als {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Volledige invoer", +"tags.title":"Tags", +"tags.categorized":"Gecategorizeerde index van {0} tags", +"tags.categorized.single":"Gecategorizeerde index van 1 tag", +"tags.alphabetical.many":"Alfabetische index van {0} tags", +"tags.alphabetical.one":"Alfabetische index van 1 tag", +"splitByLetter.tag.other":"Andere tags", +"authors.series.title":"Series: {0}", +"authors.title":"Auteurs", +"authors.alphabetical.many":"Alfabetische index van {0} auteurs", +"authors.alphabetical.one":"Alfabetische index van 1 auteur", +"splitByLetter.author.other":"Andere auteurs", +"series.title":"Series", +"series.alphabetical.many":"Alfabetische index van {0} series", +"series.alphabetical.one":"Alfabetische index van 1 serie", +"splitByLetter.series.other":"Andere series", +"recent.title":"Recent toegevoegd", +"recent.list":"{0} meest recente boeken", +"recent.list.single":"Meest recente boek", +"rating.title":"Rating", +"rating.summary":"{0}, gegroepeerd per rating", +"allbooks.title":"Alle boeken", +"allbooks.alphabetical.many":"Alfabetische index van {0} boeken", +"allbooks.alphabetical.one":"Alfabetische index van 1 boek", +"splitByLetter.book.other":"Andere boeken", +"main.title":"Calibre bibliotheek", +"main.summary":"{0} heeft gecatalogiseerd {1}", +"startup.newhome":"Standaard configuratie folder home doorverwezen naar {0}", +"startup.redirectfound":".redirect file gevonden in {0}", +"startup.redirectreadfail":"... fout bij lezen .redirect file", +"startup.redirectnotfound":"... niet in staat doorverwijs folder {0} te vinden", +"startup.redirectabandoned":"... dus doorverwijzing niet uitgevoerd", +"startup.redirecting":"home folder wordt doorverwezen naar {0}", +"startup.configusing":"Gebruikt configuratie folder {0}", +"startup.folderuserhome":"Probeer configuratie folder in user home folder {0}", +"startup.foldertilde":"Probeer configuratie folder uit tilde folder {0}", +"startup.folderjar":"Probeer configuratie folder uit .jar locatie {0}", +"startup.foldernotexist":"... maar gespecificeerde folder bestaat niet", +"i18n.and":"en", +"i18n.downloads":"Downloads, links en andere catalogi", +"i18n.links":"Links en andere catalogi", +"i18n.coversection":"Omslag", +"i18n.downloadfile":"Download bestand", +"i18n.downloadsection":"Downloads", +"i18n.relatedsection":"Gerelateerde catalogi", +"i18n.linksection":"Externe links", +"i18n.backToMain":"Terug naar de hoofdpagina van de catalogus", +"i18n.summarysection":"Omschrijving", +"i18n.dateGenerated":"Catalogus gegenereerd op {0}", +"deeplevel.summary":"{0} uitgesplitst naar auteurs, tags, etc. ", +"about.summary":"Notities hoe Calibre2Opds te gebruiken", +"usage.intro":"De opties worden uit het configuratie bestand {0} gebruikt", +"language.title":"Taal", +"config.Language.description":"Deze setting verandert de taal van het programma ; gebruik de standaard ISO taal code (e.g. EN, FR, DE...)", +"config.Language.possible":"Mogelijke waardes : {0}", +"intro.goal":"Genereer OPDS and HTML catalogi van je Calibre eboeken database", +"intro.wiki.title":"The project''s home : ", +"intro.wiki.url":"http://calibre2opds.com", +"intro.team.title":"The Calibre2Opds team :", +"intro.team.list1":"David Pierron - main programmer", +"intro.team.list2":"Dave Walker - guru, features manager and tester extraordinaire", +"intro.team.list3":"Farid Soussi - css and html guru", +"intro.team.list4":"Douglas Steele - programmer", +"intro.team.list5":"Jane Litte - beta tester and moral support", +"intro.thanks.1":"Special thanks to Kb Sriram, who not only programmed Trook, an excellent and OPDS compatible ", +"intro.thanks.2":"library manager for the Nook, but also was kind enough to donate a Nook !", +"fin":"Einde" +} diff --git a/www/lang/Localization_ru.json b/www/lang/Localization_ru.json new file mode 100644 index 0000000..d7cdb38 --- /dev/null +++ b/www/lang/Localization_ru.json @@ -0,0 +1,305 @@ +{ +"boolean.no":"нет", +"boolean.yes":"да", +"splitByLetter.letter":"{0} начиная с {1}", +"home.title":"Основной каталог", +"link.fullentry":"Полный перечень", +"title.nextpage":"следующая страница ({0} из {1})", +"title.lastpage":"следующая страница (последняя)", +"title.numberOfPages":"{0} ({1} страницы(а))", +"pubdate.title":"Год публикации", +"bookword.title":"Книги", +"bookword.none":"Нет книг", +"bookword.one":"1 книга", +"bookword.many":"{0} книг(и)", +"authorword.title":"Авторы", +"authorword.none":"Нет авторов", +"authorword.one":"1 автор", +"authorword.many":"{0} авторов(а)", +"taglevelword.title":"Уровни жанров", +"taglevelword.none":"Нет уровней жанров", +"taglevelword.one":"1 уровень жанров", +"taglevelword.many":"{0} уровней(я) жанров", +"seriesword.title":"Серии", +"seriesword.none":"Нет серий", +"seriesword.one":"1 серия", +"seriesword.many":"{0} серий(и)", +"tagword.title":"Жанры", +"tagword.none":"Нет жанров", +"tagword.one":"1 жанр", +"tagword.many":"{0} жанров", +"content.tags":"Жанры:", +"content.series":"Серии:", +"content.series.data":"Книга {0} в {1} серии", +"content.publisher":"Издательство:", +"content.published":"Опубликовано:", +"content.publisher.data":"Опубликовано {1} издательством {0}", +"content.summary":"Краткое содержание:", +"bookentry.series":"Книга {0} в {1} серии", +"bookentry.author":"{0} из {1}", +"bookentry.tags":"{0} в {1}", +"bookentry.ratings":"{0} оценено {1}", +"bookentry.goodreads":"Эта книга на Goodreads", +"bookentry.goodreads.review":"Обзор этой книги на Goodreads", +"bookentry.goodreads.author":"{0} на Goodreads", +"bookentry.wikipedia":"Эта книга на Wikipedia", +"bookentry.wikipedia.author":"{0} на Wikipedia", +"bookentry.librarything":"Эта книга на LibraryThing", +"bookentry.librarything.author":"{0} на LibraryThing", +"bookentry.amazon":"Эта книга на Amazon", +"bookentry.amazon.author":"{0} на Amazon", +"bookentry.isfdb.author":"{0} на ISFDB", +"bookentry.download":"Скачать эту электронную книгу в формате {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"Полный перечень", +"tags.title":"Жанры", +"tags.categorized":"Категорированный указатель для {0} жанров", +"tags.categorized.single":"Категорированный указатель для одного жанра", +"tags.alphabetical.many":"Алфавитный указатель для {0} жанров", +"tags.alphabetical.one":"Алфавитный указатель для одного жанра", +"splitByLetter.tag.other":"Другие жанры", +"authors.series.title":"Серии: {0}", +"authors.title":"Авторы", +"authors.starting":"Авторы, начинающиеся с", +"authors.alphabetical.many":"Алфавитный указатель для {0} авторов", +"authors.alphabetical.one":"Алфавитный указатель для одного автора", +"splitByLetter.author.other":"Другие авторы", +"series.title":"Серии", +"series.alphabetical.many":"Алфавитный указатель для {0} серий", +"series.alphabetical.one":"Алфавитный указатель для одной серии", +"splitByLetter.series.other":"Другие серии", +"recent.title":"Недавние поступления", +"recent.list":"{0} недавно поступивших(ие) книг(и)", +"recent.list.single":"Недавно поступившая одна книга", +"rating.title":"Рейтинг", +"rating.summary":"{0}, сгруппировано по рейтингу", +"allbooks.title":"Все книги", +"allbooks.alphabetical.many":"Алфавитный указатель всех {0} книг", +"allbooks.alphabetical.one":"Алфавитный указатель одной книги", +"splitByLetter.book.other":"Другие книги", +"main.title":"Библиотека Calibre", +"main.summary":"{0} каталогизировано {1}", +"i18n.and":"и", +"i18n.downloads":"Файлы для скачивания, ссылки и другие каталоги", +"i18n.links":"Ссылки и другие каталоги", +"i18n.coversection":"Обложка", +"i18n.downloadfile":"Скачать файл", +"i18n.downloadsection":"Файлы для скачивания", +"i18n.relatedsection":"Связанные каталоги", +"i18n.linksection":"Внешние ссылки", +"i18n.backToMain":"Назад на заглавную страницу каталога", +"i18n.summarysection":"Краткое содержание", +"i18n.dateGenerated":"Каталог создан {0}", +"deeplevel.summary":"{0} разбито по авторам, тэгам и т.д.", +"about.summary":"Примечания по использованию Calibre2Opds", +"about.title":"О программе", +"usage.intro":"Эта опция взята из файла конфигурации, расположенного в {0}", +"language.title":"Язык", +"languages.title":"Языки", +"languages.categorized":"Указатель для {0} языков", +"languages.categorized.single":"Указатель для одного языка", +"languages.alphabetical.many":"Алфавитный указатель для {0} языков", +"languages.alphabetical.one":"Алфавитный указатель для одного языка", +"languages.ba":"Башкирский", +"languages.by":"Белорусский", +"languages.id":"Индонезийский", +"languages.cz":"Чешский", +"languages.en":"Английский", +"languages.de":"Немецкий", +"languages.it":"Итальянский", +"languages.is":"Исландский", +"languages.eo":"Эсперанто", +"languages.la":"Латышский", +"languages.sp":"Испанский", +"languages.jp":"Японский", +"languages.ru":"Русский", +"languages.fr":"Французский", +"languages.ua":"Украинский", +"languages.zh":"Китайский", +"languages.pl":"Польский", +"languages.sv":"Шведский", +"languages.sr":"Сербский", +"languages.sk":"Словацкий", +"languages.fi":"Финский", +"languages.az":"Азербайджанский", +"languages.bg":"Болгарский", +"languages.es":"Эстонский", +"languages.cu":"Церковно-славянский", +"languages.hr":"Хорватский", +"languages.hu":"Венгерский", +"languages.kk":"Казахский", +"languages.lv":"Литовский", +"languages.pt":"Португальский", +"languages.ro":"Румынский", +"languages.uz":"Узбекский", +"languages.vi":"Вьетнамский", +"languages.nl":"Голландский", +"languages.ca":"Каталанский", +"languages.cv":"Чувашский", +"languages.da":"Датский", +"languages.el":"Греческий", +"languages.no":"Норвежский", +"languages.tu":"Туркменский", +"languages.tg":"Таджикский", +"languages.tr":"Турецкий", +"languages.ga":"Гэльский", +"tags.sf_history":"Альтернативная история", +"tags.sf_action":"Боевая Фантастика", +"tags.sf_epic":"Эпическая Фантастика", +"tags.sf_heroic":"Героическая фантастика", +"tags.sf_detective":"Детективная Фантастика", +"tags.sf_cyberpunk":"Киберпанк", +"tags.sf_space":"Космическая Фантастика", +"tags.sf_social":"Социальная фантастика", +"tags.sf_horror":"Ужасы и Мистика", +"tags.sf_humor":"Юмористическая фантастика", +"tags.sf_fantasy":"Фэнтези", +"tags.sf":"Научная Фантастика", +"tags.child_sf":"Детская Фантастика", +"tags.det_classic":"Классический Детектив", +"tags.det_police":"Полицейский Детектив", +"tags.det_action":"Боевики", +"tags.det_irony":"Иронический Детектив", +"tags.det_history":"Исторический Детектив", +"tags.det_espionage":"Шпионский Детектив", +"tags.det_crime":"Криминальный Детектив", +"tags.det_political":"Политический Детектив", +"tags.det_maniac":"Маньяки", +"tags.det_hard":"Крутой Детектив", +"tags.thriller":"Триллеры", +"tags.detective":"Детектив", +"tags.sf_detective":"Детективная Фантастика", +"tags.child_det":"Детские Остросюжетные", +"tags.love_detective":"Остросюжетные Любовные Романы", +"tags.prose":"Проза", +"tags.prose_classic":"Классическая Проза", +"tags.prose_history":"Историческая Проза", +"tags.prose_contemporary":"Современная Проза", +"tags.prose_counter":"Контркультура", +"tags.prose_rus_classic":"Русская Классика", +"tags.prose_su_classics":"Советская Классика", +"tags.humor_prose":"Юмористическая Проза", +"tags.child_prose":"Детская Проза", +"tags.love":"Любовные романы", +"tags.love_contemporary":"Современные Любовные Романы", +"tags.love_history":"Исторические Любовные Романы", +"tags.love_detective":"Остросюжетные Любовные Романы", +"tags.love_short":"Короткие Любовные Романы", +"tags.love_erotica":"Эротика", +"tags.adv_western":"Вестерны", +"tags.adv_history":"Исторические Приключения", +"tags.adv_indian":"Приключения: Индейцы", +"tags.adv_maritime":"Морские Приключения", +"tags.adv_geo":"Путешествия и География", +"tags.adv_animal":"Природа и Животные", +"tags.adventure":"Приключения: Прочее", +"tags.child_adv":"Детские Приключения", +"tags.children":"Детское", +"tags.child_tale":"Сказки", +"tags.child_verse":"Детские Стихи", +"tags.child_prose":"Детская Проза", +"tags.child_sf":"Детская Фантастика", +"tags.child_det":"Детские Остросюжетные", +"tags.child_adv":"Детские Приключения", +"tags.child_education":"Детская Образовательная литература", +"tags.children":"Детское: Прочее", +"tags.poetry":"Поэзия", +"tags.dramaturgy":"Драматургия", +"tags.humor_verse":"Юмористические Стихи", +"tags.child_verse":"Детские Стихи", +"tags.antique_ant":"Античная Литература", +"tags.antique_european":"Европейская Старинная Литература", +"tags.antique_russian":"Древнерусская Литература", +"tags.antique_east":"Древневосточная Литература", +"tags.antique_myths":"Мифы. Легенды. Эпос", +"tags.antique":"Старинная Литература: Прочее", +"tags.sci_history":"История", +"tags.sci_earth":"Науки о Земле", +"tags.sci_psychology":"Психология", +"tags.sci_culture":"Культурология", +"tags.sci_religion":"Религиоведение", +"tags.sci_philosophy":"Философия", +"tags.sci_politics":"Политика", +"tags.sci_business":"Деловая литература", +"tags.sci_juris":"Юриспруденция", +"tags.sci_linguistic":"Языкознание", +"tags.sci_medicine":"Медицина", +"tags.sci_phys":"Физика", +"tags.sci_math":"Математика", +"tags.sci_chem":"Химия", +"tags.sci_biology":"Биология", +"tags.sci_tech":"Технические", +"tags.science":"Научно-образовательная: Прочее", +"tags.adv_animal":"Природа и Животные", +"tags.comp_www":"Интернет", +"tags.comp_programming":"Программирование", +"tags.comp_hard":"Компьютерное Железо", +"tags.comp_soft":"Программы", +"tags.comp_db":"Базы Данных", +"tags.comp_osnet":"ОС и Сети", +"tags.computers":"Компьютеры: Прочее", +"tags.ref_encyc":"Энциклопедии", +"tags.ref_dict":"Словари", +"tags.ref_ref":"Справочники", +"tags.ref_guide":"Руководства", +"tags.reference":"Справочная Литература: Прочее", +"tags.nonf_biography":"Биографии и Мемуары", +"tags.nonf_publicism":"Публицистика", +"tags.nonf_criticism":"Критика", +"tags.nonfiction":"Документальное: Прочее", +"tags.nonf_military":"Военное дело", +"tags.music":"Музыка", +"tags.design":"Искусство, Дизайн", +"tags.adv_animal":"Природа и Животные", +"tags.religion_rel":"Религия", +"tags.religion_esoterics":"Эзотерика", +"tags.religion_self":"Самосовершенствование", +"tags.religion":"Религия и духовность: Прочее", +"tags.sci_religion":"Религиоведение", +"tags.humor_anecdote":"Анекдоты", +"tags.humor_prose":"Юмористическая Проза", +"tags.humor_verse":"Юмористические Стихи", +"tags.humor":"Юмор: Прочее", +"tags.home_cooking":"Кулинария", +"tags.home_pets":"Домашние Животные", +"tags.home_crafts":"Хобби, Ремесла", +"tags.home_entertain":"Развлечения", +"tags.home_health":"Здоровье", +"tags.home_garden":"Сад и Огород", +"tags.home_diy":"Сделай Сам", +"tags.home_sport":"Спорт", +"tags.home_sex":"Эротика, Секс", +"tags.home":"Дом и Семья: Прочее", +"tags.other":"Прочее", +"tags.prose_military":"Военная литература", +"config.Language.description":"Эта опция устанавливает язык, используемый программой; используйте стандартный ISO код языка (например, EN, FR, DE...)", +"config.Language.possible":"Возможные значения : {0}", +"intro.goal":"Генерация OPDS и HTML каталогов вашей базы электронных книг Calibre", +"intro.team.title":"Команда Calibre2Opds :", +"intro.team.list1":"David Pierron - главный программист", +"intro.team.list2":"Dave Walker - гуру, менеджер по фукциональности и исключительный тестер", +"intro.team.list4":"Douglas Steele - программист", +"intro.team.list5":"Jane Litte - бэта-тестер и моральная поддержка", +"intro.thanks.1":"Особые благодарности Kb Sriram, который программировал Trook, отличный и OPDS-совместимый", +"intro.thanks.2":"менеджер библиотеки Nook, для благодарности достаточно оказать спонсорскую помощь Nook !", +"fin":"Конец", +"customize.title":"Настройки внешнего вида", +"customize.style":"Стиль", +"customize.fancybox":"Использовать лайтбокс", +"customize.paging":"Максимальное количество элементов на странице (-1 - показывать все)", +"customize.pages":"Максимальное количество страниц на одном уровне", +"customize.email":"Адрес электроннной почты (для отправки файлов почтой)", +"customize.filter":"Включить фильтрацию по жанрам", +"search.result":"Результаты поиска по *{0}*", +"search.sortorder.asc":"Возр.", +"search.sortorder.desc":"Убыв.", +"permalink.alternate":"Постоянная ссылка", +"home.alternate":"Домой", +"settings.alternate":"Опции", +"search.alternate":"Поиск", +"sort.alternate":"Сортировать", +"paging.next.alternate":"Дальше", +"paging.previous.alternate":"Назад", +"mail.messagenotsent":"Невозможно отправить сообщение", +"mail.messagesent":"Сообщение отправлено" +} diff --git a/www/lang/Localization_zh.json b/www/lang/Localization_zh.json new file mode 100644 index 0000000..f40531d --- /dev/null +++ b/www/lang/Localization_zh.json @@ -0,0 +1,137 @@ +{ +"boolean.no":"否", +"boolean.yes":"是", +"splitByLetter.letter":"{0} 以 {1} 开头", +"home.title":"目录", +"link.fullentry":"全部条目", +"title.nextpage":"下一页 ({0} of {1})", +"title.lastpage":"最后一页", +"title.numberOfPages":"{0} (共 {1} 页)", +"pubdate.title":"出版时间", +"bookword.title":"书名", +"bookword.none":"没有书籍", +"bookword.one":"1 本书籍", +"bookword.many":"{0} 本书籍", +"authorword.title":"作者", +"authorword.none":"未知", +"authorword.one":"1 位作者", +"authorword.many":"{0} 位作者", +"taglevelword.title":"Tag levels", +"taglevelword.none":"No tag level", +"taglevelword.one":"1 tag level", +"taglevelword.many":"{0} tag levels", +"seriesword.title":"系列", +"seriesword.none":"没有系列", +"seriesword.one":"1 个系列", +"seriesword.many":"{0} 系列", +"tagword.title":"标签", +"tagword.none":"没有标签", +"tagword.one":"1 个标签", +"tagword.many":"{0} 标签", +"content.tags":"标签:", +"content.series":"系列:", +"content.series.data":"{0} 系列的第 {0} 本", +"content.publisher":"出版社:", +"content.published":"出版时间", +"content.added":"添加了: ", +"content.modified":"修改了:", +"content.publisher.data":"由 {0} 出版于 {1}", +"content.summary":"概要", +"bookentry.series":"Book {0} in the {1} series", +"bookentry.author":"{0} 由作者 {1}", +"bookentry.tags":"{0} 在标签 {1}", +"bookentry.ratings":"{0} 评分 {1}", +"bookentry.goodreads":"Goodreads 上的这本书", +"bookentry.goodreads.review":"在 Goodreads上评论这本书", +"bookentry.goodreads.author":"{0} 在 Goodreads", +"bookentry.wikipedia":"维基百科上的这本书", +"bookentry.wikipedia.author":"{0} 在维基百科上", +"bookentry.librarything":"LibraryThing 上的这本书", +"bookentry.librarything.author":"{0} 在 LibraryThing", +"bookentry.amazon":"Amazon上的这本书", +"bookentry.amazon.author":"{0} 在 Amazon", +"bookentry.isfdb.author":"{0} 在 ISFDB", +"bookentry.download":"下载这本书 {0}", +"bookentry.rated":"{0} {1}", +"bookentry.fullentrylink":"全部条目", +"tags.title":"标签", +"tags.categorized":"Categorized index of the {0} tags", +"tags.categorized.single":"Categorized index of the single tag", +"tags.alphabetical.many":"{0} 个标签的字母索引", +"tags.alphabetical.one":"Alphabetical index of the single tag", +"tags.alphabetical.none":"Alphabetical index of absolutely no tag", +"splitByLetter.tag.other":"其他标签", +"authors.series.title":"系列: {0}", +"authors.title":"作者", +"authors.alphabetical.many":"{0} 位作者的字母索引", +"authors.alphabetical.one":"Alphabetical index of the single author", +"authors.alphabetical.none":"Alphabetical index of absolutely no author", +"splitByLetter.author.other":"其他作者", +"series.title":"系列", +"series.alphabetical.many":"{0} 个系列的字母索引", +"series.alphabetical.one":"Alphabetical index of the single series", +"series.alphabetical.none":"Alphabetical index of absolutely no series", +"splitByLetter.series.other":"其他系列", +"recent.title":"最近添加", +"recent.list":"{0} 本最近添加的书", +"recent.list.single":"Most recent single book", +"rating.title":"Rating", +"rating.summary":"{0}, 根据评分分组", +"allbooks.title":"所有书籍", +"allbooks.alphabetical.many":"{0} 本书籍的字母索引", +"allbooks.alphabetical.one":"Alphabetical index of the single book", +"allbooks.alphabetical.none":"Alphabetical index of absolutely no book", +"splitByLetter.book.other":"其他书籍", +"main.title":"Calibre 书架", +"main.summary":"{0} has catalogued {1}", +"startup.newhome":"Default configuration folder home redirected to {0}", +"startup.redirectfound":".redirect file found in {0}", +"startup.redirectreadfail":"... failure reading .redirect file", +"startup.redirectnotfound":"... unable to find redirect folder {0}", +"startup.redirectabandoned":"... so redirect abandoned", +"startup.redirecting":"redirecting home folder to {0}", +"startup.configusing":"Using configuration folder {0}", +"startup.folderuserhome":"Try configuration folder in user home folder {0}", +"startup.foldertilde":"Try configuration folder from tilde folder {0}", +"startup.folderjar":"Try configuration folder from .jar location {0}", +"startup.foldernotexist":"... but specified folder does not exist", +"i18n.and":"和", +"i18n.downloads":"下载,链接和其他目录 ", +"i18n.links":"链接和其他目录", +"i18n.coversection":"封面", +"i18n.downloadfile":"下载文件", +"i18n.downloadsection":"下载", +"i18n.relatedsection":"相关目录", +"i18n.linksection":"外部链接", +"i18n.backToMain":"返回目录主页", +"i18n.summarysection":"描述", +"i18n.dateGenerated":"目录生成于 {0}", +"deeplevel.summary":"{0} broken up by authors, tags, etc. ", +"about.title":"关于COPS", +"about.summary":"关于Calibre2Opds的说明", +"usage.intro":"选项从此配置文件生成 {0}", +"language.title":"语言", +"config.Language.description":"此项设置将会改变程序使用的语言 ; 请使用ISO标准语言代码 (例如 EN, FR, DE...)", +"config.Language.possible":"可能的选项 : {0}", +"intro.goal":"从你的 Calibre 数据库生成 OPDS 和 HTML 目录", +"intro.wiki.title":"项目主页 : ", +"intro.wiki.url":"http://calibre2opds.com", +"intro.team.title":"项目人员 :", +"intro.team.list1":"David Pierron - main programmer", +"intro.team.list2":"Dave Walker - guru, features manager and tester extraordinaire", +"intro.team.list3":"Farid Soussi - css and html guru", +"intro.team.list4":"Douglas Steele - programmer", +"intro.team.list5":"Jane Litte - beta tester and moral support", +"intro.thanks.1":"Special thanks to Kb Sriram, who not only programmed Trook, an excellent and OPDS compatible ", +"intro.thanks.2":"library manager for the Nook, but also was kind enough to donate a Nook !", +"search.result":"*{0}* 的搜索结果", +"search.sortorder.asc":"升序", +"search.sortorder.desc":"降序", +"permalink.alternate":"永久链接", +"home.alternate":"首页", +"search.alternate":"搜索", +"sort.alternate":"排序", +"paging.next.alternate":"下一页", +"paging.previous.alternate":"上一页", +"fin":"fin" +} diff --git a/www/language.php b/www/language.php new file mode 100644 index 0000000..646c658 --- /dev/null +++ b/www/language.php @@ -0,0 +1,75 @@ + + */ + +require_once('base.php'); + +class language extends Base { + const ALL_LANGUAGES_ID = "calibre:languages"; + + public $id; + public $lang_code; + + public function __construct($pid, $plang_code) { + $this->id = $pid; + $this->lang_code = $plang_code; + } + + public function getUri () { + return "?page=".parent::PAGE_LANGUAGE_DETAIL."&id=$this->id"; + } + + public function getEntryId () { + return self::ALL_LANGUAGES_ID.":".$this->id; + } + + public static function getLanguageString ($code) { + $string = localize("languages.".$code); + if (preg_match ("/^languages/", $string)) { + return $code; + } + return $string; + } + + public static function getCount() { + $nLanguages = parent::getDb ()->query('select count(*) from languages')->fetchColumn(0); + if ($nLanguages == 0) return NULL; + $entry = new Entry (localize("languages.title"), self::ALL_LANGUAGES_ID, + str_format (localize("languages.alphabetical", $nLanguages), $nLanguages), "text", + array ( new LinkNavigation ("?page=".parent::PAGE_ALL_LANGUAGES))); + return $entry; + } + + public static function getLanguageById ($languageId) { + $result = parent::getDb ()->prepare('select id, lang_code from languages where id = ?'); + $result->execute (array ($languageId)); + if ($post = $result->fetchObject ()) { + return new Language ($post->id, Language::getLanguageString ($post->lang_code)); + } + return NULL; + } + + + + public static function getAllLanguages() { + $result = parent::getDb ()->query('select languages.id as id, languages.lang_code as lang_code, link.count as count +from languages, +(select lang_code,count(*) as count from books_languages_link group by lang_code) link +where languages.id = link.lang_code +group by languages.id, link.lang_code +order by languages.lang_code'); + $entryArray = array(); + while ($post = $result->fetchObject ()) + { + $language = new Language ($post->id, $post->lang_code); + array_push ($entryArray, new Entry (Language::getLanguageString ($language->lang_code), $language->getEntryId (), + str_format (localize("bookword", $post->count), $post->count), "text", + array ( new LinkNavigation ($language->getUri ())))); + } + return $entryArray; + } +} diff --git a/www/login.html b/www/login.html new file mode 100644 index 0000000..e4ce813 --- /dev/null +++ b/www/login.html @@ -0,0 +1,97 @@ + + + + + COPS + + + + + + +
+
+ + + +
+
+ + \ No newline at end of file diff --git a/www/resources/Magnific-Popup/jquery.magnific-popup.min.js b/www/resources/Magnific-Popup/jquery.magnific-popup.min.js new file mode 100644 index 0000000..333e86d --- /dev/null +++ b/www/resources/Magnific-Popup/jquery.magnific-popup.min.js @@ -0,0 +1,4 @@ +/*! Magnific Popup - v0.9.3 - 2013-07-16 +* http://dimsemenov.com/plugins/magnific-popup/ +* Copyright (c) 2013 Dmitry Semenov; */ +(function(e){var t,i,n,o,a,r,s,l="Close",c="BeforeClose",d="AfterClose",u="BeforeAppend",p="MarkupParse",f="Open",m="Change",g="mfp",v="."+g,h="mfp-ready",C="mfp-removing",y="mfp-prevent-close",w=function(){},b=!!window.jQuery,I=e(window),x=function(e,i){t.ev.on(g+e+v,i)},k=function(t,i,n,o){var a=document.createElement("div");return a.className="mfp-"+t,n&&(a.innerHTML=n),o?i&&i.appendChild(a):(a=e(a),i&&a.appendTo(i)),a},T=function(i,n){t.ev.triggerHandler(g+i,n),t.st.callbacks&&(i=i.charAt(0).toLowerCase()+i.slice(1),t.st.callbacks[i]&&t.st.callbacks[i].apply(t,e.isArray(n)?n:[n]))},E=function(){(t.st.focus?t.content.find(t.st.focus).eq(0):t.wrap).trigger("focus")},S=function(i){return i===s&&t.currTemplate.closeBtn||(t.currTemplate.closeBtn=e(t.st.closeMarkup.replace("%title%",t.st.tClose)),s=i),t.currTemplate.closeBtn},P=function(){e.magnificPopup.instance||(t=new w,t.init(),e.magnificPopup.instance=t)},_=function(i){if(!e(i).hasClass(y)){var n=t.st.closeOnContentClick,o=t.st.closeOnBgClick;if(n&&o)return!0;if(!t.content||e(i).hasClass("mfp-close")||t.preloader&&i===t.preloader[0])return!0;if(i===t.content[0]||e.contains(t.content[0],i)){if(n)return!0}else if(o&&e.contains(document,i))return!0;return!1}},O=function(){var e=document.createElement("p").style,t=["ms","O","Moz","Webkit"];if(void 0!==e.transition)return!0;for(;t.length;)if(t.pop()+"Transition"in e)return!0;return!1};w.prototype={constructor:w,init:function(){var i=navigator.appVersion;t.isIE7=-1!==i.indexOf("MSIE 7."),t.isIE8=-1!==i.indexOf("MSIE 8."),t.isLowIE=t.isIE7||t.isIE8,t.isAndroid=/android/gi.test(i),t.isIOS=/iphone|ipad|ipod/gi.test(i),t.supportsTransition=O(),t.probablyMobile=t.isAndroid||t.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),n=e(document.body),o=e(document),t.popupsCache={}},open:function(i){var n;if(i.isObj===!1){t.items=i.items.toArray(),t.index=0;var a,s=i.items;for(n=0;s.length>n;n++)if(a=s[n],a.parsed&&(a=a.el[0]),a===i.el[0]){t.index=n;break}}else t.items=e.isArray(i.items)?i.items:[i.items],t.index=i.index||0;if(t.isOpen)return t.updateItemHTML(),void 0;t.types=[],r="",t.ev=i.mainEl&&i.mainEl.length?i.mainEl.eq(0):o,i.key?(t.popupsCache[i.key]||(t.popupsCache[i.key]={}),t.currTemplate=t.popupsCache[i.key]):t.currTemplate={},t.st=e.extend(!0,{},e.magnificPopup.defaults,i),t.fixedContentPos="auto"===t.st.fixedContentPos?!t.probablyMobile:t.st.fixedContentPos,t.st.modal&&(t.st.closeOnContentClick=!1,t.st.closeOnBgClick=!1,t.st.showCloseBtn=!1,t.st.enableEscapeKey=!1),t.bgOverlay||(t.bgOverlay=k("bg").on("click"+v,function(){t.close()}),t.wrap=k("wrap").attr("tabindex",-1).on("click"+v,function(e){_(e.target)&&t.close()}),t.container=k("container",t.wrap)),t.contentContainer=k("content"),t.st.preloader&&(t.preloader=k("preloader",t.container,t.st.tLoading));var l=e.magnificPopup.modules;for(n=0;l.length>n;n++){var c=l[n];c=c.charAt(0).toUpperCase()+c.slice(1),t["init"+c].call(t)}T("BeforeOpen"),t.st.showCloseBtn&&(t.st.closeBtnInside?(x(p,function(e,t,i,n){i.close_replaceWith=S(n.type)}),r+=" mfp-close-btn-in"):t.wrap.append(S())),t.st.alignTop&&(r+=" mfp-align-top"),t.fixedContentPos?t.wrap.css({overflow:t.st.overflowY,overflowX:"hidden",overflowY:t.st.overflowY}):t.wrap.css({top:I.scrollTop(),position:"absolute"}),(t.st.fixedBgPos===!1||"auto"===t.st.fixedBgPos&&!t.fixedContentPos)&&t.bgOverlay.css({height:o.height(),position:"absolute"}),t.st.enableEscapeKey&&o.on("keyup"+v,function(e){27===e.keyCode&&t.close()}),I.on("resize"+v,function(){t.updateSize()}),t.st.closeOnContentClick||(r+=" mfp-auto-cursor"),r&&t.wrap.addClass(r);var d=t.wH=I.height(),u={};if(t.fixedContentPos&&t._hasScrollBar(d)){var m=t._getScrollbarSize();m&&(u.paddingRight=m)}t.fixedContentPos&&(t.isIE7?e("body, html").css("overflow","hidden"):u.overflow="hidden");var g=t.st.mainClass;t.isIE7&&(g+=" mfp-ie7"),g&&t._addClassToMFP(g),t.updateItemHTML(),T("BuildControls"),e("html").css(u),t.bgOverlay.add(t.wrap).prependTo(document.body),t._lastFocusedEl=document.activeElement,setTimeout(function(){t.content?(t._addClassToMFP(h),E()):t.bgOverlay.addClass(h),o.on("focusin"+v,function(i){return i.target===t.wrap[0]||e.contains(t.wrap[0],i.target)?void 0:(E(),!1)})},16),t.isOpen=!0,t.updateSize(d),T(f)},close:function(){t.isOpen&&(T(c),t.isOpen=!1,t.st.removalDelay&&!t.isLowIE&&t.supportsTransition?(t._addClassToMFP(C),setTimeout(function(){t._close()},t.st.removalDelay)):t._close())},_close:function(){T(l);var i=C+" "+h+" ";if(t.bgOverlay.detach(),t.wrap.detach(),t.container.empty(),t.st.mainClass&&(i+=t.st.mainClass+" "),t._removeClassFromMFP(i),t.fixedContentPos){var n={paddingRight:""};t.isIE7?e("body, html").css("overflow",""):n.overflow="",e("html").css(n)}o.off("keyup"+v+" focusin"+v),t.ev.off(v),t.wrap.attr("class","mfp-wrap").removeAttr("style"),t.bgOverlay.attr("class","mfp-bg"),t.container.attr("class","mfp-container"),!t.st.showCloseBtn||t.st.closeBtnInside&&t.currTemplate[t.currItem.type]!==!0||t.currTemplate.closeBtn&&t.currTemplate.closeBtn.detach(),t._lastFocusedEl&&e(t._lastFocusedEl).trigger("focus"),t.currItem=null,t.content=null,t.currTemplate=null,t.prevHeight=0,T(d)},updateSize:function(e){if(t.isIOS){var i=document.documentElement.clientWidth/window.innerWidth,n=window.innerHeight*i;t.wrap.css("height",n),t.wH=n}else t.wH=e||I.height();t.fixedContentPos||t.wrap.css("height",t.wH),T("Resize")},updateItemHTML:function(){var i=t.items[t.index];t.contentContainer.detach(),t.content&&t.content.detach(),i.parsed||(i=t.parseEl(t.index));var n=i.type;if(T("BeforeChange",[t.currItem?t.currItem.type:"",n]),t.currItem=i,!t.currTemplate[n]){var o=t.st[n]?t.st[n].markup:!1;T("FirstMarkupParse",o),t.currTemplate[n]=o?e(o):!0}a&&a!==i.type&&t.container.removeClass("mfp-"+a+"-holder");var r=t["get"+n.charAt(0).toUpperCase()+n.slice(1)](i,t.currTemplate[n]);t.appendContent(r,n),i.preloaded=!0,T(m,i),a=i.type,t.container.prepend(t.contentContainer),T("AfterChange")},appendContent:function(e,i){t.content=e,e?t.st.showCloseBtn&&t.st.closeBtnInside&&t.currTemplate[i]===!0?t.content.find(".mfp-close").length||t.content.append(S()):t.content=e:t.content="",T(u),t.container.addClass("mfp-"+i+"-holder"),t.contentContainer.append(t.content)},parseEl:function(i){var n=t.items[i],o=n.type;if(n=n.tagName?{el:e(n)}:{data:n,src:n.src},n.el){for(var a=t.types,r=0;a.length>r;r++)if(n.el.hasClass("mfp-"+a[r])){o=a[r];break}n.src=n.el.attr("data-mfp-src"),n.src||(n.src=n.el.attr("href"))}return n.type=o||t.st.type||"inline",n.index=i,n.parsed=!0,t.items[i]=n,T("ElementParse",n),t.items[i]},addGroup:function(e,i){var n=function(n){n.mfpEl=this,t._openClick(n,e,i)};i||(i={});var o="click.magnificPopup";i.mainEl=e,i.items?(i.isObj=!0,e.off(o).on(o,n)):(i.isObj=!1,i.delegate?e.off(o).on(o,i.delegate,n):(i.items=e,e.off(o).on(o,n)))},_openClick:function(i,n,o){var a=void 0!==o.midClick?o.midClick:e.magnificPopup.defaults.midClick;if(a||2!==i.which&&!i.ctrlKey&&!i.metaKey){var r=void 0!==o.disableOn?o.disableOn:e.magnificPopup.defaults.disableOn;if(r)if(e.isFunction(r)){if(!r.call(t))return!0}else if(r>I.width())return!0;i.type&&(i.preventDefault(),t.isOpen&&i.stopPropagation()),o.el=e(i.mfpEl),o.delegate&&(o.items=n.find(o.delegate)),t.open(o)}},updateStatus:function(e,n){if(t.preloader){i!==e&&t.container.removeClass("mfp-s-"+i),n||"loading"!==e||(n=t.st.tLoading);var o={status:e,text:n};T("UpdateStatus",o),e=o.status,n=o.text,t.preloader.html(n),t.preloader.find("a").on("click",function(e){e.stopImmediatePropagation()}),t.container.addClass("mfp-s-"+e),i=e}},_addClassToMFP:function(e){t.bgOverlay.addClass(e),t.wrap.addClass(e)},_removeClassFromMFP:function(e){this.bgOverlay.removeClass(e),t.wrap.removeClass(e)},_hasScrollBar:function(e){return(t.isIE7?o.height():document.body.scrollHeight)>(e||I.height())},_parseMarkup:function(t,i,n){var o;n.data&&(i=e.extend(n.data,i)),T(p,[t,i,n]),e.each(i,function(e,i){if(void 0===i||i===!1)return!0;if(o=e.split("_"),o.length>1){var n=t.find(v+"-"+o[0]);if(n.length>0){var a=o[1];"replaceWith"===a?n[0]!==i[0]&&n.replaceWith(i):"img"===a?n.is("img")?n.attr("src",i):n.replaceWith(''):n.attr(o[1],i)}}else t.find(v+"-"+e).html(i)})},_getScrollbarSize:function(){if(void 0===t.scrollbarSize){var e=document.createElement("div");e.id="mfp-sbm",e.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(e),t.scrollbarSize=e.offsetWidth-e.clientWidth,document.body.removeChild(e)}return t.scrollbarSize}},e.magnificPopup={instance:null,proto:w.prototype,modules:[],open:function(e,t){return P(),e||(e={}),e.isObj=!0,e.index=t||0,this.instance.open(e)},close:function(){return e.magnificPopup.instance.close()},registerModule:function(t,i){i.options&&(e.magnificPopup.defaults[t]=i.options),e.extend(this.proto,i.proto),this.modules.push(t)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'',tClose:"Close (Esc)",tLoading:"Loading..."}},e.fn.magnificPopup=function(i){P();var n=e(this);if("string"==typeof i)if("open"===i){var o,a=b?n.data("magnificPopup"):n[0].magnificPopup,r=parseInt(arguments[1],10)||0;a.items?o=a.items[r]:(o=n,a.delegate&&(o=o.find(a.delegate)),o=o.eq(r)),t._openClick({mfpEl:o},n,a)}else t.isOpen&&t[i].apply(t,Array.prototype.slice.call(arguments,1));else b?n.data("magnificPopup",i):n[0].magnificPopup=i,t.addGroup(n,i);return n};var z,M,B,H="inline",L=function(){B&&(M.after(B.addClass(z)).detach(),B=null)};e.magnificPopup.registerModule(H,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){t.types.push(H),x(l+"."+H,function(){L()})},getInline:function(i,n){if(L(),i.src){var o=t.st.inline,a=e(i.src);if(a.length){var r=a[0].parentNode;r&&r.tagName&&(M||(z=o.hiddenClass,M=k(z),z="mfp-"+z),B=a.after(M).detach().removeClass(z)),t.updateStatus("ready")}else t.updateStatus("error",o.tNotFound),a=e("
");return i.inlineElement=a,a}return t.updateStatus("ready"),t._parseMarkup(n,{},i),n}}});var A,F="ajax",j=function(){A&&n.removeClass(A)};e.magnificPopup.registerModule(F,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'The content could not be loaded.'},proto:{initAjax:function(){t.types.push(F),A=t.st.ajax.cursor,x(l+"."+F,function(){j(),t.req&&t.req.abort()})},getAjax:function(i){A&&n.addClass(A),t.updateStatus("loading");var o=e.extend({url:i.src,success:function(n,o,a){var r={data:n,xhr:a};T("ParseAjax",r),t.appendContent(e(r.data),F),i.finished=!0,j(),E(),setTimeout(function(){t.wrap.addClass(h)},16),t.updateStatus("ready"),T("AjaxContentAdded")},error:function(){j(),i.finished=i.loadError=!0,t.updateStatus("error",t.st.ajax.tError.replace("%url%",i.src))}},t.st.ajax.settings);return t.req=e.ajax(o),""}}});var N,W=function(i){if(i.data&&void 0!==i.data.title)return i.data.title;var n=t.st.image.titleSrc;if(n){if(e.isFunction(n))return n.call(t,i);if(i.el)return i.el.attr(n)||""}return""};e.magnificPopup.registerModule("image",{options:{markup:'
',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'The image could not be loaded.'},proto:{initImage:function(){var e=t.st.image,i=".image";t.types.push("image"),x(f+i,function(){"image"===t.currItem.type&&e.cursor&&n.addClass(e.cursor)}),x(l+i,function(){e.cursor&&n.removeClass(e.cursor),I.off("resize"+v)}),x("Resize"+i,t.resizeImage),t.isLowIE&&x("AfterChange",t.resizeImage)},resizeImage:function(){var e=t.currItem;if(e.img&&t.st.image.verticalFit){var i=0;t.isLowIE&&(i=parseInt(e.img.css("padding-top"),10)+parseInt(e.img.css("padding-bottom"),10)),e.img.css("max-height",t.wH-i)}},_onImageHasSize:function(e){e.img&&(e.hasSize=!0,N&&clearInterval(N),e.isCheckingImgSize=!1,T("ImageHasSize",e),e.imgHidden&&(t.content&&t.content.removeClass("mfp-loading"),e.imgHidden=!1))},findImageSize:function(e){var i=0,n=e.img[0],o=function(a){N&&clearInterval(N),N=setInterval(function(){return n.naturalWidth>0?(t._onImageHasSize(e),void 0):(i>200&&clearInterval(N),i++,3===i?o(10):40===i?o(50):100===i&&o(500),void 0)},a)};o(1)},getImage:function(i,n){var o=0,a=function(){i&&(i.img[0].complete?(i.img.off(".mfploader"),i===t.currItem&&(t._onImageHasSize(i),t.updateStatus("ready")),i.hasSize=!0,i.loaded=!0,T("ImageLoadComplete")):(o++,200>o?setTimeout(a,100):r()))},r=function(){i&&(i.img.off(".mfploader"),i===t.currItem&&(t._onImageHasSize(i),t.updateStatus("error",s.tError.replace("%url%",i.src))),i.hasSize=!0,i.loaded=!0,i.loadError=!0)},s=t.st.image,l=n.find(".mfp-img");if(l.length){var c=new Image;c.className="mfp-img",i.img=e(c).on("load.mfploader",a).on("error.mfploader",r),c.src=i.src,l.is("img")&&(i.img=i.img.clone()),i.img[0].naturalWidth>0&&(i.hasSize=!0)}return t._parseMarkup(n,{title:W(i),img_replaceWith:i.img},i),t.resizeImage(),i.hasSize?(N&&clearInterval(N),i.loadError?(n.addClass("mfp-loading"),t.updateStatus("error",s.tError.replace("%url%",i.src))):(n.removeClass("mfp-loading"),t.updateStatus("ready")),n):(t.updateStatus("loading"),i.loading=!0,i.hasSize||(i.imgHidden=!0,n.addClass("mfp-loading"),t.findImageSize(i)),n)}}});var R,Z=function(){return void 0===R&&(R=void 0!==document.createElement("p").style.MozTransform),R};e.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(e){return e.is("img")?e:e.find("img")}},proto:{initZoom:function(){var e=t.st.zoom,i=".zoom";if(e.enabled&&t.supportsTransition){var n,o,a=e.duration,r=function(t){var i=t.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),n="all "+e.duration/1e3+"s "+e.easing,o={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},a="transition";return o["-webkit-"+a]=o["-moz-"+a]=o["-o-"+a]=o[a]=n,i.css(o),i},s=function(){t.content.css("visibility","visible")};x("BuildControls"+i,function(){if(t._allowZoom()){if(clearTimeout(n),t.content.css("visibility","hidden"),image=t._getItemToZoom(),!image)return s(),void 0;o=r(image),o.css(t._getOffset()),t.wrap.append(o),n=setTimeout(function(){o.css(t._getOffset(!0)),n=setTimeout(function(){s(),setTimeout(function(){o.remove(),image=o=null,T("ZoomAnimationEnded")},16)},a)},16)}}),x(c+i,function(){if(t._allowZoom()){if(clearTimeout(n),t.st.removalDelay=a,!image){if(image=t._getItemToZoom(),!image)return;o=r(image)}o.css(t._getOffset(!0)),t.wrap.append(o),t.content.css("visibility","hidden"),setTimeout(function(){o.css(t._getOffset())},16)}}),x(l+i,function(){t._allowZoom()&&(s(),o&&o.remove())})}},_allowZoom:function(){return"image"===t.currItem.type},_getItemToZoom:function(){return t.currItem.hasSize?t.currItem.img:!1},_getOffset:function(i){var n;n=i?t.currItem.img:t.st.zoom.opener(t.currItem.el||t.currItem);var o=n.offset(),a=parseInt(n.css("padding-top"),10),r=parseInt(n.css("padding-bottom"),10);o.top-=e(window).scrollTop()-a;var s={width:n.width(),height:(b?n.innerHeight():n[0].offsetHeight)-r-a};return Z()?s["-moz-transform"]=s.transform="translate("+o.left+"px,"+o.top+"px)":(s.left=o.left,s.top=o.top),s}}});var q="iframe",D="//about:blank",K=function(e){if(t.currTemplate[q]){var i=t.currTemplate[q].find("iframe");i.length&&(e||(i[0].src=D),t.isIE8&&i.css("display",e?"block":"none"))}};e.magnificPopup.registerModule(q,{options:{markup:'
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){t.types.push(q),x("BeforeChange",function(e,t,i){t!==i&&(t===q?K():i===q&&K(!0))}),x(l+"."+q,function(){K()})},getIframe:function(i,n){var o=i.src,a=t.st.iframe;e.each(a.patterns,function(){return o.indexOf(this.index)>-1?(this.id&&(o="string"==typeof this.id?o.substr(o.lastIndexOf(this.id)+this.id.length,o.length):this.id.call(this,o)),o=this.src.replace("%id%",o),!1):void 0});var r={};return a.srcAction&&(r[a.srcAction]=o),t._parseMarkup(n,r,i),t.updateStatus("ready"),n}}});var Y=function(e){var i=t.items.length;return e>i-1?e-i:0>e?i+e:e},U=function(e,t,i){return e.replace("%curr%",t+1).replace("%total%",i)};e.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var i=t.st.gallery,n=".mfp-gallery",a=Boolean(e.fn.mfpFastClick);return t.direction=!0,i&&i.enabled?(r+=" mfp-gallery",x(f+n,function(){i.navigateByImgClick&&t.wrap.on("click"+n,".mfp-img",function(){return t.items.length>1?(t.next(),!1):void 0}),o.on("keydown"+n,function(e){37===e.keyCode?t.prev():39===e.keyCode&&t.next()})}),x("UpdateStatus"+n,function(e,i){i.text&&(i.text=U(i.text,t.currItem.index,t.items.length))}),x(p+n,function(e,n,o,a){var r=t.items.length;o.counter=r>1?U(i.tCounter,a.index,r):""}),x("BuildControls"+n,function(){if(t.items.length>1&&i.arrows&&!t.arrowLeft){var n=i.arrowMarkup,o=t.arrowLeft=e(n.replace("%title%",i.tPrev).replace("%dir%","left")).addClass(y),r=t.arrowRight=e(n.replace("%title%",i.tNext).replace("%dir%","right")).addClass(y),s=a?"mfpFastClick":"click";o[s](function(){t.prev()}),r[s](function(){t.next()}),t.isIE7&&(k("b",o[0],!1,!0),k("a",o[0],!1,!0),k("b",r[0],!1,!0),k("a",r[0],!1,!0)),t.container.append(o.add(r))}}),x(m+n,function(){t._preloadTimeout&&clearTimeout(t._preloadTimeout),t._preloadTimeout=setTimeout(function(){t.preloadNearbyImages(),t._preloadTimeout=null},16)}),x(l+n,function(){o.off(n),t.wrap.off("click"+n),t.arrowLeft&&a&&t.arrowLeft.add(t.arrowRight).destroyMfpFastClick(),t.arrowRight=t.arrowLeft=null}),void 0):!1},next:function(){t.direction=!0,t.index=Y(t.index+1),t.updateItemHTML()},prev:function(){t.direction=!1,t.index=Y(t.index-1),t.updateItemHTML()},goTo:function(e){t.direction=e>=t.index,t.index=e,t.updateItemHTML()},preloadNearbyImages:function(){var e,i=t.st.gallery.preload,n=Math.min(i[0],t.items.length),o=Math.min(i[1],t.items.length);for(e=1;(t.direction?o:n)>=e;e++)t._preloadItem(t.index+e);for(e=1;(t.direction?n:o)>=e;e++)t._preloadItem(t.index-e)},_preloadItem:function(i){if(i=Y(i),!t.items[i].preloaded){var n=t.items[i];n.parsed||(n=t.parseEl(i)),T("LazyLoad",n),"image"===n.type&&(n.img=e('').on("load.mfploader",function(){n.hasSize=!0}).on("error.mfploader",function(){n.hasSize=!0,n.loadError=!0,T("LazyLoadError",n)}).attr("src",n.src)),n.preloaded=!0}}}});var G="retina";e.magnificPopup.registerModule(G,{options:{replaceSrc:function(e){return e.src.replace(/\.\w+$/,function(e){return"@2x"+e})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var e=t.st.retina,i=e.ratio;i=isNaN(i)?i():i,i>1&&(x("ImageHasSize."+G,function(e,t){t.img.css({"max-width":t.img[0].naturalWidth/i,width:"100%"})}),x("ElementParse."+G,function(t,n){n.src=e.replaceSrc(n,i)}))}}}}),function(){var t=1e3,i="ontouchstart"in window,n=function(){I.off("touchmove"+a+" touchend"+a)},o="mfpFastClick",a="."+o;e.fn.mfpFastClick=function(o){return e(this).each(function(){var r,s=e(this);if(i){var l,c,d,u,p,f;s.on("touchstart"+a,function(e){u=!1,f=1,p=e.originalEvent?e.originalEvent.touches[0]:e.touches[0],c=p.clientX,d=p.clientY,I.on("touchmove"+a,function(e){p=e.originalEvent?e.originalEvent.touches:e.touches,f=p.length,p=p[0],(Math.abs(p.clientX-c)>10||Math.abs(p.clientY-d)>10)&&(u=!0,n())}).on("touchend"+a,function(e){n(),u||f>1||(r=!0,e.preventDefault(),clearTimeout(l),l=setTimeout(function(){r=!1},t),o())})})}s.on("click"+a,function(){r||o()})})},e.fn.destroyMfpFastClick=function(){e(this).off("touchstart"+a+" click"+a),i&&I.off("touchmove"+a+" touchend"+a)}}()})(window.jQuery||window.Zepto); \ No newline at end of file diff --git a/www/resources/Magnific-Popup/magnific-popup.css b/www/resources/Magnific-Popup/magnific-popup.css new file mode 100644 index 0000000..c49d02d --- /dev/null +++ b/www/resources/Magnific-Popup/magnific-popup.css @@ -0,0 +1,394 @@ +/* Magnific Popup CSS */ +.mfp-bg { + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1042; + overflow: hidden; + position: fixed; + background: #0b0b0b; + opacity: 0.8; + filter: alpha(opacity=80); } + +.mfp-wrap { + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1043; + position: fixed; + outline: none !important; + -webkit-backface-visibility: hidden; } + +.mfp-container { + text-align: center; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + padding: 0 8px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.mfp-container:before { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; } + +.mfp-align-top .mfp-container:before { + display: none; } + +.mfp-content { + position: relative; + display: inline-block; + vertical-align: middle; + margin: 0 auto; + text-align: left; + z-index: 1045; } + +.mfp-inline-holder .mfp-content, +.mfp-ajax-holder .mfp-content { + width: 100%; + cursor: auto; } + +.mfp-ajax-cur { + cursor: progress; } + +.mfp-zoom-out-cur, +.mfp-zoom-out-cur .mfp-image-holder .mfp-close { + cursor: -moz-zoom-out; + cursor: -webkit-zoom-out; + cursor: zoom-out; } + +.mfp-zoom { + cursor: pointer; + cursor: -webkit-zoom-in; + cursor: -moz-zoom-in; + cursor: zoom-in; } + +.mfp-auto-cursor .mfp-content { + cursor: auto; } + +.mfp-close, +.mfp-arrow, +.mfp-preloader, +.mfp-counter { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } + +.mfp-loading.mfp-figure { + display: none; } + +.mfp-hide { + display: none !important; } + +.mfp-preloader { + color: #cccccc; + position: absolute; + top: 50%; + width: auto; + text-align: center; + margin-top: -0.8em; + left: 8px; + right: 8px; + z-index: 1044; } + +.mfp-preloader a { + color: #cccccc; } + +.mfp-preloader a:hover { + color: white; } + +.mfp-s-ready .mfp-preloader { + display: none; } + +.mfp-s-error .mfp-content { + display: none; } + +button.mfp-close, +button.mfp-arrow { + overflow: visible; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + display: block; + padding: 0; + z-index: 1046; } + +button::-moz-focus-inner { + padding: 0; + border: 0; } + +.mfp-close { + width: 44px; + height: 44px; + line-height: 44px; + position: absolute; + right: 0; + top: 0; + text-decoration: none; + text-align: center; + opacity: 0.65; + padding: 0 0 18px 10px; + color: white; + font-style: normal; + font-size: 28px; + font-family: Arial, Baskerville, monospace; } + .mfp-close:hover, .mfp-close:focus { + opacity: 1; } + .mfp-close:active { + top: 1px; } + +.mfp-close-btn-in .mfp-close { + color: #333333; } + +.mfp-image-holder .mfp-close, +.mfp-iframe-holder .mfp-close { + color: white; + right: -6px; + text-align: right; + padding-right: 6px; + width: 100%; } + +.mfp-counter { + position: absolute; + top: 0; + right: 0; + color: #cccccc; + font-size: 12px; + line-height: 18px; } + +.mfp-arrow { + position: absolute; + opacity: 0.65; + margin: 0; + top: 50%; + margin-top: -55px; + padding: 0; + width: 90px; + height: 110px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + +.mfp-arrow:active { + margin-top: -54px; } + +.mfp-arrow:hover, +.mfp-arrow:focus { + opacity: 1; } + +.mfp-arrow:before, .mfp-arrow:after, +.mfp-arrow .mfp-b, +.mfp-arrow .mfp-a { + content: ''; + display: block; + width: 0; + height: 0; + position: absolute; + left: 0; + top: 0; + margin-top: 35px; + margin-left: 35px; + border: medium inset transparent; } +.mfp-arrow:after, +.mfp-arrow .mfp-a { + border-top-width: 13px; + border-bottom-width: 13px; + top: 8px; } +.mfp-arrow:before, +.mfp-arrow .mfp-b { + border-top-width: 21px; + border-bottom-width: 21px; } + +.mfp-arrow-left { + left: 0; } + .mfp-arrow-left:after, + .mfp-arrow-left .mfp-a { + border-right: 17px solid white; + margin-left: 31px; } + .mfp-arrow-left:before, + .mfp-arrow-left .mfp-b { + margin-left: 25px; + border-right: 27px solid #3f3f3f; } + +.mfp-arrow-right { + right: 0; } + .mfp-arrow-right:after, + .mfp-arrow-right .mfp-a { + border-left: 17px solid white; + margin-left: 39px; } + .mfp-arrow-right:before, + .mfp-arrow-right .mfp-b { + border-left: 27px solid #3f3f3f; } + +.mfp-iframe-holder { + padding-top: 40px; + padding-bottom: 40px; } + +.mfp-iframe-holder .mfp-content { + line-height: 0; + width: 100%; + max-width: 900px; } + +.mfp-iframe-scaler { + width: 100%; + height: 0; + overflow: hidden; + padding-top: 56.25%; } + +.mfp-iframe-scaler iframe { + position: absolute; + display: block; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); + background: black; } + +.mfp-iframe-holder .mfp-close { + top: -40px; } + +/* Main image in popup */ +img.mfp-img { + width: auto; + max-width: 100%; + height: auto; + display: block; + line-height: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 40px 0 40px; + margin: 0 auto; } + +/* The shadow behind the image */ +.mfp-figure:after { + content: ''; + position: absolute; + left: 0; + top: 40px; + bottom: 40px; + display: block; + right: 0; + width: auto; + height: auto; + z-index: -1; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); + background: #444444; } + +.mfp-figure { + line-height: 0; } + +.mfp-bottom-bar { + margin-top: -36px; + position: absolute; + top: 100%; + left: 0; + width: 100%; + cursor: auto; } + +.mfp-title { + text-align: left; + line-height: 18px; + color: #f3f3f3; + word-break: break-word; + padding-right: 36px; } + +.mfp-figure small { + color: #bdbdbd; + display: block; + font-size: 12px; + line-height: 14px; } + +.mfp-image-holder .mfp-content { + max-width: 100%; } + +.mfp-gallery .mfp-image-holder .mfp-figure { + cursor: pointer; } + +@media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) { + /** + * Remove all paddings around the image on small screen + */ + .mfp-img-mobile .mfp-image-holder { + padding-left: 0; + padding-right: 0; } + + .mfp-img-mobile img.mfp-img { + padding: 0; } + + /* The shadow behind the image */ + .mfp-img-mobile .mfp-figure:after { + top: 0; + bottom: 0; } + + .mfp-img-mobile .mfp-bottom-bar { + background: rgba(0, 0, 0, 0.6); + bottom: 0; + margin: 0; + top: auto; + padding: 3px 5px; + position: fixed; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + + .mfp-img-mobile .mfp-bottom-bar:empty { + padding: 0; } + + .mfp-img-mobile .mfp-counter { + right: 5px; + top: 3px; } + + .mfp-img-mobile .mfp-close { + top: 0; + right: 0; + width: 35px; + height: 35px; + line-height: 35px; + background: rgba(0, 0, 0, 0.6); + position: fixed; + text-align: center; + padding: 0; } + + .mfp-img-mobile .mfp-figure small { + display: inline; + margin-left: 5px; } } +@media all and (max-width: 900px) { + .mfp-arrow { + -webkit-transform: scale(0.75); + transform: scale(0.75); } + + .mfp-arrow-left { + -webkit-transform-origin: 0; + transform-origin: 0; } + + .mfp-arrow-right { + -webkit-transform-origin: 100%; + transform-origin: 100%; } + + .mfp-container { + padding-left: 6px; + padding-right: 6px; } } +.mfp-ie7 .mfp-img { + padding: 0; } +.mfp-ie7 .mfp-bottom-bar { + width: 600px; + left: 50%; + margin-left: -300px; + margin-top: 5px; + padding-bottom: 5px; } +.mfp-ie7 .mfp-container { + padding: 0; } +.mfp-ie7 .mfp-content { + padding-top: 44px; } +.mfp-ie7 .mfp-close { + top: 0; + right: 0; + padding-top: 0; } diff --git a/www/resources/PHPMailer/LICENSE b/www/resources/PHPMailer/LICENSE new file mode 100644 index 0000000..f3f1b3b --- /dev/null +++ b/www/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/www/resources/PHPMailer/README.md b/www/resources/PHPMailer/README.md new file mode 100644 index 0000000..671141d --- /dev/null +++ b/www/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/www/resources/PHPMailer/class.phpmailer.php b/www/resources/PHPMailer/class.phpmailer.php new file mode 100644 index 0000000..667f187 --- /dev/null +++ b/www/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/www/resources/PHPMailer/class.pop3.php b/www/resources/PHPMailer/class.pop3.php new file mode 100644 index 0000000..17bb675 --- /dev/null +++ b/www/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/www/resources/PHPMailer/class.smtp.php b/www/resources/PHPMailer/class.smtp.php new file mode 100644 index 0000000..8444eb8 --- /dev/null +++ b/www/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; + } + +} diff --git a/www/resources/doT/doT.min.js b/www/resources/doT/doT.min.js new file mode 100644 index 0000000..1f05f62 --- /dev/null +++ b/www/resources/doT/doT.min.js @@ -0,0 +1,7 @@ +/* Laura Doktorova https://github.com/olado/doT */(function(){function o(){var a={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},b=/&(?!#?\w+;)|<|>|"|'|\//g;return function(){return this?this.replace(b,function(c){return a[c]||c}):this}}function p(a,b,c){return(typeof b==="string"?b:b.toString()).replace(a.define||i,function(l,e,f,g){if(e.indexOf("def.")===0)e=e.substring(4);if(!(e in c))if(f===":"){a.defineParams&&g.replace(a.defineParams,function(n,h,d){c[e]={arg:h,text:d}});e in c||(c[e]=g)}else(new Function("def","def['"+ +e+"']="+g))(c);return""}).replace(a.use||i,function(l,e){if(a.useParams)e=e.replace(a.useParams,function(g,n,h,d){if(c[h]&&c[h].arg&&d){g=(h+":"+d).replace(/'|\\/g,"_");c.__exp=c.__exp||{};c.__exp[g]=c[h].text.replace(RegExp("(^|[^\\w$])"+c[h].arg+"([^\\w$])","g"),"$1"+d+"$2");return n+"def.__exp['"+g+"']"}});var f=(new Function("def","return "+e))(c);return f?p(a,f,c):f})}function m(a){return a.replace(/\\('|\\)/g,"$1").replace(/[\r\t\n]/g," ")}var j={version:"1.0.0",templateSettings:{evaluate:/\{\{([\s\S]+?\}?)\}\}/g, +interpolate:/\{\{=([\s\S]+?)\}\}/g,encode:/\{\{!([\s\S]+?)\}\}/g,use:/\{\{#([\s\S]+?)\}\}/g,useParams:/(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,define:/\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,defineParams:/^\s*([\w$]+):([\s\S]+)/,conditional:/\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,iterate:/\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,varname:"it",strip:true,append:true,selfcontained:false},template:undefined, +compile:undefined};if(typeof module!=="undefined"&&module.exports)module.exports=j;else if(typeof define==="function"&&define.amd)define(function(){return j});else(function(){return this||(0,eval)("this")})().doT=j;String.prototype.encodeHTML=o();var q={append:{start:"'+(",end:")+'",endencode:"||'').toString().encodeHTML()+'"},split:{start:"';out+=(",end:");out+='",endencode:"||'').toString().encodeHTML();out+='"}},i=/$^/;j.template=function(a,b,c){b=b||j.templateSettings;var l=b.append?q.append: +q.split,e,f=0,g;a=b.use||b.define?p(b,a,c||{}):a;a=("var out='"+(b.strip?a.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ").replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""):a).replace(/'|\\/g,"\\$&").replace(b.interpolate||i,function(h,d){return l.start+m(d)+l.end}).replace(b.encode||i,function(h,d){e=true;return l.start+m(d)+l.endencode}).replace(b.conditional||i,function(h,d,k){return d?k?"';}else if("+m(k)+"){out+='":"';}else{out+='":k?"';if("+m(k)+"){out+='":"';}out+='"}).replace(b.iterate||i,function(h, +d,k,r){if(!d)return"';} } out+='";f+=1;g=r||"i"+f;d=m(d);return"';var arr"+f+"="+d+";if(arr"+f+"){var "+k+","+g+"=-1,l"+f+"=arr"+f+".length-1;while("+g+")[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
t
",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("