1
0
mirror of https://github.com/moparisthebest/wallabag synced 2024-11-27 11:22:17 -05:00

use directly MOBIClass

This commit is contained in:
tcit 2014-07-24 21:56:04 +02:00
parent c70bfefc68
commit fb9df0c269
47 changed files with 309 additions and 1551 deletions

View File

@ -11,18 +11,30 @@ class ImageHandler {
$imgFile = @imagecreatefromstring($data); $imgFile = @imagecreatefromstring($data);
if($imgFile !== false){ if($imgFile !== false){
@imagefilter($imgFile, IMG_FILTER_GRAYSCALE); $result = self::CreateImage($imgFile);
imagedestroy($imgFile);
ob_start(); return $result;
@imagejpeg($imgFile);
$image = ob_get_contents();
ob_end_clean();
@imagedestroy($imgFile);
return $image;
} }
return false; return false;
} }
/**
* Create an image
* @param resource $img Create an image created with createimagetruecolor
* @return false|string False if failed, else the data of the image (converted to grayscale jpeg)
*/
public static function CreateImage($img){
try{
imagefilter($img, IMG_FILTER_GRAYSCALE);
ob_start();
imagejpeg($img);
$image = ob_get_contents();
ob_end_clean();
return $image;
}catch(Exception $e){
return false;
}
}
} }
?> ?>

View File

@ -18,11 +18,9 @@ require_once(dirname(__FILE__).'/FileTri.php');
require_once(dirname(__FILE__).'/Http.php'); require_once(dirname(__FILE__).'/Http.php');
require_once(dirname(__FILE__).'/http_build_url.php'); require_once(dirname(__FILE__).'/http_build_url.php');
require_once(dirname(__FILE__).'/ImageHandler.php'); require_once(dirname(__FILE__).'/ImageHandler.php');
require_once(dirname(__FILE__).'/MOBIFile.php');
require_once(dirname(__FILE__).'/OnlineArticle.php'); require_once(dirname(__FILE__).'/OnlineArticle.php');
require_once(dirname(__FILE__).'/PalmRecord.php'); require_once(dirname(__FILE__).'/PalmRecord.php');
require_once(dirname(__FILE__).'/PEOFRecord.php');
require_once(dirname(__FILE__).'/PFCISRecord.php');
require_once(dirname(__FILE__).'/PFLISRecord.php');
require_once(dirname(__FILE__).'/Prc.php'); require_once(dirname(__FILE__).'/Prc.php');
require_once(dirname(__FILE__).'/PreprocessedArticle.php'); require_once(dirname(__FILE__).'/PreprocessedArticle.php');
require_once(dirname(__FILE__).'/RecognizeURL.php'); require_once(dirname(__FILE__).'/RecognizeURL.php');
@ -43,7 +41,7 @@ require_once(dirname(__FILE__).'/Settings.php');
* $mobi->setFileSource($file); //Load a local file without any extra changes * $mobi->setFileSource($file); //Load a local file without any extra changes
* $mobi->setData($data); //Load data * $mobi->setData($data); //Load data
* *
* //If you want, you can set some optional settings * //If you want, you can set some optional settings (see Settings.php for all recognized settings)
* $options = array( * $options = array(
* "title"=>"Insert title here", * "title"=>"Insert title here",
* "author"=>"Author" * "author"=>"Author"
@ -86,7 +84,7 @@ class MOBI {
*/ */
public function setContentProvider($content){ public function setContentProvider($content){
$this->setOptions($content->getMetaData()); $this->setOptions($content->getMetaData());
$this->images = $content->getImages(); $this->setImages($content->getImages());
$this->setData($content->getTextData()); $this->setData($content->getTextData());
} }
@ -149,16 +147,9 @@ class MOBI {
$mobiHeader = new PalmRecord($settings, $dataRecords, $nRecords, $len, sizeof($this->images)); $mobiHeader = new PalmRecord($settings, $dataRecords, $nRecords, $len, sizeof($this->images));
array_unshift($dataRecords, $mobiHeader); array_unshift($dataRecords, $mobiHeader);
$dataRecords = array_merge($dataRecords, $this->images); $dataRecords = array_merge($dataRecords, $this->images);
$mobiFooter1 = new PFLISRecord($len); $dataRecords[] = $rec->createFLISRecord();
$mobiFooter2 = new PFCISRecord($len);
$mobiFooter3 = new PEOFRecord($len);
$dataRecords[] = $mobiFooter1;
$dataRecords[] = $mobiFooter2;
$dataRecords[] = $mobiFooter3;
/*$dataRecords = array_merge($dataRecords, $mobiFooter);
*$dataRecords[] = $rec->createFLISRecord();*
$dataRecords[] = $rec->createFCISRecord($len); $dataRecords[] = $rec->createFCISRecord($len);
$dataRecords[] = $rec->createEOFRecord();*/ $dataRecords[] = $rec->createEOFRecord();
$this->prc = new Prc($settings, $dataRecords); $this->prc = new Prc($settings, $dataRecords);
return $this->prc; return $this->prc;
} }
@ -183,7 +174,7 @@ class MOBI {
$length = strlen($data); $length = strlen($data);
if($this->debug) return; //In debug mode, don't start the download if($this->debug) return; //In debug mode, don't start the download
/*
header("Content-Type: application/x-mobipocket-ebook"); header("Content-Type: application/x-mobipocket-ebook");
header("Content-Disposition: attachment; filename=\"".$name."\""); header("Content-Disposition: attachment; filename=\"".$name."\"");
header("Content-Transfer-Encoding: binary"); header("Content-Transfer-Encoding: binary");
@ -192,12 +183,8 @@ class MOBI {
header('Pragma: private'); header('Pragma: private');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Content-Length: ".$length); header("Content-Length: ".$length);
echo $data;*/
echo $data;
$hh = fopen("D:\hakuna.mobi", "w");
fwrite($hh, $data);
fclose($hh);
//Finished! //Finished!
} }

View File

@ -0,0 +1,157 @@
<?php
/**
* This is the way MOBI files should be created if you want all features (TOC, images).
*
* File modified by Dawson for use in eBook Creator
* Added pagebreaks and a setting to remove table of contents.
*/
class MOBIFile extends ContentProvider {
const PARAGRAPH = 0;
const H2 = 1;
const H3 = 2;
const IMAGE = 3;
const PAGEBREAK = 4;
private $settings = array("title" => "Unknown Title", "toc" => true);
private $parts = array();
private $images = array();
/**
* Get the text data (the "html" code)
*/
public function getTextData(){
$prefix = "<html><head><guide><reference title='CONTENT' type='toc' filepos=0000000000 /></guide></head><body>";
$title = "<h1>".$this->settings["title"]."</h1>";
list($text, $entries) = $this->generateText();
if($this->settings["toc"]) {
$toc = $this->generateTOC($entries); //Generate TOC to get the right length
$toc = $this->generateTOC($entries, strlen($prefix)+strlen($toc)+strlen($title)); //Generate the real TOC
}
$suffix = "</body></html>";
return $prefix.$toc.$title.$text.$suffix;
}
/**
* Generate the body's text and the chapter entries
* @return array($string, $entries) $string is the html data, $entries
* contains the level, the title and the position of the titles.
*/
public function generateText(){
$str = "";
$entries = array();
for($i = 0; $i < sizeof($this->parts); $i++){
list($type, $data) = $this->parts[$i];
$id = "title_".$i;
switch($type){
case self::PARAGRAPH:
$str .= "<p>".$data."</p>";
break;
case self::PAGEBREAK:
$str .= '<mbp:pagebreak/>';
break;
case self::H2:
$entries[] = array("level" => 2, "position" => strlen($str), "title" => $data, "id" => $id);
$str .= "<h2 id='" . $id . "'>".$data."</h2>";
break;
case self::H3:
$entries[] = array("level" => 3, "position" => strlen($str), "title" => $data, "id" => $id);
$str .= "<h3 id='" . $id . "'>".$data."</h3>";
break;
case self::IMAGE:
$str .= "<img recindex=".str_pad($data+1, 10, "0", STR_PAD_LEFT)." />";
break;
}
}
return array($str, $entries);
}
/**
* Generate a TOC
* @param $entries The entries array generated by generateText
* @param $base The zero position
*/
public function generateTOC($entries, $base = 0){
$toc = "<h2>Contents</h2>";
$toc .= "<blockquote><table summary='Table of Contents'><col/><tbody>";
for($i = 0, $len = sizeof($entries); $i < $len; $i++){
$entry = $entries[$i];
$pos = str_pad($entry["position"]+$base, 10, "0", STR_PAD_LEFT);
$toc .= "<tr><td><a href='#".$entry["id"]."' filepos='".$pos."'>".$entry["title"]."</a></td></tr>";
}
return $toc."</tbody></b></table></blockquote><mbp:pagebreak/>";
}
/**
* Get the file records of the images
*/
public function getImages(){
return $this->images;
}
/**
* Get the metadata
*/
public function getMetaData(){
return $this->settings;
}
/**
* Change the file's settings. For example set("author", "John Doe") or set("title", "The adventures of John Doe").
* @param $key Key of the setting to insert.
*/
public function set($key, $value){
$this->settings[$key] = $value;
}
/**
* Get the file's settings.
*/
public function get($key){
return $this->settings[$key];
}
/**
* Append a paragraph of text to the file.
* @param string $text The text to insert.
*/
public function appendParagraph($text){
$this->parts[] = array(self::PARAGRAPH, $text);
}
/**
* Append a chapter title (H2)
* @param string $title The title to insert.
*/
public function appendChapterTitle($title){
$this->parts[] = array(self::H2, $title);
}
/**
* Append a section title (H3)
* @param string $title The title to insert.
*/
public function appendSectionTitle($title){
$this->parts[] = array(self::H3, $title);
}
public function appendPageBreak() {
$this->parts[] = array(self::PAGEBREAK, null);
}
/**
* Append an image.
* @param resource $img An image file (for example, created by `imagecreate`)
*/
public function appendImage($img){
$imgIndex = sizeof($this->images);
$this->images[] = new FileRecord(new Record(ImageHandler::CreateImage($img)));
$this->parts[] = array(self::IMAGE, $imgIndex);
}
}

View File

@ -38,32 +38,19 @@ class PalmRecord extends FileObject {
"huffmanRecordOffset"=>new FileInt(), "huffmanRecordOffset"=>new FileInt(),
"huffmanRecordCount"=>new FileInt(), "huffmanRecordCount"=>new FileInt(),
"unused3"=>new FileString(8), "unused3"=>new FileString(8),
"exthFlags"=>new FileInt(0x50), "exthFlags"=>new FileInt(0x40),
"unknown"=>new FileString(32), "unknown"=>new FileString(32),
"drmOffset"=>new FileInt(0xFFFFFFFF), "drmOffset"=>new FileInt(0xFFFFFFFF),
"drmCount"=>new FileInt(0xFFFFFFFF), "drmCount"=>new FileShort(0xFFFFFFFF),
"drmSize"=>new FileInt(), "drmSize"=>new FileShort(),
"drmFlags"=>new FileInt(), "drmFlags"=>new FileInt(),
"mobiFiller"=>new FileString(12), "mobiFiller"=>new FileString(72),
"offset192"=>new FileShort(0x01),
"offset194"=>new FileShort(),
"offset196"=>new FileInt(0x01),
"offset200"=>new FileInt(),
"offset204"=>new FileInt(0x01),
"offset208"=>new FileInt(),
"offset212"=>new FileInt(0x01),
"offset216"=>new FileString(8),
"offset224"=>new FileInt(0xFFFFFFFF),
"offset228"=>new FileInt(),
"offset232"=>new FileString(8),
"offset240"=>new FileInt(0x01),
"offset244"=>new FileInt(0xFFFFFFFF),
//EXTH Header //EXTH Header
"exthIdentifier"=>new FileString("EXTH", 4), "exthIdentifier"=>new FileString("EXTH", 4),
"exthHeaderLength"=>new FileInt(), "exthHeaderLength"=>new FileInt(),
"exthRecordCount"=>new FileInt(), "exthRecordCount"=>new FileInt(),
"exthRecords"=>new FileElement(), "exthRecords"=>new FileElement(),
"exthPadding"=>new FileString(),//added the 2 extra pad bytes that comes before name/title "exthPadding"=>new FileString(),
//"fullNamePadding"=>new FileString(100), //"fullNamePadding"=>new FileString(100),
"fullName"=>new FileString() "fullName"=>new FileString()
)); ));
@ -96,7 +83,7 @@ class PalmRecord extends FileObject {
} }
if($images > 0){ if($images > 0){
$this->elements->get("firstImageIndex")->set($textRecords+2); $this->elements->get("firstImageIndex")->set($textRecords+1);
} }
$this->elements->get("firstNonBookIndex")->set($textRecords+2+$images); $this->elements->get("firstNonBookIndex")->set($textRecords+2+$images);
$this->elements->get("reserved")->set(str_pad("", 40, chr(255), STR_PAD_RIGHT)); $this->elements->get("reserved")->set(str_pad("", 40, chr(255), STR_PAD_RIGHT));
@ -104,21 +91,16 @@ class PalmRecord extends FileObject {
$this->elements->set("exthRecords", $exthElems); $this->elements->set("exthRecords", $exthElems);
$pad = $l%4; $pad = $l%4;
$pad = (4-$pad)%4; $pad = (4-$pad)%4;
$this->elements->get("exthPadding")->set(str_pad("", $pad+2, "\0", STR_PAD_RIGHT)); $this->elements->get("exthPadding")->set(str_pad("", $pad, "\0", STR_PAD_RIGHT));
$this->elements->get("exthHeaderLength")->set(12+$l+$pad); $this->elements->get("exthHeaderLength")->set(12+$l+$pad);
$this->elements->get("recordCount")->set($textRecords); $this->elements->get("recordCount")->set($textRecords);
$this->elements->get("fullNameOffset")->set($this->elements->offsetToEntry("fullName"));//need to be checked $this->elements->get("fullNameOffset")->set($this->elements->offsetToEntry("fullName"));
$this->elements->get("fullNameLength")->set(strlen($settings->get("title"))); $this->elements->get("fullNameLength")->set(strlen($settings->get("title")));
$this->elements->get("fullName")->set($settings->get("title")); $this->elements->get("fullName")->set($settings->get("title"));
$this->elements->get("textLength")->set($textLength); $this->elements->get("textLength")->set($textLength);
$this->elements->get("offset194")->set($textRecords+2+$images);
$this->elements->get("offset200")->set($textRecords+4+$images);
$this->elements->get("offset208")->set($textRecords+3+$images);
$this->elements->get("offset232")->set(str_pad("", 8, chr(255), STR_PAD_RIGHT));
} }
public function getByteLength(){ public function getByteLength(){

View File

@ -40,7 +40,7 @@ class Prc extends FileElement {
foreach($records as $record){ foreach($records as $record){
$offset = new FileInt(); $offset = new FileInt();
$attr = new FileByte(); $attr = new FileByte();
$uniqueID = new FileTri(2*$i); $uniqueID = new FileTri($i);
$this->elements["recordList"]->add("Rec".$i, new FileElement(array( $this->elements["recordList"]->add("Rec".$i, new FileElement(array(
"offset"=>$offset, "offset"=>$offset,
@ -49,7 +49,7 @@ class Prc extends FileElement {
))); )));
$this->elements["records"]->add("Rec".$i, $record); $this->elements["records"]->add("Rec".$i, $record);
$i+=1; $i++;
} }
$this->updateOffsets($records); $this->updateOffsets($records);

View File

@ -62,21 +62,7 @@ class RecordFactory {
} }
public function createFLISRecord(){ public function createFLISRecord(){
$r = "FLIS";
$this->elements = new FileElement(array(
"offsetL0"=>new FileString("FLIS", 4), //FLIS
"offsetL4"=>new FileInt(0x08),
"offsetL8"=>new FileShort(0x41),
"offsetL10"=>new FileTri(),
"offsetL16"=>new FileInt(0xFFFFFFFF),
"offsetL20"=>new FileShort(0x01),
"offsetL22"=>new FileShort(0x03),
"offsetL24"=>new FileInt(0x03),
"offsetL28"=>new FileInt(0x01),
"offsetL32"=>new FileInt(0xFFFFFFFF)
));
/*$r = "FLIS";
$r .= $this->asString(8, 4); $r .= $this->asString(8, 4);
$r .= $this->asString(65, 2); $r .= $this->asString(65, 2);
$r .= $this->asString(0, 2); $r .= $this->asString(0, 2);
@ -87,7 +73,7 @@ class RecordFactory {
$r .= $this->asString(3, 4); $r .= $this->asString(3, 4);
$r .= $this->asString(1, 4); $r .= $this->asString(1, 4);
$r .= $this->asString(-1, 4); $r .= $this->asString(-1, 4);
return new Record($r);*/ return new Record($r);
} }
private function asString($int, $size){ private function asString($int, $size){

View File

@ -19,6 +19,7 @@ class Settings {
* be added with a key/value pair format. * be added with a key/value pair format.
*/ */
public function __construct($additionalSettings = array()) { public function __construct($additionalSettings = array()) {
// Most values shouldn't be changed (the result will be an invalid file)
$this->values = array( $this->values = array(
"attributes"=>0, "attributes"=>0,
"version"=>0, "version"=>0,
@ -48,13 +49,14 @@ class Settings {
"minimumVersion"=>6, "minimumVersion"=>6,
"huffmanRecordOffset"=>0, "huffmanRecordOffset"=>0,
"huffmanRecordCount"=>0, "huffmanRecordCount"=>0,
"exthFlags"=>0x50, "exthFlags"=>0x40,
"drmOffset"=>0xFFFFFFFF, "drmOffset"=>0xFFFFFFFF,
"drmCount"=>0xFFFFFFFF, "drmCount"=>0,
"drmSize"=>0, "drmSize"=>0,
"drmFlags"=>0, "drmFlags"=>0,
"extraDataFlags"=>0, "extraDataFlags"=>0,
"exthIdentifier"=>"EXTH", "exthIdentifier"=>"EXTH",
// These can be changed without any risk
"title"=>"Unknown title", "title"=>"Unknown title",
"author"=>"Unknown author", "author"=>"Unknown author",
"subject"=>"Unknown subject" "subject"=>"Unknown subject"

View File

@ -7451,7 +7451,7 @@ function Output($name='',$dest='')
// don't use length if server using compression // don't use length if server using compression
header('Content-Length: '.strlen($this->buffer)); header('Content-Length: '.strlen($this->buffer));
} }
header('Content-disposition: inline; filename="'.$name.'"'); header('Content-disposition: attachment; filename="'.$name.'"');
header('Cache-Control: public, must-revalidate, max-age=0'); header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: public'); header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');

View File

@ -1,49 +0,0 @@
<?php
/**
* A Record of a End file
*
* @author Pratyush
*/
class PEOFRecord extends FileObject {
/**
* @var FileElement
*/
private $elements;
public function __construct($leng){
$this->elements = new FileElement(array(
"offset44"=>new FileInt(0xe98e0d0a)
));
}
public function getByteLength(){
return $this->getLength();
}
public function getLength(){
return $this->elements->getByteLength();
}
public function get(){
return $this;
}
public function set($elements){
throw new Exception("Unallowed set");
}
public function serialize() {
return $this->elements->serialize();
}
public function unserialize($data) {
$this->elements->unserialize($data);
}
public function __toString(){
$output = "PalmDoc Record (".$this->getByteLength()." bytes):\n";
$output .= $this->elements;
return $output;
}
}

View File

@ -1,59 +0,0 @@
<?php
/**
* A Record of a End file
*
* @author Pratyush
*/
class PFCISRecord extends FileObject {
/**
* @var FileElement
*/
private $elements;
public function __construct($leng){
$this->elements = new FileElement(array(
"offset0"=>new FileString("FCIS", 4), //FCIS
"offset4"=>new FileInt(0x014),
"offset8"=>new FileInt(0x10),
"offset12"=>new FileInt(0x01),
"offset16"=>new FileInt(),
"offset20"=>new FileInt($leng),
"offset24"=>new FileInt(),
"offset28"=>new FileInt(0x20),
"offset32"=>new FileInt(0x08),
"offset36"=>new FileShort(0x01),
"offset38"=>new FileShort(0x01),
"offset40"=>new FileInt()
));
}
public function getByteLength(){
return $this->getLength();
}
public function getLength(){
return $this->elements->getByteLength();
}
public function get(){
return $this;
}
public function set($elements){
throw new Exception("Unallowed set");
}
public function serialize() {
return $this->elements->serialize();
}
public function unserialize($data) {
$this->elements->unserialize($data);
}
public function __toString(){
$output = "PalmDoc Record (".$this->getByteLength()." bytes):\n";
$output .= $this->elements;
return $output;
}
}

View File

@ -1,58 +0,0 @@
<?php
/**
* A Record of a End file
*
* @author Pratyush
*/
class PFLISRecord extends FileObject {
/**
* @var FileElement
*/
private $elements;
public function __construct($leng){
$this->elements = new FileElement(array(
"offsetL0"=>new FileString("FLIS", 4), //FLIS
"offsetL4"=>new FileInt(0x08),
"offsetL8"=>new FileShort(0x41),
"offsetL10"=>new FileString(6),
"offsetL16"=>new FileInt(0xFFFFFFFF),
"offsetL20"=>new FileShort(0x01),
"offsetL22"=>new FileShort(0x03),
"offsetL24"=>new FileInt(0x03),
"offsetL28"=>new FileInt(0x01),
"offsetL32"=>new FileInt(0xFFFFFFFF)
));
}
public function getByteLength(){
return $this->getLength();
}
public function getLength(){
return $this->elements->getByteLength();
}
public function get(){
return $this;
}
public function set($elements){
throw new Exception("Unallowed set");
}
public function serialize() {
return $this->elements->serialize();
}
public function unserialize($data) {
$this->elements->unserialize($data);
}
public function __toString(){
$output = "PalmDoc Record (".$this->getByteLength()." bytes):\n";
$output .= $this->elements;
return $output;
}
}

View File

@ -1,234 +0,0 @@
<?php
/**
* @author Dimmduh
* @email dimmduh@gmail.com
*/
class API{
private $service;
private $auth;
private $token;
private $source = 'GoogleReaderAPIClass-0.1';
private $client = 'scroll';
private $account_type = 'HOSTED_OR_GOOGLE';
private $clientlogin_url = 'https://www.google.com/accounts/ClientLogin';
private $reader_api_url = 'http://www.google.com/reader/api/0/';
private $session_var_auth_name = 'google_auth';
public function __construct( $email, $password, $service = 'reader' ){
if (isset( $service ) ){
$this -> service = $service;
}
/* if ( isset($_SESSION[ $this -> session_var_auth_name ] ) ){
$this -> auth = $_SESSION[ $this -> session_var_auth_name ];
echo "Loading";
} else { */
$this -> clientLogin( $email, $password );
$this -> get_token();
/* } */
}
private function request( $url, $type = 'get', $headers = false, $fields = false, $cookie = false){
$curl = curl_init();
if ( $fields ){
if ($type == 'get'){
$url .= '?'.http_build_query( $fields );
} else {
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($fields) );
}
}
if ( $headers ){
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
if ( $cookie ){
curl_setopt($curl, CURLOPT_COOKIE, $cookie);
}
curl_setopt($curl, CURLOPT_URL, $url);
if (strpos($url, 'https://') !== false){
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
$response = array();
$response['text'] = curl_exec($curl);
$response['info'] = curl_getinfo( $curl);
$response['code'] = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
$response['body'] = substr( $response['text'], $response['info']['header_size'] );
curl_close( $curl );
return $response;
}
private function request2google( $url, $type = 'get', $headers = false, $fields = false ){
if ( $this -> auth ){
$headers[] = 'Content-type: application/x-www-form-urlencoded';
$headers[] = 'Authorization: GoogleLogin auth='.$this -> auth;
if ( strpos( $url, 'http://') === false && strpos( $url, 'https://' ) === false ){
$url = $this -> reader_api_url.$url;
}
$response = $this -> request( $url, $type, $headers, $fields);
if ( $response['code'] == 200 ){
if ( isset( $fields['output'] ) ){
switch ($fields['output']){
case 'xml':
return (new SimpleXMLElement( $response['body'] ) );
break;
case 'json':
default:
return json_decode( $response['body'] );
break;
}
} else {
return $response['body'];
}
} else {
Throw new AutentificationException('Auth error: server response '.$response['code'] );
}
} else {
Throw new AutentificationException('Auth error: not finded Auth token');
}
}
public function get_tag_list( $output = 'json' ){
return $this -> request2google('tag/list', "get", false, array(
'output' => $output,
'ck' => time(),
'client' => $this -> client,
));
}
public function get_subscription_list( $output = 'json' ){
return $this -> request2google('subscription/list', "get", false, array(
'output' => $output,
'ck' => time(),
'client' => $this -> client,
));
}
public function get_preference_list( $output = 'json' ){
return $this -> request2google('preference/list', "get", false, array(
'output' => $output,
'ck' => time(),
'client' => $this -> client,
));
}
public function get_unread_count( $output = 'json' ){
return $this -> request2google('unread-count', "get", false, array(
'all' => true,
'output' => $output,
'ck' => time(),
'client' => $this -> client,
));
}
public function get_user_info( $output = 'json' ){
return $this -> request2google('user-info', "get", false, array(
'output' => $output,
'ck' => time(),
'client' => $this -> client,
));
}
private function get_token(){
$this -> token = $this -> request2google('token');
}
//get contents functions
/*
r - order
r = n - new items
r = o - old items
r = a - auto sort
*/
private function get_content( $content_url = '', $number = 20, $order = 'n', $exclude_target = '', $start_time = '', $continuation = ''){
$fields = array(
'ck' => time(),
'client' => $this -> client,
'n' => $number,
'r' => $order,
'output' => 'json',
);
if ( !empty($exclude_target) ){$fields['xt'] = $exclude_target;}
if ( !empty($start_time) ){$fields['ot'] = $start_time;}
if ( !empty($continuation) ){$fields['c'] = $continuation;}
return $this -> request2google('stream/contents/'.Utils::urlencode( $content_url ), 'get', false, $fields);
}
public function get_content_feed( $feed_url = '', $number = 20, $order = 'n', $exclude_target = '', $start_time = '', $continuation = ''){
return $this -> get_content( $feed_url, $number, $order, $exclude_target, $start_time, $continuation );
}
public function get_content_by_label( $label = '', $number = 20, $order = 'n', $exclude_target = '', $start_time = '', $continuation = ''){
return $this -> get_content( (strpos($label, '/') === false?'user/-/label/':'').$label, $number, $order, $exclude_target, $start_time, $continuation );
}
public function get_content_by_state( $state = '', $number = 20, $order = 'n', $exclude_target = '', $start_time = '', $continuation = ''){
return $this -> get_content( (strpos($state, '/') === false?'user/-/state/com.google/':'').$state, $number, $order, $exclude_target, $start_time, $continuation );
}
public function get_unread( $number = 20, $order = 'n' ){
return $this ->get_content_by_state('reading-list', $number, $order, 'user/-/state/com.google/read');
}
public function get_starred($number = 20, $order = 'n'){
return $this ->get_content_by_state('starred', $number, $order);
}
/*
Edit functions
*/
private function edit_do( $api_function , $post_fields ){
$post_fields['T'] = $this -> token;
if ( $this -> request2google( $api_function, "post", false, $post_fields ) == "OK"){
return true;
} else {
return false;
}
}
/* public function edit_subscription(
s return $this -> edit_do( 'subscription/edit',
} */
public function set_state( $itemId, $state = 'read'){
$post_fields = array(
"i" => $itemId,
"a" => 'user/-/state/com.google/'.$state,
);
//print_r( $post_fields );
return $this ->edit_do('edit-tag?client='.$this -> client, $post_fields);
}
private function clientLogin( $email, $password ){
$response = $this -> request( $this -> clientlogin_url, 'post', false, array(
"accountType" => $this -> account_type,
"Email" => $email,
"Passwd" => $password,
"service" => $this -> service,
"source" => $this -> source,
));
if ( $response['code'] == 200) {
preg_match("/Auth=([a-z0-9_\-]+)/i", $response['body'], $matches_auth);
if ($matches_auth[1]){
$this -> auth = $matches_auth[1];
$_SESSION[ $this -> session_var_auth_name ] = $this -> auth;
return true;
} else {
Throw new AutentificationException('Auth error: not finded Auth token in response');
}
} else {
Throw new AutentificationException('Auth error: server response '.$response['code'] );
}
}
}
//Exceptions
class AutentificationException extends Exception {}

View File

@ -1,136 +0,0 @@
<?php
/**
* Get images from content and prepare to save in articles
*/
class Images {
/**
* JPER quality for resize
*/
const JPEG_QUALITY = 80;
/**
* Articles content
* @var string
*/
private $_content;
/**
* Storage to keep images
* @var object Storage
*/
private $_storage;
/**
* images from content
* @var array
*/
private $_images_from_content = array();
/**
* Prepare get images
* @param Strage $storage
* @param string $article_content
*/
public function __construct(Storage $storage, $article_content)
{
$this->_storage = $storage;
$this->_content = $article_content;
$this->_images_from_content = $this->_get_images_from_content($article_content);
}
/**
* get images from url
* @param string $content
* @return array images hashtable
*/
private function _get_images_from_content($content)
{
$result = array();
preg_match_all('/src=\"([a-zA-Z0-9\.\/\-\_\?\+\%\~\&\;\=\:]+)\"/i', $content, $result);
return $result[1];
}
/**
* Start conversion
* @return string converted content
*/
public function convert()
{
foreach ( $this->_images_from_content as $n => $image_url )
{
$image = $this->_get_image($image_url);
$this->_content = str_replace($image_url, '" recindex="'.(int)basename($image), $this->_content);
}
return $this->_content;
}
/**
* Resize image
* @param string $file path
* @param int $new_width max width
*/
private function _resize($file, $new_width = 500)
{
list($width, $height) = getimagesize($file);
$new_height = 0;
//setup the new size of the image
if( $width > $new_width )
{
$ratio = $height/$width;
$new_height = $new_width * $ratio;
}
else
{
$new_width = $width;
$new_height = $height;
}
// resample the image
$new_image = imagecreatetruecolor($new_width, $new_height);
$type = exif_imagetype ( $file );
switch ( $type )
{
case IMAGETYPE_JPEG:
$old_image = imagecreatefromjpeg($file);
imagecopyresampled($new_image, $old_image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
imagejpeg($new_image, $file, self::JPEG_QUALITY);
break;
case IMAGETYPE_PNG:
$old_image = imagecreatefrompng($file);
imagecopyresampled($new_image, $old_image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
imagepng($new_image, $file);
break;
case IMAGETYPE_GIF:
$old_image = imagecreatefromgif($file);
imagecopyresampled($new_image, $old_image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
imagegif($new_image, $file);
break;
}
}
/**
* Resize image
* @return string image path
*/
private function _get_image($url)
{
$image_data = @file_get_contents($url);
if ( $image_data !== false )
{
$image_name = $this->_storage->save_image($image_data);
$this->_resize($image_name);
return $image_name;
}
}
}

View File

@ -1,250 +0,0 @@
<?php
/**
* Simple output message and args prepared
*
* @author Jakub Westfalewski <jwest@jwest.pl>
*/
class IO {
/**
* Max line height
*/
const COMMAND_LENGTH = 50;
/**
* args for run screen
* @var array
*/
protected static $_args = array
(
'help' => array('value' => FALSE, 'description' => 'show help for news2kindle'),
'grab' => array('value' => FALSE, 'description' => 'grab articles from google reader'),
'mobi' => array('value' => FALSE, 'description' => 'prepare mobi format'),
'send' => array('value' => FALSE, 'description' => 'send articles to kindle'),
'login' => array('value' => NULL, 'description' => 'your login to google account *requred'),
'password' => array('value' => NULL, 'description' => 'your password to google account *required'),
'kindle' => array('value' => NULL, 'description' => 'your kindle email *required'),
'items' => array('value' => 100, 'description' => 'max items to grab in run'),
'render' => array('value' => 'std', 'description' => 'name of html template for newspapper'),
//'timeout' => array('value' => FALSE, 'description' => 'timeout is most important than items count'),
);
/**
* Write on screen text line
* @param string $message
* @param bool $broken_line
*/
public static function msg($message, $broken_line = TRUE)
{
echo ( $broken_line ? "\n" : "" ) . $message;
}
/**
* Write command for status
* @param string $message
*/
public static function command($message)
{
$output_whitespaces = '';
for ($i = strlen($message); $i <= self::COMMAND_LENGTH; ++$i )
{
$output_whitespaces .= '-';
}
self::msg( $message . ' ' . $output_whitespaces.' ' );
}
/**
* Status - OK
*/
public static function ok()
{
$colored_string = "\033[1;37m" . "\033[42m" . ' OK ' . "\033[0m";
self::msg( $colored_string, FALSE );
}
/**
* Status - Error
*/
public static function error($message = NULL)
{
$colored_string = "\033[1;37m" . "\033[41m" . ' ERROR ' . "\033[0m";
self::msg( $colored_string, FALSE );
if ( $message !== NULL )
{
self::msg( ' - ' . $message );
}
}
/**
* Get run args
* @param string $name key config
* @return mixed config value
*/
public static function arg($name)
{
return self::$_args[$name]['value'];
}
/**
* Get config from ini
* @param string $path
* @return void
*/
protected static function _get_config($path)
{
$config = parse_ini_file( $path . 'config.ini' );
foreach($config as $key => $value)
{
self::$_args[$key]['value'] = $value;
}
}
/**
* Prepare args for script
* (from http://php.net/manual/en/features.commandline.php)
* @param array $argv array
* @param string $path
* @return bool success or error
*/
public static function prepare_args($argv, $path)
{
self::command('Parse args');
self::_get_config($path);
array_shift($argv);
$out = array();
foreach ( $argv as $arg )
{
if ( substr($arg,0,2) == '--' )
{
$eqPos = strpos($arg,'=');
if ( $eqPos === false )
{
$key = substr($arg,2);
$out[$key] = isset($out[$key]) ? $out[$key] : true;
}
else
{
$key = substr($arg,2,$eqPos-2);
$out[$key] = substr($arg,$eqPos+1);
}
}
else if ( substr($arg,0,1) == '-' )
{
if ( substr($arg,2,1) == '=' )
{
$key = substr($arg,1,1);
$out[$key] = substr($arg,3);
}
else
{
$chars = str_split(substr($arg,1));
foreach ( $chars as $char )
{
$key = $char;
$out[$key] = isset($out[$key]) ? $out[$key] : true;
}
}
}
else
{
$out[] = $arg;
}
}
try
{
$args = self::_validate_args($out);
foreach ( $args as $key => $value )
{
self::$_args[$key]['value'] = $value;
}
}
catch(Exception $e)
{
self::error($e->getMessage());
return false;
}
self::ok();
return true;
}
/**
* Validate args for script
* (from http://php.net/manual/en/features.commandline.php)
* @param array $argv array
* @return array args
*/
private static function _validate_args($args)
{
if( array_key_exists('help', $args) OR array_key_exists('h', $args) )
{
$args['grab'] = false;
$args['mobi'] = false;
$args['send'] = false;
$args['login'] = false;
$args['password'] = false;
$args['kindle'] = false;
}
foreach ( $args as $key => $arg )
{
if ( strlen($key) === 1 )
{
foreach ( self::$_args as $keyA => $argA )
{
if($keyA[0] === $key )
{
unset( $args[$key] );
$args[$keyA] = $arg;
$key = $keyA;
}
}
}
if ( ! array_key_exists($key, self::$_args) )
{
throw new Exception('Param "'.$key.'" is invalid!');
}
}
foreach ( self::$_args as $key => $arg )
{
if( self::$_args[$key]['value'] === NULL AND !array_key_exists($key, $args) )
{
throw new Exception('Param "'.$key.'" must be declared!');
}
}
return $args;
}
/**
* Prepare help
* @return array
*/
public static function get_help()
{
$output = array();
foreach ( self::$_args as $arg => $item )
{
$output[$arg] = $item['description'];
}
return $output;
}
}

View File

@ -1,249 +0,0 @@
<?php
/**
* PHP Readability
*
* Readability PHP 版本,详见
* http://code.google.com/p/arc90labs-readability/
*
* ChangeLog:
*
* [+] 2011-02-17 初始化版本
*
* @author mingcheng<i.feelinglucky#gmail.com>
* @date 2011-02-17
* @link http://www.gracecode.com/
*/
define("READABILITY_VERSION", 0.12);
class Readability2 {
// 保存判定结果的标记位名称
const ATTR_CONTENT_SCORE = "contentScore";
// DOM 解析类目前只支持 UTF-8 编码
const DOM_DEFAULT_CHARSET = "utf-8";
// 当判定失败时显示的内容
const MESSAGE_CAN_NOT_GET = "Sorry, readability was unable to parse this page for content. \n
If you feel like it should have been able to,
please let me know by mail: lucky[at]gracecode.com";
// DOM 解析类PHP5 已内置)
protected $DOM = null;
// 需要解析的源代码
protected $source = "";
// 章节的父元素列表
private $parentNodes = array();
// 需要删除的标签
private $junkTags = Array("style", "form", "iframe", "script", "button", "input", "textarea");
// 需要删除的属性
private $junkAttrs = Array("style", "class", "onclick", "onmouseover", "align", "border", "margin");
/**
* 构造函数
* @param $input_char 字符串的编码。默认 utf-8,可以省略
*/
function __construct($source, $input_char = "utf-8") {
$this->source = $source;
// DOM 解析类只能处理 UTF-8 格式的字符
$source = mb_convert_encoding($source, 'HTML-ENTITIES', $input_char);
// 预处理 HTML 标签,剔除冗余的标签等
$source = $this->preparSource($source);
// 生成 DOM 解析类
$this->DOM = new DOMDocument('1.0', $input_char);
try {
//libxml_use_internal_errors(true);
// 会有些错误信息,不过不要紧 :^)
if (!@$this->DOM->loadHTML('<?xml encoding="'.Readability2::DOM_DEFAULT_CHARSET.'">'.$source)) {
throw new Exception("Parse HTML Error!");
}
foreach ($this->DOM->childNodes as $item) {
if ($item->nodeType == XML_PI_NODE) {
$this->DOM->removeChild($item); // remove hack
}
}
// insert proper
$this->DOM->encoding = Readability2::DOM_DEFAULT_CHARSET;
} catch (Exception $e) {
// ...
}
}
/**
* 预处理 HTML 标签,使其能够准确被 DOM 解析类处理
*
* @return String
*/
private function preparSource($string) {
// 剔除多余的 HTML 编码标记,避免解析出错
preg_match("/charset=([\w|\-]+);?/", $string, $match);
if (isset($match[1])) {
$string = preg_replace("/charset=([\w|\-]+);?/", "", $string, 1);
}
// Replace all doubled-up <BR> tags with <P> tags, and remove fonts.
$string = preg_replace("/<br\/?>[ \r\n\s]*<br\/?>/i", "</p><p>", $string);
$string = preg_replace("/<\/?font[^>]*>/i", "", $string);
return trim($string);
}
/**
* 删除 DOM 元素中所有的 $TagName 标签
*
* @return DOMDocument
*/
private function removeJunkTag($RootNode, $TagName) {
$Tags = $RootNode->getElementsByTagName($TagName);
$i = 0;
while($Tag = $Tags->item($i++)) {
$parentNode = $Tag->parentNode;
$parentNode->removeChild($Tag);
}
return $RootNode;
}
/**
* 删除元素中所有不需要的属性
*/
private function removeJunkAttr($RootNode, $Attr) {
$Tags = $RootNode->getElementsByTagName("*");
$i = 0;
while($Tag = $Tags->item($i++)) {
$Tag->removeAttribute($Attr);
}
return $RootNode;
}
/**
* 根据评分获取页面主要内容的盒模型
* 判定算法来自http://code.google.com/p/arc90labs-readability/
*
* @return DOMNode
*/
private function getTopBox() {
// 获得页面所有的章节
$allParagraphs = $this->DOM->getElementsByTagName("p");
// Study all the paragraphs and find the chunk that has the best score.
// A score is determined by things like: Number of <p>'s, commas, special classes, etc.
$i = 0;
while($paragraph = $allParagraphs->item($i++)) {
$parentNode = $paragraph->parentNode;
$contentScore = intval($parentNode->getAttribute(Readability2::ATTR_CONTENT_SCORE));
$className = $parentNode->getAttribute("class");
$id = $parentNode->getAttribute("id");
// Look for a special classname
if (preg_match("/(comment|meta|footer|footnote)/i", $className)) {
$contentScore -= 50;
} else if(preg_match(
"/((^|\\s)(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)(\\s|$))/i",
$className)) {
$contentScore += 25;
}
// Look for a special ID
if (preg_match("/(comment|meta|footer|footnote)/i", $id)) {
$contentScore -= 50;
} else if (preg_match(
"/^(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)$/i",
$id)) {
$contentScore += 25;
}
// Add a point for the paragraph found
// Add points for any commas within this paragraph
if (strlen($paragraph->nodeValue) > 10) {
$contentScore += strlen($paragraph->nodeValue);
}
// 保存父元素的判定得分
$parentNode->setAttribute(Readability2::ATTR_CONTENT_SCORE, $contentScore);
// 保存章节的父元素,以便下次快速获取
array_push($this->parentNodes, $parentNode);
}
$topBox = $this->DOM->createElement('div', Readability2::MESSAGE_CAN_NOT_GET);
// Assignment from index for performance.
// See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5
for ($i = 0, $len = sizeof($this->parentNodes); $i < $len; $i++) {
$parentNode = $this->parentNodes[$i];
$contentScore = intval($parentNode->getAttribute(Readability2::ATTR_CONTENT_SCORE));
$orgContentScore = intval($topBox->getAttribute(Readability2::ATTR_CONTENT_SCORE));
if ($contentScore && $contentScore > $orgContentScore) {
$topBox = $parentNode;
}
}
// 此时,$topBox 应为已经判定后的页面内容主元素
return $topBox;
}
/**
* 获取 HTML 页面标题
*
* @return String
*/
public function getTitle() {
$title = $this->DOM->getElementsByTagName("title");
return $title->item(0);
}
/**
* 获取页面的主要内容Readability 以后的内容)
*
* @return Array
*/
public function getContent() {
if (!$this->DOM) return false;
// 获取页面标题
$ContentTitle = $this->getTitle();
// 获取页面主内容
$ContentBox = $this->getTopBox();
// 复制内容到新的 DOMDocument
$Target = new DOMDocument;
$Target->appendChild($Target->importNode($ContentBox, true));
// 删除不需要的标签
foreach ($this->junkTags as $tag) {
$Target = $this->removeJunkTag($Target, $tag);
}
// 删除不需要的属性
foreach ($this->junkAttrs as $attr) {
$Target = $this->removeJunkAttr($Target, $attr);
}
// 多个数据,以数组的形式返回
return Array(
'title' => $ContentTitle ? $ContentTitle->nodeValue : "",
'content' => $Target->saveHTML()
);
}
function __destruct() { }
}

View File

@ -1,69 +0,0 @@
<?php
/**
* Send to kindle email
* @author jwest <jwest@jwest.pl>
*/
class Send {
/**
* Your kindle email
* @var string
*/
private $_kindle_email;
/**
* Your email (must be added on amazon)
* @var string
*/
private $_email;
/**
* Prepare mail
* @param string $kindle_email your kindle email
* @param string $email email for send to kindle
*/
public function __construct($kindle_email, $email)
{
$this->_kindle_email = $kindle_email;
$this->_email = $email;
}
/**
* Send file
* @param string $file path to file
* @return bool
*/
public function send($file)
{
//prepare file
$file_size = filesize($file);
$filename = basename($file);
$handle = fopen($file, "r");
$content = fread($handle, $file_size);
fclose($handle);
$content = chunk_split(base64_encode($content));
$uid = md5(uniqid(time()));
//generate header for mail
$header = "From: News2Kindle <".$this->_email.">\r\n";
$header .= "MIME-Version: 1.0\r\n";
$header .= "Content-Type: multipart/mixed; boundary=\"".$uid."\"\r\n\r\n";
$header .= "This is a multi-part message in MIME format.\r\n";
$header .= "--".$uid."\r\n";
$header .= "Content-type:text/plain; charset=iso-8859-1\r\n";
$header .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
$header .= "send via News2Kindle script\r\n\r\n";
$header .= "--".$uid."\r\n";
$header .= "Content-Type: application/x-mobipocket-ebook; name=\"".$filename."\"\r\n";
$header .= "Content-Transfer-Encoding: base64\r\n";
$header .= "Content-Disposition: attachment; filename=\"".$filename."\"\r\n\r\n";
$header .= $content."\r\n\r\n";
$header .= "--".$uid."--";
//send mail
return mail( $this->_kindle_email, '[newsToKindle] ' . $filename, "", $header );
}
}
?>

View File

@ -1,230 +0,0 @@
<?php
/**
* Storage for news2Kindle
* @author jwest <jwest@jwest.pl>
*/
class Storage {
/**
* Newspapper name
* @var string
*/
private $_name;
/**
* Default dir
* @var string
*/
private $_default_dir = 'data/';
/**
* Info about newspapper
* @var array
*/
private $_info;
/**
* Prepare storage
* @param string $newspapper
* @param string $default_dir
*/
public function __construct($newspapper, $default_dir = NULL)
{
$this->_name = $newspapper;
if ( $default_dir !== NULL )
{
$this->_default_dir = $default_dir;
}
$this->_info = $this->_get_info();
}
/**
* save info before exit from script
*/
public function __destruct()
{
$this->_save_info($this->_info);
}
/**
* Get path
* @param bool $newspapper path to newspapper
* @return string path to repository
*/
public function get_path($newspapper = true)
{
$path = $this->_default_dir;
if ( $newspapper )
{
$path .= $this->_name . '/';
}
return $path;
}
/**
* Get info about newspapper
* @return array newspapper info
*/
private function _get_info()
{
$content = unserialize( $this->_get_file('info.dat') );
if( $content === FALSE )
{
$content = (object) array
(
'images_count' => 0,
'articles_count' => 0,
'images' => array(),
);
$this->_save_info($content);
}
return $content;
}
/**
* Get info key
* @param $key
* @return mixed
*/
public function info($key)
{
return $this->_info->$key;
}
/**
* Save info
* @param array $info info from class
*/
private function _save_info($info)
{
$this->_save_file( 'info.dat', serialize($info) );
}
/**
* Save image in storage
* @param resource $image
* @param string $name image name, if null create auto name
* @return string image name
*/
public function save_image($image, $name = NULL)
{
if ( $name === NULL )
{
$image_name = (string) $this->_info->images_count;
for ($i=strlen($image_name); $i<6; $i++)
{
$image_name = '0'.$image_name;
}
$this->_info->images_count++;
$this->_info->images[$this->_info->images_count] = $image_name;
$name = $image_name;
}
$this->_save_file($name, $image);
return $this->_default_dir . $this->_name . '/' . $name;
}
/**
* Get image
* @param string $name image name
* @return resource
*/
public function get_image($name)
{
return $this->_get_file($name);
}
/**
* Put article contents
* @param string $id unique id for article
* @param string $title
* @param string $content article content
* @param string $url url for article
* @param object $website
*/
public function add_content($id, $title, $content, $url, $website)
{
$articles = unserialize( $this->_get_file('articles.dat') );
$articles[$id] = (object) array
(
'id' => $id,
'title' => $title,
'content' => $content,
'url' => $url,
'website' => $website,
);
$this->_save_file( 'articles.dat', serialize($articles) );
}
/**
* Get articles contents
* @param string $file_name
* @param string $file_content
*/
public function get_contents()
{
return unserialize( $this->_get_file( 'articles.dat' ) );
}
/**
* Get file content
* @param string $file_name
* @param string $file_content
*/
private function _save_file($file_name, $file_content)
{
if( ! file_exists( $this->_default_dir . $this->_name ) )
{
mkdir( $this->_default_dir . $this->_name, 0777, TRUE );
}
file_put_contents($this->_default_dir . $this->_name . '/' . $file_name, $file_content);
}
/**
* Get file content
* @param string $file_name
* @param string $default_file_content (if file not exists)
* @return string file content
*/
private function _get_file($file_name, $default_file_content = NULL)
{
$content = @file_get_contents($this->_default_dir . $this->_name . '/' . $file_name);
if ($content === FALSE)
{
return $default_file_content;
}
return $content;
}
/**
* Clean newspapper after convert to mobi
*/
public function clean()
{
$files_to_remove = glob($this->_default_dir . $this->_name . '/*');
foreach ( $files_to_remove as $file )
{
unlink( $file );
}
rmdir( $this->_default_dir . $this->_name );
}
}

View File

@ -1,37 +0,0 @@
<?php
/**
* Utils for news2kindle
* @author jwest <jwest@jwest.pl>
*/
class Utils
{
/**
* URL encode
* @param string $url
* @return string $ar_url
*/
public static function urlencode( $url )
{
$ar_url = explode( '/', $url );
foreach ( $ar_url as $key => $val )
{
$ar_url[ $key ] = urlencode( $val );
}
return implode('/', $ar_url );
}
/**
* Prepare ID for google rss article
* @param string $id
* @return string
*/
public static function prepare_id($id)
{
$char_in = array('/', '.', ',', ':');
$id = str_replace($char_in, '-', $id);
return $id;
}
}

View File

@ -112,12 +112,15 @@ class Routing
$this->wallabag->deleteUser($_POST['password4deletinguser']); $this->wallabag->deleteUser($_POST['password4deletinguser']);
} elseif (isset($_GET['epub'])) { } elseif (isset($_GET['epub'])) {
$epub = new WallabagEpub($this->wallabag, $_GET['method'], $_GET['value']); $epub = new WallabagEpub($this->wallabag, $_GET['method'], $_GET['value']);
$epub->prepareData();
$epub->produceEpub(); $epub->produceEpub();
} elseif (isset($_GET['mobi'])) { } elseif (isset($_GET['mobi'])) {
$mobi = new WallabagMobi($this->wallabag, $_GET['method'], $_GET['value']); $mobi = new WallabagMobi($this->wallabag, $_GET['method'], $_GET['value']);
$mobi->prepareData();
$mobi->produceMobi(); $mobi->produceMobi();
} elseif (isset($_GET['pdf'])) { } elseif (isset($_GET['pdf'])) {
$pdf = new WallabagPDF($this->wallabag, $_GET['method'], $_GET['value']); $pdf = new WallabagPDF($this->wallabag, $_GET['method'], $_GET['value']);
$pdf->prepareData();
$pdf->producePDF(); $pdf->producePDF();
} elseif (isset($_GET['import'])) { } elseif (isset($_GET['import'])) {
$import = $this->wallabag->import(); $import = $this->wallabag->import();

View File

@ -30,34 +30,35 @@ class WallabagEBooks
case 'id': case 'id':
$entryID = filter_var($this->value, FILTER_SANITIZE_NUMBER_INT); $entryID = filter_var($this->value, FILTER_SANITIZE_NUMBER_INT);
$entry = $this->wallabag->store->retrieveOneById($entryID, $this->wallabag->user->getId()); $entry = $this->wallabag->store->retrieveOneById($entryID, $this->wallabag->user->getId());
$entries = array($entry); $this->entries = array($entry);
$bookTitle = $entry['title']; $this->bookTitle = $entry['title'];
$bookFileName = substr($bookTitle, 0, 200); $this->bookFileName = substr($this->bookTitle, 0, 200);
break; break;
case 'all': case 'all':
$entries = $this->wallabag->store->retrieveAll($this->wallabag->user->getId()); $this->entries = $this->wallabag->store->retrieveAll($this->wallabag->user->getId());
$bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system $this->bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system
$bookFileName = _('Allarticles') . date(_('dmY')); $this->bookFileName = _('Allarticles') . date(_('dmY'));
break; break;
case 'tag': case 'tag':
$tag = filter_var($this->value, FILTER_SANITIZE_STRING); $tag = filter_var($this->value, FILTER_SANITIZE_STRING);
$tags_id = $this->wallabag->store->retrieveAllTags($this->wallabag->user->getId(), $tag); $tags_id = $this->wallabag->store->retrieveAllTags($this->wallabag->user->getId(), $tag);
$tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround. $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround.
$entries = $this->wallabag->store->retrieveEntriesByTag($tag_id, $this->wallabag->user->getId()); $this->entries = $this->wallabag->store->retrieveEntriesByTag($tag_id, $this->wallabag->user->getId());
$bookTitle = sprintf(_('Articles tagged %s'), $tag); $this->bookTitle = sprintf(_('Articles tagged %s'), $tag);
$bookFileName = substr(sprintf(_('Tag %s'), $tag), 0, 200); $this->bookFileName = substr(sprintf(_('Tag %s'), $tag), 0, 200);
break; break;
case 'category': case 'category':
$category = filter_var($this->value, FILTER_SANITIZE_STRING); $category = filter_var($this->value, FILTER_SANITIZE_STRING);
$entries = $this->wallabag->store->getEntriesByView($category, $this->wallabag->user->getId()); $this->entries = $this->wallabag->store->getEntriesByView($category, $this->wallabag->user->getId());
$bookTitle = sprintf(_('All articles in category %s'), $category); $this->bookTitle = sprintf(_('All articles in category %s'), $category);
$bookFileName = substr(sprintf(_('Category %s'), $category), 0, 200); $this->bookFileName = substr(sprintf(_('Category %s'), $category), 0, 200);
break; break;
case 'search': case 'search':
$search = filter_var($this->value, FILTER_SANITIZE_STRING); $search = filter_var($this->value, FILTER_SANITIZE_STRING);
$entries = $this->store->search($search, $this->wallabag->user->getId()); Tools::logm($search);
$bookTitle = sprintf(_('All articles for search %s'), $search); $this->entries = $this->wallabag->store->search($search, $this->wallabag->user->getId());
$bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200); $this->bookTitle = sprintf(_('All articles for search %s'), $search);
$this->bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200);
break; break;
case 'default': case 'default':
die(_('Uh, there is a problem while generating epub.')); die(_('Uh, there is a problem while generating epub.'));
@ -94,7 +95,7 @@ class WallabagEpub extends WallabagEBooks
$log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL()); $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL());
$log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL()); $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL());
$book->setTitle($bookTitle); $book->setTitle($this->bookTitle);
$book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID.
//$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. //$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc.
$book->setDescription(_("Some articles saved on my wallabag")); $book->setDescription(_("Some articles saved on my wallabag"));
@ -111,7 +112,7 @@ class WallabagEpub extends WallabagEBooks
$log->logLine("Add Cover"); $log->logLine("Add Cover");
$fullTitle = "<h1> " . $bookTitle . "</h1>\n"; $fullTitle = "<h1> " . $this->bookTitle . "</h1>\n";
$book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle); $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle);
@ -122,7 +123,7 @@ class WallabagEpub extends WallabagEBooks
$book->buildTOC(); $book->buildTOC();
foreach ($entries as $entry) { //set tags as subjects foreach ($this->entries as $entry) { //set tags as subjects
$tags = $this->wallabag->store->retrieveTagsByEntry($entry['id']); $tags = $this->wallabag->store->retrieveTagsByEntry($entry['id']);
foreach ($tags as $tag) { foreach ($tags as $tag) {
$book->setSubject($tag['value']); $book->setSubject($tag['value']);
@ -139,97 +140,82 @@ class WallabagEpub extends WallabagEBooks
$book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation
} }
$book->finalize(); $book->finalize();
$zipData = $book->sendBook($bookFileName); $zipData = $book->sendBook($this->bookFileName);
} }
} }
class WallabagMobi extends WallabagEBooks class WallabagMobi extends WallabagEBooks
{ {
/** /**
* Adapted from News2Kindle * MOBI Class
* @author Jakub Westfalewski <jwest@jwest.pl> * @author Sander Kromwijk
*
*/ */
public function produceMobi() public function produceMobi($send = FALSE)
{ {
$storage = new Storage('static');
$this->prepareData();
foreach ($entries as $i => $item) {
$content = $item['content'];
$images = new Images($storage, $content);
$content = $images->convert();
$storage->add_content
(
md5($item['title']),
mb_convert_encoding($item['title'], 'HTML-ENTITIES', 'utf-8'),
$content,
$item['url']],
""
);
}
$articles = $storage->get_contents();
$toc = array();
$articles_count = count($articles);
foreach($articles as $article){
if(array_key_exists($article->website->title, $toc)){
$toc[$article->website->title]->articles[] = $article;
}else{
$toc[$article->website->title] = (object)array(
'articles' => array($article),
'title' => $article->website->title,
'streamId' => $article->website->streamId,
'url' => $article->website->htmlUrl,
);
}
}
# Good try
$mobi = new MOBI(); $mobi = new MOBI();
$mobi->setData($content);
$mobi->setOptions(array(
'title' => 'Articles from '.date('Y-m-d'),
'author' => 'wallabag',
'subject' => 'Articles from '.date('Y-m-d'),
));
$images = array(); $content = new MOBIFile();
//prepare images for mobi format $content->set("title", $this->bookTitle);
foreach ( $storage->info('images') as $n => $image ) $content->set("author", "wallabag");
{
$images[$n] = new FileRecord(new Record(file_get_contents($storage->get_path() . $image))); # introduction
//$content->appendChapterTitle("Cover");
$content->appendParagraph('<div style="text-align:center;"><p>' . _('Produced by wallabag with PHPMobi') . '</p><p>'. _('Please open <a href="https://github.com/wallabag/wallabag/issues" >an issue</a> if you have trouble with the display of this E-Book on your device.') . '</p></div>');
$content->appendImage(imagecreatefrompng("themes/baggy/img/apple-touch-icon-152.png"));
$content->appendPageBreak();
foreach ($this->entries as $item) {
$content->appendChapterTitle($item['title']);
$content->appendParagraph($item['content']);
$content->appendPageBreak();
} }
$mobi->setContentProvider($content);
$mobi->setImages($images); $mobi->download($this->bookFileName.".mobi");
$mobi->save( $storage->get_path(FALSE) . 'articles-' . date('Y-m-d') . '.mobi'); }
$storage->clean();
if ($send) {
$files = glob($storage->get_path(FALSE).'*.mobi');
$mail = new Send(KINDLEMAIL,MAIL);
foreach ( $files as $file_mobi )
{
$mail->send( $file_mobi );
}
// clean cache
foreach ( $files as $file_mobi )
{
unlink( $file_mobi );
}
}
}
} }
class WallabagPDF extends WallabagEbooks class WallabagPDF extends WallabagEbooks
{ {
public function producePDF() public function producePDF()
{ {
//$this->prepareData();
$mpdf = new mPDF('c'); $mpdf = new mPDF('c');
$mpdf->WriteHTML($html); # intro
$mpdf->Output();
exit; $html = '<h1>' . $this->bookTitle . '</h1><img src="themes/baggy/img/apple-touch-icon-152.png" />';
foreach ($this->entries as $item) {
$html .= '<h1>' . $item['title'] . '</h1>';
$html .= '<indexentry content="'. $item['title'] .'" />';
$html .= $item['content'];
}
//$mpdf->h2toc = array('H1'=>0);
# headers
$mpdf->SetHeader('{DATE j-m-Y}|{PAGENO}/{nb}|Produced with wallabag');
$mpdf->SetFooter('{PAGENO}');
$mpdf->WriteHTML($html);
# remove characters that make mpdf bug
$char_in = array('/', '.', ',', ':', '|');
$pdfExportName = preg_replace('/\s+/', '-', str_replace($char_in, '-', $this->bookFileName . '.pdf'));
$mpdf->Output('cache/' . $pdfExportName);
header('Content-Disposition: attachment; filename="' . $pdfExportName . '"');
header('Content-Transfer-Encoding: base64');
header('Content-Type: application/pdf');
echo file_get_contents('cache/' . $pdfExportName);
//exit;
} }
} }

View File

@ -42,11 +42,7 @@ require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php';
require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php'; require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php';
# mobi library # mobi library
require_once INCLUDES . '/3rdparty/libraries/send2kindle/send.php'; require_once INCLUDES . '/3rdparty/libraries/MOBIClass/MOBI.php';
require_once INCLUDES . '/3rdparty/libraries/send2kindle/images.php';
require_once INCLUDES . '/3rdparty/libraries/send2kindle/storage.php';
require_once INCLUDES . '/3rdparty/libraries/send2kindle/MOBIClass/MOBI.php';
require_once INCLUDES . '/3rdparty/libraries/send2kindle/utils.php';
# pdf library # pdf library
require_once INCLUDES . '/3rdparty/libraries/mpdf/mpdf.php'; require_once INCLUDES . '/3rdparty/libraries/mpdf/mpdf.php';

View File

@ -20,7 +20,7 @@
<input type="submit" value="{% trans "bag it!" %}" /> <input type="submit" value="{% trans "bag it!" %}" />
</form> </form>
</li> </li>
<li>{% trans "Bookmarklet: drag & drop this link to your bookmarks bar" %} <a id="bookmarklet" ondragend="this.click();" title="i am a bookmarklet, use me !" href="javascript:if(top['bookmarklet-url@wallabag.org']){top['bookmarklet-url@wallabag.org'];}else{(function(){var%20url%20=%20location.href%20||%20url;window.open('{{ poche_url }}?action=add&url='%20+%20btoa(url),'_self');})();void(0);}">{% trans "bag it!" %}</a></li> <li>{% trans "Bookmarklet: drag &amp; drop this link to your bookmarks bar" %} <a id="bookmarklet" ondragend="this.click();" title="i am a bookmarklet, use me !" href="javascript:if(top['bookmarklet-url@wallabag.org']){top['bookmarklet-url@wallabag.org'];}else{(function(){var%20url%20=%20location.href%20||%20url;window.open('{{ poche_url }}?action=add&url='%20+%20btoa(url),'_self');})();void(0);}">{% trans "bag it!" %}</a></li>
</ul> </ul>
<h2>{% trans "Upgrading wallabag" %}</h2> <h2>{% trans "Upgrading wallabag" %}</h2>
@ -126,7 +126,13 @@
<p><a href="?export" target="_blank">{% trans "Click here" %}</a> {% trans "to export your wallabag data." %}</p> <p><a href="?export" target="_blank">{% trans "Click here" %}</a> {% trans "to export your wallabag data." %}</p>
<h2>{% trans "Fancy an E-Book ?" %}</h2> <h2>{% trans "Fancy an E-Book ?" %}</h2>
<p>{% trans "Click on <a href=\"./?epub&amp;method=all\" title=\"Generate ePub\">this link</a> to get all your articles in one ebook (ePub 3 format)." %} <p>{% trans "Click to get all your articles in one ebook :" %}
<ul>
<li><a href="./?epub&amp;method=all" title="Generate ePub file">ePub 3</a></li>
<li><a href="./?mobi&amp;method=all" title="Generate Mobi file">Mobi</a></li>
<li><a href="./?pdf&amp;method=all" title="Generate PDF file">PDF</a></li>
</ul>
<br>{% trans "This can <b>take a while</b> and can <b>even fail</b> if you have too many articles, depending on your server configuration." %}</p> <br>{% trans "This can <b>take a while</b> and can <b>even fail</b> if you have too many articles, depending on your server configuration." %}</p>
<h2>{% trans "Cache" %}</h2> <h2>{% trans "Cache" %}</h2>

View File

@ -43,7 +43,7 @@
{% if entry.content| getReadingTime > 0 %} {% if entry.content| getReadingTime > 0 %}
<div class="estimatedTime"><a target="_blank" title="{% trans "estimated reading time:" %} {{ entry.content| getReadingTime }} min" class="tool reading-time"><span>{% trans "estimated reading time :" %} {{ entry.content| getReadingTime }} min</span></div> <div class="estimatedTime"><a target="_blank" title="{% trans "estimated reading time:" %} {{ entry.content| getReadingTime }} min" class="tool reading-time"><span>{% trans "estimated reading time :" %} {{ entry.content| getReadingTime }} min</span></div>
{% else %} {% else %}
<div class="estimatedTime"><a target="_blank" title="{% trans "estimated reading time:" %} {{ entry.content| getReadingTime }} min" class="tool reading-time"><span>{% trans "estimated reading time :" %} <small class="inferieur"><</small> 1 min</span></div> <div class="estimatedTime"><a target="_blank" title="{% trans "estimated reading time:" %} {{ entry.content| getReadingTime }} min" class="tool reading-time"><span>{% trans "estimated reading time :" %} <small class="inferieur"></small> 1 min</span></div>
{% endif %} {% endif %}
<ul class="tools links"> <ul class="tools links">
<li><a title="{% trans "Toggle mark as read" %}" class="tool icon-check icon {% if entry.is_read == 0 %}archive-off{% else %}archive{% endif %}" href="./?action=toggle_archive&amp;id={{ entry.id|e }}"><span>{% trans "Toggle mark as read" %}</span></a></li> <li><a title="{% trans "Toggle mark as read" %}" class="tool icon-check icon {% if entry.is_read == 0 %}archive-off{% else %}archive{% endif %}" href="./?action=toggle_archive&amp;id={{ entry.id|e }}"><span>{% trans "Toggle mark as read" %}</span></a></li>
@ -59,11 +59,21 @@
{{ block('pager') }} {{ block('pager') }}
{% if view == 'home' %}{% if nb_results > 1 %}<a title="{% trans "Mark all the entries as read" %}" href="./?action=archive_all">{{ "Mark all the entries as read" }}</a>{% endif %}{% endif %} {% if view == 'home' %}{% if nb_results > 1 %}<a title="{% trans "Mark all the entries as read" %}" href="./?action=archive_all">{{ "Mark all the entries as read" }}</a>{% endif %}{% endif %}
{% if search_term is defined %}<a title="{% trans %} Apply the tag {{ search_term }} to this search {% endtrans %}" href="./?action=add_tag&search={{ search_term }}">{% trans %} Apply the tag {{ search_term }} to this search {% endtrans %}</a>{% endif %} {% if search_term is defined %}<a title="{% trans %} Apply the tag {{ search_term }} to this search {% endtrans %}" href="./?action=add_tag&amp;search={{ search_term }}">{% trans %} Apply the tag {{ search_term }} to this search {% endtrans %}</a>{% endif %}
{% if tag %}<a title="{% trans "Download the articles from this tag in an epub" %}" href="./?epub&amp;method=tag&amp;value={{ tag.value }}">{% trans "Download the articles from this tag in an epub" %}</a> {% if tag %}
{% elseif search_term is defined %}<a title="{% trans "Download the articles from this search in an epub" %}" href="./?epub&amp;method=search&amp;value={{ search_term }}">{% trans "Download the articles from this search in an epub" %}</a> <a title="{% trans "Download the articles from this tag in an epub file" %}" href="./?epub&amp;method=tag&amp;value={{ tag.value }}">{% trans "Download as ePub3" %}</a>
{% else %}<a title="{% trans "Download the articles from this category in an epub" %}" href="./?epub&amp;method=category&amp;value={{ view }}">{% trans "Download the articles from this category in an epub" %}</a>{% endif %} <a title="{% trans "Download the articles from this tag in a mobi file" %}" href="./?mobi&amp;method=tag&amp;value={{ tag.value }}">{% trans "Download as Mobi" %}</a>
<a title="{% trans "Download the articles from this tag in a pdf file" %}" href="./?pdf&amp;method=tag&amp;value={{ tag.value }}">{% trans "Download as PDF" %}</a>
{% elseif search_term is defined %}
<a title="{% trans "Download the articles from this search in an epub" %}" href="./?epub&amp;method=search&amp;value={{ search_term }}">{% trans "Download as ePub3" %}</a>
<a title="{% trans "Download the articles from this search in a mobi file" %}" href="./?mobi&amp;method=search&amp;value={{ search_term }}">{% trans "Download as Mobi" %}</a>
<a title="{% trans "Download the articles from this search in a pdf file" %}" href="./?pdf&amp;method=search&amp;value={{ search_term }}">{% trans "Download as PDF" %}</a>
{% else %}
<a title="{% trans "Download the articles from this category in an epub" %}" href="./?epub&amp;method=category&amp;value={{ view }}">{% trans "Download as ePub3" %}</a>
<a title="{% trans "Download the articles from this category in a mobi file" %}" href="./?mobi&amp;method=category&amp;value={{ view }}">{% trans "Download as Mobi" %}</a>
<a title="{% trans "Download the articles from this category in a pdf file" %}" href="./?pdf&amp;method=category&amp;value={{ view }}">{% trans "Download as PDF" %}</a>
{% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -17,6 +17,8 @@
{% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}<li><a href="http://flattr.com/submit/auto?url={{ entry.url }}" class="tool flattr icon icon-flattr" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span></a></li>{% elseif flattr.status == constant('FLATTRED') %}<li><a href="{{ flattr.flattrItemURL }}" class="tool flattr icon icon-flattr" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span> ({{ flattr.numflattrs }})</a></li>{% endif %}{% endif %} {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}<li><a href="http://flattr.com/submit/auto?url={{ entry.url }}" class="tool flattr icon icon-flattr" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span></a></li>{% elseif flattr.status == constant('FLATTRED') %}<li><a href="{{ flattr.flattrItemURL }}" class="tool flattr icon icon-flattr" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span> ({{ flattr.numflattrs }})</a></li>{% endif %}{% endif %}
{% if constant('SHOW_PRINTLINK') == 1 %}<li><a title="{% trans "Print" %}" class="tool icon icon-print" href="javascript: window.print();"><span>{% trans "Print" %}</span></a></li>{% endif %} {% if constant('SHOW_PRINTLINK') == 1 %}<li><a title="{% trans "Print" %}" class="tool icon icon-print" href="javascript: window.print();"><span>{% trans "Print" %}</span></a></li>{% endif %}
<li><a href="./?epub&amp;method=id&amp;value={{ entry.id|e }}" title="Generate epub file">EPUB</a></li> <li><a href="./?epub&amp;method=id&amp;value={{ entry.id|e }}" title="Generate epub file">EPUB</a></li>
<li><a href="./?mobi&amp;method=id&amp;value={{ entry.id|e }}" title="Generate mobi file">MOBI</a></li>
<li><a href="./?pdf&amp;method=id&amp;value={{ entry.id|e }}" title="Generate epub file">PDF</a></li>
<li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{% trans "Does this article appear wrong?" %}" class="tool bad-display icon icon-delete"><span>{% trans "Does this article appear wrong?" %}</span></a></li> <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{% trans "Does this article appear wrong?" %}" class="tool bad-display icon icon-delete"><span>{% trans "Does this article appear wrong?" %}</span></a></li>
</ul> </ul>
</div> </div>