├── test ├── test.epub ├── test.jpg └── test.phpunit.php ├── assets ├── css │ ├── cleditor │ │ ├── images │ │ │ ├── buttons.gif │ │ │ └── toolbar.gif │ │ └── jquery.cleditor.css │ ├── smoothness │ │ ├── images │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ ├── ui-icons_cd0a0a_256x240.png │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ └── jquery-ui-1.8.18.custom.css │ └── style.css └── js │ ├── script.js │ ├── jquery.cleditor.min.js │ └── jquery-1.7.1.min.js ├── util.php ├── LICENSE ├── README ├── index.php └── epub.php /test/test.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/test/test.epub -------------------------------------------------------------------------------- /test/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/test/test.jpg -------------------------------------------------------------------------------- /assets/css/cleditor/images/buttons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/cleditor/images/buttons.gif -------------------------------------------------------------------------------- /assets/css/cleditor/images/toolbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/cleditor/images/toolbar.gif -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /assets/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitbrain/php-epub-meta/HEAD/assets/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /util.php: -------------------------------------------------------------------------------- 1 | '.htmlspecialchars($title).''. 29 | ''.htmlspecialchars($author).''; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Andreas Gohr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ====== PHP EPub Meta ====== 2 | 3 | This project aims to create a PHP class for reading and writing metadata 4 | included in the EPub ebook format. 5 | 6 | It also includes a very basic web interface to edit book metadata. 7 | 8 | Please see the issue tracker for what's missing. 9 | 10 | Forks and pull requests welcome. 11 | 12 | ===== About the EPub Manager Web Interface ===== 13 | 14 | The manager expects your ebooks in a single flat directory (no subfolders). The 15 | location of that directory has to be configured at the top of the index.php file. 16 | 17 | All the epubs need to be read- and writable by the webserver. 18 | 19 | The manager also makes some assumption on how the files should be named. The 20 | format is: "-.epub". Commas will be replaced by __ and 21 | spaces are replaced by _. 22 | 23 | Note that the manager will RENAME your files to that form when saving. 24 | 25 | Using the "Lookup Book Data" link will open a dialog that searches the book at 26 | Google Books you can use the found data using the "fill in" and "replace" 27 | buttons. The former will only fill empty fields, while the latter will replace 28 | all data. Author filling is missing currently. 29 | -------------------------------------------------------------------------------- /assets/css/cleditor/jquery.cleditor.css: -------------------------------------------------------------------------------- 1 | .cleditorMain {border:1px solid #999; padding:0 1px 1px; background-color:white} 2 | .cleditorMain iframe {border:none; margin:0; padding:0} 3 | .cleditorMain textarea {border:none; margin:0; padding:0; overflow-y:scroll; font:10pt Arial,Verdana; resize:none; outline:none /* webkit grip focus */} 4 | .cleditorToolbar {background: url('images/toolbar.gif') repeat} 5 | .cleditorGroup {float:left; height:26px} 6 | .cleditorButton {float:left; width:24px; height:24px; margin:1px 0 1px 0; background: url('images/buttons.gif')} 7 | .cleditorDisabled {opacity:0.3; filter:alpha(opacity=30)} 8 | .cleditorDivider {float:left; width:1px; height:23px; margin:1px 0 1px 0; background:#CCC} 9 | .cleditorPopup {border:solid 1px #999; background-color:white; position:absolute; font:10pt Arial,Verdana; cursor:default; z-index:10000} 10 | .cleditorList div {padding:2px 4px 2px 4px} 11 | .cleditorList p, 12 | .cleditorList h1, 13 | .cleditorList h2, 14 | .cleditorList h3, 15 | .cleditorList h4, 16 | .cleditorList h5, 17 | .cleditorList h6, 18 | .cleditorList font {padding:0; margin:0; background-color:Transparent} 19 | .cleditorColor {width:150px; padding:1px 0 0 1px} 20 | .cleditorColor div {float:left; width:14px; height:14px; margin:0 1px 1px 0} 21 | .cleditorPrompt {background-color:#F6F7F9; padding:4px; font-size:8.5pt} 22 | .cleditorPrompt input, 23 | .cleditorPrompt textarea {font:8.5pt Arial,Verdana;} 24 | .cleditorMsg {background-color:#FDFCEE; width:150px; padding:4px; font-size:8.5pt} 25 | -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | body, td, th, input, textarea { 2 | font-family: "Arial", sans-serif; 3 | color: #333; 4 | font-size: 15px; 5 | } 6 | 7 | a { 8 | color: #4183C4; 9 | text-decoration: none; 10 | } 11 | 12 | a:hover, 13 | a:active { 14 | background-color: #efefef; 15 | } 16 | 17 | #wrapper { 18 | height: 95%; 19 | width: 920px; 20 | margin: auto; 21 | padding: 0; 22 | border: 1px solid #ccc; 23 | } 24 | 25 | #booklist { 26 | float: left; 27 | width: 300px; 28 | height: 100%; 29 | overflow: auto; 30 | list-style-type: none; 31 | padding: 0; 32 | margin: 0 25px 0 0; 33 | font-size: 13px; 34 | } 35 | 36 | #booklist li { 37 | padding: 5px; 38 | } 39 | 40 | #booklist li:hover { 41 | background-color: #efefef; 42 | } 43 | 44 | #booklist li span { 45 | display: block; 46 | } 47 | #booklist li span.author { 48 | padding-left: 5px; 49 | font-size: 11px; 50 | } 51 | 52 | #booklist li.active { 53 | background-color: #4183C4; 54 | } 55 | #booklist li.active a, 56 | #booklist li.active a:hover, 57 | #booklist li.active a:active { 58 | color: #efefef; 59 | background-color: #4183C4; 60 | } 61 | 62 | 63 | #bookpanel { 64 | float: left; 65 | width: 590px; 66 | height: 100%; 67 | overflow: auto; 68 | } 69 | 70 | .center { 71 | text-align: center; 72 | } 73 | 74 | table { 75 | margin-bottom: 20px; 76 | } 77 | 78 | table th, 79 | table td { 80 | vertical-align: top; 81 | padding: 0 0 5px 0; 82 | } 83 | 84 | table th { 85 | text-align: right; 86 | font-weight: bold; 87 | padding: 3px; 88 | padding-right: 20px; 89 | } 90 | 91 | table td textarea, 92 | table td input { 93 | width: 450px; 94 | border: none; 95 | padding: 3px; 96 | margin: 0; 97 | border-bottom: solid 1px #dfdfdf; 98 | } 99 | 100 | table td .cleditormain { 101 | border: solid 1px #dfdfdf; 102 | } 103 | 104 | table td textarea { 105 | height: 250px; 106 | } 107 | 108 | table td p { 109 | margin: 0; 110 | padding: 0; 111 | } 112 | 113 | table td p input { 114 | width: 200px; 115 | } 116 | 117 | table td textarea:focus, 118 | table td input:focus { 119 | border: 1px solid #4183C4; 120 | } 121 | 122 | table th img { 123 | margin-top: 20px; 124 | } 125 | 126 | a.addauthor { 127 | margin-top: -20px; 128 | float: right; 129 | } 130 | 131 | div.license { 132 | font-size: 11px; 133 | margin-top: 300px; 134 | } 135 | 136 | #bookapi-s { 137 | float: left; 138 | } 139 | 140 | #bookapi-q { 141 | width: 600px; 142 | } 143 | 144 | #bookapi { 145 | font-size: 12px; 146 | } 147 | 148 | #bookapi div.head { 149 | margin-bottom: 10px; 150 | } 151 | 152 | #bookapi div.result { 153 | clear: both; 154 | margin-bottom: 10px; 155 | border-bottom: 1px solid #ccc; 156 | min-height: 130px; 157 | } 158 | 159 | #bookapi div.result div { 160 | margin-left: 70px; 161 | } 162 | 163 | #bookapi div.result div.buttons { 164 | float: right; 165 | text-align: right; 166 | } 167 | 168 | #bookapi img { 169 | margin-top: 30px; 170 | width: 60px; 171 | float: left; 172 | } 173 | 174 | #bookapi h1.title { 175 | font-size: 14px; 176 | } 177 | 178 | #bookapi span.subjects { 179 | font-style: italic; 180 | } 181 | -------------------------------------------------------------------------------- /test/test.phpunit.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | require '../epub.php'; 4 | 5 | 6 | class EPubTest extends PHPUnit_Framework_TestCase { 7 | 8 | protected $epub; 9 | 10 | protected function setUp(){ 11 | // sometime I might have accidentally broken the test file 12 | if(filesize('test.epub') != 768780){ 13 | die('test.epub has wrong size, make sure it\'s unmodified'); 14 | } 15 | 16 | // we work on a copy to test saving 17 | if(!copy('test.epub','test.copy.epub')){ 18 | die('failed to create copy of the test book'); 19 | } 20 | 21 | $this->epub = new EPub('test.copy.epub'); 22 | } 23 | 24 | protected function tearDown(){ 25 | unlink('test.copy.epub'); 26 | } 27 | 28 | public function testAuthors(){ 29 | // read curent value 30 | $this->assertEquals( 31 | $this->epub->Authors(), 32 | array('Shakespeare, William' => 'William Shakespeare') 33 | ); 34 | 35 | // remove value with string 36 | $this->assertEquals( 37 | $this->epub->Authors(''), 38 | array() 39 | ); 40 | 41 | // set single value by String 42 | 43 | $this->assertEquals( 44 | $this->epub->Authors('John Doe'), 45 | array('John Doe' => 'John Doe') 46 | ); 47 | 48 | // set single value by indexed array 49 | $this->assertEquals( 50 | $this->epub->Authors(array('John Doe')), 51 | array('John Doe' => 'John Doe') 52 | ); 53 | 54 | // remove value with array 55 | $this->assertEquals( 56 | $this->epub->Authors(array()), 57 | array() 58 | ); 59 | 60 | // set single value by associative array 61 | $this->assertEquals( 62 | $this->epub->Authors(array('Doe, John' => 'John Doe')), 63 | array('Doe, John' => 'John Doe') 64 | ); 65 | 66 | // set multi value by string 67 | $this->assertEquals( 68 | $this->epub->Authors('John Doe, Jane Smith'), 69 | array('John Doe' => 'John Doe', 'Jane Smith' => 'Jane Smith') 70 | ); 71 | 72 | // set multi value by indexed array 73 | $this->assertEquals( 74 | $this->epub->Authors(array('John Doe', 'Jane Smith')), 75 | array('John Doe' => 'John Doe', 'Jane Smith' => 'Jane Smith') 76 | ); 77 | 78 | // set multi value by associative array 79 | $this->assertEquals( 80 | $this->epub->Authors(array('Doe, John' => 'John Doe', 'Smith, Jane' => 'Jane Smith')), 81 | array('Doe, John' => 'John Doe', 'Smith, Jane' => 'Jane Smith') 82 | ); 83 | 84 | // check escaping 85 | $this->assertEquals( 86 | $this->epub->Authors(array('Doe, John ' => 'John Doe ')), 87 | array('Doe, John ' => 'John Doe ') 88 | ); 89 | } 90 | 91 | public function testTitle(){ 92 | // get current value 93 | $this->assertEquals( 94 | $this->epub->Title(), 95 | 'Romeo and Juliet' 96 | ); 97 | 98 | // delete current value 99 | $this->assertEquals( 100 | $this->epub->Title(''), 101 | '' 102 | ); 103 | 104 | // get current value 105 | $this->assertEquals( 106 | $this->epub->Title(), 107 | '' 108 | ); 109 | 110 | // set new value 111 | $this->assertEquals( 112 | $this->epub->Title('Foo Bar'), 113 | 'Foo Bar' 114 | ); 115 | 116 | // check escaping 117 | $this->assertEquals( 118 | $this->epub->Title('Foo Bar'), 119 | 'Foo Bar' 120 | ); 121 | } 122 | 123 | public function testSubject(){ 124 | // get current values 125 | $this->assertEquals( 126 | $this->epub->Subjects(), 127 | array('Fiction','Drama','Romance') 128 | ); 129 | 130 | // delete current values with String 131 | $this->assertEquals( 132 | $this->epub->Subjects(''), 133 | array() 134 | ); 135 | 136 | // set new values with String 137 | $this->assertEquals( 138 | $this->epub->Subjects('Fiction, Drama, Romance'), 139 | array('Fiction','Drama','Romance') 140 | ); 141 | 142 | // delete current values with Array 143 | $this->assertEquals( 144 | $this->epub->Subjects(array()), 145 | array() 146 | ); 147 | 148 | // set new values with array 149 | $this->assertEquals( 150 | $this->epub->Subjects(array('Fiction','Drama','Romance')), 151 | array('Fiction','Drama','Romance') 152 | ); 153 | 154 | // check escaping 155 | $this->assertEquals( 156 | $this->epub->Subjects(array('Fiction','Drama ','Romance')), 157 | array('Fiction','Drama ','Romance') 158 | ); 159 | } 160 | 161 | 162 | public function testCover(){ 163 | // read current cover 164 | $cover = $this->epub->Cover(); 165 | $this->assertEquals($cover['mime'],'image/png'); 166 | $this->assertEquals($cover['found'],'OPS/images/cover.png'); 167 | $this->assertEquals(strlen($cover['data']), 657911); 168 | 169 | // delete cover 170 | $cover = $this->epub->Cover(''); 171 | $this->assertEquals($cover['mime'],'image/gif'); 172 | $this->assertEquals($cover['found'],false); 173 | $this->assertEquals(strlen($cover['data']), 42); 174 | 175 | // set new cover (will return a not-found as it's not yet saved) 176 | $cover = $this->epub->Cover('test.jpg','image/jpeg'); 177 | $this->assertEquals($cover['mime'],'image/jpeg'); 178 | $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); 179 | $this->assertEquals(strlen($cover['data']), 0); 180 | 181 | // save 182 | $this->epub->save(); 183 | 184 | // read now changed cover 185 | $cover = $this->epub->Cover(); 186 | $this->assertEquals($cover['mime'],'image/jpeg'); 187 | $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); 188 | $this->assertEquals(strlen($cover['data']), filesize('test.jpg')); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /assets/js/script.js: -------------------------------------------------------------------------------- 1 | 2 | var bookapi = { 3 | $dialog: null, 4 | 5 | resulttpl: 6 | '<div class="result">' + 7 | ' <img src="" />'+ 8 | ' <div>' + 9 | ' <div class="buttons">' + 10 | ' <button class="btn-repl">replace</button><br />' + 11 | ' <button class="btn-fill">fill in</button>' + 12 | ' </div>' + 13 | ' <h1 class="title"></h1>' + 14 | ' <p class="authors"></p>' + 15 | ' <p class="description"></p>' + 16 | ' <p class="more">' + 17 | ' <span class="lang"></span>' + 18 | ' <span class="publisher"></span>' + 19 | ' <span class="subjects"></span>' + 20 | ' </p>' + 21 | ' </div>' + 22 | '</div>', 23 | 24 | init: function(){ 25 | $('body').append('<div id="bookapi"></div>'); 26 | bookapi.$dialog = $('#bookapi'); 27 | bookapi.$dialog.dialog( 28 | { 29 | autoOpen: false, 30 | title: 'Lookup Book Data', 31 | width: 800, 32 | height: 500 33 | } 34 | ); 35 | bookapi.$dialog.append('<div class="head">Lookup: <input type="text" id="bookapi-q" /></div>') 36 | .append('<div id="bookapi-out"></div>'); 37 | bookapi.$out = $('#bookapi-out'); 38 | 39 | $('#bookpanel').append('<a href="#" id="bookapi-s">Lookup Book Data</a>'); 40 | $('#bookapi-s').attr('title','Search this book at Google Books'); 41 | $('#bookapi-s').click(bookapi.open); 42 | 43 | $('#bookapi-q').keypress( 44 | function(event){ 45 | if(event.which == 13){ 46 | event.preventDefault(); 47 | bookapi.search(); 48 | } 49 | }); 50 | 51 | }, 52 | 53 | open: function(){ 54 | bookapi.$dialog.dialog('open'); 55 | 56 | var query = $('#bookpanel input[name=title]').val(); 57 | $('#bookapi-q').val(query); 58 | 59 | bookapi.search(); 60 | }, 61 | 62 | search: function(){ 63 | bookapi.$out.html('please wait...'); 64 | $.ajax({ 65 | type: 'GET', 66 | data: {'api':$('#bookapi-q').val()}, 67 | success: bookapi.searchdone, 68 | dataType: 'json' 69 | }); 70 | }, 71 | 72 | searchdone: function(data){ 73 | if(data.totalItems == 0){ 74 | bookapi.$out.html('Found no results.<br />Try adjusting the query and retry.'); 75 | return; 76 | } 77 | 78 | bookapi.$out.html(''); 79 | for(i=0; i<data.items.length; i++){ 80 | $res = $(bookapi.resulttpl); 81 | if(data.items[i].volumeInfo.title) 82 | $res.find('.title').html(data.items[i].volumeInfo.title); 83 | if(data.items[i].volumeInfo.authors) 84 | $res.find('.authors').html(data.items[i].volumeInfo.authors.join(', ')); 85 | if(data.items[i].volumeInfo.description) 86 | $res.find('.description').html(data.items[i].volumeInfo.description); 87 | if(data.items[i].volumeInfo.language) 88 | $res.find('.lang').html('['+data.items[i].volumeInfo.language+']'); 89 | if(data.items[i].volumeInfo.publisher) 90 | $res.find('.publisher').html(data.items[i].volumeInfo.publisher); 91 | if(data.items[i].volumeInfo.categories) 92 | $res.find('.subjects').html(data.items[i].volumeInfo.categories.join(', ')); 93 | if(data.items[i].volumeInfo.imageLinks) 94 | if(data.items[i].volumeInfo.imageLinks.thumbnail) 95 | $res.find('img').attr('src',data.items[i].volumeInfo.imageLinks.thumbnail); 96 | 97 | $res.find('.btn-repl').click(data.items[i].volumeInfo,bookapi.replace); 98 | $res.find('.btn-fill').click(data.items[i].volumeInfo,bookapi.fillin); 99 | 100 | bookapi.$out.append($res); 101 | } 102 | }, 103 | 104 | replace: function(event){ 105 | item = event.data; 106 | if(item.title) 107 | $('#bookpanel input[name=title]').val(item.title); 108 | if(item.description) 109 | $('#bookpanel textarea[name=description]').val(item.description); 110 | $wysiwyg[0].updateFrame(); 111 | if(item.language) 112 | $('#bookpanel input[name=language]').val(item.language); 113 | if(item.publisher) 114 | $('#bookpanel input[name=publisher]').val(item.publisher); 115 | if(item.categories) 116 | $('#bookpanel input[name=subjects]').val(item.categories.join(', ')); 117 | if(item.imageLinks){ 118 | $('#bookpanel input[name=coverurl]').val(item.imageLinks.thumbnail); 119 | $('#cover').attr('src',item.imageLinks.thumbnail); 120 | } 121 | bookapi.$dialog.dialog('close'); 122 | }, 123 | 124 | fillin: function(event){ 125 | item = event.data; 126 | 127 | if(item.title && $('#bookpanel input[name=title]').val() == '') 128 | $('#bookpanel input[name=title]').val(item.title); 129 | if(item.description && $('#bookpanel textarea[name=description]').val() == '') 130 | $('#bookpanel textarea[name=description]').val(item.description); 131 | $wysiwyg[0].updateFrame(); 132 | if(item.language && $('#bookpanel input[name=language]').val() == '') 133 | $('#bookpanel input[name=language]').val(item.language); 134 | if(item.publisher && $('#bookpanel input[name=publisher]').val() == '') 135 | $('#bookpanel input[name=publisher]').val(item.publisher); 136 | if(item.categories && $('#bookpanel input[name=subjects]').val() == '') 137 | $('#bookpanel input[name=subjects]').val(item.categories.join(', ')); 138 | if(item.imageLinks && $('#cover').hasClass('noimg')){ 139 | $('#bookpanel input[name=coverurl]').val(item.imageLinks.thumbnail); 140 | $('#cover').attr('src',item.imageLinks.thumbnail); 141 | } 142 | bookapi.$dialog.dialog('close'); 143 | } 144 | 145 | }; 146 | 147 | var author = { 148 | init: function(){ 149 | $button = $(document.createElement('a')); 150 | $button.text('+').attr('href','#'); 151 | $button.attr('title','add another author line'); 152 | $button.click(author.add); 153 | $button.addClass('addauthor'); 154 | 155 | $td = $('#authors'); 156 | $td.append($button); 157 | }, 158 | 159 | add: function(){ 160 | $td = $('#authors'); 161 | 162 | $ps = $td.find('p'); 163 | $new = $ps.first().clone(); 164 | $new.find('input').first().attr('name','authorname['+$ps.length+']').val(''); 165 | $new.find('input').last().attr('name','authoras['+$ps.length+']').val(''); 166 | 167 | $ps.last().after($new); 168 | } 169 | }; 170 | 171 | var $wysiwg = null; 172 | $(function(){ 173 | bookapi.init(); 174 | author.init(); 175 | 176 | // scroll to currently selected book 177 | $current = $('#booklist li.active'); 178 | if($current.length){ 179 | $current[0].scrollIntoView(); 180 | } 181 | 182 | // initialize the WYSIWYG editor 183 | $wysiwyg = $('textarea').cleditor({ 184 | width: 450, 185 | controls: // controls to add to the toolbar 186 | "bold italic underline strikethrough | " + 187 | "style removeformat | bullets numbering | " + 188 | "alignleft center alignright justify | undo redo | " + 189 | "link unlink | source", 190 | styles: // styles in the style popup 191 | [["Paragraph", "<p>"], ["Header 1", "<h1>"], ["Header 2", "<h2>"], 192 | ["Header 3", "<h3>"], ["Header 4","<h4>"], ["Header 5","<h5>"]] 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | <?php 2 | // modify this to point to your book directory 3 | $bookdir = '/home/andi/Dropbox/ebooks/'; 4 | 5 | 6 | error_reporting(E_ALL ^ E_NOTICE); 7 | 8 | // proxy google requests 9 | if(isset($_GET['api'])){ 10 | header('application/json; charset=UTF-8'); 11 | echo file_get_contents('https://www.googleapis.com/books/v1/volumes?q='.rawurlencode($_GET['api']).'&maxResults=25&printType=books&projection=full'); 12 | exit; 13 | } 14 | 15 | require('util.php'); 16 | 17 | // load epub data 18 | require('epub.php'); 19 | if(isset($_REQUEST['book'])){ 20 | try{ 21 | $book = $_REQUEST['book']; 22 | $book = str_replace('..','',$book); // no upper dirs, lowers might be supported later 23 | $epub = new EPub($bookdir.$book.'.epub'); 24 | }catch (Exception $e){ 25 | $error = $e->getMessage(); 26 | } 27 | } 28 | 29 | // return image data 30 | if(isset($_REQUEST['img']) && isset($epub)){ 31 | $img = $epub->Cover(); 32 | header('Content-Type: '.$img['mime']); 33 | echo $img['data']; 34 | exit; 35 | } 36 | 37 | // save epub data 38 | if($_REQUEST['save'] && isset($epub)){ 39 | $epub->Title($_POST['title']); 40 | $epub->Description($_POST['description']); 41 | $epub->Language($_POST['language']); 42 | $epub->Publisher($_POST['publisher']); 43 | $epub->Copyright($_POST['copyright']); 44 | $epub->ISBN($_POST['isbn']); 45 | $epub->Subjects($_POST['subjects']); 46 | 47 | $authors = array(); 48 | foreach((array) $_POST['authorname'] as $num => $name){ 49 | if($name){ 50 | $as = $_POST['authoras'][$num]; 51 | if(!$as) $as = $name; 52 | $authors[$as] = $name; 53 | } 54 | } 55 | $epub->Authors($authors); 56 | 57 | // handle image 58 | $cover = ''; 59 | if(preg_match('/^https?:\/\//i',$_POST['coverurl'])){ 60 | $data = @file_get_contents($_POST['coverurl']); 61 | if($data){ 62 | $cover = tempnam(sys_get_temp_dir(), 'epubcover'); 63 | file_put_contents($cover,$data); 64 | unset($data); 65 | } 66 | }elseif(is_uploaded_file($_FILES['coverfile']['tmp_name'])){ 67 | $cover = $_FILES['coverfile']['tmp_name']; 68 | } 69 | if($cover){ 70 | $info = @getimagesize($cover); 71 | if(preg_match('/^image\/(gif|jpe?g|png)$/',$info['mime'])){ 72 | $epub->Cover($cover,$info['meta']); 73 | }else{ 74 | $error = "Not a valid image file".$cover; 75 | } 76 | } 77 | 78 | // save the ebook 79 | try{ 80 | $epub->save(); 81 | }catch(Exception $e){ 82 | $error = $e->getMessage(); 83 | } 84 | 85 | // clean up temporary cover file 86 | if($cover) @unlink($cover); 87 | 88 | // rename 89 | $author = array_shift(array_keys($epub->Authors())); 90 | $title = $epub->Title(); 91 | $new = to_file($author.'-'.$title); 92 | $new = $bookdir.$new.'.epub'; 93 | $old = $epub->file(); 94 | if(realpath($new) != realpath($old)){ 95 | if(!@rename($old,$new)) $new = $old; //rename failed, stay here 96 | } 97 | $go = basename($new,'.epub'); 98 | header('Location: ?book='.rawurlencode($go)); 99 | exit; 100 | } 101 | 102 | header('Content-Type: text/html; charset=utf-8'); 103 | ?> 104 | <html> 105 | <head> 106 | <title>EPub Manager 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 |
119 |
    120 | '; 126 | echo ''.$name.''; 127 | echo ''; 128 | } 129 | ?> 130 |
131 | 132 | 133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 157 | 158 | 159 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 191 |
Title
Authors 144 | Authors() as $as => $name){ 147 | ?> 148 |

149 | 150 | () 151 |

152 | 156 |
Description
160 | 162 |
Subjects
Publisher
Copyright
Language

ISBN

Cover Image

188 | 189 | URL: 190 |

192 |
193 | 194 |
195 |
196 | 197 |

EPub Manager

198 | 199 |

View and edit epub books stored in .

200 |
201 |

',htmlspecialchars(file_get_contents('LICENSE'))) ?>

202 |
203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 |
213 | 214 | 215 | -------------------------------------------------------------------------------- /assets/js/jquery.cleditor.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CLEditor WYSIWYG HTML Editor v1.3.0 3 | http://premiumsoftware.net/cleditor 4 | requires jQuery v1.4.2 or later 5 | 6 | Copyright 2010, Chris Landowski, Premium Software, LLC 7 | Dual licensed under the MIT or GPL Version 2 licenses. 8 | */ 9 | (function(e){function aa(a){var b=this,c=a.target,d=e.data(c,x),h=s[d],f=h.popupName,i=p[f];if(!(b.disabled||e(c).attr(n)==n)){var g={editor:b,button:c,buttonName:d,popup:i,popupName:f,command:h.command,useCSS:b.options.useCSS};if(h.buttonClick&&h.buttonClick(a,g)===false)return false;if(d=="source"){if(t(b)){delete b.range;b.$area.hide();b.$frame.show();c.title=h.title}else{b.$frame.hide();b.$area.show();c.title="Show Rich Text"}setTimeout(function(){u(b)},100)}else if(!t(b))if(f){var j=e(i);if(f== 10 | "url"){if(d=="link"&&M(b)===""){z(b,"A selection is required when inserting a link.",c);return false}j.children(":button").unbind(q).bind(q,function(){var k=j.find(":text"),o=e.trim(k.val());o!==""&&v(b,g.command,o,null,g.button);k.val("http://");r();w(b)})}else f=="pastetext"&&j.children(":button").unbind(q).bind(q,function(){var k=j.find("textarea"),o=k.val().replace(/\n/g,"
");o!==""&&v(b,g.command,o,null,g.button);k.val("");r();w(b)});if(c!==e.data(i,A)){N(b,i,c);return false}return}else if(d== 11 | "print")b.$frame[0].contentWindow.print();else if(!v(b,g.command,g.value,g.useCSS,c))return false;w(b)}}function O(a){a=e(a.target).closest("div");a.css(H,a.data(x)?"#FFF":"#FFC")}function P(a){e(a.target).closest("div").css(H,"transparent")}function ba(a){var b=a.data.popup,c=a.target;if(!(b===p.msg||e(b).hasClass(B))){var d=e.data(b,A),h=e.data(d,x),f=s[h],i=f.command,g,j=this.options.useCSS;if(h=="font")g=c.style.fontFamily.replace(/"/g,"");else if(h=="size"){if(c.tagName=="DIV")c=c.children[0]; 12 | g=c.innerHTML}else if(h=="style")g="<"+c.tagName+">";else if(h=="color")g=Q(c.style.backgroundColor);else if(h=="highlight"){g=Q(c.style.backgroundColor);if(l)i="backcolor";else j=true}b={editor:this,button:d,buttonName:h,popup:b,popupName:f.popupName,command:i,value:g,useCSS:j};if(!(f.popupClick&&f.popupClick(a,b)===false)){if(b.command&&!v(this,b.command,b.value,b.useCSS,d))return false;r();w(this)}}}function C(a){for(var b=1,c=0,d=0;d"+g+"")});else if(a=="style")e.each(b.styles,function(i, 14 | g){e(m).appendTo(f).html(g[1]+g[0]+g[1].replace("<","
');c=B}else if(a=="pastetext"){f.html("Paste your content here and click submit.

");c=B}if(!c&&!d)c=S;f.addClass(c);l&&f.attr(I,"on").find("div,font,p,h1,h2,h3,h4,h5,h6").attr(I,"on");if(f.hasClass(S)||h===true)f.children().hover(O,P);p[a]=f[0]; 15 | return f[0]}function T(a,b){if(b){a.$area.attr(n,n);a.disabled=true}else{a.$area.removeAttr(n);delete a.disabled}try{if(l)a.doc.body.contentEditable=!b;else a.doc.designMode=!b?"on":"off"}catch(c){}u(a)}function v(a,b,c,d,h){D(a);if(!l){if(d===undefined||d===null)d=a.options.useCSS;a.doc.execCommand("styleWithCSS",0,d.toString())}d=true;var f;if(l&&b.toLowerCase()=="inserthtml")y(a).pasteHTML(c);else{try{d=a.doc.execCommand(b,0,c||null)}catch(i){f=i.description;d=false}d||("cutcopypaste".indexOf(b)> 16 | -1?z(a,"For security reasons, your browser does not support the "+b+" command. Try using the keyboard shortcut or context menu instead.",h):z(a,f?f:"Error executing the "+b+" command.",h))}u(a);return d}function w(a){setTimeout(function(){t(a)?a.$area.focus():a.$frame[0].contentWindow.focus();u(a)},0)}function y(a){if(l)return J(a).createRange();return J(a).getRangeAt(0)}function J(a){if(l)return a.doc.selection;return a.$frame[0].contentWindow.getSelection()}function Q(a){var b=/rgba?\((\d+), (\d+), (\d+)/.exec(a), 17 | c=a.split("");if(b)for(a=(b[1]<<16|b[2]<<8|b[3]).toString(16);a.length<6;)a="0"+a;return"#"+(a.length==6?a:c[1]+c[1]+c[2]+c[2]+c[3]+c[3])}function r(){e.each(p,function(a,b){e(b).hide().unbind(q).removeData(A)})}function U(){var a=e("link[href$='jquery.cleditor.css']").attr("href");return a.substr(0,a.length-19)+"images/"}function K(a){var b=a.$main,c=a.options;a.$frame&&a.$frame.remove();var d=a.$frame=e('