diff options
147 files changed, 22163 insertions, 6897 deletions
@@ -3,4 +3,5 @@ cache/* | |||
3 | vendor | 3 | vendor |
4 | composer.phar | 4 | composer.phar |
5 | db/poche.sqlite | 5 | db/poche.sqlite |
6 | inc/poche/config.inc.php \ No newline at end of file | 6 | inc/poche/config.inc.php |
7 | inc/3rdparty/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/ \ No newline at end of file | ||
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85c09e52..9ccb0b14 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md | |||
@@ -1,11 +1,28 @@ | |||
1 | # How contributing | 1 | # How to contribute |
2 | 2 | ||
3 | ## You found a bug | 3 | ## You found a bug |
4 | Please [open a new issue](https://github.com/wallabag/wallabag/issues/new). | 4 | Please [open a new issue](https://github.com/wallabag/wallabag/issues/new). |
5 | 5 | ||
6 | To fix the bug quickly, we need some infos: | 6 | To fix the bug quickly, we need some infos: |
7 | * your wallabag version (in ./index.php) | 7 | * your wallabag version (on top of the ./index.php file, and also on config page) |
8 | * your webserver installation : | ||
9 | * type of hosting (shared or dedicaced) | ||
10 | * in case of a dedicaced server, the server and OS used | ||
11 | * the php version used, eventually `phpinfo()` | ||
12 | * which storage system you choose at install (SQLite, MySQL/MariaDB or PostgreSQL) | ||
13 | * any problem on the `wallabag_compatibility_test.php` page | ||
14 | * any particular details which could be related | ||
15 | |||
16 | |||
17 | If relevant : | ||
8 | * the link you want to save and which causes problem | 18 | * the link you want to save and which causes problem |
19 | * the file you want to import into wallabag, or just an extract | ||
20 | |||
21 | If you have the skills : | ||
22 | * enable DEBUG mode and look the output at cache/log.txt | ||
23 | * look for errors into php and server logs | ||
24 | |||
25 | Note : If you have large portions of text, use [Github's Gist service](https://gist.github.com/) or other pastebin-like. | ||
9 | 26 | ||
10 | ## You want to fix a bug or to add a feature | 27 | ## You want to fix a bug or to add a feature |
11 | Please fork wallabag and work with **the dev branch** only. **Do not work on master branch**. \ No newline at end of file | 28 | Please fork wallabag and work with **the dev branch** only. **Do not work on master branch**. |
@@ -4,7 +4,7 @@ wallabag is a self hostable application allowing you to not miss any content any | |||
4 | More informations on our website: [wallabag.org](http://wallabag.org) | 4 | More informations on our website: [wallabag.org](http://wallabag.org) |
5 | 5 | ||
6 | ## License | 6 | ## License |
7 | Copyright © 2010-2013 Nicolas Lœuillet <nicolas@loeuillet.org> | 7 | Copyright © 2010-2014 Nicolas Lœuillet <nicolas@loeuillet.org> |
8 | This work is free. You can redistribute it and/or modify it under the | 8 | This work is free. You can redistribute it and/or modify it under the |
9 | terms of the Do What The Fuck You Want To Public License, Version 2, | 9 | terms of the Do What The Fuck You Want To Public License, Version 2, |
10 | as published by Sam Hocevar. See the COPYING file for more details. | 10 | as published by Sam Hocevar. See the COPYING file for more details. |
diff --git a/TRANSLATION.md b/TRANSLATION.md new file mode 100755 index 00000000..2e38d5cc --- /dev/null +++ b/TRANSLATION.md | |||
@@ -0,0 +1,67 @@ | |||
1 | # How to manage translations of wallabag | ||
2 | |||
3 | This guide will describe procedure of translation management of wallabag web application. | ||
4 | |||
5 | All translation are made using [gettext](http://en.wikipedia.org/wiki/Gettext) system and tools. | ||
6 | |||
7 | You will need [Poedit](http://www.poedit.net/download.php) editor to update, edit and create your translation files comfortably. In general, you can handle translations also without it: all can be done using gettext tools and your favorite plain text editor only. This guide, however, describes editing with Poedit. If you want to use gettext only, pls refer to xgettext manual page to update po files from sources (see also how it is used by Poedit below) and use msgunfmt tool to compile .mo files manually. | ||
8 | |||
9 | You need to know, that translation phrases are stored in **".po"** files (for example: `locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.po`), which are then complied in **".mo"** files using **msgfmt** gettext tool or by Poedit, which will run msgfmt for you in background. | ||
10 | |||
11 | **It's assumed, that you have wallabag installed locally on your computer or on the server you have access to.** | ||
12 | |||
13 | ## To change existing translation you will need to do: | ||
14 | |||
15 | ### 1. Clear cache | ||
16 | You can do this using **http://your-wallabag-host.com/?empty-cache** link (replace http://your-wallabag-host.com/ with real url of your wallabag application) | ||
17 | |||
18 | OR | ||
19 | |||
20 | from command line: | ||
21 | go to root of your installation of wallabag project and run next command: | ||
22 | |||
23 | `rm -rf ./cache/*` | ||
24 | |||
25 | (this may require root privileges if you run, for example Apatche web server with mod_php) | ||
26 | |||
27 | ### 2. Generate php files from all twig templates | ||
28 | Do this using next command: | ||
29 | |||
30 | `php ./locale/tools/fillCache.php` | ||
31 | |||
32 | OR | ||
33 | |||
34 | from your browser: **http://your-wallabag-host.com/locale/tools/fillCache.php** (this may require removal of .htacces file in locale/ directory). | ||
35 | |||
36 | ### 3. Configure your Poedit | ||
37 | Open Poedit editor, open Edit->Preferences. Go to "Parsers" tab, click on PHP and press "Edit" button. Make sure your "Parser command:" looks like | ||
38 | |||
39 | `xgettext --no-location --force-po -o %o %C %K %F` | ||
40 | |||
41 | Usualy it is required to add "--no-location" to default value. | ||
42 | |||
43 | ### 4. Open .po file you want to edit in Poedit and change it's settings | ||
44 | Open, for example `locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.po` file in your Poedit. | ||
45 | |||
46 | Go to "Catalog"->"Settings..." menu. Go to "Path" tab and add path to wallabag installaion in your local file system. This step can't be ommited as you will not be able to update phrases otherwise. | ||
47 | |||
48 | You can also check "project into" tab to be sure, that "Language" is set correctly (this will allow you to spell check your translation). | ||
49 | |||
50 | ### 5. Update opened .po file from sources | ||
51 | Once you have set your path correctly, you are able to update phrases from sources. Press "Update catalog - synchronize it with sources" button or go to "Catalog"->"Update from sources" menu. | ||
52 | |||
53 | As a result you will see confirmation popup with two tabs: "New strings" and "Obsolete strings". Pls review and accept changes (or press "Undo" if you see too many obsolete strings, as Poedit will remove them all - in this case please make sure all previous steps are performed w/o errors). | ||
54 | |||
55 | ### 6. Translate and save your .po file | ||
56 | If you have any dificulties on this step, please consult with Poedit manual. | ||
57 | Every time you save your .po file, Poedit will also comple appropriate .mo file by default (of course, if not disabled in preferences). | ||
58 | |||
59 | So, you are almost done. | ||
60 | |||
61 | ### 7. Clear cache again | ||
62 | This step may be required if your web server runs php scripts in name of, say, www user (i.e. Apache with mod_php, not cgi). | ||
63 | |||
64 | |||
65 | ##To create new translation | ||
66 | Please simple create appropriate directories in locale folder and perform all steps, described above. Instead of opening an existing file just create new one. | ||
67 | |||
diff --git a/check_setup.php b/check_setup.php index 96dd0f7d..2b84a744 100644 --- a/check_setup.php +++ b/check_setup.php | |||
@@ -13,16 +13,6 @@ if (version_compare(PHP_VERSION, '5.4.0', '<')) { | |||
13 | } | 13 | } |
14 | } | 14 | } |
15 | 15 | ||
16 | // Check PDO Sqlite | ||
17 | if (! extension_loaded('pdo_sqlite')) { | ||
18 | die('PHP extension required: pdo_sqlite'); | ||
19 | } | ||
20 | |||
21 | // Check ZIP | ||
22 | if (! extension_loaded('zip')) { | ||
23 | die('PHP extension required: zip'); | ||
24 | } | ||
25 | |||
26 | // Check if /cache is writeable | 16 | // Check if /cache is writeable |
27 | if (! is_writable('cache')) { | 17 | if (! is_writable('cache')) { |
28 | die('The directory "cache" must be writeable by your web server user'); | 18 | die('The directory "cache" must be writeable by your web server user'); |
diff --git a/inc/3rdparty/Session.class.php b/inc/3rdparty/Session.class.php index b30a31f3..59dfbe67 100644 --- a/inc/3rdparty/Session.class.php +++ b/inc/3rdparty/Session.class.php | |||
@@ -31,9 +31,9 @@ class Session | |||
31 | public static $sessionName = ''; | 31 | public static $sessionName = ''; |
32 | // If the user does not access any page within this time, | 32 | // If the user does not access any page within this time, |
33 | // his/her session is considered expired (3600 sec. = 1 hour) | 33 | // his/her session is considered expired (3600 sec. = 1 hour) |
34 | public static $inactivityTimeout = 86400; | 34 | public static $inactivityTimeout = 3600; |
35 | // Extra timeout for long sessions (if enabled) (82800 sec. = 23 hours) | 35 | // Extra timeout for long sessions (if enabled) (82800 sec. = 23 hours) |
36 | public static $longSessionTimeout = 31536000; | 36 | public static $longSessionTimeout = 7776000; // 7776000 = 90 days |
37 | // If you get disconnected often or if your IP address changes often. | 37 | // If you get disconnected often or if your IP address changes often. |
38 | // Let you disable session cookie hijacking protection | 38 | // Let you disable session cookie hijacking protection |
39 | public static $disableSessionProtection = false; | 39 | public static $disableSessionProtection = false; |
@@ -48,8 +48,13 @@ class Session | |||
48 | /** | 48 | /** |
49 | * Initialize session | 49 | * Initialize session |
50 | */ | 50 | */ |
51 | public static function init() | 51 | public static function init($longlastingsession = false) |
52 | { | 52 | { |
53 | //check if session name is correct | ||
54 | if ( (session_id() && !empty(self::$sessionName) && session_name()!=self::$sessionName) || $longlastingsession ) { | ||
55 | session_destroy(); | ||
56 | } | ||
57 | |||
53 | // Force cookie path (but do not change lifetime) | 58 | // Force cookie path (but do not change lifetime) |
54 | $cookie = session_get_cookie_params(); | 59 | $cookie = session_get_cookie_params(); |
55 | // Default cookie expiration and path. | 60 | // Default cookie expiration and path. |
@@ -61,12 +66,22 @@ class Session | |||
61 | if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") { | 66 | if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") { |
62 | $ssl = true; | 67 | $ssl = true; |
63 | } | 68 | } |
64 | session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['HTTP_HOST'], $ssl); | 69 | |
70 | if ( $longlastingsession ) { | ||
71 | session_set_cookie_params(self::$longSessionTimeout, $cookiedir, null, $ssl, true); | ||
72 | } | ||
73 | else { | ||
74 | session_set_cookie_params(0, $cookiedir, null, $ssl, true); | ||
75 | } | ||
76 | //set server side valid session timeout | ||
77 | //WARNING! this may not work in shared session environment. See http://www.php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime about min value: it can be set in any application | ||
78 | ini_set('session.gc_maxlifetime', self::$longSessionTimeout); | ||
79 | |||
65 | // Use cookies to store session. | 80 | // Use cookies to store session. |
66 | ini_set('session.use_cookies', 1); | 81 | ini_set('session.use_cookies', 1); |
67 | // Force cookies for session (phpsessionID forbidden in URL) | 82 | // Force cookies for session (phpsessionID forbidden in URL) |
68 | ini_set('session.use_only_cookies', 1); | 83 | ini_set('session.use_only_cookies', 1); |
69 | if (!session_id()) { | 84 | if ( !session_id() ) { |
70 | // Prevent php to use sessionID in URL if cookies are disabled. | 85 | // Prevent php to use sessionID in URL if cookies are disabled. |
71 | ini_set('session.use_trans_sid', false); | 86 | ini_set('session.use_trans_sid', false); |
72 | if (!empty(self::$sessionName)) { | 87 | if (!empty(self::$sessionName)) { |
@@ -115,6 +130,9 @@ class Session | |||
115 | if (self::banCanLogin()) { | 130 | if (self::banCanLogin()) { |
116 | if ($login === $loginTest && $password === $passwordTest) { | 131 | if ($login === $loginTest && $password === $passwordTest) { |
117 | self::banLoginOk(); | 132 | self::banLoginOk(); |
133 | |||
134 | self::init($longlastingsession); | ||
135 | |||
118 | // Generate unique random number to sign forms (HMAC) | 136 | // Generate unique random number to sign forms (HMAC) |
119 | $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand()); | 137 | $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand()); |
120 | $_SESSION['ip'] = self::_allIPs(); | 138 | $_SESSION['ip'] = self::_allIPs(); |
@@ -135,6 +153,7 @@ class Session | |||
135 | self::banLoginFailed(); | 153 | self::banLoginFailed(); |
136 | } | 154 | } |
137 | 155 | ||
156 | self::init(); | ||
138 | return false; | 157 | return false; |
139 | } | 158 | } |
140 | 159 | ||
@@ -143,7 +162,14 @@ class Session | |||
143 | */ | 162 | */ |
144 | public static function logout() | 163 | public static function logout() |
145 | { | 164 | { |
146 | unset($_SESSION['uid'],$_SESSION['ip'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['longlastingsession'], $_SESSION['poche_user']); | 165 | // unset($_SESSION['uid'],$_SESSION['ip'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['longlastingsession'], $_SESSION['poche_user']); |
166 | |||
167 | // Destruction du cookie (le code peut paraître complexe mais c'est pour être certain de reprendre les mêmes paramètres) | ||
168 | $args = array_merge(array(session_name(), ''), array_values(session_get_cookie_params())); | ||
169 | $args[2] = time() - 3600; | ||
170 | call_user_func_array('setcookie', $args); | ||
171 | // Suppression physique de la session | ||
172 | session_destroy(); | ||
147 | } | 173 | } |
148 | 174 | ||
149 | /** | 175 | /** |
@@ -157,7 +183,7 @@ class Session | |||
157 | || (self::$disableSessionProtection === false | 183 | || (self::$disableSessionProtection === false |
158 | && $_SESSION['ip'] !== self::_allIPs()) | 184 | && $_SESSION['ip'] !== self::_allIPs()) |
159 | || time() >= $_SESSION['expires_on']) { | 185 | || time() >= $_SESSION['expires_on']) { |
160 | self::logout(); | 186 | //self::logout(); |
161 | 187 | ||
162 | return false; | 188 | return false; |
163 | } | 189 | } |
diff --git a/inc/3rdparty/class.messages.php b/inc/3rdparty/class.messages.php index e60bd3a1..27c28f43 100644 --- a/inc/3rdparty/class.messages.php +++ b/inc/3rdparty/class.messages.php | |||
@@ -59,6 +59,7 @@ class Messages { | |||
59 | $this->msgId = md5(uniqid()); | 59 | $this->msgId = md5(uniqid()); |
60 | 60 | ||
61 | // Create the session array if it doesnt already exist | 61 | // Create the session array if it doesnt already exist |
62 | settype($_SESSION, 'array'); | ||
62 | if( !array_key_exists('flash_messages', $_SESSION) ) $_SESSION['flash_messages'] = array(); | 63 | if( !array_key_exists('flash_messages', $_SESSION) ) $_SESSION['flash_messages'] = array(); |
63 | 64 | ||
64 | } | 65 | } |
@@ -228,4 +229,4 @@ class Messages { | |||
228 | 229 | ||
229 | 230 | ||
230 | } // end class | 231 | } // end class |
231 | ?> \ No newline at end of file | 232 | ?> |
diff --git a/inc/3rdparty/config.php b/inc/3rdparty/config.php index e618117b..ec680d86 100755 --- a/inc/3rdparty/config.php +++ b/inc/3rdparty/config.php | |||
@@ -19,7 +19,7 @@ if (!isset($options)) $options = new stdClass(); | |||
19 | // Enable service | 19 | // Enable service |
20 | // ---------------------- | 20 | // ---------------------- |
21 | // Set this to false if you want to disable the service. | 21 | // Set this to false if you want to disable the service. |
22 | // If set to false, no feed is produced and users will | 22 | // If set to false, no feed is produced and users will |
23 | // be told that the service is disabled. | 23 | // be told that the service is disabled. |
24 | $options->enabled = true; | 24 | $options->enabled = true; |
25 | 25 | ||
@@ -43,10 +43,64 @@ $options->default_entries = 5; | |||
43 | // ---------------------- | 43 | // ---------------------- |
44 | // The maximum number of feed items to process when no access key is supplied. | 44 | // The maximum number of feed items to process when no access key is supplied. |
45 | // This limits the user-supplied &max=x value. For example, if the user | 45 | // This limits the user-supplied &max=x value. For example, if the user |
46 | // asks for 20 items to be processed (&max=20), if max_entries is set to | 46 | // asks for 20 items to be processed (&max=20), if max_entries is set to |
47 | // 10, only 10 will be processed. | 47 | // 10, only 10 will be processed. |
48 | $options->max_entries = 10; | 48 | $options->max_entries = 10; |
49 | 49 | ||
50 | // Full content | ||
51 | // ---------------------- | ||
52 | // By default Full-Text RSS includes the extracted content in the output. | ||
53 | // You can exclude this from the output by passing '&content=0' in the querystring. | ||
54 | // | ||
55 | // Possible values... | ||
56 | // Always include: true | ||
57 | // Never include: false | ||
58 | // Include unless user overrides (&content=0): 'user' (default) | ||
59 | // | ||
60 | // Note: currently this does not disable full content extraction. It simply omits it | ||
61 | // from the output. | ||
62 | $options->content = 'user'; | ||
63 | |||
64 | // Excerpts | ||
65 | // ---------------------- | ||
66 | // By default Full-Text RSS does not include excerpts in the output. | ||
67 | // You can enable this by passing '&summary=1' in the querystring. | ||
68 | // This will include a plain text excerpt from the extracted content. | ||
69 | // | ||
70 | // Possible values... | ||
71 | // Always include: true (recommended for new users) | ||
72 | // Never include: false | ||
73 | // Don't include unless user overrides (&summary=1): 'user' (default) | ||
74 | // | ||
75 | // Important: if both content and excerpts are requested, the excerpt will be | ||
76 | // placed in the description element and the full content inside content:encoded. | ||
77 | // If excerpts are not requested, the full content will go inside the description element. | ||
78 | // | ||
79 | // Why are we not returning both excerpts and content by default? | ||
80 | // Mainly for backward compatibility. | ||
81 | // Excerpts should appear in the feed item's description element. Previous versions | ||
82 | // of Full-Text RSS did not return excerpts, so the description element was always | ||
83 | // used for the full content (as recommended by the RSS advisory). When returning both, | ||
84 | // we need somewhere else to place the content (content:encoded). | ||
85 | // Having both enabled should not create any problems for news readers, but it may create | ||
86 | // problems for developers upgrading from one of our earlier versions who may now find | ||
87 | // their applications are returning excerpts instead of the full content they were | ||
88 | // expecting. To avoid such surprises for users who are upgrading Full-Text RSS, | ||
89 | // excerpts must be explicitly requested in the querystring by default. | ||
90 | // | ||
91 | // Why not use a different element name for excerpts? | ||
92 | // According to the RSS advisory: | ||
93 | // "Publishers who employ summaries should store the summary in description and | ||
94 | // the full content in content:encoded, ordering description first within the item. | ||
95 | // On items with no summary, the full content should be stored in description." | ||
96 | // See: http://www.rssboard.org/rss-profile#namespace-elements-content-encoded | ||
97 | // | ||
98 | // For more consistent element naming, we recommend new users set this option to true. | ||
99 | // The full content can still be excluded via the querystring, but the element names | ||
100 | // will not change: when $options->summary = true, the description element will always | ||
101 | // be reserved for the excerpt and content:encoded always for full content. | ||
102 | $options->summary = 'user'; | ||
103 | |||
50 | // Rewrite relative URLs | 104 | // Rewrite relative URLs |
51 | // ---------------------- | 105 | // ---------------------- |
52 | // With this enabled relative URLs found in the extracted content | 106 | // With this enabled relative URLs found in the extracted content |
@@ -67,7 +121,7 @@ $options->exclude_items_on_fail = 'user'; | |||
67 | // Enable multi-page support | 121 | // Enable multi-page support |
68 | // ------------------------- | 122 | // ------------------------- |
69 | // If enabled, we will try to follow next page links on multi-page articles. | 123 | // If enabled, we will try to follow next page links on multi-page articles. |
70 | // Currently this only happens for sites where next_page_link has been defined | 124 | // Currently this only happens for sites where next_page_link has been defined |
71 | // in a site config file. | 125 | // in a site config file. |
72 | $options->multipage = true; | 126 | $options->multipage = true; |
73 | 127 | ||
@@ -125,10 +179,10 @@ $options->detect_language = 1; | |||
125 | 179 | ||
126 | // Registration key | 180 | // Registration key |
127 | // --------------- | 181 | // --------------- |
128 | // The registration key is optional. It is not required to use Full-Text RSS, | 182 | // The registration key is optional. It is not required to use Full-Text RSS, |
129 | // and does not affect the normal operation of Full-Text RSS. It is currently | 183 | // and does not affect the normal operation of Full-Text RSS. It is currently |
130 | // only used on admin pages which help you update site patterns with the | 184 | // only used on admin pages which help you update site patterns with the |
131 | // latest version offered by FiveFilters.org. For these admin-related | 185 | // latest version offered by FiveFilters.org. For these admin-related |
132 | // tasks to complete, we will require a valid registration key. | 186 | // tasks to complete, we will require a valid registration key. |
133 | // If you would like one, you can purchase the latest version of Full-Text RSS | 187 | // If you would like one, you can purchase the latest version of Full-Text RSS |
134 | // at http://fivefilters.org/content-only/ | 188 | // at http://fivefilters.org/content-only/ |
@@ -144,12 +198,12 @@ $options->registration_key = ''; | |||
144 | // ---------------------- | 198 | // ---------------------- |
145 | // Certain pages/actions, e.g. updating site patterns with our online tool, will require admin credentials. | 199 | // Certain pages/actions, e.g. updating site patterns with our online tool, will require admin credentials. |
146 | // To use these pages, enter a password here and you'll be prompted for it when you try to access those pages. | 200 | // To use these pages, enter a password here and you'll be prompted for it when you try to access those pages. |
147 | // If no password or username is set, pages requiring admin privelages will be inaccessible. | 201 | // If no password or username is set, pages requiring admin privelages will be inaccessible. |
148 | // The default username is 'admin'. | 202 | // The default username is 'admin'. |
149 | // If overriding with an environment variable, separate username and password with a colon, e.g.: | 203 | // If overriding with an environment variable, separate username and password with a colon, e.g.: |
150 | // ftr_admin_credentials: admin:my-secret-password | 204 | // ftr_admin_credentials: admin:my-secret-password |
151 | // Example: $options->admin_credentials = array('username'=>'admin', 'password'=>'my-secret-password'); | 205 | // Example: $options->admin_credentials = array('username'=>'admin', 'password'=>'my-secret-password'); |
152 | $options->admin_credentials = array('username'=>'admin', 'password'=>'admin'); | 206 | $options->admin_credentials = array('username'=>'admin', 'password'=>''); |
153 | 207 | ||
154 | // URLs to allow | 208 | // URLs to allow |
155 | // ---------------------- | 209 | // ---------------------- |
@@ -178,12 +232,12 @@ $options->key_required = false; | |||
178 | // ---------------------- | 232 | // ---------------------- |
179 | // By default, when processing feeds, we assume item titles in the feed | 233 | // By default, when processing feeds, we assume item titles in the feed |
180 | // have not been truncated. So after processing web pages, the extracted titles | 234 | // have not been truncated. So after processing web pages, the extracted titles |
181 | // are not used in the generated feed. If you prefer to have extracted titles in | 235 | // are not used in the generated feed. If you prefer to have extracted titles in |
182 | // the feed you can either set this to false, in which case we will always favour | 236 | // the feed you can either set this to false, in which case we will always favour |
183 | // extracted titles. Alternatively, if set to 'user' (default) we'll use the | 237 | // extracted titles. Alternatively, if set to 'user' (default) we'll use the |
184 | // extracted title if you pass '&use_extracted_title' in the querystring. | 238 | // extracted title if you pass '&use_extracted_title' in the querystring. |
185 | // Possible values: | 239 | // Possible values: |
186 | // * Favour feed titles: true | 240 | // * Favour feed titles: true |
187 | // * Favour extracted titles: false | 241 | // * Favour extracted titles: false |
188 | // * Favour feed titles with user override: 'user' (default) | 242 | // * Favour feed titles with user override: 'user' (default) |
189 | // Note: this has no effect when the input URL is to a web page - in these cases | 243 | // Note: this has no effect when the input URL is to a web page - in these cases |
@@ -192,17 +246,17 @@ $options->favour_feed_titles = 'user'; | |||
192 | 246 | ||
193 | // Access keys (password protected access) | 247 | // Access keys (password protected access) |
194 | // ------------------------------------ | 248 | // ------------------------------------ |
195 | // NOTE: You do not need an API key from fivefilters.org to run your own | 249 | // NOTE: You do not need an API key from fivefilters.org to run your own |
196 | // copy of the code. This is here if you'd like to restrict access to | 250 | // copy of the code. This is here if you'd like to restrict access to |
197 | // _your_ copy. | 251 | // _your_ copy. |
198 | // Keys let you group users - those with a key and those without - and | 252 | // Keys let you group users - those with a key and those without - and |
199 | // restrict access to the service to those without a key. | 253 | // restrict access to the service to those without a key. |
200 | // If you want everyone to access the service in the same way, you can | 254 | // If you want everyone to access the service in the same way, you can |
201 | // leave the array below empty and ignore the access key options further down. | 255 | // leave the array below empty and ignore the access key options further down. |
202 | // The options further down let you control how the service should behave | 256 | // The options further down let you control how the service should behave |
203 | // in each mode. | 257 | // in each mode. |
204 | // Note: Explicitly including the index number (1 and 2 in the examples below) | 258 | // Note: Explicitly including the index number (1 and 2 in the examples below) |
205 | // is highly recommended (when generating feeds, we encode the key and | 259 | // is highly recommended (when generating feeds, we encode the key and |
206 | // refer to it by index number and hash). | 260 | // refer to it by index number and hash). |
207 | $options->api_keys = array(); | 261 | $options->api_keys = array(); |
208 | // Example: | 262 | // Example: |
@@ -232,13 +286,13 @@ $options->max_entries_with_key = 10; | |||
232 | // filter the resulting HTML for XSS attacks, making it redundant for | 286 | // filter the resulting HTML for XSS attacks, making it redundant for |
233 | // Full-Text RSS do the same. Similarly with frameworks/CMS which display | 287 | // Full-Text RSS do the same. Similarly with frameworks/CMS which display |
234 | // feed content - the content should be treated like any other user-submitted content. | 288 | // feed content - the content should be treated like any other user-submitted content. |
235 | // | 289 | // |
236 | // If you are writing an application yourself which is processing feeds generated by | 290 | // If you are writing an application yourself which is processing feeds generated by |
237 | // Full-Text RSS, you can either filter the HTML yourself to remove potential XSS attacks | 291 | // Full-Text RSS, you can either filter the HTML yourself to remove potential XSS attacks |
238 | // or enable this option. This might be useful if you are processing our generated | 292 | // or enable this option. This might be useful if you are processing our generated |
239 | // feeds with JavaScript on the client side - although there's client side xss | 293 | // feeds with JavaScript on the client side - although there's client side xss |
240 | // filtering available too, e.g. https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer | 294 | // filtering available too, e.g. https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer |
241 | // | 295 | // |
242 | // If enabled, we'll pass retrieved HTML content through htmLawed with | 296 | // If enabled, we'll pass retrieved HTML content through htmLawed with |
243 | // safe flag on and style attributes denied, see | 297 | // safe flag on and style attributes denied, see |
244 | // http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawed_README.htm#s3.6 | 298 | // http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawed_README.htm#s3.6 |
@@ -253,8 +307,8 @@ $options->xss_filter = 'user'; | |||
253 | // Allowed parsers | 307 | // Allowed parsers |
254 | // ---------------------- | 308 | // ---------------------- |
255 | // Full-Text RSS attempts to use PHP's libxml extension to process HTML. | 309 | // Full-Text RSS attempts to use PHP's libxml extension to process HTML. |
256 | // While fast, on some sites it may not always produce good results. | 310 | // While fast, on some sites it may not always produce good results. |
257 | // For these sites, you can specify an alternative HTML parser: | 311 | // For these sites, you can specify an alternative HTML parser: |
258 | // parser: html5lib | 312 | // parser: html5lib |
259 | // The html5lib parser is bundled with Full-Text RSS. | 313 | // The html5lib parser is bundled with Full-Text RSS. |
260 | // see http://code.google.com/p/html5lib/ | 314 | // see http://code.google.com/p/html5lib/ |
@@ -273,7 +327,7 @@ $options->cors = false; | |||
273 | 327 | ||
274 | // Use APC user cache? | 328 | // Use APC user cache? |
275 | // ---------------------- | 329 | // ---------------------- |
276 | // If enabled we will store site config files (when requested | 330 | // If enabled we will store site config files (when requested |
277 | // for the first time) in APC's user cache. Keys prefixed with 'sc.' | 331 | // for the first time) in APC's user cache. Keys prefixed with 'sc.' |
278 | // This improves performance by reducing disk access. | 332 | // This improves performance by reducing disk access. |
279 | // Note: this has no effect if APC is unavailable on your server. | 333 | // Note: this has no effect if APC is unavailable on your server. |
@@ -346,7 +400,7 @@ $options->rewrite_url = array( | |||
346 | // Valid actions: | 400 | // Valid actions: |
347 | // * 'exclude' - exclude this item from the result | 401 | // * 'exclude' - exclude this item from the result |
348 | // * 'link' - create HTML link to the item | 402 | // * 'link' - create HTML link to the item |
349 | $options->content_type_exc = array( | 403 | $options->content_type_exc = array( |
350 | 'application/pdf' => array('action'=>'link', 'name'=>'PDF'), | 404 | 'application/pdf' => array('action'=>'link', 'name'=>'PDF'), |
351 | 'image' => array('action'=>'link', 'name'=>'Image'), | 405 | 'image' => array('action'=>'link', 'name'=>'Image'), |
352 | 'audio' => array('action'=>'link', 'name'=>'Audio'), | 406 | 'audio' => array('action'=>'link', 'name'=>'Audio'), |
@@ -375,13 +429,13 @@ $options->cache_cleanup = 100; | |||
375 | /// DO NOT CHANGE ANYTHING BELOW THIS /////////// | 429 | /// DO NOT CHANGE ANYTHING BELOW THIS /////////// |
376 | ///////////////////////////////////////////////// | 430 | ///////////////////////////////////////////////// |
377 | 431 | ||
378 | if (!defined('_FF_FTR_VERSION')) define('_FF_FTR_VERSION', '3.1'); | 432 | if (!defined('_FF_FTR_VERSION')) define('_FF_FTR_VERSION', '3.2'); |
379 | 433 | ||
380 | if (basename(__FILE__) == 'config.php') { | 434 | if (basename(__FILE__) == 'config.php') { |
381 | if (file_exists(dirname(__FILE__).'/custom_config.php')) { | 435 | if (file_exists(dirname(__FILE__).'/custom_config.php')) { |
382 | require_once dirname(__FILE__).'/custom_config.php'; | 436 | require_once dirname(__FILE__).'/custom_config.php'; |
383 | } | 437 | } |
384 | 438 | ||
385 | // check for environment variables - often used on cloud platforms | 439 | // check for environment variables - often used on cloud platforms |
386 | // environment variables should be prefixed with 'ftr_', e.g. | 440 | // environment variables should be prefixed with 'ftr_', e.g. |
387 | // ftr_max_entries: 1 | 441 | // ftr_max_entries: 1 |
diff --git a/inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php b/inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php new file mode 100644 index 00000000..376b6133 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php | |||
@@ -0,0 +1,266 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * This should be a complete list of all HTML entities, mapped to their UTF-8 character codes. | ||
4 | * | ||
5 | * @author A. Grandt | ||
6 | * @copyright A. Grandt 2009-2013 | ||
7 | * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
8 | * @version 3.00 | ||
9 | */ | ||
10 | global $htmlEntities; | ||
11 | $htmlEntities = array(); | ||
12 | |||
13 | $htmlEntities["""] ="\x22"; // " ((double) quotation mark) | ||
14 | $htmlEntities["&"] ="\x26"; // & (ampersand) | ||
15 | $htmlEntities["'"] ="\x27"; // ' (apostrophe = apostrophe-quote) | ||
16 | $htmlEntities["<"] ="\x3C"; // < (less-than sign) | ||
17 | $htmlEntities[">"] ="\x3E"; // > (greater-than sign) | ||
18 | $htmlEntities[" "] ="\xC2\xA0"; //   (non-breaking space) | ||
19 | $htmlEntities["¡"] ="\xC2\xA1"; // ¡ (inverted exclamation mark) | ||
20 | $htmlEntities["¢"] ="\xC2\xA2"; // ¢ (cent) | ||
21 | $htmlEntities["£"] ="\xC2\xA3"; // £ (pound) | ||
22 | $htmlEntities["¤"] ="\xC2\xA4"; // ¤ (currency) | ||
23 | $htmlEntities["¥"] ="\xC2\xA5"; // ¥ (yen) | ||
24 | $htmlEntities["¦"] ="\xC2\xA6"; // ¦ (broken vertical bar) | ||
25 | $htmlEntities["§"] ="\xC2\xA7"; // § (section) | ||
26 | $htmlEntities["¨"] ="\xC2\xA8"; // ¨ (spacing diaeresis) | ||
27 | $htmlEntities["©"] ="\xC2\xA9"; // © (copyright) | ||
28 | $htmlEntities["ª"] ="\xC2\xAA"; // ª (feminine ordinal indicator) | ||
29 | $htmlEntities["«"] ="\xC2\xAB"; // « (angle quotation mark (left)) | ||
30 | $htmlEntities["¬"] ="\xC2\xAC"; // ¬ (negation) | ||
31 | $htmlEntities["­"] ="\xC2\xAD"; // ­ (soft hyphen) | ||
32 | $htmlEntities["®"] ="\xC2\xAE"; // ® (registered trademark) | ||
33 | $htmlEntities["¯"] ="\xC2\xAF"; // ¯ (spacing macron) | ||
34 | $htmlEntities["°"] ="\xC2\xB0"; // ° (degree) | ||
35 | $htmlEntities["±"] ="\xC2\xB1"; // ± (plus-or-minus) | ||
36 | $htmlEntities["²"] ="\xC2\xB2"; // ² (superscript 2) | ||
37 | $htmlEntities["³"] ="\xC2\xB3"; // ³ (superscript 3) | ||
38 | $htmlEntities["´"] ="\xC2\xB4"; // ´ (spacing acute) | ||
39 | $htmlEntities["µ"] ="\xC2\xB5"; // µ (micro) | ||
40 | $htmlEntities["¶"] ="\xC2\xB6"; // ¶ (paragraph) | ||
41 | $htmlEntities["·"] ="\xC2\xB7"; // · (middle dot) | ||
42 | $htmlEntities["¸"] ="\xC2\xB8"; // ¸ (spacing cedilla) | ||
43 | $htmlEntities["¹"] ="\xC2\xB9"; // ¹ (superscript 1) | ||
44 | $htmlEntities["º"] ="\xC2\xBA"; // º (masculine ordinal indicator) | ||
45 | $htmlEntities["»"] ="\xC2\xBB"; // » (angle quotation mark (right)) | ||
46 | $htmlEntities["¼"] ="\xC2\xBC"; // ¼ (fraction 1/4) | ||
47 | $htmlEntities["½"] ="\xC2\xBD"; // ½ (fraction 1/2) | ||
48 | $htmlEntities["¾"] ="\xC2\xBE"; // ¾ (fraction 3/4) | ||
49 | $htmlEntities["¿"] ="\xC2\xBF"; // ¿ (inverted question mark) | ||
50 | $htmlEntities["À"] ="\xC3\x80"; // À (capital a, grave accent) | ||
51 | $htmlEntities["Á"] ="\xC3\x81"; // Á (capital a, acute accent) | ||
52 | $htmlEntities["Â"] ="\xC3\x82"; // Â (capital a, circumflex accent) | ||
53 | $htmlEntities["Ã"] ="\xC3\x83"; // Ã (capital a, tilde) | ||
54 | $htmlEntities["Ä"] ="\xC3\x84"; // Ä (capital a, umlaut mark) | ||
55 | $htmlEntities["Å"] ="\xC3\x85"; // Å (capital a, ring) | ||
56 | $htmlEntities["Æ"] ="\xC3\x86"; // Æ (capital ae) | ||
57 | $htmlEntities["Ç"] ="\xC3\x87"; // Ç (capital c, cedilla) | ||
58 | $htmlEntities["È"] ="\xC3\x88"; // È (capital e, grave accent) | ||
59 | $htmlEntities["É"] ="\xC3\x89"; // É (capital e, acute accent) | ||
60 | $htmlEntities["Ê"] ="\xC3\x8A"; // Ê (capital e, circumflex accent) | ||
61 | $htmlEntities["Ë"] ="\xC3\x8B"; // Ë (capital e, umlaut mark) | ||
62 | $htmlEntities["Ì"] ="\xC3\x8C"; // Ì (capital i, grave accent) | ||
63 | $htmlEntities["Í"] ="\xC3\x8D"; // Í (capital i, acute accent) | ||
64 | $htmlEntities["Î"] ="\xC3\x8E"; // Î (capital i, circumflex accent) | ||
65 | $htmlEntities["Ï"] ="\xC3\x8F"; // Ï (capital i, umlaut mark) | ||
66 | $htmlEntities["Ð"] ="\xC3\x90"; // Ð (capital eth, Icelandic) | ||
67 | $htmlEntities["Ñ"] ="\xC3\x91"; // Ñ (capital n, tilde) | ||
68 | $htmlEntities["Ò"] ="\xC3\x92"; // Ò (capital o, grave accent) | ||
69 | $htmlEntities["Ó"] ="\xC3\x93"; // Ó (capital o, acute accent) | ||
70 | $htmlEntities["Ô"] ="\xC3\x94"; // Ô (capital o, circumflex accent) | ||
71 | $htmlEntities["Õ"] ="\xC3\x95"; // Õ (capital o, tilde) | ||
72 | $htmlEntities["Ö"] ="\xC3\x96"; // Ö (capital o, umlaut mark) | ||
73 | $htmlEntities["×"] ="\xC3\x97"; // × (multiplication) | ||
74 | $htmlEntities["Ø"] ="\xC3\x98"; // Ø (capital o, slash) | ||
75 | $htmlEntities["Ù"] ="\xC3\x99"; // Ù (capital u, grave accent) | ||
76 | $htmlEntities["Ú"] ="\xC3\x9A"; // Ú (capital u, acute accent) | ||
77 | $htmlEntities["Û"] ="\xC3\x9B"; // Û (capital u, circumflex accent) | ||
78 | $htmlEntities["Ü"] ="\xC3\x9C"; // Ü (capital u, umlaut mark) | ||
79 | $htmlEntities["Ý"] ="\xC3\x9D"; // Ý (capital y, acute accent) | ||
80 | $htmlEntities["Þ"] ="\xC3\x9E"; // Þ (capital THORN, Icelandic) | ||
81 | $htmlEntities["ß"] ="\xC3\x9F"; // ß (small sharp s, German) | ||
82 | $htmlEntities["à"] ="\xC3\xA0"; // à (small a, grave accent) | ||
83 | $htmlEntities["á"] ="\xC3\xA1"; // á (small a, acute accent) | ||
84 | $htmlEntities["â"] ="\xC3\xA2"; // â (small a, circumflex accent) | ||
85 | $htmlEntities["ã"] ="\xC3\xA3"; // ã (small a, tilde) | ||
86 | $htmlEntities["ä"] ="\xC3\xA4"; // ä (small a, umlaut mark) | ||
87 | $htmlEntities["å"] ="\xC3\xA5"; // å (small a, ring) | ||
88 | $htmlEntities["æ"] ="\xC3\xA6"; // æ (small ae) | ||
89 | $htmlEntities["ç"] ="\xC3\xA7"; // ç (small c, cedilla) | ||
90 | $htmlEntities["è"] ="\xC3\xA8"; // è (small e, grave accent) | ||
91 | $htmlEntities["é"] ="\xC3\xA9"; // é (small e, acute accent) | ||
92 | $htmlEntities["ê"] ="\xC3\xAA"; // ê (small e, circumflex accent) | ||
93 | $htmlEntities["ë"] ="\xC3\xAB"; // ë (small e, umlaut mark) | ||
94 | $htmlEntities["ì"] ="\xC3\xAC"; // ì (small i, grave accent) | ||
95 | $htmlEntities["í"] ="\xC3\xAD"; // í (small i, acute accent) | ||
96 | $htmlEntities["î"] ="\xC3\xAE"; // î (small i, circumflex accent) | ||
97 | $htmlEntities["ï"] ="\xC3\xAF"; // ï (small i, umlaut mark) | ||
98 | $htmlEntities["ð"] ="\xC3\xB0"; // ð (small eth, Icelandic) | ||
99 | $htmlEntities["ñ"] ="\xC3\xB1"; // ñ (small n, tilde) | ||
100 | $htmlEntities["ò"] ="\xC3\xB2"; // ò (small o, grave accent) | ||
101 | $htmlEntities["ó"] ="\xC3\xB3"; // ó (small o, acute accent) | ||
102 | $htmlEntities["ô"] ="\xC3\xB4"; // ô (small o, circumflex accent) | ||
103 | $htmlEntities["õ"] ="\xC3\xB5"; // õ (small o, tilde) | ||
104 | $htmlEntities["ö"] ="\xC3\xB6"; // ö (small o, umlaut mark) | ||
105 | $htmlEntities["÷"] ="\xC3\xB7"; // ÷ (division) | ||
106 | $htmlEntities["ø"] ="\xC3\xB8"; // ø (small o, slash) | ||
107 | $htmlEntities["ù"] ="\xC3\xB9"; // ù (small u, grave accent) | ||
108 | $htmlEntities["ú"] ="\xC3\xBA"; // ú (small u, acute accent) | ||
109 | $htmlEntities["û"] ="\xC3\xBB"; // û (small u, circumflex accent) | ||
110 | $htmlEntities["ü"] ="\xC3\xBC"; // ü (small u, umlaut mark) | ||
111 | $htmlEntities["ý"] ="\xC3\xBD"; // ý (small y, acute accent) | ||
112 | $htmlEntities["þ"] ="\xC3\xBE"; // þ (small thorn, Icelandic) | ||
113 | $htmlEntities["ÿ"] ="\xC3\xBF"; // ÿ (small y, umlaut mark) | ||
114 | $htmlEntities["Œ"] ="\xC5\x92"; // Œ (capital ligature OE) | ||
115 | $htmlEntities["œ"] ="\xC5\x93"; // œ (small ligature oe) | ||
116 | $htmlEntities["Š"] ="\xC5\xA0"; // Š (capital S with caron) | ||
117 | $htmlEntities["š"] ="\xC5\xA1"; // š (small S with caron) | ||
118 | $htmlEntities["Ÿ"] ="\xC5\xB8"; // Ÿ (capital Y with diaeres) | ||
119 | $htmlEntities["ƒ"] ="\xC6\x92"; // ƒ (f with hook) | ||
120 | $htmlEntities["ˆ"] ="\xCB\x86"; // ˆ (modifier letter circumflex accent) | ||
121 | $htmlEntities["˜"] ="\xCB\x9C"; // ˜ (small tilde) | ||
122 | $htmlEntities["Α"] ="\xCE\x91"; // Α (Alpha) | ||
123 | $htmlEntities["Β"] ="\xCE\x92"; // Β (Beta) | ||
124 | $htmlEntities["Γ"] ="\xCE\x93"; // Γ (Gamma) | ||
125 | $htmlEntities["Δ"] ="\xCE\x94"; // Δ (Delta) | ||
126 | $htmlEntities["Ε"] ="\xCE\x95"; // Ε (Epsilon) | ||
127 | $htmlEntities["Ζ"] ="\xCE\x96"; // Ζ (Zeta) | ||
128 | $htmlEntities["Η"] ="\xCE\x97"; // Η (Eta) | ||
129 | $htmlEntities["Θ"] ="\xCE\x98"; // Θ (Theta) | ||
130 | $htmlEntities["Ι"] ="\xCE\x99"; // Ι (Iota) | ||
131 | $htmlEntities["Κ"] ="\xCE\x9A"; // Κ (Kappa) | ||
132 | $htmlEntities["Λ"] ="\xCE\x9B"; // Λ (Lambda) | ||
133 | $htmlEntities["Μ"] ="\xCE\x9C"; // Μ (Mu) | ||
134 | $htmlEntities["Ν"] ="\xCE\x9D"; // Ν (Nu) | ||
135 | $htmlEntities["Ξ"] ="\xCE\x9E"; // Ξ (Xi) | ||
136 | $htmlEntities["Ο"] ="\xCE\x9F"; // Ο (Omicron) | ||
137 | $htmlEntities["Π"] ="\xCE\xA0"; // Π (Pi) | ||
138 | $htmlEntities["Ρ"] ="\xCE\xA1"; // Ρ (Rho) | ||
139 | $htmlEntities["Σ"] ="\xCE\xA3"; // Σ (Sigma) | ||
140 | $htmlEntities["Τ"] ="\xCE\xA4"; // Τ (Tau) | ||
141 | $htmlEntities["Υ"] ="\xCE\xA5"; // Υ (Upsilon) | ||
142 | $htmlEntities["Φ"] ="\xCE\xA6"; // Φ (Phi) | ||
143 | $htmlEntities["Χ"] ="\xCE\xA7"; // Χ (Chi) | ||
144 | $htmlEntities["Ψ"] ="\xCE\xA8"; // Ψ (Psi) | ||
145 | $htmlEntities["Ω"] ="\xCE\xA9"; // Ω (Omega) | ||
146 | $htmlEntities["α"] ="\xCE\xB1"; // α (alpha) | ||
147 | $htmlEntities["β"] ="\xCE\xB2"; // β (beta) | ||
148 | $htmlEntities["γ"] ="\xCE\xB3"; // γ (gamma) | ||
149 | $htmlEntities["δ"] ="\xCE\xB4"; // δ (delta) | ||
150 | $htmlEntities["ε"] ="\xCE\xB5"; // ε (epsilon) | ||
151 | $htmlEntities["ζ"] ="\xCE\xB6"; // ζ (zeta) | ||
152 | $htmlEntities["η"] ="\xCE\xB7"; // η (eta) | ||
153 | $htmlEntities["θ"] ="\xCE\xB8"; // θ (theta) | ||
154 | $htmlEntities["ι"] ="\xCE\xB9"; // ι (iota) | ||
155 | $htmlEntities["κ"] ="\xCE\xBA"; // κ (kappa) | ||
156 | $htmlEntities["λ"] ="\xCE\xBB"; // λ (lambda) | ||
157 | $htmlEntities["μ"] ="\xCE\xBC"; // μ (mu) | ||
158 | $htmlEntities["ν"] ="\xCE\xBD"; // ν (nu) | ||
159 | $htmlEntities["ξ"] ="\xCE\xBE"; // ξ (xi) | ||
160 | $htmlEntities["ο"] ="\xCE\xBF"; // ο (omicron) | ||
161 | $htmlEntities["π"] ="\xCF\x80"; // π (pi) | ||
162 | $htmlEntities["ρ"] ="\xCF\x81"; // ρ (rho) | ||
163 | $htmlEntities["ς"] ="\xCF\x82"; // ς (sigmaf) | ||
164 | $htmlEntities["σ"] ="\xCF\x83"; // σ (sigma) | ||
165 | $htmlEntities["τ"] ="\xCF\x84"; // τ (tau) | ||
166 | $htmlEntities["υ"] ="\xCF\x85"; // υ (upsilon) | ||
167 | $htmlEntities["φ"] ="\xCF\x86"; // φ (phi) | ||
168 | $htmlEntities["χ"] ="\xCF\x87"; // χ (chi) | ||
169 | $htmlEntities["ψ"] ="\xCF\x88"; // ψ (psi) | ||
170 | $htmlEntities["ω"] ="\xCF\x89"; // ω (omega) | ||
171 | $htmlEntities["ϑ"] ="\xCF\x91"; // ϑ (theta symbol) | ||
172 | $htmlEntities["ϒ"] ="\xCF\x92"; // ϒ (upsilon symbol) | ||
173 | $htmlEntities["ϖ"] ="\xCF\x96"; // ϖ (pi symbol) | ||
174 | $htmlEntities[" "] ="\xE2\x80\x82"; //   (en space) | ||
175 | $htmlEntities[" "] ="\xE2\x80\x83"; //   (em space) | ||
176 | $htmlEntities[" "] ="\xE2\x80\x89"; //   (thin space) | ||
177 | $htmlEntities["‌"] ="\xE2\x80\x8C"; // ‌ (zero width non-joiner) | ||
178 | $htmlEntities["‍"] ="\xE2\x80\x8D"; // ‍ (zero width joiner) | ||
179 | $htmlEntities["‎"] ="\xE2\x80\x8E"; // ‎ (left-to-right mark) | ||
180 | $htmlEntities["‏"] ="\xE2\x80\x8F"; // ‏ (right-to-left mark) | ||
181 | $htmlEntities["–"] ="\xE2\x80\x93"; // – (en dash) | ||
182 | $htmlEntities["—"] ="\xE2\x80\x94"; // — (em dash) | ||
183 | $htmlEntities["‘"] ="\xE2\x80\x98"; // ‘ (left single quotation mark) | ||
184 | $htmlEntities["’"] ="\xE2\x80\x99"; // ’ (right single quotation mark) | ||
185 | $htmlEntities["‚"] ="\xE2\x80\x9A"; // ‚ (single low-9 quotation mark) | ||
186 | $htmlEntities["“"] ="\xE2\x80\x9C"; // “ (left double quotation mark) | ||
187 | $htmlEntities["”"] ="\xE2\x80\x9D"; // ” (right double quotation mark) | ||
188 | $htmlEntities["„"] ="\xE2\x80\x9E"; // „ (double low-9 quotation mark) | ||
189 | $htmlEntities["†"] ="\xE2\x80\xA0"; // † (dagger) | ||
190 | $htmlEntities["‡"] ="\xE2\x80\xA1"; // ‡ (double dagger) | ||
191 | $htmlEntities["•"] ="\xE2\x80\xA2"; // • (bullet) | ||
192 | $htmlEntities["…"] ="\xE2\x80\xA6"; // … (horizontal ellipsis) | ||
193 | $htmlEntities["‰"] ="\xE2\x80\xB0"; // ‰ (per mille) | ||
194 | $htmlEntities["′"] ="\xE2\x80\xB2"; // ′ (minutes or prime) | ||
195 | $htmlEntities["″"] ="\xE2\x80\xB3"; // ″ (seconds or Double Prime) | ||
196 | $htmlEntities["‹"] ="\xE2\x80\xB9"; // ‹ (single left angle quotation) | ||
197 | $htmlEntities["›"] ="\xE2\x80\xBA"; // › (single right angle quotation) | ||
198 | $htmlEntities["‾"] ="\xE2\x80\xBE"; // ‾ (overline) | ||
199 | $htmlEntities["⁄"] ="\xE2\x81\x84"; // ⁄ (fraction slash) | ||
200 | $htmlEntities["€"] ="\xE2\x82\xAC"; // € (euro) | ||
201 | $htmlEntities["ℑ"] ="\xE2\x84\x91"; // ℑ (blackletter capital I) | ||
202 | $htmlEntities["℘"] ="\xE2\x84\x98"; // ℘ (script capital P) | ||
203 | $htmlEntities["ℜ"] ="\xE2\x84\x9C"; // ℜ (blackletter capital R) | ||
204 | $htmlEntities["™"] ="\xE2\x84\xA2"; // ™ (trademark) | ||
205 | $htmlEntities["ℵ"] ="\xE2\x84\xB5"; // ℵ (alef) | ||
206 | $htmlEntities["←"] ="\xE2\x86\x90"; // ← (left arrow) | ||
207 | $htmlEntities["↑"] ="\xE2\x86\x91"; // ↑ (up arrow) | ||
208 | $htmlEntities["→"] ="\xE2\x86\x92"; // → (right arrow) | ||
209 | $htmlEntities["↓"] ="\xE2\x86\x93"; // ↓ (down arrow) | ||
210 | $htmlEntities["↔"] ="\xE2\x86\x94"; // ↔ (left right arrow) | ||
211 | $htmlEntities["↵"] ="\xE2\x86\xB5"; // ↵ (carriage return arrow) | ||
212 | $htmlEntities["⇐"] ="\xE2\x87\x90"; // ⇐ (left double arrow) | ||
213 | $htmlEntities["⇑"] ="\xE2\x87\x91"; // ⇑ (up double arrow) | ||
214 | $htmlEntities["⇒"] ="\xE2\x87\x92"; // ⇒ (right double arrow) | ||
215 | $htmlEntities["⇓"] ="\xE2\x87\x93"; // ⇓ (down double arrow) | ||
216 | $htmlEntities["⇔"] ="\xE2\x87\x94"; // ⇔ (left right double arrow) | ||
217 | $htmlEntities["∀"] ="\xE2\x88\x80"; // ∀ (for all) | ||
218 | $htmlEntities["∂"] ="\xE2\x88\x82"; // ∂ (partial differential) | ||
219 | $htmlEntities["∃"] ="\xE2\x88\x83"; // ∃ (there exists) | ||
220 | $htmlEntities["∅"] ="\xE2\x88\x85"; // ∅ (empty set) | ||
221 | $htmlEntities["∇"] ="\xE2\x88\x87"; // ∇ (backward difference) | ||
222 | $htmlEntities["∈"] ="\xE2\x88\x88"; // ∈ (element of) | ||
223 | $htmlEntities["∉"] ="\xE2\x88\x89"; // ∉ (not an element of) | ||
224 | $htmlEntities["∋"] ="\xE2\x88\x8B"; // ∋ (ni = contains as member) | ||
225 | $htmlEntities["∏"] ="\xE2\x88\x8F"; // ∏ (n-ary product) | ||
226 | $htmlEntities["∑"] ="\xE2\x88\x91"; // ∑ (n-ary sumation) | ||
227 | $htmlEntities["−"] ="\xE2\x88\x92"; // − (minus) | ||
228 | $htmlEntities["∗"] ="\xE2\x88\x97"; // ∗ (asterisk operator) | ||
229 | $htmlEntities["√"] ="\xE2\x88\x9A"; // √ (square root) | ||
230 | $htmlEntities["∝"] ="\xE2\x88\x9D"; // ∝ (proportional to) | ||
231 | $htmlEntities["∞"] ="\xE2\x88\x9E"; // ∞ (infinity) | ||
232 | $htmlEntities["∠"] ="\xE2\x88\xA0"; // ∠ (angle) | ||
233 | $htmlEntities["∧"] ="\xE2\x88\xA7"; // ∧ (logical and) | ||
234 | $htmlEntities["∨"] ="\xE2\x88\xA8"; // ∨ (logical or) | ||
235 | $htmlEntities["∩"] ="\xE2\x88\xA9"; // ∩ (intersection) | ||
236 | $htmlEntities["∪"] ="\xE2\x88\xAA"; // ∪ (union) | ||
237 | $htmlEntities["∫"] ="\xE2\x88\xAB"; // ∫ (integral) | ||
238 | $htmlEntities["∴"] ="\xE2\x88\xB4"; // ∴ (therefore) | ||
239 | $htmlEntities["∼"] ="\xE2\x88\xBC"; // ∼ (similar to) | ||
240 | $htmlEntities["≅"] ="\xE2\x89\x85"; // ≅ (congruent to) | ||
241 | $htmlEntities["≈"] ="\xE2\x89\x88"; // ≈ (approximately equal) | ||
242 | $htmlEntities["≠"] ="\xE2\x89\xA0"; // ≠ (not equal) | ||
243 | $htmlEntities["≡"] ="\xE2\x89\xA1"; // ≡ (equivalent) | ||
244 | $htmlEntities["≤"] ="\xE2\x89\xA4"; // ≤ (less or equal) | ||
245 | $htmlEntities["≥"] ="\xE2\x89\xA5"; // ≥ (greater or equal) | ||
246 | $htmlEntities["⊂"] ="\xE2\x8A\x82"; // ⊂ (subset of) | ||
247 | $htmlEntities["⊃"] ="\xE2\x8A\x83"; // ⊃ (superset of) | ||
248 | $htmlEntities["⊄"] ="\xE2\x8A\x84"; // ⊄ (not subset of) | ||
249 | $htmlEntities["⊆"] ="\xE2\x8A\x86"; // ⊆ (subset or equal) | ||
250 | $htmlEntities["⊇"] ="\xE2\x8A\x87"; // ⊇ (superset or equal) | ||
251 | $htmlEntities["⊕"] ="\xE2\x8A\x95"; // ⊕ (circled plus) | ||
252 | $htmlEntities["⊗"] ="\xE2\x8A\x87"; // ⊗ (circled times) | ||
253 | $htmlEntities["⊥"] ="\xE2\x8A\xA5"; // ⊥ (perpendicular) | ||
254 | $htmlEntities["⋅"] ="\xE2\x8C\x85"; // ⋅ (dot operator) | ||
255 | $htmlEntities["⌈"] ="\xE2\x8C\x88"; // ⌈ (left ceiling) | ||
256 | $htmlEntities["⌉"] ="\xE2\x8C\x89"; // ⌉ (right ceiling) | ||
257 | $htmlEntities["⌊"] ="\xE2\x8C\x8A"; // ⌊ (left floor) | ||
258 | $htmlEntities["⌋"] ="\xE2\x8C\x8B"; // ⌋ (right floor) | ||
259 | $htmlEntities["⟨"] ="\xE2\x8C\xA9"; // 〈 (left angle bracket = bra) | ||
260 | $htmlEntities["⟩"] ="\xE2\x8C\xAA"; // 〉 (right angle bracket = ket) | ||
261 | $htmlEntities["◊"] ="\xE2\x97\x8A"; // ◊ (lozenge) | ||
262 | $htmlEntities["♠"] ="\xE2\x99\xA0"; // ♠ (spade) | ||
263 | $htmlEntities["♣"] ="\xE2\x99\xA3"; // ♣ (club) | ||
264 | $htmlEntities["♥"] ="\xE2\x99\xA5"; // ♥ (heart) | ||
265 | $htmlEntities["♦"] ="\xE2\x99\xA6"; // ♦ (diamond) | ||
266 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/PHPePub/EPub.NCX.php b/inc/3rdparty/libraries/PHPePub/EPub.NCX.php new file mode 100644 index 00000000..e5da05cd --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.NCX.php | |||
@@ -0,0 +1,782 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * ePub NCX file structure | ||
4 | * | ||
5 | * @author A. Grandt <php@grandt.com> | ||
6 | * @copyright 2009-2014 A. Grandt | ||
7 | * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
8 | * @version 3.20 | ||
9 | */ | ||
10 | class Ncx { | ||
11 | const _VERSION = 3.20; | ||
12 | |||
13 | const MIMETYPE = "application/x-dtbncx+xml"; | ||
14 | |||
15 | private $bookVersion = EPub::BOOK_VERSION_EPUB2; | ||
16 | |||
17 | private $navMap = NULL; | ||
18 | private $uid = NULL; | ||
19 | private $meta = array(); | ||
20 | private $docTitle = NULL; | ||
21 | private $docAuthor = NULL; | ||
22 | |||
23 | private $currentLevel = NULL; | ||
24 | private $lastLevel = NULL; | ||
25 | |||
26 | private $languageCode = "en"; | ||
27 | private $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT; | ||
28 | |||
29 | public $chapterList = array(); | ||
30 | public $referencesTitle = "Guide"; | ||
31 | public $referencesClass = "references"; | ||
32 | public $referencesId = "references"; | ||
33 | public $referencesList = array(); | ||
34 | public $referencesName = array(); | ||
35 | public $referencesOrder = NULL; | ||
36 | |||
37 | /** | ||
38 | * Class constructor. | ||
39 | * | ||
40 | * @param string $uid | ||
41 | * @param string $docTitle | ||
42 | * @param string $docAuthor | ||
43 | * @param string $languageCode | ||
44 | * @param string $writingDirection | ||
45 | */ | ||
46 | function __construct($uid = NULL, $docTitle = NULL, $docAuthor = NULL, $languageCode = "en", $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT) { | ||
47 | $this->navMap = new NavMap($writingDirection); | ||
48 | $this->currentLevel = $this->navMap; | ||
49 | $this->setUid($uid); | ||
50 | $this->setDocTitle($docTitle); | ||
51 | $this->setDocAuthor($docAuthor); | ||
52 | $this->setLanguageCode($languageCode); | ||
53 | $this->setWritingDirection($writingDirection); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * Class destructor | ||
58 | * | ||
59 | * @return void | ||
60 | */ | ||
61 | function __destruct() { | ||
62 | unset($this->bookVersion, $this->navMap, $this->uid, $this->meta); | ||
63 | unset($this->docTitle, $this->docAuthor, $this->currentLevel, $this->lastLevel); | ||
64 | unset($this->languageCode, $this->writingDirection, $this->chapterList, $this->referencesTitle); | ||
65 | unset($this->referencesClass, $this->referencesId, $this->referencesList, $this->referencesName); | ||
66 | unset($this->referencesOrder); | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * | ||
71 | * Enter description here ... | ||
72 | * | ||
73 | * @param string $bookVersion | ||
74 | */ | ||
75 | function setVersion($bookVersion) { | ||
76 | $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; | ||
77 | } | ||
78 | |||
79 | /** | ||
80 | * | ||
81 | * @return bool TRUE if the book is set to type ePub 2 | ||
82 | */ | ||
83 | function isEPubVersion2() { | ||
84 | return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * | ||
89 | * Enter description here ... | ||
90 | * | ||
91 | * @param string $uid | ||
92 | */ | ||
93 | function setUid($uid) { | ||
94 | $this->uid = is_string($uid) ? trim($uid) : NULL; | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * | ||
99 | * Enter description here ... | ||
100 | * | ||
101 | * @param string $docTitle | ||
102 | */ | ||
103 | function setDocTitle($docTitle) { | ||
104 | $this->docTitle = is_string($docTitle) ? trim($docTitle) : NULL; | ||
105 | } | ||
106 | |||
107 | /** | ||
108 | * | ||
109 | * Enter description here ... | ||
110 | * | ||
111 | * @param string $docAuthor | ||
112 | */ | ||
113 | function setDocAuthor($docAuthor) { | ||
114 | $this->docAuthor = is_string($docAuthor) ? trim($docAuthor) : NULL; | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * | ||
119 | * Enter description here ... | ||
120 | * | ||
121 | * @param string $languageCode | ||
122 | */ | ||
123 | function setLanguageCode($languageCode) { | ||
124 | $this->languageCode = is_string($languageCode) ? trim($languageCode) : "en"; | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * | ||
129 | * Enter description here ... | ||
130 | * | ||
131 | * @param string $writingDirection | ||
132 | */ | ||
133 | function setWritingDirection($writingDirection) { | ||
134 | $this->writingDirection = is_string($writingDirection) ? trim($writingDirection) : EPub::DIRECTION_LEFT_TO_RIGHT; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * | ||
139 | * Enter description here ... | ||
140 | * | ||
141 | * @param NavMap $navMap | ||
142 | */ | ||
143 | function setNavMap($navMap) { | ||
144 | if ($navMap != NULL && is_object($navMap) && get_class($navMap) === "NavMap") { | ||
145 | $this->navMap = $navMap; | ||
146 | } | ||
147 | } | ||
148 | |||
149 | /** | ||
150 | * Add one chapter level. | ||
151 | * | ||
152 | * Subsequent chapters will be added to this level. | ||
153 | * | ||
154 | * @param string $navTitle | ||
155 | * @param string $navId | ||
156 | * @param string $navClass | ||
157 | * @param string $isNavHidden | ||
158 | * @param string $writingDirection | ||
159 | * @return NavPoint | ||
160 | */ | ||
161 | function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { | ||
162 | $navPoint = FALSE; | ||
163 | if (isset($navTitle) && isset($navClass)) { | ||
164 | $navPoint = new NavPoint($navTitle, NULL, $navId, $navClass, $isNavHidden, $writingDirection); | ||
165 | $this->addNavPoint($navPoint); | ||
166 | } | ||
167 | if ($this->lastLevel !== NULL) { | ||
168 | $this->currentLevel = $this->lastLevel; | ||
169 | } | ||
170 | return $navPoint; | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Step back one chapter level. | ||
175 | * | ||
176 | * Subsequent chapters will be added to this chapters parent level. | ||
177 | */ | ||
178 | function backLevel() { | ||
179 | $this->lastLevel = $this->currentLevel; | ||
180 | $this->currentLevel = $this->currentLevel->getParent(); | ||
181 | } | ||
182 | |||
183 | /** | ||
184 | * Step back to the root level. | ||
185 | * | ||
186 | * Subsequent chapters will be added to the rooot NavMap. | ||
187 | */ | ||
188 | function rootLevel() { | ||
189 | $this->lastLevel = $this->currentLevel; | ||
190 | $this->currentLevel = $this->navMap; | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * Step back to the given level. | ||
195 | * Useful for returning to a previous level from deep within the structure. | ||
196 | * Values below 2 will have the same effect as rootLevel() | ||
197 | * | ||
198 | * @param int $newLevel | ||
199 | */ | ||
200 | function setCurrentLevel($newLevel) { | ||
201 | if ($newLevel <= 1) { | ||
202 | $this->rootLevel(); | ||
203 | } else { | ||
204 | while ($this->currentLevel->getLevel() > $newLevel) { | ||
205 | $this->backLevel(); | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | |||
210 | /** | ||
211 | * Get current level count. | ||
212 | * The indentation of the current structure point. | ||
213 | * | ||
214 | * @return current level count; | ||
215 | */ | ||
216 | function getCurrentLevel() { | ||
217 | return $this->currentLevel->getLevel(); | ||
218 | } | ||
219 | |||
220 | /** | ||
221 | * Add child NavPoints to current level. | ||
222 | * | ||
223 | * @param NavPoint $navPoint | ||
224 | */ | ||
225 | function addNavPoint($navPoint) { | ||
226 | $this->lastLevel = $this->currentLevel->addNavPoint($navPoint); | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * | ||
231 | * Enter description here ... | ||
232 | * | ||
233 | * @return NavMap | ||
234 | */ | ||
235 | function getNavMap() { | ||
236 | return $this->navMap; | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * | ||
241 | * Enter description here ... | ||
242 | * | ||
243 | * @param string $name | ||
244 | * @param string $content | ||
245 | */ | ||
246 | function addMetaEntry($name, $content) { | ||
247 | $name = is_string($name) ? trim($name) : NULL; | ||
248 | $content = is_string($content) ? trim($content) : NULL; | ||
249 | |||
250 | if ($name != NULL && $content != NULL) { | ||
251 | $this->meta[] = array($name => $content); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | /** | ||
256 | * | ||
257 | * Enter description here ... | ||
258 | * | ||
259 | * @return string | ||
260 | */ | ||
261 | function finalize() { | ||
262 | $nav = $this->navMap->finalize(); | ||
263 | |||
264 | $ncx = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; | ||
265 | if ($this->isEPubVersion2()) { | ||
266 | $ncx .= "<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"\n" | ||
267 | . " \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n"; | ||
268 | } | ||
269 | $ncx .= "<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\" xml:lang=\"" . $this->languageCode . "\" dir=\"" . $this->writingDirection . "\">\n" | ||
270 | . "\t<head>\n" | ||
271 | . "\t\t<meta name=\"dtb:uid\" content=\"" . $this->uid . "\" />\n" | ||
272 | . "\t\t<meta name=\"dtb:depth\" content=\"" . $this->navMap->getNavLevels() . "\" />\n" | ||
273 | . "\t\t<meta name=\"dtb:totalPageCount\" content=\"0\" />\n" | ||
274 | . "\t\t<meta name=\"dtb:maxPageNumber\" content=\"0\" />\n"; | ||
275 | |||
276 | if (sizeof($this->meta)) { | ||
277 | foreach ($this->meta as $metaEntry) { | ||
278 | list($name, $content) = each($metaEntry); | ||
279 | $ncx .= "\t\t<meta name=\"" . $name . "\" content=\"" . $content . "\" />\n"; | ||
280 | } | ||
281 | } | ||
282 | |||
283 | $ncx .= "\t</head>\n\n\t<docTitle>\n\t\t<text>" | ||
284 | . $this->docTitle | ||
285 | . "</text>\n\t</docTitle>\n\n\t<docAuthor>\n\t\t<text>" | ||
286 | . $this->docAuthor | ||
287 | . "</text>\n\t</docAuthor>\n\n" | ||
288 | . $nav; | ||
289 | |||
290 | return $ncx . "</ncx>\n"; | ||
291 | } | ||
292 | |||
293 | /** | ||
294 | * | ||
295 | * @param string $title | ||
296 | * @param string $cssFileName | ||
297 | * @return string | ||
298 | */ | ||
299 | function finalizeEPub3($title = "Table of Contents", $cssFileName = NULL) { | ||
300 | $end = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | ||
301 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\"\n" | ||
302 | . " xmlns:epub=\"http://www.idpf.org/2007/ops\"\n" | ||
303 | . " xml:lang=\"" . $this->languageCode . "\" lang=\"" . $this->languageCode . "\" dir=\"" . $this->writingDirection . "\">\n" | ||
304 | . "\t<head>\n" | ||
305 | . "\t\t<title>" . $this->docTitle . "</title>\n" | ||
306 | . "\t\t<meta http-equiv=\"default-style\" content=\"text/html; charset=utf-8\"/>\n"; | ||
307 | if ($cssFileName !== NULL) { | ||
308 | $end .= "\t\t<link rel=\"stylesheet\" href=\"" . $cssFileName . "\" type=\"text/css\"/>\n"; | ||
309 | } | ||
310 | $end .= "\t</head>\n" | ||
311 | . "\t<body epub:type=\"frontmatter toc\">\n" | ||
312 | . "\t\t<header>\n" | ||
313 | . "\t\t\t<h1>" . $title . "</h1>\n" | ||
314 | . "\t\t</header>\n" | ||
315 | . $this->navMap->finalizeEPub3() | ||
316 | . $this->finalizeEPub3Landmarks() | ||
317 | . "\t</body>\n" | ||
318 | . "</html>\n"; | ||
319 | |||
320 | return $end; | ||
321 | } | ||
322 | |||
323 | /** | ||
324 | * Build the references for the ePub 2 toc. | ||
325 | * These are merely reference pages added to the end of the navMap though. | ||
326 | * | ||
327 | * @return string | ||
328 | */ | ||
329 | function finalizeReferences() { | ||
330 | if (isset($this->referencesList) && sizeof($this->referencesList) > 0) { | ||
331 | $this->rootLevel(); | ||
332 | $this->subLevel($this->referencesTitle, $this->referencesId, $this->referencesClass); | ||
333 | $refId = 1; | ||
334 | while (list($item, $descriptive) = each($this->referencesOrder)) { | ||
335 | if (array_key_exists($item, $this->referencesList)) { | ||
336 | $name = (empty($this->referencesName[$item]) ? $descriptive : $this->referencesName[$item]); | ||
337 | $navPoint = new NavPoint($name, $this->referencesList[$item], "ref-" . $refId++); | ||
338 | $this->addNavPoint($navPoint); | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | } | ||
343 | |||
344 | /** | ||
345 | * Build the landmarks for the ePub 3 toc. | ||
346 | * @return string | ||
347 | */ | ||
348 | function finalizeEPub3Landmarks() { | ||
349 | $lm = ""; | ||
350 | if (isset($this->referencesList) && sizeof($this->referencesList) > 0) { | ||
351 | $lm = "\t\t\t<nav epub:type=\"landmarks\">\n" | ||
352 | . "\t\t\t\t<h2" | ||
353 | . ($this->writingDirection === EPub::DIRECTION_RIGHT_TO_LEFT ? " dir=\"rtl\"" : "") | ||
354 | . ">" . $this->referencesTitle . "</h2>\n" | ||
355 | . "\t\t\t\t<ol>\n"; | ||
356 | |||
357 | $li = ""; | ||
358 | while (list($item, $descriptive) = each($this->referencesOrder)) { | ||
359 | if (array_key_exists($item, $this->referencesList)) { | ||
360 | $li .= "\t\t\t\t\t<li><a epub:type=\"" | ||
361 | . $item | ||
362 | . "\" href=\"" . $this->referencesList[$item] . "\">" | ||
363 | . (empty($this->referencesName[$item]) ? $descriptive : $this->referencesName[$item]) | ||
364 | . "</a></li>\n"; | ||
365 | } | ||
366 | } | ||
367 | if (empty($li)) { | ||
368 | return ""; | ||
369 | } | ||
370 | |||
371 | $lm .= $li | ||
372 | . "\t\t\t\t</ol>\n" | ||
373 | . "\t\t\t</nav>\n"; | ||
374 | } | ||
375 | return $lm; | ||
376 | } | ||
377 | } | ||
378 | |||
379 | /** | ||
380 | * ePub NavMap class | ||
381 | */ | ||
382 | class NavMap { | ||
383 | const _VERSION = 3.00; | ||
384 | |||
385 | private $navPoints = array(); | ||
386 | private $navLevels = 0; | ||
387 | private $writingDirection = NULL; | ||
388 | |||
389 | /** | ||
390 | * Class constructor. | ||
391 | * | ||
392 | * @return void | ||
393 | */ | ||
394 | function __construct($writingDirection = NULL) { | ||
395 | $this->setWritingDirection($writingDirection); | ||
396 | } | ||
397 | |||
398 | /** | ||
399 | * Class destructor | ||
400 | * | ||
401 | * @return void | ||
402 | */ | ||
403 | function __destruct() { | ||
404 | unset($this->navPoints, $this->navLevels, $this->writingDirection); | ||
405 | } | ||
406 | |||
407 | /** | ||
408 | * Set the writing direction to be used for this NavPoint. | ||
409 | * | ||
410 | * @param string $writingDirection | ||
411 | */ | ||
412 | function setWritingDirection($writingDirection) { | ||
413 | $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : NULL; | ||
414 | } | ||
415 | |||
416 | function getWritingDirection() { | ||
417 | return $this->writingDirection; | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * Add a navPoint to the root of the NavMap. | ||
422 | * | ||
423 | * @param NavPoint $navPoint | ||
424 | * @return NavMap | ||
425 | */ | ||
426 | function addNavPoint($navPoint) { | ||
427 | if ($navPoint != NULL && is_object($navPoint) && get_class($navPoint) === "NavPoint") { | ||
428 | $navPoint->setParent($this); | ||
429 | if ($navPoint->getWritingDirection() == NULL) { | ||
430 | $navPoint->setWritingDirection($this->writingDirection); | ||
431 | } | ||
432 | $this->navPoints[] = $navPoint; | ||
433 | return $navPoint; | ||
434 | } | ||
435 | return $this; | ||
436 | } | ||
437 | |||
438 | /** | ||
439 | * The final max depth for the "dtb:depth" meta attribute | ||
440 | * Only available after finalize have been called. | ||
441 | * | ||
442 | * @return number | ||
443 | */ | ||
444 | function getNavLevels() { | ||
445 | return $this->navLevels+1; | ||
446 | } | ||
447 | |||
448 | function getLevel() { | ||
449 | return 1; | ||
450 | } | ||
451 | |||
452 | function getParent() { | ||
453 | return $this; | ||
454 | } | ||
455 | |||
456 | /** | ||
457 | * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization | ||
458 | * | ||
459 | */ | ||
460 | function finalize() { | ||
461 | $playOrder = 0; | ||
462 | $this->navLevels = 0; | ||
463 | |||
464 | $nav = "\t<navMap>\n"; | ||
465 | if (sizeof($this->navPoints) > 0) { | ||
466 | $this->navLevels++; | ||
467 | foreach ($this->navPoints as $navPoint) { | ||
468 | $retLevel = $navPoint->finalize($nav, $playOrder, 0); | ||
469 | if ($retLevel > $this->navLevels) { | ||
470 | $this->navLevels = $retLevel; | ||
471 | } | ||
472 | } | ||
473 | } | ||
474 | return $nav . "\t</navMap>\n"; | ||
475 | } | ||
476 | |||
477 | /** | ||
478 | * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization | ||
479 | * | ||
480 | */ | ||
481 | function finalizeEPub3() { | ||
482 | $playOrder = 0; | ||
483 | $level = 0; | ||
484 | $this->navLevels = 0; | ||
485 | |||
486 | $nav = "\t\t<nav epub:type=\"toc\" id=\"toc\">\n"; | ||
487 | |||
488 | if (sizeof($this->navPoints) > 0) { | ||
489 | $this->navLevels++; | ||
490 | |||
491 | $nav .= str_repeat("\t", $level) . "\t\t\t<ol epub:type=\"list\">\n"; | ||
492 | foreach ($this->navPoints as $navPoint) { | ||
493 | $retLevel = $navPoint->finalizeEPub3($nav, $playOrder, 0); | ||
494 | if ($retLevel > $this->navLevels) { | ||
495 | $this->navLevels = $retLevel; | ||
496 | } | ||
497 | } | ||
498 | $nav .= str_repeat("\t", $level) . "\t\t\t</ol>\n"; | ||
499 | } | ||
500 | |||
501 | return $nav . "\t\t</nav>\n"; | ||
502 | } | ||
503 | } | ||
504 | |||
505 | /** | ||
506 | * ePub NavPoint class | ||
507 | */ | ||
508 | class NavPoint { | ||
509 | const _VERSION = 3.00; | ||
510 | |||
511 | private $label = NULL; | ||
512 | private $contentSrc = NULL; | ||
513 | private $id = NULL; | ||
514 | private $navClass = NULL; | ||
515 | private $isNavHidden = FALSE; | ||
516 | private $navPoints = array(); | ||
517 | private $parent = NULL; | ||
518 | |||
519 | /** | ||
520 | * Class constructor. | ||
521 | * | ||
522 | * All three attributes are mandatory, though if ID is set to null (default) the value will be generated. | ||
523 | * | ||
524 | * @param string $label | ||
525 | * @param string $contentSrc | ||
526 | * @param string $id | ||
527 | * @param string $navClass | ||
528 | * @param bool $isNavHidden | ||
529 | * @param string $writingDirection | ||
530 | */ | ||
531 | function __construct($label, $contentSrc = NULL, $id = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { | ||
532 | $this->setLabel($label); | ||
533 | $this->setContentSrc($contentSrc); | ||
534 | $this->setId($id); | ||
535 | $this->setNavClass($navClass); | ||
536 | $this->setNavHidden($isNavHidden); | ||
537 | $this->setWritingDirection($writingDirection); | ||
538 | } | ||
539 | |||
540 | /** | ||
541 | * Class destructor | ||
542 | * | ||
543 | * @return void | ||
544 | */ | ||
545 | function __destruct() { | ||
546 | unset($this->label, $this->contentSrc, $this->id, $this->navClass); | ||
547 | unset($this->isNavHidden, $this->navPoints, $this->parent); | ||
548 | } | ||
549 | |||
550 | /** | ||
551 | * Set the Text label for the NavPoint. | ||
552 | * | ||
553 | * The label is mandatory. | ||
554 | * | ||
555 | * @param string $label | ||
556 | */ | ||
557 | function setLabel($label) { | ||
558 | $this->label = is_string($label) ? trim($label) : NULL; | ||
559 | } | ||
560 | |||
561 | /** | ||
562 | * Get the Text label for the NavPoint. | ||
563 | * | ||
564 | * @return string Label | ||
565 | */ | ||
566 | function getLabel() { | ||
567 | return $this->label; | ||
568 | } | ||
569 | |||
570 | /** | ||
571 | * Set the src reference for the NavPoint. | ||
572 | * | ||
573 | * The src is mandatory for ePub 2. | ||
574 | * | ||
575 | * @param string $contentSrc | ||
576 | */ | ||
577 | function setContentSrc($contentSrc) { | ||
578 | $this->contentSrc = isset($contentSrc) && is_string($contentSrc) ? trim($contentSrc) : NULL; | ||
579 | } | ||
580 | |||
581 | /** | ||
582 | * Get the src reference for the NavPoint. | ||
583 | * | ||
584 | * @return string content src url. | ||
585 | */ | ||
586 | function getContentSrc() { | ||
587 | return $this->contentSrc; | ||
588 | } | ||
589 | /** | ||
590 | * Set the parent for this NavPoint. | ||
591 | * | ||
592 | * @param NavPoint or NavMap $parent | ||
593 | */ | ||
594 | function setParent($parent) { | ||
595 | if ($parent != NULL && is_object($parent) && | ||
596 | (get_class($parent) === "NavPoint" || get_class($parent) === "NavMap") ) { | ||
597 | $this->parent = $parent; | ||
598 | } | ||
599 | } | ||
600 | |||
601 | /** | ||
602 | * Get the parent to this NavPoint. | ||
603 | * | ||
604 | * @return NavPoint, or NavMap if the parent is the root. | ||
605 | */ | ||
606 | function getParent() { | ||
607 | return $this->parent; | ||
608 | } | ||
609 | |||
610 | /** | ||
611 | * Get the current level. 1 = document root. | ||
612 | * | ||
613 | * @return int level | ||
614 | */ | ||
615 | function getLevel() { | ||
616 | return $this->parent === NULL ? 1 : $this->parent->getLevel()+1; | ||
617 | } | ||
618 | |||
619 | /** | ||
620 | * Set the id for the NavPoint. | ||
621 | * | ||
622 | * The id must be unique, and is mandatory. | ||
623 | * | ||
624 | * @param string $id | ||
625 | */ | ||
626 | function setId($id) { | ||
627 | $this->id = is_string($id) ? trim($id) : NULL; | ||
628 | } | ||
629 | |||
630 | /** | ||
631 | * Set the class to be used for this NavPoint. | ||
632 | * | ||
633 | * @param string $navClass | ||
634 | */ | ||
635 | function setNavClass($navClass) { | ||
636 | $this->navClass = isset($navClass) && is_string($navClass) ? trim($navClass) : NULL; | ||
637 | } | ||
638 | |||
639 | /** | ||
640 | * Set the class to be used for this NavPoint. | ||
641 | * | ||
642 | * @param string $navClass | ||
643 | */ | ||
644 | function setNavHidden($isNavHidden) { | ||
645 | $this->isNavHidden = $isNavHidden === TRUE; | ||
646 | } | ||
647 | |||
648 | /** | ||
649 | * Set the writing direction to be used for this NavPoint. | ||
650 | * | ||
651 | * @param string $writingDirection | ||
652 | */ | ||
653 | function setWritingDirection($writingDirection) { | ||
654 | $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : NULL; | ||
655 | } | ||
656 | |||
657 | function getWritingDirection() { | ||
658 | return $this->writingDirection; | ||
659 | } | ||
660 | |||
661 | /** | ||
662 | * Add child NavPoints for multi level NavMaps. | ||
663 | * | ||
664 | * @param NavPoint $navPoint | ||
665 | */ | ||
666 | function addNavPoint($navPoint) { | ||
667 | if ($navPoint != NULL && is_object($navPoint) && get_class($navPoint) === "NavPoint") { | ||
668 | $navPoint->setParent($this); | ||
669 | if ($navPoint->getWritingDirection() == NULL) { | ||
670 | $navPoint->setWritingDirection($this->writingDirection); | ||
671 | } | ||
672 | $this->navPoints[] = $navPoint; | ||
673 | return $navPoint; | ||
674 | } | ||
675 | return $this; | ||
676 | } | ||
677 | |||
678 | /** | ||
679 | * | ||
680 | * Enter description here ... | ||
681 | * | ||
682 | * @param string $nav | ||
683 | * @param int $playOrder | ||
684 | * @param int $level | ||
685 | * @return int | ||
686 | */ | ||
687 | function finalize(&$nav = "", &$playOrder = 0, $level = 0) { | ||
688 | $maxLevel = $level; | ||
689 | $levelAdjust = 0; | ||
690 | |||
691 | if ($this->isNavHidden) { | ||
692 | return $maxLevel; | ||
693 | } | ||
694 | |||
695 | if (isset($this->contentSrc)) { | ||
696 | $playOrder++; | ||
697 | |||
698 | if ($this->id == NULL) { | ||
699 | $this->id = "navpoint-" . $playOrder; | ||
700 | } | ||
701 | $nav .= str_repeat("\t", $level) . "\t\t<navPoint id=\"" . $this->id . "\" playOrder=\"" . $playOrder . "\">\n" | ||
702 | . str_repeat("\t", $level) . "\t\t\t<navLabel>\n" | ||
703 | . str_repeat("\t", $level) . "\t\t\t\t<text>" . $this->label . "</text>\n" | ||
704 | . str_repeat("\t", $level) . "\t\t\t</navLabel>\n" | ||
705 | . str_repeat("\t", $level) . "\t\t\t<content src=\"" . $this->contentSrc . "\" />\n"; | ||
706 | } else { | ||
707 | $levelAdjust++; | ||
708 | } | ||
709 | |||
710 | if (sizeof($this->navPoints) > 0) { | ||
711 | $maxLevel++; | ||
712 | foreach ($this->navPoints as $navPoint) { | ||
713 | $retLevel = $navPoint->finalize($nav, $playOrder, ($level+1+$levelAdjust)); | ||
714 | if ($retLevel > $maxLevel) { | ||
715 | $maxLevel = $retLevel; | ||
716 | } | ||
717 | } | ||
718 | } | ||
719 | |||
720 | if (isset($this->contentSrc)) { | ||
721 | $nav .= str_repeat("\t", $level) . "\t\t</navPoint>\n"; | ||
722 | } | ||
723 | |||
724 | return $maxLevel; | ||
725 | } | ||
726 | |||
727 | /** | ||
728 | * | ||
729 | * Enter description here ... | ||
730 | * | ||
731 | * @param string $nav | ||
732 | * @param int $playOrder | ||
733 | * @param int $level | ||
734 | * @return int | ||
735 | */ | ||
736 | function finalizeEPub3(&$nav = "", &$playOrder = 0, $level = 0, $subLevelClass = NULL, $subLevelHidden = FALSE) { | ||
737 | $maxLevel = $level; | ||
738 | |||
739 | if ($this->id == NULL) { | ||
740 | $this->id = "navpoint-" . $playOrder; | ||
741 | } | ||
742 | $indent = str_repeat("\t", $level) . "\t\t\t\t"; | ||
743 | |||
744 | $nav .= $indent . "<li id=\"" . $this->id . "\""; | ||
745 | if (isset($this->writingDirection)) { | ||
746 | $nav .= " dir=\"" . $this->writingDirection . "\""; | ||
747 | } | ||
748 | $nav .= ">\n"; | ||
749 | |||
750 | if (isset($this->contentSrc)) { | ||
751 | $nav .= $indent . "\t<a href=\"" . $this->contentSrc . "\">" . $this->label . "</a>\n"; | ||
752 | } else { | ||
753 | $nav .= $indent . "\t<span>" . $this->label . "</span>\n"; | ||
754 | } | ||
755 | |||
756 | if (sizeof($this->navPoints) > 0) { | ||
757 | $maxLevel++; | ||
758 | |||
759 | $nav .= $indent . "\t<ol epub:type=\"list\""; | ||
760 | if (isset($subLevelClass)) { | ||
761 | $nav .= " class=\"" . $subLevelClass . "\""; | ||
762 | } | ||
763 | if ($subLevelHidden) { | ||
764 | $nav .= " hidden=\"hidden\""; | ||
765 | } | ||
766 | $nav .= ">\n"; | ||
767 | |||
768 | foreach ($this->navPoints as $navPoint) { | ||
769 | $retLevel = $navPoint->finalizeEPub3($nav, $playOrder, ($level+2), $subLevelClass, $subLevelHidden); | ||
770 | if ($retLevel > $maxLevel) { | ||
771 | $maxLevel = $retLevel; | ||
772 | } | ||
773 | } | ||
774 | $nav .= $indent . "\t</ol>\n"; | ||
775 | } | ||
776 | |||
777 | $nav .= $indent . "</li>\n"; | ||
778 | |||
779 | return $maxLevel; | ||
780 | } | ||
781 | } | ||
782 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/PHPePub/EPub.OPF.php b/inc/3rdparty/libraries/PHPePub/EPub.OPF.php new file mode 100644 index 00000000..803a2108 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.OPF.php | |||
@@ -0,0 +1,1226 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * ePub OPF file structure | ||
4 | * | ||
5 | * @author A. Grandt <php@grandt.com> | ||
6 | * @copyright 2009-2014 A. Grandt | ||
7 | * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
8 | * @version 3.20 | ||
9 | */ | ||
10 | class Opf { | ||
11 | const _VERSION = 3.20; | ||
12 | |||
13 | /* Core Media types. | ||
14 | * These types are the only guaranteed mime types any ePub reader must understand. | ||
15 | * Any other type muse define a fall back whose fallback chain will end in one of these. | ||
16 | */ | ||
17 | const TYPE_GIF = "image/gif"; | ||
18 | const TYPE_JPEG = "image/jpeg"; | ||
19 | const TYPE_PNG = "image/png"; | ||
20 | const TYPE_SVG = "image/svg+xml"; | ||
21 | const TYPE_XHTML = "application/xhtml+xml"; | ||
22 | const TYPE_DTBOOK = "application/x-dtbook+xml"; | ||
23 | const TYPE_CSS = "text/css"; | ||
24 | const TYPE_XML = "application/xml"; | ||
25 | const TYPE_OEB1_DOC = "text/x-oeb1-document"; // Deprecated | ||
26 | const TYPE_OEB1_CSS = "text/x-oeb1-css"; // Deprecated | ||
27 | const TYPE_NCX = "application/x-dtbncx+xml"; | ||
28 | |||
29 | private $bookVersion = EPub::BOOK_VERSION_EPUB2; | ||
30 | private $ident = "BookId"; | ||
31 | |||
32 | public $date = NULL; | ||
33 | public $metadata = NULL; | ||
34 | public $manifest = NULL; | ||
35 | public $spine = NULL; | ||
36 | public $guide = NULL; | ||
37 | |||
38 | /** | ||
39 | * Class constructor. | ||
40 | * | ||
41 | * @return void | ||
42 | */ | ||
43 | function __construct($ident = "BookId", $bookVersion = EPub::BOOK_VERSION_EPUB2) { | ||
44 | $this->setIdent($ident); | ||
45 | $this->setVersion($bookVersion); | ||
46 | $this->metadata = new Metadata(); | ||
47 | $this->manifest = new Manifest(); | ||
48 | $this->spine = new Spine(); | ||
49 | $this->guide = new Guide(); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Class destructor | ||
54 | * | ||
55 | * @return void | ||
56 | */ | ||
57 | function __destruct() { | ||
58 | unset ($this->bookVersion, $this->ident, $this->date, $this->metadata, $this->manifest, $this->spine, $this->guide); | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * | ||
63 | * Enter description here ... | ||
64 | * | ||
65 | * @param string $ident | ||
66 | */ | ||
67 | function setVersion($bookVersion) { | ||
68 | $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; | ||
69 | } | ||
70 | |||
71 | function isEPubVersion2() { | ||
72 | return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * | ||
77 | * Enter description here ... | ||
78 | * | ||
79 | * @param string $ident | ||
80 | */ | ||
81 | function setIdent($ident = "BookId") { | ||
82 | $this->ident = is_string($ident) ? trim($ident) : "BookId"; | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * | ||
87 | * Enter description here ... | ||
88 | * | ||
89 | * @return string | ||
90 | */ | ||
91 | function finalize() { | ||
92 | $opf = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | ||
93 | . "<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"" . $this->ident . "\" version=\"" . $this->bookVersion . "\">\n"; | ||
94 | |||
95 | $opf .= $this->metadata->finalize($this->bookVersion, $this->date); | ||
96 | $opf .= $this->manifest->finalize($this->bookVersion); | ||
97 | $opf .= $this->spine->finalize(); | ||
98 | |||
99 | if ($this->guide->length() > 0) { | ||
100 | $opf .= $this->guide->finalize(); | ||
101 | } | ||
102 | |||
103 | return $opf . "</package>\n"; | ||
104 | } | ||
105 | |||
106 | // Convenience functions: | ||
107 | |||
108 | /** | ||
109 | * | ||
110 | * Enter description here ... | ||
111 | * | ||
112 | * @param string $title | ||
113 | * @param string $language | ||
114 | * @param string $identifier | ||
115 | * @param string $identifierScheme | ||
116 | */ | ||
117 | function initialize($title, $language, $identifier, $identifierScheme) { | ||
118 | $this->metadata->addDublinCore(new DublinCore("title", $title)); | ||
119 | $this->metadata->addDublinCore(new DublinCore("language", $language)); | ||
120 | |||
121 | $dc = new DublinCore("identifier", $identifier); | ||
122 | $dc->addAttr("id", $this->ident); | ||
123 | $dc->addOpfAttr("scheme", $identifierScheme); | ||
124 | $this->metadata->addDublinCore($dc); | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * | ||
129 | * Enter description here ... | ||
130 | * | ||
131 | * @param string $id | ||
132 | * @param string $href | ||
133 | * @param string $mediaType | ||
134 | */ | ||
135 | function addItem($id, $href, $mediaType, $properties = NULL) { | ||
136 | $this->manifest->addItem(new Item($id, $href, $mediaType, $properties)); | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * | ||
141 | * Enter description here ... | ||
142 | * | ||
143 | * @param string $idref | ||
144 | * @param bool $linear | ||
145 | */ | ||
146 | function addItemRef($idref, $linear = TRUE) { | ||
147 | $this->spine->addItemref(new Itemref($idref, $linear)); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * | ||
152 | * Enter description here ... | ||
153 | * | ||
154 | * @param string $type | ||
155 | * @param string $title | ||
156 | * @param string $href | ||
157 | */ | ||
158 | function addReference($type, $title, $href) { | ||
159 | $this->guide->addReference(new Reference($type, $title, $href)); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * | ||
164 | * Enter description here ... | ||
165 | * | ||
166 | * @param string $name | ||
167 | * @param string $value | ||
168 | */ | ||
169 | function addDCMeta($name, $value) { | ||
170 | $this->metadata->addDublinCore(new DublinCore($name, $value)); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * | ||
175 | * Enter description here ... | ||
176 | * | ||
177 | * @param string $name | ||
178 | * @param string $content | ||
179 | */ | ||
180 | function addMeta($name, $content) { | ||
181 | $this->metadata->addMeta($name, $content); | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * | ||
186 | * Enter description here ... | ||
187 | * | ||
188 | * @param string $name | ||
189 | * @param string $fileAs | ||
190 | * @param string $role Use the MarcCode constants | ||
191 | */ | ||
192 | function addCreator($name, $fileAs = NULL, $role = NULL) { | ||
193 | $dc = new DublinCore(DublinCore::CREATOR, trim($name)); | ||
194 | |||
195 | if ($fileAs !== NULL) { | ||
196 | $dc->addOpfAttr("file-as", trim($fileAs)); | ||
197 | } | ||
198 | |||
199 | if ($role !== NULL) { | ||
200 | $dc->addOpfAttr("role", trim($role)); | ||
201 | } | ||
202 | |||
203 | $this->metadata->addDublinCore($dc); | ||
204 | } | ||
205 | |||
206 | /** | ||
207 | * | ||
208 | * Enter description here ... | ||
209 | * | ||
210 | * @param string $name | ||
211 | * @param string $fileAs | ||
212 | * @param string $role Use the MarcCode constants | ||
213 | */ | ||
214 | function addColaborator($name, $fileAs = NULL, $role = NULL) { | ||
215 | $dc = new DublinCore(DublinCore::CONTRIBUTOR, trim($name)); | ||
216 | |||
217 | if ($fileAs !== NULL) { | ||
218 | $dc->addOpfAttr("file-as", trim($fileAs)); | ||
219 | } | ||
220 | |||
221 | if ($role !== NULL) { | ||
222 | $dc->addOpfAttr("role", trim($role)); | ||
223 | } | ||
224 | |||
225 | $this->metadata->addDublinCore($dc); | ||
226 | } | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * ePub OPF Metadata structures | ||
231 | */ | ||
232 | class Metadata { | ||
233 | const _VERSION = 3.00; | ||
234 | |||
235 | private $dc = array(); | ||
236 | private $meta = array(); | ||
237 | |||
238 | /** | ||
239 | * Class constructor. | ||
240 | * | ||
241 | * @return void | ||
242 | */ | ||
243 | function __construct() { | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * Class destructor | ||
248 | * | ||
249 | * @return void | ||
250 | */ | ||
251 | function __destruct() { | ||
252 | unset ($this->dc, $this->meta); | ||
253 | } | ||
254 | |||
255 | /** | ||
256 | * | ||
257 | * Enter description here ... | ||
258 | * | ||
259 | * @param DublinCore $dc | ||
260 | */ | ||
261 | function addDublinCore($dc) { | ||
262 | if ($dc != NULL && is_object($dc) && get_class($dc) === "DublinCore") { | ||
263 | $this->dc[] = $dc; | ||
264 | } | ||
265 | } | ||
266 | |||
267 | /** | ||
268 | * | ||
269 | * Enter description here ... | ||
270 | * | ||
271 | * @param string $name | ||
272 | * @param string $content | ||
273 | */ | ||
274 | function addMeta($name, $content) { | ||
275 | $name = is_string($name) ? trim($name) : NULL; | ||
276 | if (isset($name)) { | ||
277 | $content = is_string($content) ? trim($content) : NULL; | ||
278 | } | ||
279 | if (isset($content)) { | ||
280 | $this->meta[] = array ($name => $content); | ||
281 | } | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * | ||
286 | * @param string $bookVersion | ||
287 | * @param int $date | ||
288 | * @return string | ||
289 | */ | ||
290 | function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2, $date = NULL) { | ||
291 | $metadata = "\t<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"; | ||
292 | if ($bookVersion === EPub::BOOK_VERSION_EPUB2) { | ||
293 | $metadata .= "\t\txmlns:opf=\"http://www.idpf.org/2007/opf\"\n\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"; | ||
294 | } else { | ||
295 | $metadata .= "\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"; | ||
296 | if (!isset($date)) { | ||
297 | $date = time(); | ||
298 | } | ||
299 | $metadata .= "\t\t<meta property=\"dcterms:modified\">" . gmdate("Y-m-d\TH:i:s\Z", $date) . "</meta>\n"; | ||
300 | } | ||
301 | |||
302 | foreach ($this->dc as $dc) { | ||
303 | $metadata .= $dc->finalize($bookVersion); | ||
304 | } | ||
305 | |||
306 | foreach ($this->meta as $data) { | ||
307 | list($name, $content) = each($data); | ||
308 | $metadata .= "\t\t<meta name=\"" . $name . "\" content=\"" . $content . "\" />\n"; | ||
309 | } | ||
310 | |||
311 | return $metadata . "\t</metadata>\n"; | ||
312 | } | ||
313 | } | ||
314 | |||
315 | /** | ||
316 | * ePub OPF Dublin Core (dc:) Metadata structures | ||
317 | */ | ||
318 | class DublinCore { | ||
319 | const _VERSION = 3.00; | ||
320 | |||
321 | const CONTRIBUTOR = "contributor"; | ||
322 | const COVERAGE = "coverage"; | ||
323 | const CREATOR = "creator"; | ||
324 | const DATE = "date"; | ||
325 | const DESCRIPTION = "description"; | ||
326 | const FORMAT = "format"; | ||
327 | const IDENTIFIER = "identifier"; | ||
328 | const LANGUAGE = "language"; | ||
329 | const PUBLISHER = "publisher"; | ||
330 | const RELATION = "relation"; | ||
331 | const RIGHTS = "rights"; | ||
332 | const SOURCE = "source"; | ||
333 | const SUBJECT = "subject"; | ||
334 | const TITLE = "title"; | ||
335 | const TYPE = "type"; | ||
336 | |||
337 | private $dcName = NULL; | ||
338 | private $dcValue = NULL; | ||
339 | private $attr = array(); | ||
340 | private $opfAttr = array(); | ||
341 | |||
342 | /** | ||
343 | * Class constructor. | ||
344 | * | ||
345 | * @return void | ||
346 | */ | ||
347 | function __construct($name, $value) { | ||
348 | $this->setDc($name, $value); | ||
349 | } | ||
350 | |||
351 | /** | ||
352 | * Class destructor | ||
353 | * | ||
354 | * @return void | ||
355 | */ | ||
356 | function __destruct() { | ||
357 | unset ($this->dcName, $this->dcValue, $this->attr, $this->opfAttr); | ||
358 | } | ||
359 | |||
360 | /** | ||
361 | * | ||
362 | * Enter description here ... | ||
363 | * | ||
364 | * @param string $name | ||
365 | * @param string $value | ||
366 | */ | ||
367 | function setDc($name, $value) { | ||
368 | $this->dcName = is_string($name) ? trim($name) : NULL; | ||
369 | if (isset($this->dcName)) { | ||
370 | $this->dcValue = isset($value) ? (string)$value : NULL; | ||
371 | } | ||
372 | if (! isset($this->dcValue)) { | ||
373 | $this->dcName = NULL; | ||
374 | } | ||
375 | } | ||
376 | |||
377 | /** | ||
378 | * | ||
379 | * Enter description here ... | ||
380 | * | ||
381 | * @param string $attrName | ||
382 | * @param string $attrValue | ||
383 | */ | ||
384 | function addAttr($attrName, $attrValue) { | ||
385 | $attrName = is_string($attrName) ? trim($attrName) : NULL; | ||
386 | if (isset($attrName)) { | ||
387 | $attrValue = is_string($attrValue) ? trim($attrValue) : NULL; | ||
388 | } | ||
389 | if (isset($attrValue)) { | ||
390 | $this->attr[$attrName] = $attrValue; | ||
391 | } | ||
392 | } | ||
393 | |||
394 | /** | ||
395 | * | ||
396 | * Enter description here ... | ||
397 | * | ||
398 | * @param string $opfAttrName | ||
399 | * @param string $opfAttrValue | ||
400 | */ | ||
401 | function addOpfAttr($opfAttrName, $opfAttrValue) { | ||
402 | $opfAttrName = is_string($opfAttrName) ? trim($opfAttrName) : NULL; | ||
403 | if (isset($opfAttrName)) { | ||
404 | $opfAttrValue = is_string($opfAttrValue) ? trim($opfAttrValue) : NULL; | ||
405 | } | ||
406 | if (isset($opfAttrValue)) { | ||
407 | $this->opfAttr[$opfAttrName] = $opfAttrValue; | ||
408 | } | ||
409 | } | ||
410 | |||
411 | |||
412 | /** | ||
413 | * | ||
414 | * @param string $bookVersion | ||
415 | * @return string | ||
416 | */ | ||
417 | function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2) { | ||
418 | $dc = "\t\t<dc:" . $this->dcName; | ||
419 | |||
420 | if (sizeof($this->attr) > 0) { | ||
421 | while (list($name, $content) = each($this->attr)) { | ||
422 | $dc .= " " . $name . "=\"" . $content . "\""; | ||
423 | } | ||
424 | } | ||
425 | |||
426 | if ($bookVersion === EPub::BOOK_VERSION_EPUB2 && sizeof($this->opfAttr) > 0) { | ||
427 | while (list($name, $content) = each($this->opfAttr)) { | ||
428 | $dc .= " opf:" . $name . "=\"" . $content . "\""; | ||
429 | } | ||
430 | } | ||
431 | |||
432 | return $dc . ">" . $this->dcValue . "</dc:" . $this->dcName . ">\n"; | ||
433 | } | ||
434 | } | ||
435 | |||
436 | /** | ||
437 | * ePub OPF Manifest structure | ||
438 | */ | ||
439 | class Manifest { | ||
440 | const _VERSION = 3.00; | ||
441 | |||
442 | private $items = array(); | ||
443 | |||
444 | /** | ||
445 | * Class constructor. | ||
446 | * | ||
447 | * @return void | ||
448 | */ | ||
449 | function __construct() { | ||
450 | } | ||
451 | |||
452 | /** | ||
453 | * Class destructor | ||
454 | * | ||
455 | * @return void | ||
456 | */ | ||
457 | function __destruct() { | ||
458 | unset ($this->items); | ||
459 | } | ||
460 | |||
461 | /** | ||
462 | * | ||
463 | * Enter description here ... | ||
464 | * | ||
465 | * @param Item $item | ||
466 | */ | ||
467 | function addItem($item) { | ||
468 | if ($item != NULL && is_object($item) && get_class($item) === "Item") { | ||
469 | $this->items[] = $item; | ||
470 | } | ||
471 | } | ||
472 | |||
473 | /** | ||
474 | * | ||
475 | * @param string $bookVersion | ||
476 | * @return string | ||
477 | */ | ||
478 | function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2) { | ||
479 | $manifest = "\n\t<manifest>\n"; | ||
480 | foreach ($this->items as $item) { | ||
481 | $manifest .= $item->finalize($bookVersion); | ||
482 | } | ||
483 | return $manifest . "\t</manifest>\n"; | ||
484 | } | ||
485 | } | ||
486 | |||
487 | /** | ||
488 | * ePub OPF Item structure | ||
489 | */ | ||
490 | class Item { | ||
491 | const _VERSION = 3.00; | ||
492 | |||
493 | private $id = NULL; | ||
494 | private $href = NULL; | ||
495 | private $mediaType = NULL; | ||
496 | private $properties = NULL; | ||
497 | private $requiredNamespace = NULL; | ||
498 | private $requiredModules = NULL; | ||
499 | private $fallback = NULL; | ||
500 | private $fallbackStyle = NULL; | ||
501 | |||
502 | /** | ||
503 | * Class constructor. | ||
504 | * | ||
505 | * @return void | ||
506 | */ | ||
507 | function __construct($id, $href, $mediaType, $properties = NULL) { | ||
508 | $this->setId($id); | ||
509 | $this->setHref($href); | ||
510 | $this->setMediaType($mediaType); | ||
511 | $this->setProperties($properties); | ||
512 | } | ||
513 | |||
514 | /** | ||
515 | * Class destructor | ||
516 | * | ||
517 | * @return void | ||
518 | */ | ||
519 | function __destruct() { | ||
520 | unset ($this->id, $this->href, $this->mediaType); | ||
521 | unset ($this->properties, $this->requiredNamespace, $this->requiredModules, $this->fallback, $this->fallbackStyle); | ||
522 | } | ||
523 | |||
524 | /** | ||
525 | * | ||
526 | * Enter description here ... | ||
527 | * | ||
528 | * @param string $id | ||
529 | */ | ||
530 | function setId($id) { | ||
531 | $this->id = is_string($id) ? trim($id) : NULL; | ||
532 | } | ||
533 | |||
534 | /** | ||
535 | * | ||
536 | * Enter description here ... | ||
537 | * | ||
538 | * @param string $href | ||
539 | */ | ||
540 | function setHref($href) { | ||
541 | $this->href = is_string($href) ? trim($href) : NULL; | ||
542 | } | ||
543 | |||
544 | /** | ||
545 | * | ||
546 | * Enter description here ... | ||
547 | * | ||
548 | * @param string $mediaType | ||
549 | */ | ||
550 | function setMediaType($mediaType) { | ||
551 | $this->mediaType = is_string($mediaType) ? trim($mediaType) : NULL; | ||
552 | } | ||
553 | |||
554 | /** | ||
555 | * | ||
556 | * Enter description here ... | ||
557 | * | ||
558 | * @param string $properties | ||
559 | */ | ||
560 | function setProperties($properties) { | ||
561 | $this->properties = is_string($properties) ? trim($properties) : NULL; | ||
562 | } | ||
563 | |||
564 | /** | ||
565 | * | ||
566 | * Enter description here ... | ||
567 | * | ||
568 | * @param string $requiredNamespace | ||
569 | */ | ||
570 | function setRequiredNamespace($requiredNamespace) { | ||
571 | $this->requiredNamespace = is_string($requiredNamespace) ? trim($requiredNamespace) : NULL; | ||
572 | } | ||
573 | |||
574 | /** | ||
575 | * | ||
576 | * Enter description here ... | ||
577 | * | ||
578 | * @param string $requiredModules | ||
579 | */ | ||
580 | function setRequiredModules($requiredModules) { | ||
581 | $this->requiredModules = is_string($requiredModules) ? trim($requiredModules) : NULL; | ||
582 | } | ||
583 | |||
584 | /** | ||
585 | * | ||
586 | * Enter description here ... | ||
587 | * | ||
588 | * @param string $fallback | ||
589 | */ | ||
590 | function setfallback($fallback) { | ||
591 | $this->fallback = is_string($fallback) ? trim($fallback) : NULL; | ||
592 | } | ||
593 | |||
594 | /** | ||
595 | * | ||
596 | * Enter description here ... | ||
597 | * | ||
598 | * @param string $fallbackStyle | ||
599 | */ | ||
600 | function setFallbackStyle($fallbackStyle) { | ||
601 | $this->fallbackStyle = is_string($fallbackStyle) ? trim($fallbackStyle) : NULL; | ||
602 | } | ||
603 | |||
604 | /** | ||
605 | * | ||
606 | * @param string $bookVersion | ||
607 | * @return string | ||
608 | */ | ||
609 | function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2) { | ||
610 | $item = "\t\t<item id=\"" . $this->id . "\" href=\"" . $this->href . "\" media-type=\"" . $this->mediaType . "\" "; | ||
611 | if ($bookVersion === EPub::BOOK_VERSION_EPUB3 && isset($this->properties)) { | ||
612 | $item .= "properties=\"" . $this->properties . "\" "; | ||
613 | } | ||
614 | if (isset($this->requiredNamespace)) { | ||
615 | $item .= "\n\t\t\trequired-namespace=\"" . $this->requiredNamespace . "\" "; | ||
616 | if (isset($this->requiredModules)) { | ||
617 | $item .= "required-modules=\"" . $this->requiredModules . "\" "; | ||
618 | } | ||
619 | } | ||
620 | if (isset($this->fallback)) { | ||
621 | $item .= "\n\t\t\tfallback=\"" . $this->fallback . "\" "; | ||
622 | } | ||
623 | if (isset($this->fallbackStyle)) { | ||
624 | $item .= "\n\t\t\tfallback-style=\"" . $this->fallbackStyle . "\" "; | ||
625 | } | ||
626 | return $item . "/>\n"; | ||
627 | } | ||
628 | } | ||
629 | |||
630 | /** | ||
631 | * ePub OPF Spine structure | ||
632 | */ | ||
633 | class Spine { | ||
634 | const _VERSION = 1.00; | ||
635 | |||
636 | private $itemrefs = array(); | ||
637 | private $toc = NULL; | ||
638 | |||
639 | /** | ||
640 | * Class constructor. | ||
641 | * | ||
642 | * @return void | ||
643 | */ | ||
644 | function __construct($toc = "ncx") { | ||
645 | $this->setToc($toc); | ||
646 | } | ||
647 | |||
648 | /** | ||
649 | * Class destructor | ||
650 | * | ||
651 | * @return void | ||
652 | */ | ||
653 | function __destruct() { | ||
654 | unset ($this->itemrefs, $this->toc); | ||
655 | } | ||
656 | |||
657 | /** | ||
658 | * | ||
659 | * Enter description here ... | ||
660 | * | ||
661 | * @param string $toc | ||
662 | */ | ||
663 | function setToc($toc) { | ||
664 | $this->toc = is_string($toc) ? trim($toc) : NULL; | ||
665 | } | ||
666 | |||
667 | /** | ||
668 | * | ||
669 | * Enter description here ... | ||
670 | * | ||
671 | * @param Itemref $itemref | ||
672 | */ | ||
673 | function addItemref($itemref) { | ||
674 | if ($itemref != NULL | ||
675 | && is_object($itemref) | ||
676 | && get_class($itemref) === "Itemref" | ||
677 | && !isset($this->itemrefs[$itemref->getIdref()])) { | ||
678 | $this->itemrefs[$itemref->getIdref()] = $itemref; | ||
679 | } | ||
680 | } | ||
681 | |||
682 | /** | ||
683 | * | ||
684 | * Enter description here ... | ||
685 | * | ||
686 | * @return string | ||
687 | */ | ||
688 | function finalize() { | ||
689 | $spine = "\n\t<spine toc=\"" . $this->toc . "\">\n"; | ||
690 | foreach ($this->itemrefs as $itemref) { | ||
691 | $spine .= $itemref->finalize(); | ||
692 | } | ||
693 | return $spine . "\t</spine>\n"; | ||
694 | } | ||
695 | } | ||
696 | |||
697 | /** | ||
698 | * ePub OPF ItemRef structure | ||
699 | */ | ||
700 | class Itemref { | ||
701 | const _VERSION = 3.00; | ||
702 | |||
703 | private $idref = NULL; | ||
704 | private $linear = TRUE; | ||
705 | |||
706 | /** | ||
707 | * Class constructor. | ||
708 | * | ||
709 | * @return void | ||
710 | */ | ||
711 | function __construct($idref, $linear = TRUE) { | ||
712 | $this->setIdref($idref); | ||
713 | $this->setLinear($linear); | ||
714 | } | ||
715 | |||
716 | /** | ||
717 | * Class destructor | ||
718 | * | ||
719 | * @return void | ||
720 | */ | ||
721 | function __destruct() { | ||
722 | unset ($this->idref, $this->linear); | ||
723 | } | ||
724 | |||
725 | /** | ||
726 | * | ||
727 | * Enter description here ... | ||
728 | * | ||
729 | * @param string $idref | ||
730 | */ | ||
731 | function setIdref($idref) { | ||
732 | $this->idref = is_string($idref) ? trim($idref) : NULL; | ||
733 | } | ||
734 | |||
735 | /** | ||
736 | * | ||
737 | * Enter description here ... | ||
738 | * | ||
739 | * @return string $idref | ||
740 | */ | ||
741 | function getIdref() { | ||
742 | return $this->idref; | ||
743 | } | ||
744 | |||
745 | /** | ||
746 | * | ||
747 | * Enter description here ... | ||
748 | * | ||
749 | * @param bool $linear | ||
750 | */ | ||
751 | function setLinear($linear = TRUE) { | ||
752 | $this->linear = $linear === TRUE; | ||
753 | } | ||
754 | |||
755 | /** | ||
756 | * | ||
757 | * Enter description here ... | ||
758 | * | ||
759 | * @return string | ||
760 | */ | ||
761 | function finalize() { | ||
762 | $itemref = "\t\t<itemref idref=\"" . $this->idref . "\""; | ||
763 | if ($this->linear == FALSE) { | ||
764 | return $itemref .= " linear=\"no\" />\n"; | ||
765 | } | ||
766 | return $itemref . " />\n"; | ||
767 | } | ||
768 | } | ||
769 | |||
770 | /** | ||
771 | * ePub OPF Guide structure | ||
772 | */ | ||
773 | class Guide { | ||
774 | const _VERSION = 3.00; | ||
775 | |||
776 | private $references = array(); | ||
777 | |||
778 | /** | ||
779 | * Class constructor. | ||
780 | * | ||
781 | * @return void | ||
782 | */ | ||
783 | function __construct() { | ||
784 | } | ||
785 | |||
786 | /** | ||
787 | * Class destructor | ||
788 | * | ||
789 | * @return void | ||
790 | */ | ||
791 | function __destruct() { | ||
792 | unset ($this->references); | ||
793 | } | ||
794 | |||
795 | /** | ||
796 | * | ||
797 | * Enter description here ... | ||
798 | * | ||
799 | */ | ||
800 | function length() { | ||
801 | return sizeof($this->references); | ||
802 | } | ||
803 | |||
804 | /** | ||
805 | * | ||
806 | * Enter description here ... | ||
807 | * | ||
808 | * @param Reference $reference | ||
809 | */ | ||
810 | function addReference($reference) { | ||
811 | if ($reference != NULL && is_object($reference) && get_class($reference) === "Reference") { | ||
812 | $this->references[] = $reference; | ||
813 | } | ||
814 | } | ||
815 | |||
816 | /** | ||
817 | * | ||
818 | * Enter description here ... | ||
819 | * | ||
820 | * @return string | ||
821 | */ | ||
822 | function finalize() { | ||
823 | $ref = ""; | ||
824 | if (sizeof($this->references) > 0) { | ||
825 | $ref = "\n\t<guide>\n"; | ||
826 | foreach ($this->references as $reference) { | ||
827 | $ref .= $reference->finalize(); | ||
828 | } | ||
829 | $ref .= "\t</guide>\n"; | ||
830 | } | ||
831 | return $ref; | ||
832 | } | ||
833 | } | ||
834 | |||
835 | /** | ||
836 | * Reference constants | ||
837 | */ | ||
838 | class Reference { | ||
839 | const _VERSION = 1.00; | ||
840 | |||
841 | /* REFERENCE types are derived from the "Chicago Manual of Style" | ||
842 | */ | ||
843 | |||
844 | /** Acknowledgements page */ | ||
845 | const ACKNOWLEDGEMENTS = "acknowledgements"; | ||
846 | |||
847 | /** Bibliography page */ | ||
848 | const BIBLIOGRAPHY = "bibliography"; | ||
849 | |||
850 | /** Colophon page */ | ||
851 | const COLOPHON = "colophon"; | ||
852 | |||
853 | /** Copyright page */ | ||
854 | const COPYRIGHT_PAGE = "copyright-page"; | ||
855 | |||
856 | /** Dedication */ | ||
857 | const DEDICATION = "dedication"; | ||
858 | |||
859 | /** Epigraph */ | ||
860 | const EPIGRAPH = "epigraph"; | ||
861 | |||
862 | /** Foreword */ | ||
863 | const FOREWORD = "foreword"; | ||
864 | |||
865 | /** Glossary page */ | ||
866 | const GLOSSARY = "glossary"; | ||
867 | |||
868 | /** back-of-book style index */ | ||
869 | const INDEX = "index"; | ||
870 | |||
871 | /** List of illustrations */ | ||
872 | const LIST_OF_ILLUSTRATIONS = "loi"; | ||
873 | |||
874 | /** List of tables */ | ||
875 | const LIST_OF_TABLES = "lot"; | ||
876 | |||
877 | /** Notes page */ | ||
878 | const NOTES = "notes"; | ||
879 | |||
880 | /** Preface page */ | ||
881 | const PREFACE = "preface"; | ||
882 | |||
883 | /** Table of contents */ | ||
884 | const TABLE_OF_CONTENTS = "toc"; | ||
885 | |||
886 | /** Page with possibly title, author, publisher, and other metadata */ | ||
887 | const TITLE_PAGE = "titlepage"; | ||
888 | |||
889 | /** First page of the book, ie. first page of the first chapter */ | ||
890 | const TEXT = "text"; | ||
891 | |||
892 | // ****************** | ||
893 | // ePub3 constants | ||
894 | // ****************** | ||
895 | |||
896 | // Document partitions | ||
897 | /** The publications cover(s), jacket information, etc. This is officially in ePub3, but works for ePub 2 as well */ | ||
898 | const COVER = "cover"; | ||
899 | |||
900 | /** Preliminary material to the content body, such as tables of contents, dedications, etc. */ | ||
901 | const FRONTMATTER = "frontmatter"; | ||
902 | |||
903 | /** The main (body) content of a document. */ | ||
904 | const BODYMATTER = "bodymatter"; | ||
905 | |||
906 | /** Ancillary material occurring after the document body, such as indices, appendices, etc. */ | ||
907 | const BACKMATTER = "backmatter"; | ||
908 | |||
909 | |||
910 | private $type = NULL; | ||
911 | private $title = NULL; | ||
912 | private $href = NULL; | ||
913 | |||
914 | /** | ||
915 | * Class constructor. | ||
916 | * | ||
917 | * @param string $type | ||
918 | * @param string $title | ||
919 | * @param string $href | ||
920 | */ | ||
921 | function __construct($type, $title, $href) { | ||
922 | $this->setType($type); | ||
923 | $this->setTitle($title); | ||
924 | $this->setHref($href); | ||
925 | } | ||
926 | |||
927 | /** | ||
928 | * Class destructor | ||
929 | * | ||
930 | * @return void | ||
931 | */ | ||
932 | function __destruct() { | ||
933 | unset ($this->type, $this->title, $this->href); | ||
934 | } | ||
935 | |||
936 | /** | ||
937 | * | ||
938 | * Enter description here ... | ||
939 | * | ||
940 | * @param string $type | ||
941 | */ | ||
942 | function setType($type) { | ||
943 | $this->type = is_string($type) ? trim($type) : NULL; | ||
944 | } | ||
945 | |||
946 | /** | ||
947 | * | ||
948 | * Enter description here ... | ||
949 | * | ||
950 | * @param string $title | ||
951 | */ | ||
952 | function setTitle($title) { | ||
953 | $this->title = is_string($title) ? trim($title) : NULL; | ||
954 | } | ||
955 | |||
956 | /** | ||
957 | * | ||
958 | * Enter description here ... | ||
959 | * | ||
960 | * @param string $href | ||
961 | */ | ||
962 | function setHref($href) { | ||
963 | $this->href = is_string($href) ? trim($href) : NULL; | ||
964 | } | ||
965 | |||
966 | /** | ||
967 | * | ||
968 | * Enter description here ... | ||
969 | * | ||
970 | * @return string | ||
971 | */ | ||
972 | function finalize() { | ||
973 | return "\t\t<reference type=\"" . $this->type . "\" title=\"" . $this->title . "\" href=\"" . $this->href . "\" />\n"; | ||
974 | } | ||
975 | } | ||
976 | |||
977 | /** | ||
978 | * Common Marc codes. | ||
979 | * Ref: http://www.loc.gov/marc/relators/ | ||
980 | */ | ||
981 | class MarcCode { | ||
982 | const _VERSION = 3.00; | ||
983 | |||
984 | /** | ||
985 | * Adapter | ||
986 | * | ||
987 | * Use for a person who | ||
988 | * 1) reworks a musical composition, usually for a different medium, or | ||
989 | * 2) rewrites novels or stories for motion pictures or other audiovisual medium. | ||
990 | */ | ||
991 | const ADAPTER = "adp"; | ||
992 | |||
993 | /** | ||
994 | * Annotator | ||
995 | * | ||
996 | * Use for a person who writes manuscript annotations on a printed item. | ||
997 | */ | ||
998 | const ANNOTATOR = "ann"; | ||
999 | |||
1000 | /** | ||
1001 | * Arranger | ||
1002 | * | ||
1003 | * Use for a person who transcribes a musical composition, usually for a different | ||
1004 | * medium from that of the original; in an arrangement the musical substance remains | ||
1005 | * essentially unchanged. | ||
1006 | */ | ||
1007 | const ARRANGER = "arr"; | ||
1008 | |||
1009 | /** | ||
1010 | * Artist | ||
1011 | * | ||
1012 | * Use for a person (e.g., a painter) who conceives, and perhaps also implements, | ||
1013 | * an original graphic design or work of art, if specific codes (e.g., [egr], | ||
1014 | * [etr]) are not desired. For book illustrators, prefer Illustrator [ill]. | ||
1015 | */ | ||
1016 | const ARTIST = "art"; | ||
1017 | |||
1018 | /** | ||
1019 | * Associated name | ||
1020 | * | ||
1021 | * Use as a general relator for a name associated with or found in an item or | ||
1022 | * collection, or which cannot be determined to be that of a Former owner [fmo] | ||
1023 | * or other designated relator indicative of provenance. | ||
1024 | */ | ||
1025 | const ASSOCIATED_NAME = "asn"; | ||
1026 | |||
1027 | /** | ||
1028 | * Author | ||
1029 | * | ||
1030 | * Use for a person or corporate body chiefly responsible for the intellectual | ||
1031 | * or artistic content of a work. This term may also be used when more than one | ||
1032 | * person or body bears such responsibility. | ||
1033 | */ | ||
1034 | const AUTHOR = "aut"; | ||
1035 | |||
1036 | /** | ||
1037 | * Author in quotations or text extracts | ||
1038 | * | ||
1039 | * Use for a person whose work is largely quoted or extracted in a works to which | ||
1040 | * he or she did not contribute directly. Such quotations are found particularly | ||
1041 | * in exhibition catalogs, collections of photographs, etc. | ||
1042 | */ | ||
1043 | const AUTHOR_IN_QUOTES = "aqt"; | ||
1044 | |||
1045 | /** | ||
1046 | * Author of afterword, colophon, etc. | ||
1047 | * | ||
1048 | * Use for a person or corporate body responsible for an afterword, postface, | ||
1049 | * colophon, etc. but who is not the chief author of a work. | ||
1050 | */ | ||
1051 | const AUTHOR_OF_AFTERWORD = "aft"; | ||
1052 | |||
1053 | /** | ||
1054 | * Author of introduction, etc. | ||
1055 | * | ||
1056 | * Use for a person or corporate body responsible for an introduction, preface, | ||
1057 | * foreword, or other critical matter, but who is not the chief author. | ||
1058 | */ | ||
1059 | const AUTHOR_OF_INTRO = "aui"; | ||
1060 | |||
1061 | /** | ||
1062 | * Bibliographic antecedent | ||
1063 | * | ||
1064 | * Use for the author responsible for a work upon which the work represented by | ||
1065 | * the catalog record is based. This can be appropriate for adaptations, sequels, | ||
1066 | * continuations, indexes, etc. | ||
1067 | */ | ||
1068 | const BIB_ANTECEDENT = "ant"; | ||
1069 | |||
1070 | /** | ||
1071 | * Book producer | ||
1072 | * | ||
1073 | * Use for the person or firm responsible for the production of books and other | ||
1074 | * print media, if specific codes (e.g., [bkd], [egr], [tyd], [prt]) are not desired. | ||
1075 | */ | ||
1076 | const BOOK_PRODUCER = "bkp"; | ||
1077 | |||
1078 | /** | ||
1079 | * Collaborator | ||
1080 | * | ||
1081 | * Use for a person or corporate body that takes a limited part in the elaboration | ||
1082 | * of a work of another author or that brings complements (e.g., appendices, notes) | ||
1083 | * to the work of another author. | ||
1084 | */ | ||
1085 | const COLABORATOR = "clb"; | ||
1086 | |||
1087 | /** | ||
1088 | * Commentator | ||
1089 | * | ||
1090 | * Use for a person who provides interpretation, analysis, or a discussion of the | ||
1091 | * subject matter on a recording, motion picture, or other audiovisual medium. | ||
1092 | * Compiler [com] Use for a person who produces a work or publication by selecting | ||
1093 | * and putting together material from the works of various persons or bodies. | ||
1094 | */ | ||
1095 | const COMMENTATOR = "cmm"; | ||
1096 | |||
1097 | /** | ||
1098 | * Designer | ||
1099 | * | ||
1100 | * Use for a person or organization responsible for design if specific codes (e.g., | ||
1101 | * [bkd], [tyd]) are not desired. | ||
1102 | */ | ||
1103 | const DESIGNER = "dsr"; | ||
1104 | |||
1105 | /** | ||
1106 | * Editor | ||
1107 | * | ||
1108 | * Use for a person who prepares for publication a work not primarily his/her own, | ||
1109 | * such as by elucidating text, adding introductory or other critical matter, or | ||
1110 | * technically directing an editorial staff. | ||
1111 | */ | ||
1112 | const EDITORT = "edt"; | ||
1113 | |||
1114 | /** | ||
1115 | * Illustrator | ||
1116 | * | ||
1117 | * Use for the person who conceives, and perhaps also implements, a design or | ||
1118 | * illustration, usually to accompany a written text. | ||
1119 | */ | ||
1120 | const ILLUSTRATOR = "ill"; | ||
1121 | |||
1122 | /** | ||
1123 | * Lyricist | ||
1124 | * | ||
1125 | * Use for the writer of the text of a song. | ||
1126 | */ | ||
1127 | const LYRICIST = "lyr"; | ||
1128 | |||
1129 | /** | ||
1130 | * Metadata contact | ||
1131 | * | ||
1132 | * Use for the person or organization primarily responsible for compiling and | ||
1133 | * maintaining the original description of a metadata set (e.g., geospatial | ||
1134 | * metadata set). | ||
1135 | */ | ||
1136 | const METADATA_CONTACT = "mdc"; | ||
1137 | |||
1138 | /** | ||
1139 | * Musician | ||
1140 | * | ||
1141 | * Use for the person who performs music or contributes to the musical content | ||
1142 | * of a work when it is not possible or desirable to identify the function more | ||
1143 | * precisely. | ||
1144 | */ | ||
1145 | const MUSICIAN = "mus"; | ||
1146 | |||
1147 | /** | ||
1148 | * Narrator | ||
1149 | * | ||
1150 | * Use for the speaker who relates the particulars of an act, occurrence, or | ||
1151 | * course of events. | ||
1152 | */ | ||
1153 | const NARRATOR = "nrt"; | ||
1154 | |||
1155 | /** | ||
1156 | * Other | ||
1157 | * | ||
1158 | * Use for relator codes from other lists which have no equivalent in the MARC | ||
1159 | * list or for terms which have not been assigned a code. | ||
1160 | */ | ||
1161 | const OTHER = "oth"; | ||
1162 | |||
1163 | /** | ||
1164 | * Photographer | ||
1165 | * | ||
1166 | * Use for the person or organization responsible for taking photographs, whether | ||
1167 | * they are used in their original form or as reproductions. | ||
1168 | */ | ||
1169 | const PHOTOGRAPHER = "pht"; | ||
1170 | |||
1171 | /** | ||
1172 | * Printer | ||
1173 | * | ||
1174 | * Use for the person or organization who prints texts, whether from type or plates. | ||
1175 | */ | ||
1176 | const PRINTER = "prt"; | ||
1177 | |||
1178 | /** | ||
1179 | * Redactor | ||
1180 | * | ||
1181 | * Use for a person who writes or develops the framework for an item without | ||
1182 | * being intellectually responsible for its content. | ||
1183 | */ | ||
1184 | const REDACTOR = "red"; | ||
1185 | |||
1186 | /** | ||
1187 | * Reviewer | ||
1188 | * | ||
1189 | * Use for a person or corporate body responsible for the review of book, motion | ||
1190 | * picture, performance, etc. | ||
1191 | */ | ||
1192 | const REVIEWER = "rev"; | ||
1193 | |||
1194 | /** | ||
1195 | * Sponsor | ||
1196 | * | ||
1197 | * Use for the person or agency that issued a contract, or under whose auspices | ||
1198 | * a work has been written, printed, published, etc. | ||
1199 | */ | ||
1200 | const SPONSOR = "spn"; | ||
1201 | |||
1202 | /** | ||
1203 | * Thesis advisor | ||
1204 | * | ||
1205 | * Use for the person under whose supervision a degree candidate develops and | ||
1206 | * presents a thesis, memoir, or text of a dissertation. | ||
1207 | */ | ||
1208 | const THESIS_ADVISOR = "ths"; | ||
1209 | |||
1210 | /** | ||
1211 | * Transcriber | ||
1212 | * | ||
1213 | * Use for a person who prepares a handwritten or typewritten copy from original | ||
1214 | * material, including from dictated or orally recorded material. | ||
1215 | */ | ||
1216 | const TRANSCRIBER = "trc"; | ||
1217 | |||
1218 | /** | ||
1219 | * Translator | ||
1220 | * | ||
1221 | * Use for a person who renders a text from one language into another, or from | ||
1222 | * an older form of a language into the modern form. | ||
1223 | */ | ||
1224 | const TRANSLATOR = "trl"; | ||
1225 | } | ||
1226 | ?> | ||
diff --git a/inc/3rdparty/libraries/PHPePub/EPub.php b/inc/3rdparty/libraries/PHPePub/EPub.php new file mode 100644 index 00000000..d9b990b7 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.php | |||
@@ -0,0 +1,2438 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Create an ePub compatible book file. | ||
4 | * | ||
5 | * Please note, once finalized a book can no longer have chapters of data added or changed. | ||
6 | * | ||
7 | * License: GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
8 | * | ||
9 | * Thanks to: Adam Schmalhofer and Kirstyn Fox for invaluable input and for "nudging" me in the right direction :) | ||
10 | * | ||
11 | * @author A. Grandt <php@grandt.com> | ||
12 | * @copyright 2009-2014 A. Grandt | ||
13 | * @license GNU LGPL 2.1 | ||
14 | * @version 3.20 | ||
15 | * @link http://www.phpclasses.org/package/6115 | ||
16 | * @link https://github.com/Grandt/PHPePub | ||
17 | * @uses Zip.php version 1.50; http://www.phpclasses.org/browse/package/6110.html or https://github.com/Grandt/PHPZip | ||
18 | */ | ||
19 | class EPub { | ||
20 | const VERSION = 3.20; | ||
21 | const REQ_ZIP_VERSION = 1.60; | ||
22 | |||
23 | const IDENTIFIER_UUID = 'UUID'; | ||
24 | const IDENTIFIER_URI = 'URI'; | ||
25 | const IDENTIFIER_ISBN = 'ISBN'; | ||
26 | |||
27 | /** Ignore all external references, and do not process the file for these */ | ||
28 | const EXTERNAL_REF_IGNORE = 0; | ||
29 | /** Process the file for external references and add them to the book */ | ||
30 | const EXTERNAL_REF_ADD = 1; | ||
31 | /** Process the file for external references and add them to the book, but remove images, and img tags */ | ||
32 | const EXTERNAL_REF_REMOVE_IMAGES = 2; | ||
33 | /** Process the file for external references and add them to the book, but replace images, and img tags with [image] */ | ||
34 | const EXTERNAL_REF_REPLACE_IMAGES = 3; | ||
35 | |||
36 | const DIRECTION_LEFT_TO_RIGHT = "ltr"; | ||
37 | const DIRECTION_RIGHT_TO_LEFT = "rtl"; | ||
38 | |||
39 | const BOOK_VERSION_EPUB2 = "2.0"; | ||
40 | const BOOK_VERSION_EPUB3 = "3.0"; | ||
41 | |||
42 | private $bookVersion = EPub::BOOK_VERSION_EPUB2; | ||
43 | |||
44 | private $debugInside = FALSE; | ||
45 | |||
46 | public $maxImageWidth = 768; | ||
47 | public $maxImageHeight = 1024; | ||
48 | |||
49 | public $splitDefaultSize = 250000; | ||
50 | /** Gifs can crash some early ADE based readers, and are disabled by default. | ||
51 | * getImage will convert these if it can, unless this is set to TRUE. | ||
52 | */ | ||
53 | public $isGifImagesEnabled = FALSE; | ||
54 | public $isReferencesAddedToToc = TRUE; | ||
55 | |||
56 | private $zip; | ||
57 | |||
58 | private $title = ""; | ||
59 | private $language = "en"; | ||
60 | private $identifier = ""; | ||
61 | private $identifierType = ""; | ||
62 | private $description = ""; | ||
63 | private $author = ""; | ||
64 | private $authorSortKey = ""; | ||
65 | private $publisherName = ""; | ||
66 | private $publisherURL = ""; | ||
67 | private $date = 0; | ||
68 | private $rights = ""; | ||
69 | private $coverage = ""; | ||
70 | private $relation = ""; | ||
71 | private $sourceURL = ""; | ||
72 | |||
73 | private $chapterCount = 0; | ||
74 | private $opf = NULL; | ||
75 | private $ncx = NULL; | ||
76 | private $isFinalized = FALSE; | ||
77 | private $isCoverImageSet = FALSE; | ||
78 | private $buildTOC = FALSE; | ||
79 | private $tocTitle = NULL; | ||
80 | private $tocFileName = NULL; | ||
81 | private $tocCSSClass = NULL; | ||
82 | private $tocAddReferences = FALSE; | ||
83 | private $tocCssFileName = NULL; | ||
84 | |||
85 | private $fileList = array(); | ||
86 | private $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT; | ||
87 | private $languageCode = "en"; | ||
88 | |||
89 | /** | ||
90 | * Used for building the TOC. | ||
91 | * If this list is overwritten it MUST contain at least "text" as an element. | ||
92 | */ | ||
93 | public $referencesOrder = NULL; | ||
94 | |||
95 | private $dateformat = 'Y-m-d\TH:i:s.000000P'; // ISO 8601 long | ||
96 | private $dateformatShort = 'Y-m-d'; // short date format to placate ePubChecker. | ||
97 | private $headerDateFormat = "D, d M Y H:i:s T"; | ||
98 | |||
99 | protected $isCurlInstalled; | ||
100 | protected $isGdInstalled; | ||
101 | protected $isExifInstalled; | ||
102 | protected $isFileGetContentsInstalled; | ||
103 | protected $isFileGetContentsExtInstalled; | ||
104 | |||
105 | private $bookRoot = "OEBPS/"; | ||
106 | private $docRoot = NULL; | ||
107 | private $EPubMark = TRUE; | ||
108 | private $generator = ""; | ||
109 | |||
110 | private $log = NULL; | ||
111 | public $isLogging = TRUE; | ||
112 | |||
113 | public $encodeHTML = FALSE; | ||
114 | |||
115 | private $mimetypes = array( | ||
116 | "js" => "application/x-javascript", "swf" => "application/x-shockwave-flash", "xht" => "application/xhtml+xml", "xhtml" => "application/xhtml+xml", "zip" => "application/zip", | ||
117 | "aif" => "audio/x-aiff", "aifc" => "audio/x-aiff", "aiff" => "audio/x-aiff", "au" => "audio/basic", "kar" => "audio/midi", "m3u" => "audio/x-mpegurl", "mid" => "audio/midi", "midi" => "audio/midi", "mp2" => "audio/mpeg", "mp3" => "audio/mpeg", "mpga" => "audio/mpeg", "oga" => "audio/ogg", "ogg" => "audio/ogg", "ra" => "audio/x-realaudio", "ram" => "audio/x-pn-realaudio", "rm" => "audio/x-pn-realaudio", "rpm" => "audio/x-pn-realaudio-plugin", "snd" => "audio/basic", "wav" => "audio/x-wav", | ||
118 | "bmp" => "image/bmp", "djv" => "image/vnd.djvu", "djvu" => "image/vnd.djvu", "gif" => "image/gif", "ief" => "image/ief", "jpe" => "image/jpeg", "jpeg" => "image/jpeg", "jpg" => "image/jpeg", "pbm" => "image/x-portable-bitmap", "pgm" => "image/x-portable-graymap", "png" => "image/png", "pnm" => "image/x-portable-anymap", "ppm" => "image/x-portable-pixmap", "ras" => "image/x-cmu-raster", "rgb" => "image/x-rgb", "tif" => "image/tif", "tiff" => "image/tiff", "wbmp" => "image/vnd.wap.wbmp", "xbm" => "image/x-xbitmap", "xpm" => "image/x-xpixmap", "xwd" => "image/x-windowdump", | ||
119 | "asc" => "text/plain", "css" => "text/css", "etx" => "text/x-setext", "htm" => "text/html", "html" => "text/html", "rtf" => "text/rtf", "rtx" => "text/richtext", "sgm" => "text/sgml", "sgml" => "text/sgml", "tsv" => "text/tab-seperated-values", "txt" => "text/plain", "wml" => "text/vnd.wap.wml", "wmls" => "text/vnd.wap.wmlscript", "xml" => "text/xml", "xsl" => "text/xml", | ||
120 | "avi" => "video/x-msvideo", "mov" => "video/quicktime", "movie" => "video/x-sgi-movie", "mp4" => "video/mp4", "mpe" => "video/mpeg", "mpeg" => "video/mpeg", "mpg" => "video/mpeg", "mxu" => "video/vnd.mpegurl", "ogv" => "video/ogg", "qt" => "video/quicktime", "webm" => "video/webm"); | ||
121 | |||
122 | // These are the ONLY allowed types in that these are the ones ANY reader must support, any other MUST have the fallback attribute pointing to one of these. | ||
123 | private $coreMediaTypes = array("image/gif", "image/jpeg", "image/png", "image/svg+xml", "application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/css", "text/x-oeb1-css", "text/x-oeb1-document"); | ||
124 | |||
125 | private $opsContentTypes = array("application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/x-oeb1-document"); | ||
126 | |||
127 | private $forbiddenCharacters = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%"); | ||
128 | |||
129 | private $htmlContentHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<title></title>\n</head>\n<body>\n"; | ||
130 | private $htmlContentFooter = "</body>\n</html>\n"; | ||
131 | |||
132 | /** | ||
133 | * Class constructor. | ||
134 | * | ||
135 | * @return void | ||
136 | */ | ||
137 | function __construct($bookVersion = EPub::BOOK_VERSION_EPUB2, $debugInside = FALSE, $languageCode = "en", $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT) { | ||
138 | include_once("Zip.php"); | ||
139 | include_once("Logger.php"); | ||
140 | |||
141 | if (!$debugInside) { | ||
142 | error_reporting(E_ERROR | E_PARSE); | ||
143 | } | ||
144 | |||
145 | $this->bookVersion = $bookVersion; | ||
146 | $this->writingDirection = $writingDirection; | ||
147 | $this->languageCode = $languageCode; | ||
148 | |||
149 | $this->log = new Logger("EPub", $this->isLogging); | ||
150 | |||
151 | /* Prepare Logging. Just in case it's used. later */ | ||
152 | if ($this->isLogging) { | ||
153 | $this->log->logLine("EPub class version....: " . self::VERSION); | ||
154 | $this->log->logLine("EPub req. Zip version.: " . self::REQ_ZIP_VERSION); | ||
155 | $this->log->logLine("Zip version...........: " . Zip::VERSION); | ||
156 | $this->log->dumpInstalledModules(); | ||
157 | } | ||
158 | |||
159 | if (!defined("Zip::VERSION") || Zip::VERSION < self::REQ_ZIP_VERSION) { | ||
160 | die("<p>EPub version " . self::VERSION . " requires Zip.php at version " . self::REQ_ZIP_VERSION . " or higher.<br />You can obtain the latest version from <a href=\"http://www.phpclasses.org/browse/package/6110.html\">http://www.phpclasses.org/browse/package/6110.html</a>.</p>"); | ||
161 | } | ||
162 | |||
163 | include_once("EPubChapterSplitter.php"); | ||
164 | include_once("EPub.HtmlEntities.php"); | ||
165 | include_once("EPub.NCX.php"); | ||
166 | include_once("EPub.OPF.php"); | ||
167 | |||
168 | $this->initialize(); | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * Class destructor | ||
173 | * | ||
174 | * @return void | ||
175 | * @TODO make sure elements in the destructor match the current class elements | ||
176 | */ | ||
177 | function __destruct() { | ||
178 | unset($this->bookVersion, $this->maxImageWidth, $this->maxImageHeight); | ||
179 | unset($this->splitDefaultSize, $this->isGifImagesEnabled, $this->isReferencesAddedToToc); | ||
180 | unset($this->zip, $this->title, $this->language, $this->identifier, $this->identifierType); | ||
181 | unset($this->description, $this->author, $this->authorSortKey, $this->publisherName); | ||
182 | unset($this->publisherURL, $this->date, $this->rights, $this->coverage, $this->relation); | ||
183 | unset($this->sourceURL, $this->chapterCount, $this->opf, $this->ncx, $this->isFinalized); | ||
184 | unset($this->isCoverImageSet, $this->fileList, $this->writingDirection, $this->languageCode); | ||
185 | unset($this->referencesOrder, $this->dateformat, $this->dateformatShort, $this->headerDateFormat); | ||
186 | unset($this->isCurlInstalled, $this->isGdInstalled, $this->isExifInstalled); | ||
187 | unset($this->isFileGetContentsInstalled, $this->isFileGetContentsExtInstalled, $this->bookRoot); | ||
188 | unset($this->docRoot, $this->EPubMark, $this->generator, $this->log, $this->isLogging); | ||
189 | unset($this->encodeHTML, $this->mimetypes, $this->coreMediaTypes, $this->opsContentTypes); | ||
190 | unset($this->forbiddenCharacters, $this->htmlContentHeader, $this->htmlContentFooter); | ||
191 | unset($this->buildTOC, $this->tocTitle, $this->tocCSSClass, $this->tocAddReferences); | ||
192 | unset($this->tocFileName, $this->tocCssFileName); | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * initialize defaults. | ||
197 | */ | ||
198 | private function initialize() { | ||
199 | $this->referencesOrder = array( | ||
200 | Reference::COVER => "Cover Page", | ||
201 | Reference::TITLE_PAGE => "Title Page", | ||
202 | Reference::ACKNOWLEDGEMENTS => "Acknowledgements", | ||
203 | Reference::BIBLIOGRAPHY => "Bibliography", | ||
204 | Reference::COLOPHON => "Colophon", | ||
205 | Reference::COPYRIGHT_PAGE => "Copyright", | ||
206 | Reference::DEDICATION => "Dedication", | ||
207 | Reference::EPIGRAPH => "Epigraph", | ||
208 | Reference::FOREWORD => "Foreword", | ||
209 | Reference::TABLE_OF_CONTENTS => "Table of Contents", | ||
210 | Reference::NOTES => "Notes", | ||
211 | Reference::PREFACE => "Preface", | ||
212 | Reference::TEXT => "First Page", | ||
213 | Reference::LIST_OF_ILLUSTRATIONS => "List of Illustrations", | ||
214 | Reference::LIST_OF_TABLES => "List of Tables", | ||
215 | Reference::GLOSSARY => "Glossary", | ||
216 | Reference::INDEX => "Index"); | ||
217 | |||
218 | $this->docRoot = filter_input(INPUT_SERVER, "DOCUMENT_ROOT") . "/"; | ||
219 | |||
220 | $this->isCurlInstalled = extension_loaded('curl') && function_exists('curl_version'); | ||
221 | $this->isGdInstalled = extension_loaded('gd') && function_exists('gd_info'); | ||
222 | $this->isExifInstalled = extension_loaded('exif') && function_exists('exif_imagetype'); | ||
223 | $this->isFileGetContentsInstalled = function_exists('file_get_contents'); | ||
224 | $this->isFileGetContentsExtInstalled = $this->isFileGetContentsInstalled && ini_get('allow_url_fopen'); | ||
225 | |||
226 | $this->zip = new Zip(); | ||
227 | $this->zip->setExtraField(FALSE); | ||
228 | $this->zip->addFile("application/epub+zip", "mimetype"); | ||
229 | $this->zip->setExtraField(TRUE); | ||
230 | $this->zip->addDirectory("META-INF"); | ||
231 | |||
232 | $this->content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n\t<rootfiles>\n\t\t<rootfile full-path=\"" . $this->bookRoot . "book.opf\" media-type=\"application/oebps-package+xml\" />\n\t</rootfiles>\n</container>\n"; | ||
233 | |||
234 | if (!$this->isEPubVersion2()) { | ||
235 | $this->htmlContentHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | ||
236 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
237 | . "<head>" | ||
238 | . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | ||
239 | . "<title></title>\n" | ||
240 | . "</head>\n" | ||
241 | . "<body>\n"; | ||
242 | } | ||
243 | |||
244 | $this->zip->addFile($this->content, "META-INF/container.xml", 0, NULL, FALSE); | ||
245 | $this->content = NULL; | ||
246 | $this->ncx = new Ncx(NULL, NULL, NULL, $this->languageCode, $this->writingDirection); | ||
247 | $this->opf = new Opf(); | ||
248 | $this->ncx->setVersion($this->bookVersion); | ||
249 | $this->opf->setVersion($this->bookVersion); | ||
250 | $this->opf->addItem("ncx", "book.ncx", Ncx::MIMETYPE); | ||
251 | $this->chapterCount = 0; | ||
252 | } | ||
253 | |||
254 | /** | ||
255 | * Add dynamically generated data as a file to the book. | ||
256 | * | ||
257 | * @param string $fileName Filename to use for the file, must be unique for the book. | ||
258 | * @param string $fileId Unique identifier for the file. | ||
259 | * @param string $fileData File data | ||
260 | * @param string $mimetype file mime type | ||
261 | * @return bool $success | ||
262 | */ | ||
263 | function addFile($fileName, $fileId, $fileData, $mimetype) { | ||
264 | if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { | ||
265 | return FALSE; | ||
266 | } | ||
267 | |||
268 | $fileName = $this->normalizeFileName($fileName); | ||
269 | |||
270 | $compress = (strpos($mimetype, "image/") !== 0); | ||
271 | |||
272 | $this->zip->addFile($fileData, $this->bookRoot.$fileName, 0, NULL, $compress); | ||
273 | $this->fileList[$fileName] = $fileName; | ||
274 | $this->opf->addItem($fileId, $fileName, $mimetype); | ||
275 | return TRUE; | ||
276 | } | ||
277 | |||
278 | /** | ||
279 | * Add a large file directly from the filestystem to the book. | ||
280 | * | ||
281 | * @param string $fileName Filename to use for the file, must be unique for the book. | ||
282 | * @param string $fileId Unique identifier for the file. | ||
283 | * @param string $filePath File path | ||
284 | * @param string $mimetype file mime type | ||
285 | * @return bool $success | ||
286 | */ | ||
287 | function addLargeFile($fileName, $fileId, $filePath, $mimetype) { | ||
288 | if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { | ||
289 | return FALSE; | ||
290 | } | ||
291 | $fileName = $this->normalizeFileName($fileName); | ||
292 | |||
293 | if ($this->zip->addLargeFile($filePath, $this->bookRoot.$fileName)) { | ||
294 | $this->fileList[$fileName] = $fileName; | ||
295 | $this->opf->addItem($fileId, $fileName, $mimetype); | ||
296 | return TRUE; | ||
297 | } | ||
298 | return FALSE; | ||
299 | } | ||
300 | |||
301 | /** | ||
302 | * Add a CSS file to the book. | ||
303 | * | ||
304 | * @param string $fileName Filename to use for the CSS file, must be unique for the book. | ||
305 | * @param string $fileId Unique identifier for the file. | ||
306 | * @param string $fileData CSS data | ||
307 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? See documentation for <code>processCSSExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE. | ||
308 | * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE. | ||
309 | * | ||
310 | * @return bool $success | ||
311 | */ | ||
312 | function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { | ||
313 | if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { | ||
314 | return FALSE; | ||
315 | } | ||
316 | $fileName = Zip::getRelativePath($fileName); | ||
317 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | ||
318 | |||
319 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | ||
320 | $cssDir = pathinfo($fileName); | ||
321 | $cssDir = preg_replace('#^[/\.]+#i', "", $cssDir["dirname"] . "/"); | ||
322 | if (!empty($cssDir)) { | ||
323 | $cssDir = preg_replace('#[^/]+/#i', "../", $cssDir); | ||
324 | } | ||
325 | |||
326 | $this->processCSSExternalReferences($fileData, $externalReferences, $baseDir, $cssDir); | ||
327 | } | ||
328 | |||
329 | $this->addFile($fileName, "css_" . $fileId, $fileData, "text/css"); | ||
330 | |||
331 | return TRUE; | ||
332 | } | ||
333 | |||
334 | /** | ||
335 | * Add a chapter to the book, as a chapter should not exceed 250kB, you can parse an array with multiple parts as $chapterData. | ||
336 | * These will still only show up as a single chapter in the book TOC. | ||
337 | * | ||
338 | * @param string $chapterName Name of the chapter, will be use din the TOC | ||
339 | * @param string $fileName Filename to use for the chapter, must be unique for the book. | ||
340 | * @param string $chapter Chapter text in XHTML or array $chapterData valid XHTML data for the chapter. File should NOT exceed 250kB. | ||
341 | * @param bool $autoSplit Should the chapter be split if it exceeds the default split size? Default=FALSE, only used if $chapterData is a string. | ||
342 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE. | ||
343 | * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE. | ||
344 | * @return mixed $success FALSE if the addition failed, else the new NavPoint. | ||
345 | */ | ||
346 | function addChapter($chapterName, $fileName, $chapterData = NULL, $autoSplit = FALSE, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { | ||
347 | if ($this->isFinalized) { | ||
348 | return FALSE; | ||
349 | } | ||
350 | $fileName = Zip::getRelativePath($fileName); | ||
351 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | ||
352 | $fileName = $this->sanitizeFileName($fileName); | ||
353 | |||
354 | $chapter = $chapterData; | ||
355 | if ($autoSplit && is_string($chapterData) && mb_strlen($chapterData) > $this->splitDefaultSize) { | ||
356 | $splitter = new EPubChapterSplitter(); | ||
357 | |||
358 | $chapterArray = $splitter->splitChapter($chapterData); | ||
359 | if (count($chapterArray) > 1) { | ||
360 | $chapter = $chapterArray; | ||
361 | } | ||
362 | } | ||
363 | |||
364 | if (!empty($chapter) && is_string($chapter)) { | ||
365 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | ||
366 | $htmlDirInfo = pathinfo($fileName); | ||
367 | $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/"); | ||
368 | $this->processChapterExternalReferences($chapter, $externalReferences, $baseDir, $htmlDir); | ||
369 | } | ||
370 | |||
371 | if ($this->encodeHTML === TRUE) { | ||
372 | $chapter = $this->encodeHtml($chapter); | ||
373 | } | ||
374 | |||
375 | $this->chapterCount++; | ||
376 | $this->addFile($fileName, "chapter" . $this->chapterCount, $chapter, "application/xhtml+xml"); | ||
377 | $this->opf->addItemRef("chapter" . $this->chapterCount); | ||
378 | |||
379 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); | ||
380 | $this->ncx->addNavPoint($navPoint); | ||
381 | $this->ncx->chapterList[$chapterName] = $navPoint; | ||
382 | } else if (is_array($chapter)) { | ||
383 | $fileNameParts = pathinfo($fileName); | ||
384 | $extension = $fileNameParts['extension']; | ||
385 | $name = $fileNameParts['filename']; | ||
386 | |||
387 | $partCount = 0; | ||
388 | $this->chapterCount++; | ||
389 | |||
390 | $oneChapter = each($chapter); | ||
391 | while ($oneChapter) { | ||
392 | list($k, $v) = $oneChapter; | ||
393 | if ($this->encodeHTML === TRUE) { | ||
394 | $v = $this->encodeHtml($v); | ||
395 | } | ||
396 | |||
397 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | ||
398 | $this->processChapterExternalReferences($v, $externalReferences, $baseDir); | ||
399 | } | ||
400 | $partCount++; | ||
401 | $partName = $name . "_" . $partCount; | ||
402 | $this->addFile($partName . "." . $extension, $partName, $v, "application/xhtml+xml"); | ||
403 | $this->opf->addItemRef($partName); | ||
404 | |||
405 | $oneChapter = each($chapter); | ||
406 | } | ||
407 | $partName = $name . "_1." . $extension; | ||
408 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $partName, $partName); | ||
409 | $this->ncx->addNavPoint($navPoint); | ||
410 | |||
411 | $this->ncx->chapterList[$chapterName] = $navPoint; | ||
412 | } else if (!isset($chapterData) && strpos($fileName, "#") > 0) { | ||
413 | $this->chapterCount++; | ||
414 | //$this->opf->addItemRef("chapter" . $this->chapterCount); | ||
415 | |||
416 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); | ||
417 | $this->ncx->addNavPoint($navPoint); | ||
418 | $this->ncx->chapterList[$chapterName] = $navPoint; | ||
419 | } else if (!isset($chapterData) && $fileName=="TOC.xhtml") { | ||
420 | $this->chapterCount++; | ||
421 | $this->opf->addItemRef("toc"); | ||
422 | |||
423 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); | ||
424 | $this->ncx->addNavPoint($navPoint); | ||
425 | $this->ncx->chapterList[$chapterName] = $navPoint; | ||
426 | } | ||
427 | return $navPoint; | ||
428 | } | ||
429 | |||
430 | /** | ||
431 | * Add one chapter level. | ||
432 | * | ||
433 | * Subsequent chapters will be added to this level. | ||
434 | * | ||
435 | * @param string $navTitle | ||
436 | * @param string $navId | ||
437 | * @param string $navClass | ||
438 | * @param int $isNavHidden | ||
439 | * @param string $writingDirection | ||
440 | * @return NavPoint The new NavPoint for that level. | ||
441 | */ | ||
442 | function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { | ||
443 | return $this->ncx->subLevel($this->decodeHtmlEntities($navTitle), $navId, $navClass, $isNavHidden, $writingDirection); | ||
444 | } | ||
445 | |||
446 | /** | ||
447 | * Step back one chapter level. | ||
448 | * | ||
449 | * Subsequent chapters will be added to this chapters parent level. | ||
450 | */ | ||
451 | function backLevel() { | ||
452 | $this->ncx->backLevel(); | ||
453 | } | ||
454 | |||
455 | /** | ||
456 | * Step back to the root level. | ||
457 | * | ||
458 | * Subsequent chapters will be added to the rooot NavMap. | ||
459 | */ | ||
460 | function rootLevel() { | ||
461 | $this->ncx->rootLevel(); | ||
462 | } | ||
463 | |||
464 | /** | ||
465 | * Step back to the given level. | ||
466 | * Useful for returning to a previous level from deep within the structure. | ||
467 | * Values below 2 will have the same effect as rootLevel() | ||
468 | * | ||
469 | * @param int $newLevel | ||
470 | */ | ||
471 | function setCurrentLevel($newLevel) { | ||
472 | $this->ncx->setCurrentLevel($newLevel); | ||
473 | } | ||
474 | |||
475 | /** | ||
476 | * Get current level count. | ||
477 | * The indentation of the current structure point. | ||
478 | * | ||
479 | * @return current level count; | ||
480 | */ | ||
481 | function getCurrentLevel() { | ||
482 | return $this->ncx->getCurrentLevel(); | ||
483 | } | ||
484 | |||
485 | /** | ||
486 | * Wrap ChapterContent with Head and Footer | ||
487 | * | ||
488 | * @param $content | ||
489 | * @return string $content | ||
490 | */ | ||
491 | private function wrapChapter($content) { | ||
492 | return $this->htmlContentHeader . "\n" . $content . "\n" . $this->htmlContentFooter; | ||
493 | } | ||
494 | |||
495 | /** | ||
496 | * Reference pages is usually one or two pages for items such as Table of Contents, reference lists, Author notes or Acknowledgements. | ||
497 | * These do not show up in the regular navigation list. | ||
498 | * | ||
499 | * As they are supposed to be short. | ||
500 | * | ||
501 | * @param string $pageName Name of the chapter, will be use din the TOC | ||
502 | * @param string $fileName Filename to use for the chapter, must be unique for the book. | ||
503 | * @param string $pageData Page content in XHTML. File should NOT exceed 250kB. | ||
504 | * @param string $reference Reference key | ||
505 | * @param int $externalReferences How to handle external references. See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE. | ||
506 | * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE. | ||
507 | * @return bool $success | ||
508 | */ | ||
509 | function addReferencePage($pageName, $fileName, $pageData, $reference, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { | ||
510 | if ($this->isFinalized) { | ||
511 | return FALSE; | ||
512 | } | ||
513 | $fileName = Zip::getRelativePath($fileName); | ||
514 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | ||
515 | |||
516 | |||
517 | if (!empty($pageData) && is_string($pageData)) { | ||
518 | if ($this->encodeHTML === TRUE) { | ||
519 | $pageData = $this->encodeHtml($pageData); | ||
520 | } | ||
521 | |||
522 | $this->wrapChapter($pageData); | ||
523 | |||
524 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | ||
525 | $htmlDirInfo = pathinfo($fileName); | ||
526 | $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/"); | ||
527 | $this->processChapterExternalReferences($pageData, $externalReferences, $baseDir, $htmlDir); | ||
528 | } | ||
529 | |||
530 | $this->addFile($fileName, "ref_" . $reference, $pageData, "application/xhtml+xml"); | ||
531 | |||
532 | if ($reference !== Reference::TABLE_OF_CONTENTS || !isset($this->ncx->referencesList[$reference])) { | ||
533 | $this->opf->addItemRef("ref_" . $reference, FALSE); | ||
534 | $this->opf->addReference($reference, $pageName, $fileName); | ||
535 | |||
536 | $this->ncx->referencesList[$reference] = $fileName; | ||
537 | $this->ncx->referencesName[$reference] = $pageName; | ||
538 | } | ||
539 | return TRUE; | ||
540 | } | ||
541 | return TRUE; | ||
542 | } | ||
543 | |||
544 | /** | ||
545 | * Add custom metadata to the book. | ||
546 | * | ||
547 | * It is up to the builder to make sure there are no collisions. Metadata are just key value pairs. | ||
548 | * | ||
549 | * @param string $name | ||
550 | * @param string $content | ||
551 | */ | ||
552 | function addCustomMetadata($name, $content) { | ||
553 | $this->opf->addMeta($name, $content); | ||
554 | } | ||
555 | |||
556 | /** | ||
557 | * Add DublinCore metadata to the book | ||
558 | * | ||
559 | * Use the DublinCore constants included in EPub, ie DublinCore::DATE | ||
560 | * | ||
561 | * @param string $dublinCore name | ||
562 | * @param string $value | ||
563 | */ | ||
564 | function addDublinCoreMetadata($dublinCoreConstant, $value) { | ||
565 | if ($this->isFinalized) { | ||
566 | return; | ||
567 | } | ||
568 | |||
569 | $this->opf->addDCMeta($dublinCoreConstant, $this->decodeHtmlEntities($value)); | ||
570 | } | ||
571 | |||
572 | /** | ||
573 | * Add a cover image to the book. | ||
574 | * If the $imageData is not set, the function assumes the $fileName is the path to the image file. | ||
575 | * | ||
576 | * The styling and structure of the generated XHTML is heavily inspired by the XHTML generated by Calibre. | ||
577 | * | ||
578 | * @param string $fileName Filename to use for the image, must be unique for the book. | ||
579 | * @param string $imageData Binary image data | ||
580 | * @param string $mimetype Image mimetype, such as "image/jpeg" or "image/png". | ||
581 | * @return bool $success | ||
582 | */ | ||
583 | function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL,$bookTitle) { | ||
584 | if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) { | ||
585 | return FALSE; | ||
586 | } | ||
587 | |||
588 | if ($imageData == NULL) { | ||
589 | // assume $fileName is the valid file path. | ||
590 | if (!file_exists($fileName)) { | ||
591 | // Attempt to locate the file using the doc root. | ||
592 | $rp = realpath($this->docRoot . "/" . $fileName); | ||
593 | |||
594 | if ($rp !== FALSE) { | ||
595 | // only assign the docroot path if it actually exists there. | ||
596 | $fileName = $rp; | ||
597 | } | ||
598 | } | ||
599 | $image = $this->getImage($fileName); | ||
600 | $imageData = $image['image']; | ||
601 | $mimetype = $image['mime']; | ||
602 | $fileName = preg_replace("#\.[^\.]+$#", "." . $image['ext'], $fileName); | ||
603 | } | ||
604 | |||
605 | |||
606 | $path = pathinfo($fileName); | ||
607 | $imgPath = "images/" . $path["basename"]; | ||
608 | |||
609 | if (empty($mimetype) && file_exists($fileName)) { | ||
610 | list($width, $height, $type, $attr) = getimagesize($fileName); | ||
611 | $mimetype = image_type_to_mime_type($type); | ||
612 | } | ||
613 | if (empty($mimetype)) { | ||
614 | $ext = strtolower($path['extension']); | ||
615 | if ($ext == "jpg") { | ||
616 | $ext = "jpeg"; | ||
617 | } | ||
618 | $mimetype = "image/" . $ext; | ||
619 | } | ||
620 | |||
621 | $coverPage = ""; | ||
622 | |||
623 | if ($this->isEPubVersion2()) { | ||
624 | $coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | ||
625 | . "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n" | ||
626 | . " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" | ||
627 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\" xml:lang=\"en\">\n" | ||
628 | . "\t<head>\n" | ||
629 | . "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n" | ||
630 | . "\t\t<title>Cover Image</title>\n" | ||
631 | . "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n" | ||
632 | . "\t</head>\n" | ||
633 | . "\t<body>\n" | ||
634 | . "\t" . $bookTitle . "\n" | ||
635 | . "\t\t<div>\n" | ||
636 | . "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 100%\"/>\n" | ||
637 | . "\t\t</div>\n" | ||
638 | . "\t</body>\n" | ||
639 | . "</html>\n"; | ||
640 | } else { | ||
641 | $coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | ||
642 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
643 | . "<head>" | ||
644 | . "\t<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | ||
645 | . "\t\t<title>Cover Image</title>\n" | ||
646 | . "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n" | ||
647 | . "\t</head>\n" | ||
648 | . "\t<body>\n" | ||
649 | . "\t\t<section epub:type=\"cover\">\n" | ||
650 | . "\t" . $bookTitle . "\n" | ||
651 | . "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 30%\"/>\n" | ||
652 | . "\t\t</section>\n" | ||
653 | . "\t</body>\n" | ||
654 | . "</html>\n"; | ||
655 | } | ||
656 | $coverPageCss = "@page, body, div, img {\n" | ||
657 | . "\tpadding: 0pt;\n" | ||
658 | . "\tmargin:0pt;\n" | ||
659 | . "}\n\nbody {\n" | ||
660 | . "\ttext-align: center;\n" | ||
661 | . "}\n"; | ||
662 | |||
663 | $this->addCSSFile("Styles/CoverPage.css", "CoverPageCss", $coverPageCss); | ||
664 | $this->addFile($imgPath, "CoverImage", $imageData, $mimetype); | ||
665 | $this->addReferencePage("CoverPage", "CoverPage.xhtml", $coverPage, "cover"); | ||
666 | $this->isCoverImageSet = TRUE; | ||
667 | return TRUE; | ||
668 | } | ||
669 | |||
670 | /** | ||
671 | * Process external references from a HTML to the book. The chapter itself is not stored. | ||
672 | * the HTML is scanned for <link..., <style..., and <img tags. | ||
673 | * Embedded CSS styles and links will also be processed. | ||
674 | * Script tags are not processed, as scripting should be avoided in e-books. | ||
675 | * | ||
676 | * EPub keeps track of added files, and duplicate files referenced across multiple | ||
677 | * chapters, are only added once. | ||
678 | * | ||
679 | * If the $doc is a string, it is assumed to be the content of an HTML file, | ||
680 | * else is it assumes to be a DOMDocument. | ||
681 | * | ||
682 | * Basedir is the root dir the HTML is supposed to "live" in, used to resolve | ||
683 | * relative references such as <code><img src="../images/image.png"/></code> | ||
684 | * | ||
685 | * $externalReferences determines how the function will handle external references. | ||
686 | * | ||
687 | * @param mixed &$doc (referenced) | ||
688 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD. | ||
689 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
690 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
691 | * | ||
692 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | ||
693 | */ | ||
694 | protected function processChapterExternalReferences(&$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") { | ||
695 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | ||
696 | return FALSE; | ||
697 | } | ||
698 | |||
699 | $backPath = preg_replace('#[^/]+/#i', "../", $htmlDir); | ||
700 | $isDocAString = is_string($doc); | ||
701 | $xmlDoc = NULL; | ||
702 | |||
703 | if ($isDocAString) { | ||
704 | $xmlDoc = new DOMDocument(); | ||
705 | @$xmlDoc->loadHTML($doc); | ||
706 | } else { | ||
707 | $xmlDoc = $doc; | ||
708 | } | ||
709 | |||
710 | $this->processChapterStyles($xmlDoc, $externalReferences, $baseDir, $htmlDir); | ||
711 | $this->processChapterLinks($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath); | ||
712 | $this->processChapterImages($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath); | ||
713 | $this->processChapterSources($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath); | ||
714 | |||
715 | if ($isDocAString) { | ||
716 | //$html = $xmlDoc->saveXML(); | ||
717 | |||
718 | $htmlNode = $xmlDoc->getElementsByTagName("html"); | ||
719 | $headNode = $xmlDoc->getElementsByTagName("head"); | ||
720 | $bodyNode = $xmlDoc->getElementsByTagName("body"); | ||
721 | |||
722 | $htmlNS = ""; | ||
723 | for ($index = 0; $index < $htmlNode->item(0)->attributes->length; $index++) { | ||
724 | $nodeName = $htmlNode->item(0)->attributes->item($index)->nodeName; | ||
725 | $nodeValue = $htmlNode->item(0)->attributes->item($index)->nodeValue; | ||
726 | |||
727 | if ($nodeName != "xmlns") { | ||
728 | $htmlNS .= " $nodeName=\"$nodeValue\""; | ||
729 | } | ||
730 | } | ||
731 | |||
732 | $xml = new DOMDocument('1.0', "utf-8"); | ||
733 | $xml->lookupPrefix("http://www.w3.org/1999/xhtml"); | ||
734 | $xml->preserveWhiteSpace = FALSE; | ||
735 | $xml->formatOutput = TRUE; | ||
736 | |||
737 | $xml2Doc = new DOMDocument('1.0', "utf-8"); | ||
738 | $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml"); | ||
739 | $xml2Doc->loadXML("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\"$htmlNS>\n</html>\n"); | ||
740 | $html = $xml2Doc->getElementsByTagName("html")->item(0); | ||
741 | $html->appendChild($xml2Doc->importNode($headNode->item(0), TRUE)); | ||
742 | $html->appendChild($xml2Doc->importNode($bodyNode->item(0), TRUE)); | ||
743 | |||
744 | // force pretty printing and correct formatting, should not be needed, but it is. | ||
745 | $xml->loadXML($xml2Doc->saveXML()); | ||
746 | $doc = $xml->saveXML(); | ||
747 | |||
748 | if (!$this->isEPubVersion2()) { | ||
749 | $doc = preg_replace('#^\s*<!DOCTYPE\ .+?>\s*#im', '', $doc); | ||
750 | } | ||
751 | } | ||
752 | return TRUE; | ||
753 | } | ||
754 | |||
755 | /** | ||
756 | * Process images referenced from an CSS file to the book. | ||
757 | * | ||
758 | * $externalReferences determins how the function will handle external references. | ||
759 | * | ||
760 | * @param string &$cssFile (referenced) | ||
761 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD. | ||
762 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
763 | * @param string $cssDir The of the CSS file's directory from the root of the archive. | ||
764 | * | ||
765 | * @return bool FALSE if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | ||
766 | */ | ||
767 | protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = "") { | ||
768 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | ||
769 | return FALSE; | ||
770 | } | ||
771 | |||
772 | $backPath = preg_replace('#[^/]+/#i', "../", $cssDir); | ||
773 | $imgs = null; | ||
774 | preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER); | ||
775 | |||
776 | $itemCount = count($imgs); | ||
777 | for ($idx = 0; $idx < $itemCount; $idx++) { | ||
778 | $img = $imgs[$idx]; | ||
779 | if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES || $externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { | ||
780 | $cssFile = str_replace($img[0], "", $cssFile); | ||
781 | } else { | ||
782 | $source = $img[1]; | ||
783 | |||
784 | $pathData = pathinfo($source); | ||
785 | $internalSrc = $pathData['basename']; | ||
786 | $internalPath = ""; | ||
787 | $isSourceExternal = FALSE; | ||
788 | |||
789 | if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $cssDir, $backPath)) { | ||
790 | $cssFile = str_replace($img[0], "url('" . $backPath . $internalPath . "')", $cssFile); | ||
791 | } else if ($isSourceExternal) { | ||
792 | $cssFile = str_replace($img[0], "", $cssFile); // External image is missing | ||
793 | } // else do nothing, if the image is local, and missing, assume it's been generated. | ||
794 | } | ||
795 | } | ||
796 | return TRUE; | ||
797 | } | ||
798 | |||
799 | /** | ||
800 | * Process style tags in a DOMDocument. Styles will be passed as CSS files and reinserted into the document. | ||
801 | * | ||
802 | * @param DOMDocument &$xmlDoc (referenced) | ||
803 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD. | ||
804 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
805 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
806 | * | ||
807 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | ||
808 | */ | ||
809 | protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") { | ||
810 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | ||
811 | return FALSE; | ||
812 | } | ||
813 | // process inlined CSS styles in style tags. | ||
814 | $styles = $xmlDoc->getElementsByTagName("style"); | ||
815 | $styleCount = $styles->length; | ||
816 | for ($styleIdx = 0; $styleIdx < $styleCount; $styleIdx++) { | ||
817 | $style = $styles->item($styleIdx); | ||
818 | |||
819 | $styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $style->nodeValue); | ||
820 | $styleData = preg_replace('#[/\*\s]*\]\]\>[\s\*/]*#im', "", $styleData); | ||
821 | |||
822 | $this->processCSSExternalReferences($styleData, $externalReferences, $baseDir, $htmlDir); | ||
823 | $style->nodeValue = "\n" . trim($styleData) . "\n"; | ||
824 | } | ||
825 | return TRUE; | ||
826 | } | ||
827 | |||
828 | /** | ||
829 | * Process link tags in a DOMDocument. Linked files will be loaded into the archive, and the link src will be rewritten to point to that location. | ||
830 | * Link types text/css will be passed as CSS files. | ||
831 | * | ||
832 | * @param DOMDocument &$xmlDoc (referenced) | ||
833 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD. | ||
834 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
835 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
836 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | ||
837 | * | ||
838 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | ||
839 | */ | ||
840 | protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") { | ||
841 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | ||
842 | return FALSE; | ||
843 | } | ||
844 | // process link tags. | ||
845 | $links = $xmlDoc->getElementsByTagName("link"); | ||
846 | $linkCount = $links->length; | ||
847 | for ($linkIdx = 0; $linkIdx < $linkCount; $linkIdx++) { | ||
848 | $link = $links->item($linkIdx); | ||
849 | $source = $link->attributes->getNamedItem("href")->nodeValue; | ||
850 | $sourceData = NULL; | ||
851 | |||
852 | $pathData = pathinfo($source); | ||
853 | $internalSrc = $pathData['basename']; | ||
854 | |||
855 | if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { | ||
856 | $urlinfo = parse_url($source); | ||
857 | |||
858 | if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) { | ||
859 | $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1); | ||
860 | } | ||
861 | |||
862 | @$sourceData = getFileContents($source); | ||
863 | } else if (strpos($source, "/") === 0) { | ||
864 | @$sourceData = file_get_contents($this->docRoot . $source); | ||
865 | } else { | ||
866 | @$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source); | ||
867 | } | ||
868 | |||
869 | if (!empty($sourceData)) { | ||
870 | if (!array_key_exists($internalSrc, $this->fileList)) { | ||
871 | $mime = $link->attributes->getNamedItem("type")->nodeValue; | ||
872 | if (empty($mime)) { | ||
873 | $mime = "text/plain"; | ||
874 | } | ||
875 | if ($mime == "text/css") { | ||
876 | $this->processCSSExternalReferences($sourceData, $externalReferences, $baseDir, $htmlDir); | ||
877 | $this->addCSSFile($internalSrc, $internalSrc, $sourceData, EPub::EXTERNAL_REF_IGNORE, $baseDir); | ||
878 | $link->setAttribute("href", $backPath . $internalSrc); | ||
879 | } else { | ||
880 | $this->addFile($internalSrc, $internalSrc, $sourceData, $mime); | ||
881 | } | ||
882 | $this->fileList[$internalSrc] = $source; | ||
883 | } else { | ||
884 | $link->setAttribute("href", $backPath . $internalSrc); | ||
885 | } | ||
886 | } // else do nothing, if the link is local, and missing, assume it's been generated. | ||
887 | } | ||
888 | return TRUE; | ||
889 | } | ||
890 | |||
891 | /** | ||
892 | * Process img tags in a DOMDocument. | ||
893 | * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly. | ||
894 | * | ||
895 | * @param DOMDocument &$xmlDoc (referenced) | ||
896 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD. | ||
897 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
898 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
899 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | ||
900 | * | ||
901 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | ||
902 | */ | ||
903 | protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") { | ||
904 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | ||
905 | return FALSE; | ||
906 | } | ||
907 | // process img tags. | ||
908 | $postProcDomElememts = array(); | ||
909 | $images = $xmlDoc->getElementsByTagName("img"); | ||
910 | $itemCount = $images->length; | ||
911 | |||
912 | for ($idx = 0; $idx < $itemCount; $idx++) { | ||
913 | $img = $images->item($idx); | ||
914 | |||
915 | if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) { | ||
916 | $postProcDomElememts[] = $img; | ||
917 | } else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { | ||
918 | $altNode = $img->attributes->getNamedItem("alt"); | ||
919 | $alt = "image"; | ||
920 | if ($altNode !== NULL && strlen($altNode->nodeValue) > 0) { | ||
921 | $alt = $altNode->nodeValue; | ||
922 | } | ||
923 | $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "<em>[" . $alt . "]</em>")); | ||
924 | } else { | ||
925 | $source = $img->attributes->getNamedItem("src")->nodeValue; | ||
926 | |||
927 | $parsedSource = parse_url($source); | ||
928 | $internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); | ||
929 | $internalPath = ""; | ||
930 | $isSourceExternal = FALSE; | ||
931 | |||
932 | if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) { | ||
933 | $img->setAttribute("src", $backPath . $internalPath); | ||
934 | } else if ($isSourceExternal) { | ||
935 | $postProcDomElememts[] = $img; // External image is missing | ||
936 | } // else do nothing, if the image is local, and missing, assume it's been generated. | ||
937 | } | ||
938 | } | ||
939 | |||
940 | foreach ($postProcDomElememts as $target) { | ||
941 | if (is_array($target)) { | ||
942 | $target[0]->parentNode->replaceChild($target[1], $target[0]); | ||
943 | } else { | ||
944 | $target->parentNode->removeChild($target); | ||
945 | } | ||
946 | } | ||
947 | return TRUE; | ||
948 | } | ||
949 | |||
950 | /** | ||
951 | * Process source tags in a DOMDocument. | ||
952 | * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly. | ||
953 | * | ||
954 | * @param DOMDocument &$xmlDoc (referenced) | ||
955 | * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD. | ||
956 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
957 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
958 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | ||
959 | * | ||
960 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | ||
961 | */ | ||
962 | protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") { | ||
963 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | ||
964 | return FALSE; | ||
965 | } | ||
966 | |||
967 | if ($this->bookVersion !== EPub::BOOK_VERSION_EPUB3) { | ||
968 | // ePub 2 does not support multimedia formats, and they must be removed. | ||
969 | $externalReferences = EPub::EXTERNAL_REF_REMOVE_IMAGES; | ||
970 | } | ||
971 | |||
972 | $postProcDomElememts = array(); | ||
973 | $images = $xmlDoc->getElementsByTagName("source"); | ||
974 | $itemCount = $images->length; | ||
975 | for ($idx = 0; $idx < $itemCount; $idx++) { | ||
976 | $img = $images->item($idx); | ||
977 | if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) { | ||
978 | $postProcDomElememts[] = $img; | ||
979 | } else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { | ||
980 | $altNode = $img->attributes->getNamedItem("alt"); | ||
981 | $alt = "image"; | ||
982 | if ($altNode !== NULL && strlen($altNode->nodeValue) > 0) { | ||
983 | $alt = $altNode->nodeValue; | ||
984 | } | ||
985 | $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "[" . $alt . "]")); | ||
986 | } else { | ||
987 | $source = $img->attributes->getNamedItem("src")->nodeValue; | ||
988 | |||
989 | $parsedSource = parse_url($source); | ||
990 | $internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); | ||
991 | $internalPath = ""; | ||
992 | $isSourceExternal = FALSE; | ||
993 | |||
994 | if ($this->resolveMedia($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) { | ||
995 | $img->setAttribute("src", $backPath . $internalPath); | ||
996 | } else if ($isSourceExternal) { | ||
997 | $postProcDomElememts[] = $img; // External image is missing | ||
998 | } // else do nothing, if the image is local, and missing, assume it's been generated. | ||
999 | } | ||
1000 | } | ||
1001 | } | ||
1002 | |||
1003 | /** | ||
1004 | * Resolve an image src and determine it's target location and add it to the book. | ||
1005 | * | ||
1006 | * @param string $source Image Source link. | ||
1007 | * @param string &$internalPath (referenced) Return value, will be set to the target path and name in the book. | ||
1008 | * @param string &$internalSrc (referenced) Return value, will be set to the target name in the book. | ||
1009 | * @param string &$isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL. | ||
1010 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
1011 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
1012 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | ||
1013 | */ | ||
1014 | protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") { | ||
1015 | if ($this->isFinalized) { | ||
1016 | return FALSE; | ||
1017 | } | ||
1018 | $imageData = NULL; | ||
1019 | |||
1020 | if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { | ||
1021 | $urlinfo = parse_url($source); | ||
1022 | $urlPath = pathinfo($urlinfo['path']); | ||
1023 | |||
1024 | if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) { | ||
1025 | $internalSrc = $this->sanitizeFileName(urldecode(substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1))); | ||
1026 | } | ||
1027 | $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME); | ||
1028 | $isSourceExternal = TRUE; | ||
1029 | $imageData = $this->getImage($source); | ||
1030 | } else if (strpos($source, "/") === 0) { | ||
1031 | $internalPath = pathinfo($source, PATHINFO_DIRNAME); | ||
1032 | |||
1033 | $path = $source; | ||
1034 | if (!file_exists($path)) { | ||
1035 | $path = $this->docRoot . $path; | ||
1036 | } | ||
1037 | |||
1038 | $imageData = $this->getImage($path); | ||
1039 | } else { | ||
1040 | $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); | ||
1041 | |||
1042 | $path = $baseDir . "/" . $source; | ||
1043 | if (!file_exists($path)) { | ||
1044 | $path = $this->docRoot . $path; | ||
1045 | } | ||
1046 | |||
1047 | $imageData = $this->getImage($path); | ||
1048 | } | ||
1049 | if ($imageData !== FALSE) { | ||
1050 | $iSrcInfo = pathinfo($internalSrc); | ||
1051 | if (!empty($imageData['ext']) && $imageData['ext'] != $iSrcInfo['extension']) { | ||
1052 | $internalSrc = $iSrcInfo['filename'] . "." . $imageData['ext']; | ||
1053 | } | ||
1054 | $internalPath = Zip::getRelativePath("images/" . $internalPath . "/" . $internalSrc); | ||
1055 | if (!array_key_exists($internalPath, $this->fileList)) { | ||
1056 | $this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']); | ||
1057 | $this->fileList[$internalPath] = $source; | ||
1058 | } | ||
1059 | return TRUE; | ||
1060 | } | ||
1061 | return FALSE; | ||
1062 | } | ||
1063 | |||
1064 | /** | ||
1065 | * Resolve a media src and determine it's target location and add it to the book. | ||
1066 | * | ||
1067 | * @param string $source Source link. | ||
1068 | * @param string $internalPath (referenced) Return value, will be set to the target path and name in the book. | ||
1069 | * @param string $internalSrc (referenced) Return value, will be set to the target name in the book. | ||
1070 | * @param string $isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL. | ||
1071 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | ||
1072 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | ||
1073 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | ||
1074 | */ | ||
1075 | protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") { | ||
1076 | if ($this->isFinalized) { | ||
1077 | return FALSE; | ||
1078 | } | ||
1079 | $mediaPath = NULL; | ||
1080 | $tmpFile; | ||
1081 | |||
1082 | if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { | ||
1083 | $urlinfo = parse_url($source); | ||
1084 | |||
1085 | if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) { | ||
1086 | $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1); | ||
1087 | } | ||
1088 | $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME); | ||
1089 | $isSourceExternal = TRUE; | ||
1090 | $mediaPath = $this->getFileContents($source, true); | ||
1091 | $tmpFile = $mediaPath; | ||
1092 | } else if (strpos($source, "/") === 0) { | ||
1093 | $internalPath = pathinfo($source, PATHINFO_DIRNAME); | ||
1094 | |||
1095 | $mediaPath = $source; | ||
1096 | if (!file_exists($mediaPath)) { | ||
1097 | $mediaPath = $this->docRoot . $mediaPath; | ||
1098 | } | ||
1099 | } else { | ||
1100 | $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); | ||
1101 | |||
1102 | $mediaPath = $baseDir . "/" . $source; | ||
1103 | if (!file_exists($mediaPath)) { | ||
1104 | $mediaPath = $this->docRoot . $mediaPath; | ||
1105 | } | ||
1106 | } | ||
1107 | |||
1108 | if ($mediaPath !== FALSE) { | ||
1109 | $mime = $this->getMime($source); | ||
1110 | $internalPath = Zip::getRelativePath("media/" . $internalPath . "/" . $internalSrc); | ||
1111 | |||
1112 | if (!array_key_exists($internalPath, $this->fileList) && | ||
1113 | $this->addLargeFile($internalPath, "m_" . $internalSrc, $mediaPath, $mime)) { | ||
1114 | $this->fileList[$internalPath] = $source; | ||
1115 | } | ||
1116 | if (isset($tmpFile)) { | ||
1117 | unlink($tmpFile); | ||
1118 | } | ||
1119 | return TRUE; | ||
1120 | } | ||
1121 | return FALSE; | ||
1122 | } | ||
1123 | |||
1124 | /** | ||
1125 | * Get Book Chapter count. | ||
1126 | * | ||
1127 | * @access public | ||
1128 | * @return number of chapters | ||
1129 | */ | ||
1130 | function getChapterCount() { | ||
1131 | return $this->chapterCount; | ||
1132 | } | ||
1133 | |||
1134 | /** | ||
1135 | * Book title, mandatory. | ||
1136 | * | ||
1137 | * Used for the dc:title metadata parameter in the OPF file as well as the DocTitle attribute in the NCX file. | ||
1138 | * | ||
1139 | * @param string $title | ||
1140 | * @access public | ||
1141 | * @return bool $success | ||
1142 | */ | ||
1143 | function setTitle($title) { | ||
1144 | if ($this->isFinalized) { | ||
1145 | return FALSE; | ||
1146 | } | ||
1147 | $this->title = $title; | ||
1148 | return TRUE; | ||
1149 | } | ||
1150 | |||
1151 | /** | ||
1152 | * Get Book title. | ||
1153 | * | ||
1154 | * @access public | ||
1155 | * @return $title | ||
1156 | */ | ||
1157 | function getTitle() { | ||
1158 | return $this->title; | ||
1159 | } | ||
1160 | |||
1161 | /** | ||
1162 | * Book language, mandatory | ||
1163 | * | ||
1164 | * Use the RFC3066 Language codes, such as "en", "da", "fr" etc. | ||
1165 | * Defaults to "en". | ||
1166 | * | ||
1167 | * Used for the dc:language metadata parameter in the OPF file. | ||
1168 | * | ||
1169 | * @param string $language | ||
1170 | * @access public | ||
1171 | * @return bool $success | ||
1172 | */ | ||
1173 | function setLanguage($language) { | ||
1174 | if ($this->isFinalized || mb_strlen($language) != 2) { | ||
1175 | return FALSE; | ||
1176 | } | ||
1177 | $this->language = $language; | ||
1178 | return TRUE; | ||
1179 | } | ||
1180 | |||
1181 | /** | ||
1182 | * Get Book language. | ||
1183 | * | ||
1184 | * @access public | ||
1185 | * @return $language | ||
1186 | */ | ||
1187 | function getLanguage() { | ||
1188 | return $this->language; | ||
1189 | } | ||
1190 | |||
1191 | /** | ||
1192 | * Unique book identifier, mandatory. | ||
1193 | * Use the URI, or ISBN if available. | ||
1194 | * | ||
1195 | * An unambiguous reference to the resource within a given context. | ||
1196 | * | ||
1197 | * Recommended best practice is to identify the resource by means of a | ||
1198 | * string conforming to a formal identification system. | ||
1199 | * | ||
1200 | * Used for the dc:identifier metadata parameter in the OPF file, as well | ||
1201 | * as dtb:uid in the NCX file. | ||
1202 | * | ||
1203 | * Identifier type should only be: | ||
1204 | * EPub::IDENTIFIER_URI | ||
1205 | * EPub::IDENTIFIER_ISBN | ||
1206 | * EPub::IDENTIFIER_UUID | ||
1207 | * | ||
1208 | * @param string $identifier | ||
1209 | * @param string $identifierType | ||
1210 | * @access public | ||
1211 | * @return bool $success | ||
1212 | */ | ||
1213 | function setIdentifier($identifier, $identifierType) { | ||
1214 | if ($this->isFinalized || ($identifierType !== EPub::IDENTIFIER_URI && $identifierType !== EPub::IDENTIFIER_ISBN && $identifierType !== EPub::IDENTIFIER_UUID)) { | ||
1215 | return FALSE; | ||
1216 | } | ||
1217 | $this->identifier = $identifier; | ||
1218 | $this->identifierType = $identifierType; | ||
1219 | return TRUE; | ||
1220 | } | ||
1221 | |||
1222 | /** | ||
1223 | * Get Book identifier. | ||
1224 | * | ||
1225 | * @access public | ||
1226 | * @return $identifier | ||
1227 | */ | ||
1228 | function getIdentifier() { | ||
1229 | return $this->identifier; | ||
1230 | } | ||
1231 | |||
1232 | /** | ||
1233 | * Get Book identifierType. | ||
1234 | * | ||
1235 | * @access public | ||
1236 | * @return $identifierType | ||
1237 | */ | ||
1238 | function getIdentifierType() { | ||
1239 | return $this->identifierType; | ||
1240 | } | ||
1241 | |||
1242 | /** | ||
1243 | * Book description, optional. | ||
1244 | * | ||
1245 | * An account of the resource. | ||
1246 | * | ||
1247 | * Description may include but is not limited to: an abstract, a table of | ||
1248 | * contents, a graphical representation, or a free-text account of the | ||
1249 | * resource. | ||
1250 | * | ||
1251 | * Used for the dc:source metadata parameter in the OPF file | ||
1252 | * | ||
1253 | * @param string $description | ||
1254 | * @access public | ||
1255 | * @return bool $success | ||
1256 | */ | ||
1257 | function setDescription($description) { | ||
1258 | if ($this->isFinalized) { | ||
1259 | return FALSE; | ||
1260 | } | ||
1261 | $this->description = $description; | ||
1262 | return TRUE; | ||
1263 | } | ||
1264 | |||
1265 | /** | ||
1266 | * Get Book description. | ||
1267 | * | ||
1268 | * @access public | ||
1269 | * @return $description | ||
1270 | */ | ||
1271 | function getDescription() { | ||
1272 | return $this->description; | ||
1273 | } | ||
1274 | |||
1275 | /** | ||
1276 | * Book author or creator, optional. | ||
1277 | * The $authorSortKey is basically how the name is to be sorted, usually | ||
1278 | * it's "Lastname, First names" where the $author is the straight | ||
1279 | * "Firstnames Lastname" | ||
1280 | * | ||
1281 | * An entity primarily responsible for making the resource. | ||
1282 | * | ||
1283 | * Examples of a Creator include a person, an organization, or a service. | ||
1284 | * Typically, the name of a Creator should be used to indicate the entity. | ||
1285 | * | ||
1286 | * Used for the dc:creator metadata parameter in the OPF file and the | ||
1287 | * docAuthor attribure in the NCX file. | ||
1288 | * The sort key is used for the opf:file-as attribute in dc:creator. | ||
1289 | * | ||
1290 | * @param string $author | ||
1291 | * @param string $authorSortKey | ||
1292 | * @access public | ||
1293 | * @return bool $success | ||
1294 | */ | ||
1295 | function setAuthor($author, $authorSortKey) { | ||
1296 | if ($this->isFinalized) { | ||
1297 | return FALSE; | ||
1298 | } | ||
1299 | $this->author = $author; | ||
1300 | $this->authorSortKey = $authorSortKey; | ||
1301 | return TRUE; | ||
1302 | } | ||
1303 | |||
1304 | /** | ||
1305 | * Get Book author. | ||
1306 | * | ||
1307 | * @access public | ||
1308 | * @return $author | ||
1309 | */ | ||
1310 | function getAuthor() { | ||
1311 | return $this->author; | ||
1312 | } | ||
1313 | |||
1314 | /** | ||
1315 | * Publisher Information, optional. | ||
1316 | * | ||
1317 | * An entity responsible for making the resource available. | ||
1318 | * | ||
1319 | * Examples of a Publisher include a person, an organization, or a service. | ||
1320 | * Typically, the name of a Publisher should be used to indicate the entity. | ||
1321 | * | ||
1322 | * Used for the dc:publisher and dc:relation metadata parameters in the OPF file. | ||
1323 | * | ||
1324 | * @param string $publisherName | ||
1325 | * @param string $publisherURL | ||
1326 | * @access public | ||
1327 | * @return bool $success | ||
1328 | */ | ||
1329 | function setPublisher($publisherName, $publisherURL) { | ||
1330 | if ($this->isFinalized) { | ||
1331 | return FALSE; | ||
1332 | } | ||
1333 | $this->publisherName = $publisherName; | ||
1334 | $this->publisherURL = $publisherURL; | ||
1335 | return TRUE; | ||
1336 | } | ||
1337 | |||
1338 | /** | ||
1339 | * Get Book publisherName. | ||
1340 | * | ||
1341 | * @access public | ||
1342 | * @return $publisherName | ||
1343 | */ | ||
1344 | function getPublisherName() { | ||
1345 | return $this->publisherName; | ||
1346 | } | ||
1347 | |||
1348 | /** | ||
1349 | * Get Book publisherURL. | ||
1350 | * | ||
1351 | * @access public | ||
1352 | * @return $publisherURL | ||
1353 | */ | ||
1354 | function getPublisherURL() { | ||
1355 | return $this->publisherURL; | ||
1356 | } | ||
1357 | |||
1358 | /** | ||
1359 | * Release date, optional. If left blank, the time of the finalization will | ||
1360 | * be used. | ||
1361 | * | ||
1362 | * A point or period of time associated with an event in the lifecycle of | ||
1363 | * the resource. | ||
1364 | * | ||
1365 | * Date may be used to express temporal information at any level of | ||
1366 | * granularity. Recommended best practice is to use an encoding scheme, | ||
1367 | * such as the W3CDTF profile of ISO 8601 [W3CDTF]. | ||
1368 | * | ||
1369 | * Used for the dc:date metadata parameter in the OPF file | ||
1370 | * | ||
1371 | * @param long $timestamp | ||
1372 | * @access public | ||
1373 | * @return bool $success | ||
1374 | */ | ||
1375 | function setDate($timestamp) { | ||
1376 | if ($this->isFinalized) { | ||
1377 | return FALSE; | ||
1378 | } | ||
1379 | $this->date = $timestamp; | ||
1380 | $this->opf->date = $timestamp; | ||
1381 | return TRUE; | ||
1382 | } | ||
1383 | |||
1384 | /** | ||
1385 | * Get Book date. | ||
1386 | * | ||
1387 | * @access public | ||
1388 | * @return $date | ||
1389 | */ | ||
1390 | function getDate() { | ||
1391 | return $this->date; | ||
1392 | } | ||
1393 | |||
1394 | /** | ||
1395 | * Book (copy)rights, optional. | ||
1396 | * | ||
1397 | * Information about rights held in and over the resource. | ||
1398 | * | ||
1399 | * Typically, rights information includes a statement about various | ||
1400 | * property rights associated with the resource, including intellectual | ||
1401 | * property rights. | ||
1402 | * | ||
1403 | * Used for the dc:rights metadata parameter in the OPF file | ||
1404 | * | ||
1405 | * @param string $rightsText | ||
1406 | * @access public | ||
1407 | * @return bool $success | ||
1408 | */ | ||
1409 | function setRights($rightsText) { | ||
1410 | if ($this->isFinalized) { | ||
1411 | return FALSE; | ||
1412 | } | ||
1413 | $this->rights = $rightsText; | ||
1414 | return TRUE; | ||
1415 | } | ||
1416 | |||
1417 | /** | ||
1418 | * Get Book rights. | ||
1419 | * | ||
1420 | * @access public | ||
1421 | * @return $rights | ||
1422 | */ | ||
1423 | function getRights() { | ||
1424 | return $this->rights; | ||
1425 | } | ||
1426 | |||
1427 | /** | ||
1428 | * Add book Subject. | ||
1429 | * | ||
1430 | * The topic of the resource. | ||
1431 | * | ||
1432 | * Typically, the subject will be represented using keywords, key phrases, | ||
1433 | * or classification codes. Recommended best practice is to use a | ||
1434 | * controlled vocabulary. To describe the spatial or temporal topic of the | ||
1435 | * resource, use the Coverage element. | ||
1436 | * | ||
1437 | * @param string $subject | ||
1438 | */ | ||
1439 | function setSubject($subject) { | ||
1440 | if ($this->isFinalized) { | ||
1441 | return; | ||
1442 | } | ||
1443 | $this->opf->addDCMeta(DublinCore::SUBJECT, $this->decodeHtmlEntities($subject)); | ||
1444 | } | ||
1445 | |||
1446 | /** | ||
1447 | * Book source URL, optional. | ||
1448 | * | ||
1449 | * A related resource from which the described resource is derived. | ||
1450 | * | ||
1451 | * The described resource may be derived from the related resource in whole | ||
1452 | * or in part. Recommended best practice is to identify the related | ||
1453 | * resource by means of a string conforming to a formal identification system. | ||
1454 | * | ||
1455 | * Used for the dc:source metadata parameter in the OPF file | ||
1456 | * | ||
1457 | * @param string $sourceURL | ||
1458 | * @access public | ||
1459 | * @return bool $success | ||
1460 | */ | ||
1461 | function setSourceURL($sourceURL) { | ||
1462 | if ($this->isFinalized) { | ||
1463 | return FALSE; | ||
1464 | } | ||
1465 | $this->sourceURL = $sourceURL; | ||
1466 | return TRUE; | ||
1467 | } | ||
1468 | |||
1469 | /** | ||
1470 | * Get Book sourceURL. | ||
1471 | * | ||
1472 | * @access public | ||
1473 | * @return $sourceURL | ||
1474 | */ | ||
1475 | function getSourceURL() { | ||
1476 | return $this->sourceURL; | ||
1477 | } | ||
1478 | |||
1479 | /** | ||
1480 | * Coverage, optional. | ||
1481 | * | ||
1482 | * The spatial or temporal topic of the resource, the spatial applicability | ||
1483 | * of the resource, or the jurisdiction under which the resource is relevant. | ||
1484 | * | ||
1485 | * Spatial topic and spatial applicability may be a named place or a location | ||
1486 | * specified by its geographic coordinates. Temporal topic may be a named | ||
1487 | * period, date, or date range. A jurisdiction may be a named administrative | ||
1488 | * entity or a geographic place to which the resource applies. Recommended | ||
1489 | * best practice is to use a controlled vocabulary such as the Thesaurus of | ||
1490 | * Geographic Names [TGN]. Where appropriate, named places or time periods | ||
1491 | * can be used in preference to numeric identifiers such as sets of | ||
1492 | * coordinates or date ranges. | ||
1493 | * | ||
1494 | * Used for the dc:coverage metadata parameter in the OPF file | ||
1495 | * | ||
1496 | * Same as ->addDublinCoreMetadata(DublinCore::COVERAGE, $coverage); | ||
1497 | * | ||
1498 | * @param string $coverage | ||
1499 | * @access public | ||
1500 | * @return bool $success | ||
1501 | */ | ||
1502 | function setCoverage($coverage) { | ||
1503 | if ($this->isFinalized) { | ||
1504 | return FALSE; | ||
1505 | } | ||
1506 | $this->coverage = $coverage; | ||
1507 | return TRUE; | ||
1508 | } | ||
1509 | |||
1510 | /** | ||
1511 | * Get Book coverage. | ||
1512 | * | ||
1513 | * @access public | ||
1514 | * @return $coverage | ||
1515 | */ | ||
1516 | function getCoverage() { | ||
1517 | return $this->coverage; | ||
1518 | } | ||
1519 | |||
1520 | /** | ||
1521 | * Set book Relation. | ||
1522 | * | ||
1523 | * A related resource. | ||
1524 | * | ||
1525 | * Recommended best practice is to identify the related resource by means | ||
1526 | * of a string conforming to a formal identification system. | ||
1527 | * | ||
1528 | * @param string $relation | ||
1529 | */ | ||
1530 | function setRelation($relation) { | ||
1531 | if ($this->isFinalized) { | ||
1532 | return; | ||
1533 | } | ||
1534 | $this->relation = $relation; | ||
1535 | } | ||
1536 | |||
1537 | /** | ||
1538 | * Get the book relation. | ||
1539 | * | ||
1540 | * @return string The relation. | ||
1541 | */ | ||
1542 | function getRelation() { | ||
1543 | return $this->relation; | ||
1544 | } | ||
1545 | |||
1546 | /** | ||
1547 | * Set book Generator. | ||
1548 | * | ||
1549 | * The generator is a meta tag added to the ncx file, it is not visible | ||
1550 | * from within the book, but is a kind of electronic watermark. | ||
1551 | * | ||
1552 | * @param string $generator | ||
1553 | */ | ||
1554 | function setGenerator($generator) { | ||
1555 | if ($this->isFinalized) { | ||
1556 | return; | ||
1557 | } | ||
1558 | $this->generator = $generator; | ||
1559 | } | ||
1560 | |||
1561 | /** | ||
1562 | * Get the book relation. | ||
1563 | * | ||
1564 | * @return string The generator identity string. | ||
1565 | */ | ||
1566 | function getGenerator() { | ||
1567 | return $this->generator; | ||
1568 | } | ||
1569 | |||
1570 | /** | ||
1571 | * Set ePub date formate to the short yyyy-mm-dd form, for compliance with | ||
1572 | * a bug in EpubCheck, prior to its version 1.1. | ||
1573 | * | ||
1574 | * The latest version of ePubCheck can be obtained here: | ||
1575 | * http://code.google.com/p/epubcheck/ | ||
1576 | * | ||
1577 | * @access public | ||
1578 | * @return bool $success | ||
1579 | */ | ||
1580 | function setShortDateFormat() { | ||
1581 | if ($this->isFinalized) { | ||
1582 | return FALSE; | ||
1583 | } | ||
1584 | $this->dateformat = $this->dateformatShort; | ||
1585 | return TRUE; | ||
1586 | } | ||
1587 | |||
1588 | /** | ||
1589 | * @Deprecated | ||
1590 | */ | ||
1591 | function setIgnoreEmptyBuffer($ignoreEmptyBuffer = TRUE) { | ||
1592 | die ("Function was deprecated, functionality is no longer needed."); | ||
1593 | } | ||
1594 | |||
1595 | /** | ||
1596 | * Set the references title for the ePub 3 landmarks section | ||
1597 | * | ||
1598 | * @param string $referencesTitle | ||
1599 | * @param string $referencesId | ||
1600 | * @param string $referencesClass | ||
1601 | * @return bool | ||
1602 | */ | ||
1603 | function setReferencesTitle($referencesTitle = "Guide", $referencesId = "", $referencesClass = "references") { | ||
1604 | if ($this->isFinalized) { | ||
1605 | return FALSE; | ||
1606 | } | ||
1607 | $this->ncx->referencesTitle = is_string($referencesTitle) ? trim($referencesTitle) : "Guide"; | ||
1608 | $this->ncx->referencesId = is_string($referencesId) ? trim($referencesId) : "references"; | ||
1609 | $this->ncx->referencesClass = is_string($referencesClass) ? trim($referencesClass) : "references"; | ||
1610 | return TRUE; | ||
1611 | } | ||
1612 | |||
1613 | /** | ||
1614 | * Set the references title for the ePub 3 landmarks section | ||
1615 | * | ||
1616 | * @param bool $referencesTitle | ||
1617 | */ | ||
1618 | function setisReferencesAddedToToc($isReferencesAddedToToc = TRUE) { | ||
1619 | if ($this->isFinalized) { | ||
1620 | return FALSE; | ||
1621 | } | ||
1622 | $this->isReferencesAddedToToc = $isReferencesAddedToToc === TRUE; | ||
1623 | return TRUE; | ||
1624 | } | ||
1625 | |||
1626 | /** | ||
1627 | * Get Book status. | ||
1628 | * | ||
1629 | * @access public | ||
1630 | * @return bool | ||
1631 | */ | ||
1632 | function isFinalized() { | ||
1633 | return $this->isFinalized; | ||
1634 | } | ||
1635 | |||
1636 | /** | ||
1637 | * Build the Table of Contents. This is not strictly necessary, as most eReaders will build it from the navigation structure in the .ncx file. | ||
1638 | * | ||
1639 | * @param string $cssFileName Include a link to this css file in the TOC html. | ||
1640 | * @param string $tocCSSClass The TOC is a <div>, if you need special formatting, you can add a css class for that div. Default is "toc". | ||
1641 | * @param string $title Title of the Table of contents. Default is "Table of Contents". Use this for ie. languages other than English. | ||
1642 | * @param bool $addReferences include reference pages in the TOC, using the $referencesOrder array to determine the order of the pages in the TOC. Default is TRUE. | ||
1643 | * @param bool $addToIndex Add the TOC to the NCX index at the current leve/position. Default is FALSE | ||
1644 | * @param string $tocFileName Change teh default name of the TOC file. The default is "TOC.xhtml" | ||
1645 | */ | ||
1646 | function buildTOC($cssFileName = NULL, $tocCSSClass = "toc", $title = "Table of Contents", $addReferences = TRUE, $addToIndex = FALSE, $tocFileName = "TOC.xhtml") { | ||
1647 | if ($this->isFinalized) { | ||
1648 | return FALSE; | ||
1649 | } | ||
1650 | $this->buildTOC = TRUE; | ||
1651 | $this->tocTitle = $title; | ||
1652 | $this->tocFileName = $this->normalizeFileName($tocFileName); | ||
1653 | if (!empty($cssFileName)) { | ||
1654 | $this->tocCSSFileName = $this->normalizeFileName($cssFileName); | ||
1655 | } | ||
1656 | $this->tocCSSClass = $tocCSSClass; | ||
1657 | $this->tocAddReferences = $addReferences; | ||
1658 | |||
1659 | $this->opf->addItemRef("ref_" . Reference::TABLE_OF_CONTENTS, FALSE); | ||
1660 | $this->opf->addReference(Reference::TABLE_OF_CONTENTS, $title, $this->tocFileName); | ||
1661 | |||
1662 | if ($addToIndex) { | ||
1663 | $navPoint = new NavPoint($this->decodeHtmlEntities($title), $this->tocFileName, "ref_" . Reference::TABLE_OF_CONTENTS); | ||
1664 | $this->ncx->addNavPoint($navPoint); | ||
1665 | } else { | ||
1666 | $this->ncx->referencesList[Reference::TABLE_OF_CONTENTS] = $this->tocFileName; | ||
1667 | $this->ncx->referencesName[Reference::TABLE_OF_CONTENTS] = $title; | ||
1668 | } | ||
1669 | } | ||
1670 | |||
1671 | private function finalizeTOC() { | ||
1672 | if (!$this->buildTOC) { | ||
1673 | return FALSE; | ||
1674 | } | ||
1675 | |||
1676 | if (empty($this->tocTitle)) { | ||
1677 | $this->tocTitle = "Table of Contents"; | ||
1678 | } | ||
1679 | |||
1680 | $tocData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; | ||
1681 | |||
1682 | if ($this->isEPubVersion2()) { | ||
1683 | $tocData .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n" | ||
1684 | . " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" | ||
1685 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" | ||
1686 | . "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; | ||
1687 | } else { | ||
1688 | $tocData .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
1689 | . "<head>\n<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"; | ||
1690 | } | ||
1691 | |||
1692 | if (!empty($this->tocCssFileName)) { | ||
1693 | $tocData .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . $this->tocCssFileName . "\" />\n"; | ||
1694 | } | ||
1695 | |||
1696 | $tocData .= "<title>" . $this->tocTitle . "</title>\n" | ||
1697 | . "</head>\n" | ||
1698 | . "<body>\n" | ||
1699 | . "<h3>" . $this->tocTitle . "</h3>\n<div"; | ||
1700 | |||
1701 | if (!empty($this->tocCSSClass)) { | ||
1702 | $tocData .= " class=\"" . $this->tocCSSClass . "\""; | ||
1703 | } | ||
1704 | $tocData .= ">\n"; | ||
1705 | |||
1706 | while (list($item, $descriptive) = each($this->referencesOrder)) { | ||
1707 | if ($item === "text") { | ||
1708 | while (list($chapterName, $navPoint) = each($this->ncx->chapterList)) { | ||
1709 | $fileName = $navPoint->getContentSrc(); | ||
1710 | $level = $navPoint->getLevel() -2; | ||
1711 | $tocData .= "\t<p>" . str_repeat("      ", $level) . "<a href=\"" . $this->sanitizeFileName($fileName) . "\">" . $chapterName . "</a></p>\n"; | ||
1712 | } | ||
1713 | } else if ($this->tocAddReferences === TRUE) { | ||
1714 | if (array_key_exists($item, $this->ncx->referencesList)) { | ||
1715 | $tocData .= "\t<p><a href=\"" . $this->ncx->referencesList[$item] . "\">" . $descriptive . "</a></p>\n"; | ||
1716 | } else if ($item === "toc") { | ||
1717 | $tocData .= "\t<p><a href=\"TOC.xhtml\">" . $this->tocTitle . "</a></p>\n"; | ||
1718 | } else if ($item === "cover" && $this->isCoverImageSet) { | ||
1719 | $tocData .= "\t<p><a href=\"CoverPage.xhtml\">" . $descriptive . "</a></p>\n"; | ||
1720 | } | ||
1721 | } | ||
1722 | } | ||
1723 | $tocData .= "</div>\n</body>\n</html>\n"; | ||
1724 | |||
1725 | $this->addReferencePage($this->tocTitle, $this->tocFileName, $tocData, Reference::TABLE_OF_CONTENTS); | ||
1726 | |||
1727 | } | ||
1728 | |||
1729 | /** | ||
1730 | * @return bool | ||
1731 | */ | ||
1732 | function isEPubVersion2() { | ||
1733 | return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; | ||
1734 | } | ||
1735 | |||
1736 | /** | ||
1737 | * @param string $cssFileName | ||
1738 | * @param string $title | ||
1739 | * @return string | ||
1740 | */ | ||
1741 | function buildEPub3TOC($cssFileName = NULL, $title = "Table of Contents") { | ||
1742 | $this->ncx->referencesOrder = $this->referencesOrder; | ||
1743 | $this->ncx->setDocTitle($this->decodeHtmlEntities($this->title)); | ||
1744 | return $this->ncx->finalizeEPub3($title, $cssFileName); | ||
1745 | } | ||
1746 | |||
1747 | /** | ||
1748 | * @param string $fileName | ||
1749 | * @param string $tocData | ||
1750 | * @return bool | ||
1751 | */ | ||
1752 | function addEPub3TOC($fileName, $tocData) { | ||
1753 | if ($this->isEPubVersion2() || $this->isFinalized || array_key_exists($fileName, $this->fileList)) { | ||
1754 | return FALSE; | ||
1755 | } | ||
1756 | $fileName = Zip::getRelativePath($fileName); | ||
1757 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | ||
1758 | |||
1759 | $this->zip->addFile($tocData, $this->bookRoot.$fileName); | ||
1760 | |||
1761 | $this->fileList[$fileName] = $fileName; | ||
1762 | $this->opf->addItem("toc", $fileName, "application/xhtml+xml", "nav"); | ||
1763 | return TRUE; | ||
1764 | } | ||
1765 | |||
1766 | /** | ||
1767 | * Check for mandatory parameters and finalize the e-book. | ||
1768 | * Once finalized, the book is locked for further additions. | ||
1769 | * | ||
1770 | * @return bool $success | ||
1771 | */ | ||
1772 | function finalize() { | ||
1773 | if ($this->isFinalized || $this->chapterCount == 0 || empty($this->title) || empty($this->language)) { | ||
1774 | return FALSE; | ||
1775 | } | ||
1776 | |||
1777 | if (empty($this->identifier) || empty($this->identifierType)) { | ||
1778 | $this->setIdentifier($this->createUUID(4), EPub::IDENTIFIER_UUID); | ||
1779 | } | ||
1780 | |||
1781 | if ($this->date == 0) { | ||
1782 | $this->date = time(); | ||
1783 | } | ||
1784 | |||
1785 | if (empty($this->sourceURL)) { | ||
1786 | $this->sourceURL = $this->getCurrentPageURL(); | ||
1787 | } | ||
1788 | |||
1789 | if (empty($this->publisherURL)) { | ||
1790 | $this->sourceURL = $this->getCurrentServerURL(); | ||
1791 | } | ||
1792 | |||
1793 | // Generate OPF data: | ||
1794 | $this->opf->setIdent("BookId"); | ||
1795 | $this->opf->initialize($this->title, $this->language, $this->identifier, $this->identifierType); | ||
1796 | |||
1797 | $DCdate = new DublinCore(DublinCore::DATE, gmdate($this->dateformat, $this->date)); | ||
1798 | $DCdate->addOpfAttr("event", "publication"); | ||
1799 | $this->opf->metadata->addDublinCore($DCdate); | ||
1800 | |||
1801 | if (!empty($this->description)) { | ||
1802 | $this->opf->addDCMeta(DublinCore::DESCRIPTION, $this->decodeHtmlEntities($this->description)); | ||
1803 | } | ||
1804 | |||
1805 | if (!empty($this->publisherName)) { | ||
1806 | $this->opf->addDCMeta(DublinCore::PUBLISHER, $this->decodeHtmlEntities($this->publisherName)); | ||
1807 | } | ||
1808 | |||
1809 | if (!empty($this->publisherURL)) { | ||
1810 | $this->opf->addDCMeta(DublinCore::RELATION, $this->decodeHtmlEntities($this->publisherURL)); | ||
1811 | } | ||
1812 | |||
1813 | if (!empty($this->author)) { | ||
1814 | $author = $this->decodeHtmlEntities($this->author); | ||
1815 | $this->opf->addCreator($author, $this->decodeHtmlEntities($this->authorSortKey), MarcCode::AUTHOR); | ||
1816 | $this->ncx->setDocAuthor($author); | ||
1817 | } | ||
1818 | |||
1819 | if (!empty($this->rights)) { | ||
1820 | $this->opf->addDCMeta(DublinCore::RIGHTS, $this->decodeHtmlEntities($this->rights)); | ||
1821 | } | ||
1822 | |||
1823 | if (!empty($this->coverage)) { | ||
1824 | $this->opf->addDCMeta(DublinCore::COVERAGE, $this->decodeHtmlEntities($this->coverage)); | ||
1825 | } | ||
1826 | |||
1827 | if (!empty($this->sourceURL)) { | ||
1828 | $this->opf->addDCMeta(DublinCore::SOURCE, $this->sourceURL); | ||
1829 | } | ||
1830 | |||
1831 | if (!empty($this->relation)) { | ||
1832 | $this->opf->addDCMeta(DublinCore::RELATION, $this->decodeHtmlEntities($this->relation)); | ||
1833 | } | ||
1834 | |||
1835 | if ($this->isCoverImageSet) { | ||
1836 | $this->opf->addMeta("cover", "coverImage"); | ||
1837 | } | ||
1838 | |||
1839 | if (!empty($this->generator)) { | ||
1840 | $gen = $this->decodeHtmlEntities($this->generator); | ||
1841 | $this->opf->addMeta("generator", $gen); | ||
1842 | $this->ncx->addMetaEntry("dtb:generator", $gen); | ||
1843 | } | ||
1844 | |||
1845 | if ($this->EPubMark) { | ||
1846 | $this->opf->addMeta("generator", "EPub (Version " . self::VERSION . ") by A. Grandt, http://www.phpclasses.org/package/6115"); | ||
1847 | } | ||
1848 | |||
1849 | reset($this->ncx->chapterList); | ||
1850 | list($firstChapterName, $firstChapterNavPoint) = each($this->ncx->chapterList); | ||
1851 | $firstChapterFileName = $firstChapterNavPoint->getContentSrc(); | ||
1852 | $this->opf->addReference(Reference::TEXT, $this->decodeHtmlEntities($firstChapterName), $firstChapterFileName); | ||
1853 | |||
1854 | $this->ncx->setUid($this->identifier); | ||
1855 | |||
1856 | $this->ncx->setDocTitle($this->decodeHtmlEntities($this->title)); | ||
1857 | |||
1858 | $this->ncx->referencesOrder = $this->referencesOrder; | ||
1859 | if ($this->isReferencesAddedToToc) { | ||
1860 | $this->ncx->finalizeReferences(); | ||
1861 | } | ||
1862 | |||
1863 | $this->finalizeTOC(); | ||
1864 | |||
1865 | if (!$this->isEPubVersion2()) { | ||
1866 | $this->addEPub3TOC("epub3toc.xhtml", $this->buildEPub3TOC()); | ||
1867 | } | ||
1868 | |||
1869 | $opfFinal = $this->fixEncoding($this->opf->finalize()); | ||
1870 | $ncxFinal = $this->fixEncoding($this->ncx->finalize()); | ||
1871 | |||
1872 | if (mb_detect_encoding($opfFinal, 'UTF-8', true) === "UTF-8") { | ||
1873 | $this->zip->addFile($opfFinal, $this->bookRoot."book.opf"); | ||
1874 | } else { | ||
1875 | $this->zip->addFile(mb_convert_encoding($opfFinal, "UTF-8"), $this->bookRoot."book.opf"); | ||
1876 | } | ||
1877 | |||
1878 | if (mb_detect_encoding($ncxFinal, 'UTF-8', true) === "UTF-8") { | ||
1879 | $this->zip->addFile($ncxFinal, $this->bookRoot."book.ncx"); | ||
1880 | } else { | ||
1881 | $this->zip->addFile(mb_convert_encoding($ncxFinal, "UTF-8"), $this->bookRoot."book.ncx"); | ||
1882 | } | ||
1883 | |||
1884 | $this->opf = NULL; | ||
1885 | $this->ncx = NULL; | ||
1886 | |||
1887 | $this->isFinalized = TRUE; | ||
1888 | return TRUE; | ||
1889 | } | ||
1890 | |||
1891 | /** | ||
1892 | * Ensure the encoded string is a valid UTF-8 string. | ||
1893 | * | ||
1894 | * Note, that a mb_detect_encoding on the returned string will still return ASCII if the entire string is comprized of characters in the 1-127 range. | ||
1895 | * | ||
1896 | * @link: http://snippetdb.com/php/convert-string-to-utf-8-for-mysql | ||
1897 | * @param string $in_str | ||
1898 | * @return string converted string. | ||
1899 | */ | ||
1900 | function fixEncoding($in_str) { | ||
1901 | if (mb_detect_encoding($in_str) == "UTF-8" && mb_check_encoding($in_str,"UTF-8")) { | ||
1902 | return $in_str; | ||
1903 | } else { | ||
1904 | return utf8_encode($in_str); | ||
1905 | } | ||
1906 | } | ||
1907 | |||
1908 | /** | ||
1909 | * Return the finalized book. | ||
1910 | * | ||
1911 | * @return string with the book in binary form. | ||
1912 | */ | ||
1913 | function getBook() { | ||
1914 | if (!$this->isFinalized) { | ||
1915 | $this->finalize(); | ||
1916 | } | ||
1917 | |||
1918 | return $this->zip->getZipData(); | ||
1919 | } | ||
1920 | |||
1921 | /** | ||
1922 | * Remove disallowed characters from string to get a nearly safe filename | ||
1923 | * | ||
1924 | * @param string $fileName | ||
1925 | * @return mixed|string | ||
1926 | */ | ||
1927 | function sanitizeFileName($fileName) { | ||
1928 | $fileName1 = str_replace($this->forbiddenCharacters, '', $fileName); | ||
1929 | $fileName2 = preg_replace('/[\s-]+/', '-', $fileName1); | ||
1930 | return trim($fileName2, '.-_'); | ||
1931 | |||
1932 | } | ||
1933 | |||
1934 | /** | ||
1935 | * Cleanup the filepath, and remove leading . and / characters. | ||
1936 | * | ||
1937 | * Sometimes, when a path is generated from multiple fragments, | ||
1938 | * you can get something like "../data/html/../images/image.jpeg" | ||
1939 | * ePub files don't work well with that, this will normalize that | ||
1940 | * example path to "data/images/image.jpeg" | ||
1941 | * | ||
1942 | * @param string $fileName | ||
1943 | * @return string normalized filename | ||
1944 | */ | ||
1945 | function normalizeFileName($fileName) { | ||
1946 | return preg_replace('#^[/\.]+#i', "", Zip::getRelativePath($fileName)); | ||
1947 | } | ||
1948 | |||
1949 | /** | ||
1950 | * Save the ePub file to local disk. | ||
1951 | * | ||
1952 | * @param string $fileName | ||
1953 | * @param string $baseDir If empty baseDir is absolute to server path, if omitted it's relative to script path | ||
1954 | * @return The sent file name if successfull, FALSE if it failed. | ||
1955 | */ | ||
1956 | function saveBook($fileName, $baseDir = '.') { | ||
1957 | |||
1958 | // Make fileName safe | ||
1959 | $fileName = $this->sanitizeFileName($fileName); | ||
1960 | |||
1961 | // Finalize book, if it's not done already | ||
1962 | if (!$this->isFinalized) { | ||
1963 | $this->finalize(); | ||
1964 | } | ||
1965 | |||
1966 | if (stripos(strrev($fileName), "bupe.") !== 0) { | ||
1967 | $fileName .= ".epub"; | ||
1968 | } | ||
1969 | |||
1970 | // Try to open file access | ||
1971 | $fh = fopen($baseDir.'/'.$fileName, "w"); | ||
1972 | |||
1973 | if ($fh) { | ||
1974 | fputs($fh, $this->getBook()); | ||
1975 | fclose($fh); | ||
1976 | |||
1977 | // if file is written return TRUE | ||
1978 | return $fileName; | ||
1979 | } | ||
1980 | |||
1981 | // return FALSE by default | ||
1982 | return FALSE; | ||
1983 | } | ||
1984 | |||
1985 | /** | ||
1986 | * Return the finalized book size. | ||
1987 | * | ||
1988 | * @return string | ||
1989 | */ | ||
1990 | function getBookSize() { | ||
1991 | if (!$this->isFinalized) { | ||
1992 | $this->finalize(); | ||
1993 | } | ||
1994 | |||
1995 | return $this->zip->getArchiveSize(); | ||
1996 | } | ||
1997 | |||
1998 | /** | ||
1999 | * Send the book as a zip download | ||
2000 | * | ||
2001 | * Sending will fail if the output buffer is in use. You can override this limit by | ||
2002 | * calling setIgnoreEmptyBuffer(TRUE), though the function will still fail if that | ||
2003 | * buffer is not empty. | ||
2004 | * | ||
2005 | * @param string $fileName The name of the book without the .epub at the end. | ||
2006 | * @return The sent file name if successfull, FALSE if it failed. | ||
2007 | */ | ||
2008 | function sendBook($fileName) { | ||
2009 | if (!$this->isFinalized) { | ||
2010 | $this->finalize(); | ||
2011 | } | ||
2012 | |||
2013 | if (stripos(strrev($fileName), "bupe.") !== 0) { | ||
2014 | $fileName .= ".epub"; | ||
2015 | } | ||
2016 | |||
2017 | if (TRUE === $this->zip->sendZip($fileName, "application/epub+zip")) { | ||
2018 | return $fileName; | ||
2019 | } | ||
2020 | return FALSE; | ||
2021 | } | ||
2022 | |||
2023 | /** | ||
2024 | * Generates an UUID. | ||
2025 | * | ||
2026 | * Default version (4) will generate a random UUID, version 3 will URL based UUID. | ||
2027 | * | ||
2028 | * Added for convinience | ||
2029 | * | ||
2030 | * @param int $bookVersion UUID version to retrieve, See lib.uuid.manual.html for details. | ||
2031 | * @param string $url | ||
2032 | * @return string The formatted uuid | ||
2033 | */ | ||
2034 | function createUUID($bookVersion = 4, $url = NULL) { | ||
2035 | include_once("lib.uuid.php"); | ||
2036 | return UUID::mint($bookVersion, $url, UUID::nsURL); | ||
2037 | } | ||
2038 | |||
2039 | /** | ||
2040 | * Get the url of the current page. | ||
2041 | * Example use: Default Source URL | ||
2042 | * | ||
2043 | * $return string Page URL. | ||
2044 | */ | ||
2045 | function getCurrentPageURL() { | ||
2046 | $pageURL = $this->getCurrentServerURL() . filter_input(INPUT_SERVER, "REQUEST_URI"); | ||
2047 | return $pageURL; | ||
2048 | } | ||
2049 | |||
2050 | /** | ||
2051 | * Get the url of the server. | ||
2052 | * Example use: Default Publisher URL | ||
2053 | * | ||
2054 | * $return string Server URL. | ||
2055 | */ | ||
2056 | function getCurrentServerURL() { | ||
2057 | $serverURL = 'http'; | ||
2058 | $https = filter_input(INPUT_SERVER, "HTTPS"); | ||
2059 | $port = filter_input(INPUT_SERVER, "SERVER_PORT"); | ||
2060 | |||
2061 | if ($https === "on") { | ||
2062 | $serverURL .= "s"; | ||
2063 | } | ||
2064 | $serverURL .= "://" . filter_input(INPUT_SERVER, "SERVER_NAME"); | ||
2065 | if ($port != "80") { | ||
2066 | $serverURL .= ":" . $port; | ||
2067 | } | ||
2068 | return $serverURL . '/'; | ||
2069 | } | ||
2070 | |||
2071 | /** | ||
2072 | * Try to determine the mimetype of the file path. | ||
2073 | * | ||
2074 | * @param string $source Path | ||
2075 | * @return string mimetype, or FALSE. | ||
2076 | */ | ||
2077 | function getMime($source) { | ||
2078 | return $this->mimetypes[pathinfo($source, PATHINFO_EXTENSION)]; | ||
2079 | } | ||
2080 | |||
2081 | /** | ||
2082 | * Get an image from a file or url, return it resized if the image exceeds the $maxImageWidth or $maxImageHeight directives. | ||
2083 | * | ||
2084 | * The return value is an array. | ||
2085 | * ['width'] is the width of the image. | ||
2086 | * ['height'] is the height of the image. | ||
2087 | * ['mime'] is the mime type of the image. Resized images are always in jpeg format. | ||
2088 | * ['image'] is the image data. | ||
2089 | * ['ext'] is the extension of the image file. | ||
2090 | * | ||
2091 | * @param string $source path or url to file. | ||
2092 | * $return array | ||
2093 | */ | ||
2094 | function getImage($source) { | ||
2095 | $width = -1; | ||
2096 | $height = -1; | ||
2097 | $mime = "application/octet-stream"; | ||
2098 | $type = FALSE; | ||
2099 | $ext = ""; | ||
2100 | |||
2101 | |||
2102 | $image = $this->getFileContents($source); | ||
2103 | |||
2104 | if ($image !== FALSE && strlen($image) > 0) { | ||
2105 | $imageFile = imagecreatefromstring($image); | ||
2106 | if ($imageFile !== false) { | ||
2107 | $width = ImageSX($imageFile); | ||
2108 | $height = ImageSY($imageFile); | ||
2109 | } | ||
2110 | if ($this->isExifInstalled) { | ||
2111 | @$type = exif_imagetype($source); | ||
2112 | $mime = image_type_to_mime_type($type); | ||
2113 | } | ||
2114 | if ($mime === "application/octet-stream") { | ||
2115 | $mime = $this->image_file_type_from_binary($image); | ||
2116 | } | ||
2117 | if ($mime === "application/octet-stream") { | ||
2118 | $mime = $this->getMimeTypeFromUrl($source); | ||
2119 | } | ||
2120 | } else { | ||
2121 | return FALSE; | ||
2122 | } | ||
2123 | |||
2124 | if ($width <= 0 || $height <= 0) { | ||
2125 | return FALSE; | ||
2126 | } | ||
2127 | |||
2128 | $ratio = 1; | ||
2129 | |||
2130 | if ($this->isGdInstalled) { | ||
2131 | if ($width > $this->maxImageWidth) { | ||
2132 | $ratio = $this->maxImageWidth/$width; | ||
2133 | } | ||
2134 | if ($height*$ratio > $this->maxImageHeight) { | ||
2135 | $ratio = $this->maxImageHeight/$height; | ||
2136 | } | ||
2137 | |||
2138 | if ($ratio < 1 || empty($mime) || ($this->isGifImagesEnabled !== FALSE && $mime == "image/gif")) { | ||
2139 | $image_o = imagecreatefromstring($image); | ||
2140 | $image_p = imagecreatetruecolor($width*$ratio, $height*$ratio); | ||
2141 | |||
2142 | if ($mime == "image/png") { | ||
2143 | imagealphablending($image_p, false); | ||
2144 | imagesavealpha($image_p, true); | ||
2145 | imagealphablending($image_o, true); | ||
2146 | |||
2147 | imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height); | ||
2148 | ob_start(); | ||
2149 | imagepng($image_p, NULL, 9); | ||
2150 | $image = ob_get_contents(); | ||
2151 | ob_end_clean(); | ||
2152 | |||
2153 | $ext = "png"; | ||
2154 | } else { | ||
2155 | imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height); | ||
2156 | ob_start(); | ||
2157 | imagejpeg($image_p, NULL, 80); | ||
2158 | $image = ob_get_contents(); | ||
2159 | ob_end_clean(); | ||
2160 | |||
2161 | $mime = "image/jpeg"; | ||
2162 | $ext = "jpg"; | ||
2163 | } | ||
2164 | imagedestroy($image_o); | ||
2165 | imagedestroy($image_p); | ||
2166 | } | ||
2167 | } | ||
2168 | |||
2169 | if ($ext === "") { | ||
2170 | static $mimeToExt = array ( | ||
2171 | 'image/jpeg' => 'jpg', | ||
2172 | 'image/gif' => 'gif', | ||
2173 | 'image/png' => 'png' | ||
2174 | ); | ||
2175 | |||
2176 | if (isset($mimeToExt[$mime])) { | ||
2177 | $ext = $mimeToExt[$mime]; | ||
2178 | } | ||
2179 | } | ||
2180 | |||
2181 | $rv = array(); | ||
2182 | $rv['width'] = $width*$ratio; | ||
2183 | $rv['height'] = $height*$ratio; | ||
2184 | $rv['mime'] = $mime; | ||
2185 | $rv['image'] = $image; | ||
2186 | $rv['ext'] = $ext; | ||
2187 | |||
2188 | return $rv; | ||
2189 | } | ||
2190 | |||
2191 | /** | ||
2192 | * Get file contents, using curl if available, else file_get_contents | ||
2193 | * | ||
2194 | * @param string $source | ||
2195 | * @return bool | ||
2196 | */ | ||
2197 | function getFileContents($source, $toTempFile = FALSE) { | ||
2198 | $isExternal = preg_match('#^(http|ftp)s?://#i', $source) == 1; | ||
2199 | |||
2200 | if ($isExternal && $this->isCurlInstalled) { | ||
2201 | $ch = curl_init(); | ||
2202 | $outFile = NULL; | ||
2203 | $fp = NULL; | ||
2204 | $res = FALSE; | ||
2205 | $info = array('http_code' => 500); | ||
2206 | |||
2207 | curl_setopt($ch, CURLOPT_HEADER, 0); | ||
2208 | curl_setopt($ch, CURLOPT_URL, str_replace(" ","%20",$source)); | ||
2209 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | ||
2210 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | ||
2211 | curl_setopt($ch, CURLOPT_BUFFERSIZE, 4096); | ||
2212 | |||
2213 | if ($toTempFile) { | ||
2214 | $outFile = tempnam(sys_get_temp_dir(), "EPub_v" . EPub::VERSION . "_"); | ||
2215 | $fp = fopen($outFile, "w+b"); | ||
2216 | curl_setopt($ch, CURLOPT_FILE, $fp); | ||
2217 | |||
2218 | $res = curl_exec($ch); | ||
2219 | $info = curl_getinfo($ch); | ||
2220 | |||
2221 | curl_close($ch); | ||
2222 | fclose($fp); | ||
2223 | } else { | ||
2224 | $res = curl_exec($ch); | ||
2225 | $info = curl_getinfo($ch); | ||
2226 | |||
2227 | curl_close($ch); | ||
2228 | } | ||
2229 | |||
2230 | if ($info['http_code'] == 200 && $res != false) { | ||
2231 | if ($toTempFile) { | ||
2232 | return $outFile; | ||
2233 | } | ||
2234 | return $res; | ||
2235 | } | ||
2236 | return FALSE; | ||
2237 | } | ||
2238 | |||
2239 | if ($this->isFileGetContentsInstalled && (!$isExternal || $this->isFileGetContentsExtInstalled)) { | ||
2240 | @$data = file_get_contents($source); | ||
2241 | return $data; | ||
2242 | } | ||
2243 | return FALSE; | ||
2244 | } | ||
2245 | |||
2246 | /** | ||
2247 | * get mime type from image data | ||
2248 | * | ||
2249 | * By fireweasel found on http://stackoverflow.com/questions/2207095/get-image-mimetype-from-resource-in-php-gd | ||
2250 | * @staticvar array $type | ||
2251 | * @param object $binary | ||
2252 | * @return string | ||
2253 | */ | ||
2254 | function image_file_type_from_binary($binary) { | ||
2255 | $hits = 0; | ||
2256 | if (!preg_match( | ||
2257 | '/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(?:\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/', | ||
2258 | $binary, $hits)) { | ||
2259 | return 'application/octet-stream'; | ||
2260 | } | ||
2261 | static $type = array ( | ||
2262 | 1 => 'image/jpeg', | ||
2263 | 2 => 'image/gif', | ||
2264 | 3 => 'image/png', | ||
2265 | 4 => 'image/x-windows-bmp', | ||
2266 | 5 => 'image/tiff', | ||
2267 | 6 => 'image/x-ilbm', | ||
2268 | ); | ||
2269 | return $type[count($hits) - 1]; | ||
2270 | } | ||
2271 | |||
2272 | /** | ||
2273 | * @param string $source URL Source | ||
2274 | * @return string MimeType | ||
2275 | */ | ||
2276 | function getMimeTypeFromUrl($source) { | ||
2277 | $ext = FALSE; | ||
2278 | |||
2279 | $srev = strrev($source); | ||
2280 | $pos = strpos($srev, "?"); | ||
2281 | if ($pos !== FALSE) { | ||
2282 | $srev = substr($srev, $pos+1); | ||
2283 | } | ||
2284 | |||
2285 | $pos = strpos($srev, "."); | ||
2286 | if ($pos !== FALSE) { | ||
2287 | $ext = strtolower(strrev(substr($srev, 0, $pos))); | ||
2288 | } | ||
2289 | |||
2290 | if ($ext !== FALSE) { | ||
2291 | return $this->getMimeTypeFromExtension($ext); | ||
2292 | } | ||
2293 | return "application/octet-stream"; | ||
2294 | } | ||
2295 | |||
2296 | /** | ||
2297 | * @param string $ext Extension | ||
2298 | * @return string MimeType | ||
2299 | */ | ||
2300 | function getMimeTypeFromExtension($ext) { | ||
2301 | switch ($ext) { | ||
2302 | case "jpg": | ||
2303 | case "jpe": | ||
2304 | case "jpeg": | ||
2305 | return 'image/jpeg'; | ||
2306 | case "gif": | ||
2307 | return 'image/gif'; | ||
2308 | case "png": | ||
2309 | return 'image/png'; | ||
2310 | case "bmp": | ||
2311 | return 'image/x-windows-bmp'; | ||
2312 | case "tif": | ||
2313 | case "tiff": | ||
2314 | case "cpt": | ||
2315 | return 'image/tiff'; | ||
2316 | case "lbm": | ||
2317 | case "ilbm": | ||
2318 | return 'image/x-ilbm'; | ||
2319 | default: | ||
2320 | return "application/octet-stream"; | ||
2321 | } | ||
2322 | } | ||
2323 | |||
2324 | /** | ||
2325 | * Encode html code to use html entities, safeguarding it from potential character encoding peoblems | ||
2326 | * This function is a bit different from the vanilla htmlentities function in that it does not encode html tags. | ||
2327 | * | ||
2328 | * The regexp is taken from the PHP Manual discussion, it was written by user "busbyjon". | ||
2329 | * http://www.php.net/manual/en/function.htmlentities.php#90111 | ||
2330 | * | ||
2331 | * @param string $string string to encode. | ||
2332 | */ | ||
2333 | public function encodeHtml($string) { | ||
2334 | $string = strtr($string, $this->html_encoding_characters); | ||
2335 | |||
2336 | //return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&\\1", $string); | ||
2337 | //return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&", $string); | ||
2338 | return $string; | ||
2339 | } | ||
2340 | |||
2341 | /** | ||
2342 | * Helper function to create a DOM fragment with given markup. | ||
2343 | * | ||
2344 | * @author Adam Schmalhofer | ||
2345 | * | ||
2346 | * @param DOMDocument $dom | ||
2347 | * @param string $markup | ||
2348 | * @return DOMNode fragment in a node. | ||
2349 | */ | ||
2350 | protected function createDomFragment($dom, $markup) { | ||
2351 | $node = $dom->createDocumentFragment(); | ||
2352 | $node->appendXML($markup); | ||
2353 | return $node; | ||
2354 | } | ||
2355 | |||
2356 | /** | ||
2357 | * Retrieve an array of file names currently added to the book. | ||
2358 | * $key is the filename used in the book | ||
2359 | * $value is the original filename, will be the same as $key for most entries | ||
2360 | * | ||
2361 | * @return array file list | ||
2362 | */ | ||
2363 | function getFileList() { | ||
2364 | return $this->fileList; | ||
2365 | } | ||
2366 | |||
2367 | /** | ||
2368 | * @deprecated Use Zip::getRelativePath($relPath) instead. | ||
2369 | */ | ||
2370 | function relPath($relPath) { | ||
2371 | die ("Function was deprecated, use Zip::getRelativePath(\$relPath); instead"); | ||
2372 | } | ||
2373 | |||
2374 | /** | ||
2375 | * Set default chapter target size. | ||
2376 | * Default is 250000 bytes, and minimum is 10240 bytes. | ||
2377 | * | ||
2378 | * @param int $size segment size in bytes | ||
2379 | * @return void | ||
2380 | */ | ||
2381 | function setSplitSize($size) { | ||
2382 | $this->splitDefaultSize = (int)$size; | ||
2383 | if ($size < 10240) { | ||
2384 | $this->splitDefaultSize = 10240; // Making the file smaller than 10k is not a good idea. | ||
2385 | } | ||
2386 | } | ||
2387 | |||
2388 | /** | ||
2389 | * Get the chapter target size. | ||
2390 | * | ||
2391 | * @return $size | ||
2392 | */ | ||
2393 | function getSplitSize() { | ||
2394 | return $this->splitDefaultSize; | ||
2395 | } | ||
2396 | |||
2397 | /** | ||
2398 | * Remove all non essential html tags and entities. | ||
2399 | * | ||
2400 | * @global type $htmlEntities | ||
2401 | * @param string $string | ||
2402 | * @return string with the stripped entities. | ||
2403 | */ | ||
2404 | function decodeHtmlEntities($string) { | ||
2405 | global $htmlEntities; | ||
2406 | |||
2407 | $string = preg_replace('~\s*<br\s*/*\s*>\s*~i', "\n", $string); | ||
2408 | $string = preg_replace('~\s*</(p|div)\s*>\s*~i', "\n\n", $string); | ||
2409 | $string = preg_replace('~<[^>]*>~', '', $string); | ||
2410 | |||
2411 | $string = strtr($string, $htmlEntities); | ||
2412 | |||
2413 | $string = str_replace('&', '&', $string); | ||
2414 | $string = str_replace('&amp;', '&', $string); | ||
2415 | $string = preg_replace('~&(#x*[a-fA-F0-9]+;)~', '&\1', $string); | ||
2416 | $string = str_replace('<', '<', $string); | ||
2417 | $string = str_replace('>', '>', $string); | ||
2418 | |||
2419 | return $string; | ||
2420 | } | ||
2421 | |||
2422 | /** | ||
2423 | * Simply remove all HTML tags, brute force and no finesse. | ||
2424 | * | ||
2425 | * @param string $string html | ||
2426 | * @return string | ||
2427 | */ | ||
2428 | function html2text($string) { | ||
2429 | return preg_replace('~<[^>]*>~', '', $string); | ||
2430 | } | ||
2431 | |||
2432 | /** | ||
2433 | * @return string | ||
2434 | */ | ||
2435 | function getLog() { | ||
2436 | return $this->log->getLog(); | ||
2437 | } | ||
2438 | } | ||
diff --git a/inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php b/inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php new file mode 100644 index 00000000..1d44f238 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php | |||
@@ -0,0 +1,201 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Split an HTML file into smaller html files, retaining the formatting and structure for the individual parts. | ||
4 | * What this splitter does is using DOM to try and retain any formatting in the file, including rebuilding the DOM tree for subsequent parts. | ||
5 | * Split size is considered max target size. The actual size is the result of an even split across the resulting files. | ||
6 | * | ||
7 | * @author A. Grandt <php@grandt.com> | ||
8 | * @copyright 2009-2014 A. Grandt | ||
9 | * @license GNU LGPL 2.1 | ||
10 | * @link http://www.phpclasses.org/package/6115 | ||
11 | * @link https://github.com/Grandt/PHPePub | ||
12 | * @version 3.20 | ||
13 | */ | ||
14 | class EPubChapterSplitter { | ||
15 | const VERSION = 3.20; | ||
16 | |||
17 | private $splitDefaultSize = 250000; | ||
18 | private $bookVersion = EPub::BOOK_VERSION_EPUB2; | ||
19 | |||
20 | /** | ||
21 | * | ||
22 | * Enter description here ... | ||
23 | * | ||
24 | * @param unknown_type $ident | ||
25 | */ | ||
26 | function setVersion($bookVersion) { | ||
27 | $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Set default chapter target size. | ||
32 | * Default is 250000 bytes, and minimum is 10240 bytes. | ||
33 | * | ||
34 | * @param $size segment size in bytes | ||
35 | * @return void | ||
36 | */ | ||
37 | function setSplitSize($size) { | ||
38 | $this->splitDefaultSize = (int)$size; | ||
39 | if ($size < 10240) { | ||
40 | $this->splitDefaultSize = 10240; // Making the file smaller than 10k is not a good idea. | ||
41 | } | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * Get the chapter target size. | ||
46 | * | ||
47 | * @return $size | ||
48 | */ | ||
49 | function getSplitSize() { | ||
50 | return $this->splitDefaultSize; | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Split $chapter into multiple parts. | ||
55 | * | ||
56 | * The search string can either be a regular string or a PHP PECL Regular Expression pattern as defined here: http://www.php.net/manual/en/pcre.pattern.php | ||
57 | * If the search string is a regular string, the matching will be for lines in the HTML starting with the string given | ||
58 | * | ||
59 | * @param String $chapter XHTML file | ||
60 | * @param Bool $splitOnSearchString Split on chapter boundaries, Splitting on search strings disables the split size check. | ||
61 | * @param String $searchString Chapter string to search for can be fixed text, or a regular expression pattern. | ||
62 | * | ||
63 | * @return array with 1 or more parts | ||
64 | */ | ||
65 | function splitChapter($chapter, $splitOnSearchString = false, $searchString = '/^Chapter\\ /i') { | ||
66 | $chapterData = array(); | ||
67 | $isSearchRegexp = $splitOnSearchString && (preg_match('#^(\D|\S|\W).+\1[imsxeADSUXJu]*$#m', $searchString) == 1); | ||
68 | if ($splitOnSearchString && !$isSearchRegexp) { | ||
69 | $searchString = '#^<.+?>' . preg_quote($searchString, '#') . "#"; | ||
70 | } | ||
71 | |||
72 | if (!$splitOnSearchString && strlen($chapter) <= $this->splitDefaultSize) { | ||
73 | return array($chapter); | ||
74 | } | ||
75 | |||
76 | $xmlDoc = new DOMDocument(); | ||
77 | @$xmlDoc->loadHTML($chapter); | ||
78 | |||
79 | $head = $xmlDoc->getElementsByTagName("head"); | ||
80 | $body = $xmlDoc->getElementsByTagName("body"); | ||
81 | |||
82 | $htmlPos = stripos($chapter, "<html"); | ||
83 | $htmlEndPos = stripos($chapter, ">", $htmlPos); | ||
84 | $newXML = substr($chapter, 0, $htmlEndPos+1) . "\n</html>"; | ||
85 | if (strpos(trim($newXML), "<?xml ") === FALSE) { | ||
86 | $newXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" . $newXML; | ||
87 | } | ||
88 | $headerLength = strlen($newXML); | ||
89 | |||
90 | $files = array(); | ||
91 | $chapterNames = array(); | ||
92 | $domDepth = 0; | ||
93 | $domPath = array(); | ||
94 | $domClonedPath = array(); | ||
95 | |||
96 | $curFile = $xmlDoc->createDocumentFragment(); | ||
97 | $files[] = $curFile; | ||
98 | $curParent = $curFile; | ||
99 | $curSize = 0; | ||
100 | |||
101 | $bodyLen = strlen($xmlDoc->saveXML($body->item(0))); | ||
102 | $headLen = strlen($xmlDoc->saveXML($head->item(0))) + $headerLength; | ||
103 | |||
104 | $partSize = $this->splitDefaultSize - $headLen; | ||
105 | |||
106 | if ($bodyLen > $partSize) { | ||
107 | $parts = ceil($bodyLen / $partSize); | ||
108 | $partSize = ($bodyLen / $parts) - $headLen; | ||
109 | } | ||
110 | |||
111 | $node = $body->item(0)->firstChild; | ||
112 | |||
113 | do { | ||
114 | $nodeData = $xmlDoc->saveXML($node); | ||
115 | $nodeLen = strlen($nodeData); | ||
116 | |||
117 | if ($nodeLen > $partSize && $node->hasChildNodes()) { | ||
118 | $domPath[] = $node; | ||
119 | $domClonedPath[] = $node->cloneNode(false); | ||
120 | $domDepth++; | ||
121 | |||
122 | $node = $node->firstChild; | ||
123 | } | ||
124 | |||
125 | $node2 = $node->nextSibling; | ||
126 | |||
127 | if ($node != null && $node->nodeName != "#text") { | ||
128 | $doSplit = false; | ||
129 | if ($splitOnSearchString) { | ||
130 | $doSplit = preg_match($searchString, $nodeData) == 1; | ||
131 | if ($doSplit) { | ||
132 | $chapterNames[] = trim($nodeData); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | if ($curSize > 0 && ($doSplit || (!$splitOnSearchString && $curSize + $nodeLen > $partSize))) { | ||
137 | $curFile = $xmlDoc->createDocumentFragment(); | ||
138 | $files[] = $curFile; | ||
139 | $curParent = $curFile; | ||
140 | if ($domDepth > 0) { | ||
141 | reset($domPath); | ||
142 | reset($domClonedPath); | ||
143 | $oneDomClonedPath = each($domClonedPath); | ||
144 | while ($oneDomClonedPath) { | ||
145 | list($k, $v) = $oneDomClonedPath; | ||
146 | $newParent = $v->cloneNode(false); | ||
147 | $curParent->appendChild($newParent); | ||
148 | $curParent = $newParent; | ||
149 | $oneDomClonedPath = each($domClonedPath); | ||
150 | } | ||
151 | } | ||
152 | $curSize = strlen($xmlDoc->saveXML($curFile)); | ||
153 | } | ||
154 | $curParent->appendChild($node->cloneNode(true)); | ||
155 | $curSize += $nodeLen; | ||
156 | } | ||
157 | |||
158 | $node = $node2; | ||
159 | while ($node == null && $domDepth > 0) { | ||
160 | $domDepth--; | ||
161 | $node = end($domPath)->nextSibling; | ||
162 | array_pop($domPath); | ||
163 | array_pop($domClonedPath); | ||
164 | $curParent = $curParent->parentNode; | ||
165 | } | ||
166 | } while ($node != null); | ||
167 | |||
168 | $curFile = null; | ||
169 | $curSize = 0; | ||
170 | |||
171 | $xml = new DOMDocument('1.0', $xmlDoc->xmlEncoding); | ||
172 | $xml->lookupPrefix("http://www.w3.org/1999/xhtml"); | ||
173 | $xml->preserveWhiteSpace = false; | ||
174 | $xml->formatOutput = true; | ||
175 | |||
176 | for ($idx = 0; $idx < count($files); $idx++) { | ||
177 | $xml2Doc = new DOMDocument('1.0', $xmlDoc->xmlEncoding); | ||
178 | $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml"); | ||
179 | $xml2Doc->loadXML($newXML); | ||
180 | $html = $xml2Doc->getElementsByTagName("html")->item(0); | ||
181 | $html->appendChild($xml2Doc->importNode($head->item(0), true)); | ||
182 | $body = $xml2Doc->createElement("body"); | ||
183 | $html->appendChild($body); | ||
184 | $body->appendChild($xml2Doc->importNode($files[$idx], true)); | ||
185 | |||
186 | // force pretty printing and correct formatting, should not be needed, but it is. | ||
187 | $xml->loadXML($xml2Doc->saveXML()); | ||
188 | |||
189 | $doc = $xml->saveXML(); | ||
190 | |||
191 | if ($this->bookVersion === EPub::BOOK_VERSION_EPUB3) { | ||
192 | $doc = preg_replace('#^\s*<!DOCTYPE\ .+?>\s*#im', '', $doc); | ||
193 | } | ||
194 | |||
195 | $chapterData[$splitOnSearchString ? $chapterNames[$idx] : $idx] = $doc; | ||
196 | } | ||
197 | |||
198 | return $chapterData; | ||
199 | } | ||
200 | } | ||
201 | ?> | ||
diff --git a/inc/3rdparty/libraries/PHPePub/Logger.php b/inc/3rdparty/libraries/PHPePub/Logger.php new file mode 100644 index 00000000..314019cb --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/Logger.php | |||
@@ -0,0 +1,92 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Simple log line aggregator. | ||
4 | * | ||
5 | * @author A. Grandt <php@grandt.com> | ||
6 | * @copyright 2012-2013 A. Grandt | ||
7 | * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
8 | * @version 1.00 | ||
9 | */ | ||
10 | class Logger { | ||
11 | const VERSION = 1.00; | ||
12 | |||
13 | private $log = ""; | ||
14 | private $tStart; | ||
15 | private $tLast; | ||
16 | private $name = NULL; | ||
17 | private $isLogging = FALSE; | ||
18 | private $isDebugging = FALSE; | ||
19 | |||
20 | /** | ||
21 | * Class constructor. | ||
22 | * | ||
23 | * @return void | ||
24 | */ | ||
25 | function __construct($name = NULL, $isLogging = FALSE) { | ||
26 | if ($name === NULL) { | ||
27 | $this->name = ""; | ||
28 | } else { | ||
29 | $this->name = $name . " : "; | ||
30 | } | ||
31 | $this->isLogging = $isLogging; | ||
32 | $this->start(); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Class destructor | ||
37 | * | ||
38 | * @return void | ||
39 | * @TODO make sure elements in the destructor match the current class elements | ||
40 | */ | ||
41 | function __destruct() { | ||
42 | unset($this->log); | ||
43 | } | ||
44 | |||
45 | function start() { | ||
46 | /* Prepare Logging. Just in case it's used. later */ | ||
47 | if ($this->isLogging) { | ||
48 | $this->tStart = gettimeofday(); | ||
49 | $this->tLast = $this->tStart; | ||
50 | $this->log = "<h1>Log: " . $this->name . "</h1>\n<pre>Started: " . gmdate("D, d M Y H:i:s T", $this->tStart['sec']) . "\n Δ Start ; Δ Last ;"; | ||
51 | $this->logLine("Start"); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | function dumpInstalledModules() { | ||
56 | if ($this->isLogging) { | ||
57 | $isCurlInstalled = extension_loaded('curl') && function_exists('curl_version'); | ||
58 | $isGdInstalled = extension_loaded('gd') && function_exists('gd_info'); | ||
59 | $isExifInstalled = extension_loaded('exif') && function_exists('exif_imagetype'); | ||
60 | $isFileGetContentsInstalled = function_exists('file_get_contents'); | ||
61 | $isFileGetContentsExtInstalled = $isFileGetContentsInstalled && ini_get('allow_url_fopen'); | ||
62 | |||
63 | $this->logLine("isCurlInstalled...............: " . ($isCurlInstalled ? "Yes" : "No")); | ||
64 | $this->logLine("isGdInstalled.................: " . ($isGdInstalled ? "Yes" : "No")); | ||
65 | $this->logLine("isExifInstalled...............: " . ($isExifInstalled ? "Yes" : "No")); | ||
66 | $this->logLine("isFileGetContentsInstalled....: " . ($isFileGetContentsInstalled ? "Yes" : "No")); | ||
67 | $this->logLine("isFileGetContentsExtInstalled.: " . ($isFileGetContentsExtInstalled ? "Yes" : "No")); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | function logLine($line) { | ||
72 | if ($this->isLogging) { | ||
73 | $tTemp = gettimeofday(); | ||
74 | $tS = $this->tStart['sec'] + (((int)($this->tStart['usec']/100))/10000); | ||
75 | $tL = $this->tLast['sec'] + (((int)($this->tLast['usec']/100))/10000); | ||
76 | $tT = $tTemp['sec'] + (((int)($tTemp['usec']/100))/10000); | ||
77 | |||
78 | $logline = sprintf("\n+%08.04f; +%08.04f; ", ($tT-$tS), ($tT-$tL)) . $this->name . $line; | ||
79 | $this->log .= $logline; | ||
80 | $this->tLast = $tTemp; | ||
81 | |||
82 | if ($this->isDebugging) { | ||
83 | echo "<pre>" . $logline . "\n</pre>\n"; | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | |||
88 | function getLog() { | ||
89 | return $this->log; | ||
90 | } | ||
91 | } | ||
92 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/PHPePub/Zip.php b/inc/3rdparty/libraries/PHPePub/Zip.php new file mode 100644 index 00000000..01e03566 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/Zip.php | |||
@@ -0,0 +1,818 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Class to create and manage a Zip file. | ||
4 | * | ||
5 | * Initially inspired by CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) | ||
6 | * and | ||
7 | * http://www.pkware.com/documents/casestudies/APPNOTE.TXT Zip file specification. | ||
8 | * | ||
9 | * License: GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
10 | * | ||
11 | * @author A. Grandt <php@grandt.com> | ||
12 | * @copyright 2009-2014 A. Grandt | ||
13 | * @license GNU LGPL 2.1 | ||
14 | * @link http://www.phpclasses.org/package/6110 | ||
15 | * @link https://github.com/Grandt/PHPZip | ||
16 | * @version 1.60 | ||
17 | */ | ||
18 | class Zip { | ||
19 | const VERSION = 1.60; | ||
20 | |||
21 | const ZIP_LOCAL_FILE_HEADER = "\x50\x4b\x03\x04"; // Local file header signature | ||
22 | const ZIP_CENTRAL_FILE_HEADER = "\x50\x4b\x01\x02"; // Central file header signature | ||
23 | const ZIP_END_OF_CENTRAL_DIRECTORY = "\x50\x4b\x05\x06\x00\x00\x00\x00"; //end of Central directory record | ||
24 | |||
25 | const EXT_FILE_ATTR_DIR = 010173200020; // Permission 755 drwxr-xr-x = (((S_IFDIR | 0755) << 16) | S_DOS_D); | ||
26 | const EXT_FILE_ATTR_FILE = 020151000040; // Permission 644 -rw-r--r-- = (((S_IFREG | 0644) << 16) | S_DOS_A); | ||
27 | |||
28 | const ATTR_VERSION_TO_EXTRACT = "\x14\x00"; // Version needed to extract | ||
29 | const ATTR_MADE_BY_VERSION = "\x1E\x03"; // Made By Version | ||
30 | |||
31 | // Unix file types | ||
32 | const S_IFIFO = 0010000; // named pipe (fifo) | ||
33 | const S_IFCHR = 0020000; // character special | ||
34 | const S_IFDIR = 0040000; // directory | ||
35 | const S_IFBLK = 0060000; // block special | ||
36 | const S_IFREG = 0100000; // regular | ||
37 | const S_IFLNK = 0120000; // symbolic link | ||
38 | const S_IFSOCK = 0140000; // socket | ||
39 | |||
40 | // setuid/setgid/sticky bits, the same as for chmod: | ||
41 | |||
42 | const S_ISUID = 0004000; // set user id on execution | ||
43 | const S_ISGID = 0002000; // set group id on execution | ||
44 | const S_ISTXT = 0001000; // sticky bit | ||
45 | |||
46 | // And of course, the other 12 bits are for the permissions, the same as for chmod: | ||
47 | // When addding these up, you can also just write the permissions as a simgle octal number | ||
48 | // ie. 0755. The leading 0 specifies octal notation. | ||
49 | const S_IRWXU = 0000700; // RWX mask for owner | ||
50 | const S_IRUSR = 0000400; // R for owner | ||
51 | const S_IWUSR = 0000200; // W for owner | ||
52 | const S_IXUSR = 0000100; // X for owner | ||
53 | const S_IRWXG = 0000070; // RWX mask for group | ||
54 | const S_IRGRP = 0000040; // R for group | ||
55 | const S_IWGRP = 0000020; // W for group | ||
56 | const S_IXGRP = 0000010; // X for group | ||
57 | const S_IRWXO = 0000007; // RWX mask for other | ||
58 | const S_IROTH = 0000004; // R for other | ||
59 | const S_IWOTH = 0000002; // W for other | ||
60 | const S_IXOTH = 0000001; // X for other | ||
61 | const S_ISVTX = 0001000; // save swapped text even after use | ||
62 | |||
63 | // Filetype, sticky and permissions are added up, and shifted 16 bits left BEFORE adding the DOS flags. | ||
64 | |||
65 | // DOS file type flags, we really only use the S_DOS_D flag. | ||
66 | |||
67 | const S_DOS_A = 0000040; // DOS flag for Archive | ||
68 | const S_DOS_D = 0000020; // DOS flag for Directory | ||
69 | const S_DOS_V = 0000010; // DOS flag for Volume | ||
70 | const S_DOS_S = 0000004; // DOS flag for System | ||
71 | const S_DOS_H = 0000002; // DOS flag for Hidden | ||
72 | const S_DOS_R = 0000001; // DOS flag for Read Only | ||
73 | |||
74 | private $zipMemoryThreshold = 1048576; // Autocreate tempfile if the zip data exceeds 1048576 bytes (1 MB) | ||
75 | |||
76 | private $zipData = NULL; | ||
77 | private $zipFile = NULL; | ||
78 | private $zipComment = NULL; | ||
79 | private $cdRec = array(); // central directory | ||
80 | private $offset = 0; | ||
81 | private $isFinalized = FALSE; | ||
82 | private $addExtraField = TRUE; | ||
83 | |||
84 | private $streamChunkSize = 65536; | ||
85 | private $streamFilePath = NULL; | ||
86 | private $streamTimestamp = NULL; | ||
87 | private $streamFileComment = NULL; | ||
88 | private $streamFile = NULL; | ||
89 | private $streamData = NULL; | ||
90 | private $streamFileLength = 0; | ||
91 | private $streamExtFileAttr = null; | ||
92 | |||
93 | /** | ||
94 | * Constructor. | ||
95 | * | ||
96 | * @param boolean $useZipFile Write temp zip data to tempFile? Default FALSE | ||
97 | */ | ||
98 | function __construct($useZipFile = FALSE) { | ||
99 | if ($useZipFile) { | ||
100 | $this->zipFile = tmpfile(); | ||
101 | } else { | ||
102 | $this->zipData = ""; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | function __destruct() { | ||
107 | if (is_resource($this->zipFile)) { | ||
108 | fclose($this->zipFile); | ||
109 | } | ||
110 | $this->zipData = NULL; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Extra fields on the Zip directory records are Unix time codes needed for compatibility on the default Mac zip archive tool. | ||
115 | * These are enabled as default, as they do no harm elsewhere and only add 26 bytes per file added. | ||
116 | * | ||
117 | * @param bool $setExtraField TRUE (default) will enable adding of extra fields, anything else will disable it. | ||
118 | */ | ||
119 | function setExtraField($setExtraField = TRUE) { | ||
120 | $this->addExtraField = ($setExtraField === TRUE); | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * Set Zip archive comment. | ||
125 | * | ||
126 | * @param string $newComment New comment. NULL to clear. | ||
127 | * @return bool $success | ||
128 | */ | ||
129 | public function setComment($newComment = NULL) { | ||
130 | if ($this->isFinalized) { | ||
131 | return FALSE; | ||
132 | } | ||
133 | $this->zipComment = $newComment; | ||
134 | |||
135 | return TRUE; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Set zip file to write zip data to. | ||
140 | * This will cause all present and future data written to this class to be written to this file. | ||
141 | * This can be used at any time, even after the Zip Archive have been finalized. Any previous file will be closed. | ||
142 | * Warning: If the given file already exists, it will be overwritten. | ||
143 | * | ||
144 | * @param string $fileName | ||
145 | * @return bool $success | ||
146 | */ | ||
147 | public function setZipFile($fileName) { | ||
148 | if (is_file($fileName)) { | ||
149 | unlink($fileName); | ||
150 | } | ||
151 | $fd=fopen($fileName, "x+b"); | ||
152 | if (is_resource($this->zipFile)) { | ||
153 | rewind($this->zipFile); | ||
154 | while (!feof($this->zipFile)) { | ||
155 | fwrite($fd, fread($this->zipFile, $this->streamChunkSize)); | ||
156 | } | ||
157 | |||
158 | fclose($this->zipFile); | ||
159 | } else { | ||
160 | fwrite($fd, $this->zipData); | ||
161 | $this->zipData = NULL; | ||
162 | } | ||
163 | $this->zipFile = $fd; | ||
164 | |||
165 | return TRUE; | ||
166 | } | ||
167 | |||
168 | /** | ||
169 | * Add an empty directory entry to the zip archive. | ||
170 | * Basically this is only used if an empty directory is added. | ||
171 | * | ||
172 | * @param string $directoryPath Directory Path and name to be added to the archive. | ||
173 | * @param int $timestamp (Optional) Timestamp for the added directory, if omitted or set to 0, the current time will be used. | ||
174 | * @param string $fileComment (Optional) Comment to be added to the archive for this directory. To use fileComment, timestamp must be given. | ||
175 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
176 | * @return bool $success | ||
177 | */ | ||
178 | public function addDirectory($directoryPath, $timestamp = 0, $fileComment = NULL, $extFileAttr = self::EXT_FILE_ATTR_DIR) { | ||
179 | if ($this->isFinalized) { | ||
180 | return FALSE; | ||
181 | } | ||
182 | $directoryPath = str_replace("\\", "/", $directoryPath); | ||
183 | $directoryPath = rtrim($directoryPath, "/"); | ||
184 | |||
185 | if (strlen($directoryPath) > 0) { | ||
186 | $this->buildZipEntry($directoryPath.'/', $fileComment, "\x00\x00", "\x00\x00", $timestamp, "\x00\x00\x00\x00", 0, 0, $extFileAttr); | ||
187 | return TRUE; | ||
188 | } | ||
189 | return FALSE; | ||
190 | } | ||
191 | |||
192 | /** | ||
193 | * Add a file to the archive at the specified location and file name. | ||
194 | * | ||
195 | * @param string $data File data. | ||
196 | * @param string $filePath Filepath and name to be used in the archive. | ||
197 | * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. | ||
198 | * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. | ||
199 | * @param bool $compress (Optional) Compress file, if set to FALSE the file will only be stored. Default TRUE. | ||
200 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
201 | * @return bool $success | ||
202 | */ | ||
203 | public function addFile($data, $filePath, $timestamp = 0, $fileComment = NULL, $compress = TRUE, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
204 | if ($this->isFinalized) { | ||
205 | return FALSE; | ||
206 | } | ||
207 | |||
208 | if (is_resource($data) && get_resource_type($data) == "stream") { | ||
209 | $this->addLargeFile($data, $filePath, $timestamp, $fileComment, $extFileAttr); | ||
210 | return FALSE; | ||
211 | } | ||
212 | |||
213 | $gzData = ""; | ||
214 | $gzType = "\x08\x00"; // Compression type 8 = deflate | ||
215 | $gpFlags = "\x00\x00"; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression. | ||
216 | $dataLength = strlen($data); | ||
217 | $fileCRC32 = pack("V", crc32($data)); | ||
218 | |||
219 | if ($compress) { | ||
220 | $gzTmp = gzcompress($data); | ||
221 | $gzData = substr(substr($gzTmp, 0, strlen($gzTmp) - 4), 2); // gzcompress adds a 2 byte header and 4 byte CRC we can't use. | ||
222 | // The 2 byte header does contain useful data, though in this case the 2 parameters we'd be interrested in will always be 8 for compression type, and 2 for General purpose flag. | ||
223 | $gzLength = strlen($gzData); | ||
224 | } else { | ||
225 | $gzLength = $dataLength; | ||
226 | } | ||
227 | |||
228 | if ($gzLength >= $dataLength) { | ||
229 | $gzLength = $dataLength; | ||
230 | $gzData = $data; | ||
231 | $gzType = "\x00\x00"; // Compression type 0 = stored | ||
232 | $gpFlags = "\x00\x00"; // Compression type 0 = stored | ||
233 | } | ||
234 | |||
235 | if (!is_resource($this->zipFile) && ($this->offset + $gzLength) > $this->zipMemoryThreshold) { | ||
236 | $this->zipflush(); | ||
237 | } | ||
238 | |||
239 | $this->buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr); | ||
240 | |||
241 | $this->zipwrite($gzData); | ||
242 | |||
243 | return TRUE; | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * Add the content to a directory. | ||
248 | * | ||
249 | * @author Adam Schmalhofer <Adam.Schmalhofer@gmx.de> | ||
250 | * @author A. Grandt | ||
251 | * | ||
252 | * @param string $realPath Path on the file system. | ||
253 | * @param string $zipPath Filepath and name to be used in the archive. | ||
254 | * @param bool $recursive Add content recursively, default is TRUE. | ||
255 | * @param bool $followSymlinks Follow and add symbolic links, if they are accessible, default is TRUE. | ||
256 | * @param array &$addedFiles Reference to the added files, this is used to prevent duplicates, efault is an empty array. | ||
257 | * If you start the function by parsing an array, the array will be populated with the realPath | ||
258 | * and zipPath kay/value pairs added to the archive by the function. | ||
259 | * @param bool $overrideFilePermissions Force the use of the file/dir permissions set in the $extDirAttr | ||
260 | * and $extFileAttr parameters. | ||
261 | * @param int $extDirAttr Permissions for directories. | ||
262 | * @param int $extFileAttr Permissions for files. | ||
263 | */ | ||
264 | public function addDirectoryContent($realPath, $zipPath, $recursive = TRUE, $followSymlinks = TRUE, &$addedFiles = array(), | ||
265 | $overrideFilePermissions = FALSE, $extDirAttr = self::EXT_FILE_ATTR_DIR, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
266 | if (file_exists($realPath) && !isset($addedFiles[realpath($realPath)])) { | ||
267 | if (is_dir($realPath)) { | ||
268 | if ($overrideFilePermissions) { | ||
269 | $this->addDirectory($zipPath, 0, null, $extDirAttr); | ||
270 | } else { | ||
271 | $this->addDirectory($zipPath, 0, null, self::getFileExtAttr($realPath)); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | $addedFiles[realpath($realPath)] = $zipPath; | ||
276 | |||
277 | $iter = new DirectoryIterator($realPath); | ||
278 | foreach ($iter as $file) { | ||
279 | if ($file->isDot()) { | ||
280 | continue; | ||
281 | } | ||
282 | $newRealPath = $file->getPathname(); | ||
283 | $newZipPath = self::pathJoin($zipPath, $file->getFilename()); | ||
284 | |||
285 | if (file_exists($newRealPath) && ($followSymlinks === TRUE || !is_link($newRealPath))) { | ||
286 | if ($file->isFile()) { | ||
287 | $addedFiles[realpath($newRealPath)] = $newZipPath; | ||
288 | if ($overrideFilePermissions) { | ||
289 | $this->addLargeFile($newRealPath, $newZipPath, 0, null, $extFileAttr); | ||
290 | } else { | ||
291 | $this->addLargeFile($newRealPath, $newZipPath, 0, null, self::getFileExtAttr($newRealPath)); | ||
292 | } | ||
293 | } else if ($recursive === TRUE) { | ||
294 | $this->addDirectoryContent($newRealPath, $newZipPath, $recursive, $followSymlinks, $addedFiles, $overrideFilePermissions, $extDirAttr, $extFileAttr); | ||
295 | } else { | ||
296 | if ($overrideFilePermissions) { | ||
297 | $this->addDirectory($zipPath, 0, null, $extDirAttr); | ||
298 | } else { | ||
299 | $this->addDirectory($zipPath, 0, null, self::getFileExtAttr($newRealPath)); | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | /** | ||
308 | * Add a file to the archive at the specified location and file name. | ||
309 | * | ||
310 | * @param string $dataFile File name/path. | ||
311 | * @param string $filePath Filepath and name to be used in the archive. | ||
312 | * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. | ||
313 | * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. | ||
314 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
315 | * @return bool $success | ||
316 | */ | ||
317 | public function addLargeFile($dataFile, $filePath, $timestamp = 0, $fileComment = NULL, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
318 | if ($this->isFinalized) { | ||
319 | return FALSE; | ||
320 | } | ||
321 | |||
322 | if (is_string($dataFile) && is_file($dataFile)) { | ||
323 | $this->processFile($dataFile, $filePath, $timestamp, $fileComment, $extFileAttr); | ||
324 | } else if (is_resource($dataFile) && get_resource_type($dataFile) == "stream") { | ||
325 | $fh = $dataFile; | ||
326 | $this->openStream($filePath, $timestamp, $fileComment, $extFileAttr); | ||
327 | |||
328 | while (!feof($fh)) { | ||
329 | $this->addStreamData(fread($fh, $this->streamChunkSize)); | ||
330 | } | ||
331 | $this->closeStream($this->addExtraField); | ||
332 | } | ||
333 | return TRUE; | ||
334 | } | ||
335 | |||
336 | /** | ||
337 | * Create a stream to be used for large entries. | ||
338 | * | ||
339 | * @param string $filePath Filepath and name to be used in the archive. | ||
340 | * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. | ||
341 | * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. | ||
342 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
343 | * @return bool $success | ||
344 | */ | ||
345 | public function openStream($filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
346 | if (!function_exists('sys_get_temp_dir')) { | ||
347 | die ("ERROR: Zip " . self::VERSION . " requires PHP version 5.2.1 or above if large files are used."); | ||
348 | } | ||
349 | |||
350 | if ($this->isFinalized) { | ||
351 | return FALSE; | ||
352 | } | ||
353 | |||
354 | $this->zipflush(); | ||
355 | |||
356 | if (strlen($this->streamFilePath) > 0) { | ||
357 | $this->closeStream(); | ||
358 | } | ||
359 | |||
360 | $this->streamFile = tempnam(sys_get_temp_dir(), 'Zip'); | ||
361 | $this->streamData = fopen($this->streamFile, "wb"); | ||
362 | $this->streamFilePath = $filePath; | ||
363 | $this->streamTimestamp = $timestamp; | ||
364 | $this->streamFileComment = $fileComment; | ||
365 | $this->streamFileLength = 0; | ||
366 | $this->streamExtFileAttr = $extFileAttr; | ||
367 | |||
368 | return TRUE; | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * Add data to the open stream. | ||
373 | * | ||
374 | * @param string $data | ||
375 | * @return mixed length in bytes added or FALSE if the archive is finalized or there are no open stream. | ||
376 | */ | ||
377 | public function addStreamData($data) { | ||
378 | if ($this->isFinalized || strlen($this->streamFilePath) == 0) { | ||
379 | return FALSE; | ||
380 | } | ||
381 | |||
382 | $length = fwrite($this->streamData, $data, strlen($data)); | ||
383 | if ($length != strlen($data)) { | ||
384 | die ("<p>Length mismatch</p>\n"); | ||
385 | } | ||
386 | $this->streamFileLength += $length; | ||
387 | |||
388 | return $length; | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * Close the current stream. | ||
393 | * | ||
394 | * @return bool $success | ||
395 | */ | ||
396 | public function closeStream() { | ||
397 | if ($this->isFinalized || strlen($this->streamFilePath) == 0) { | ||
398 | return FALSE; | ||
399 | } | ||
400 | |||
401 | fflush($this->streamData); | ||
402 | fclose($this->streamData); | ||
403 | |||
404 | $this->processFile($this->streamFile, $this->streamFilePath, $this->streamTimestamp, $this->streamFileComment, $this->streamExtFileAttr); | ||
405 | |||
406 | $this->streamData = null; | ||
407 | $this->streamFilePath = null; | ||
408 | $this->streamTimestamp = null; | ||
409 | $this->streamFileComment = null; | ||
410 | $this->streamFileLength = 0; | ||
411 | $this->streamExtFileAttr = null; | ||
412 | |||
413 | // Windows is a little slow at times, so a millisecond later, we can unlink this. | ||
414 | unlink($this->streamFile); | ||
415 | |||
416 | $this->streamFile = null; | ||
417 | |||
418 | return TRUE; | ||
419 | } | ||
420 | |||
421 | private function processFile($dataFile, $filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
422 | if ($this->isFinalized) { | ||
423 | return FALSE; | ||
424 | } | ||
425 | |||
426 | $tempzip = tempnam(sys_get_temp_dir(), 'ZipStream'); | ||
427 | |||
428 | $zip = new ZipArchive; | ||
429 | if ($zip->open($tempzip) === TRUE) { | ||
430 | $zip->addFile($dataFile, 'file'); | ||
431 | $zip->close(); | ||
432 | } | ||
433 | |||
434 | $file_handle = fopen($tempzip, "rb"); | ||
435 | $stats = fstat($file_handle); | ||
436 | $eof = $stats['size']-72; | ||
437 | |||
438 | fseek($file_handle, 6); | ||
439 | |||
440 | $gpFlags = fread($file_handle, 2); | ||
441 | $gzType = fread($file_handle, 2); | ||
442 | fread($file_handle, 4); | ||
443 | $fileCRC32 = fread($file_handle, 4); | ||
444 | $v = unpack("Vval", fread($file_handle, 4)); | ||
445 | $gzLength = $v['val']; | ||
446 | $v = unpack("Vval", fread($file_handle, 4)); | ||
447 | $dataLength = $v['val']; | ||
448 | |||
449 | $this->buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr); | ||
450 | |||
451 | fseek($file_handle, 34); | ||
452 | $pos = 34; | ||
453 | |||
454 | while (!feof($file_handle) && $pos < $eof) { | ||
455 | $datalen = $this->streamChunkSize; | ||
456 | if ($pos + $this->streamChunkSize > $eof) { | ||
457 | $datalen = $eof-$pos; | ||
458 | } | ||
459 | $data = fread($file_handle, $datalen); | ||
460 | $pos += $datalen; | ||
461 | |||
462 | $this->zipwrite($data); | ||
463 | } | ||
464 | |||
465 | fclose($file_handle); | ||
466 | |||
467 | unlink($tempzip); | ||
468 | } | ||
469 | |||
470 | /** | ||
471 | * Close the archive. | ||
472 | * A closed archive can no longer have new files added to it. | ||
473 | * | ||
474 | * @return bool $success | ||
475 | */ | ||
476 | public function finalize() { | ||
477 | if (!$this->isFinalized) { | ||
478 | if (strlen($this->streamFilePath) > 0) { | ||
479 | $this->closeStream(); | ||
480 | } | ||
481 | $cd = implode("", $this->cdRec); | ||
482 | |||
483 | $cdRecSize = pack("v", sizeof($this->cdRec)); | ||
484 | $cdRec = $cd . self::ZIP_END_OF_CENTRAL_DIRECTORY | ||
485 | . $cdRecSize . $cdRecSize | ||
486 | . pack("VV", strlen($cd), $this->offset); | ||
487 | if (!empty($this->zipComment)) { | ||
488 | $cdRec .= pack("v", strlen($this->zipComment)) . $this->zipComment; | ||
489 | } else { | ||
490 | $cdRec .= "\x00\x00"; | ||
491 | } | ||
492 | |||
493 | $this->zipwrite($cdRec); | ||
494 | |||
495 | $this->isFinalized = TRUE; | ||
496 | $this->cdRec = NULL; | ||
497 | |||
498 | return TRUE; | ||
499 | } | ||
500 | return FALSE; | ||
501 | } | ||
502 | |||
503 | /** | ||
504 | * Get the handle ressource for the archive zip file. | ||
505 | * If the zip haven't been finalized yet, this will cause it to become finalized | ||
506 | * | ||
507 | * @return zip file handle | ||
508 | */ | ||
509 | public function getZipFile() { | ||
510 | if (!$this->isFinalized) { | ||
511 | $this->finalize(); | ||
512 | } | ||
513 | |||
514 | $this->zipflush(); | ||
515 | |||
516 | rewind($this->zipFile); | ||
517 | |||
518 | return $this->zipFile; | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * Get the zip file contents | ||
523 | * If the zip haven't been finalized yet, this will cause it to become finalized | ||
524 | * | ||
525 | * @return zip data | ||
526 | */ | ||
527 | public function getZipData() { | ||
528 | if (!$this->isFinalized) { | ||
529 | $this->finalize(); | ||
530 | } | ||
531 | if (!is_resource($this->zipFile)) { | ||
532 | return $this->zipData; | ||
533 | } else { | ||
534 | rewind($this->zipFile); | ||
535 | $filestat = fstat($this->zipFile); | ||
536 | return fread($this->zipFile, $filestat['size']); | ||
537 | } | ||
538 | } | ||
539 | |||
540 | /** | ||
541 | * Send the archive as a zip download | ||
542 | * | ||
543 | * @param String $fileName The name of the Zip archive, in ISO-8859-1 (or ASCII) encoding, ie. "archive.zip". Optional, defaults to NULL, which means that no ISO-8859-1 encoded file name will be specified. | ||
544 | * @param String $contentType Content mime type. Optional, defaults to "application/zip". | ||
545 | * @param String $utf8FileName The name of the Zip archive, in UTF-8 encoding. Optional, defaults to NULL, which means that no UTF-8 encoded file name will be specified. | ||
546 | * @param bool $inline Use Content-Disposition with "inline" instead of "attached". Optional, defaults to FALSE. | ||
547 | * @return bool $success | ||
548 | */ | ||
549 | function sendZip($fileName = null, $contentType = "application/zip", $utf8FileName = null, $inline = false) { | ||
550 | if (!$this->isFinalized) { | ||
551 | $this->finalize(); | ||
552 | } | ||
553 | |||
554 | $headerFile = null; | ||
555 | $headerLine = null; | ||
556 | if (!headers_sent($headerFile, $headerLine) or die("<p><strong>Error:</strong> Unable to send file $fileName. HTML Headers have already been sent from <strong>$headerFile</strong> in line <strong>$headerLine</strong></p>")) { | ||
557 | if ((ob_get_contents() === FALSE || ob_get_contents() == '') or die("\n<p><strong>Error:</strong> Unable to send file <strong>$fileName</strong>. Output buffer contains the following text (typically warnings or errors):<br>" . htmlentities(ob_get_contents()) . "</p>")) { | ||
558 | if (ini_get('zlib.output_compression')) { | ||
559 | ini_set('zlib.output_compression', 'Off'); | ||
560 | } | ||
561 | |||
562 | header("Pragma: public"); | ||
563 | header("Last-Modified: " . gmdate("D, d M Y H:i:s T")); | ||
564 | header("Expires: 0"); | ||
565 | header("Accept-Ranges: bytes"); | ||
566 | header("Connection: close"); | ||
567 | header("Content-Type: " . $contentType); | ||
568 | $cd = "Content-Disposition: "; | ||
569 | if ($inline) { | ||
570 | $cd .= "inline"; | ||
571 | } else{ | ||
572 | $cd .= "attached"; | ||
573 | } | ||
574 | if ($fileName) { | ||
575 | $cd .= '; filename="' . $fileName . '"'; | ||
576 | } | ||
577 | if ($utf8FileName) { | ||
578 | $cd .= "; filename*=UTF-8''" . rawurlencode($utf8FileName); | ||
579 | } | ||
580 | header($cd); | ||
581 | header("Content-Length: ". $this->getArchiveSize()); | ||
582 | |||
583 | if (!is_resource($this->zipFile)) { | ||
584 | echo $this->zipData; | ||
585 | } else { | ||
586 | rewind($this->zipFile); | ||
587 | |||
588 | while (!feof($this->zipFile)) { | ||
589 | echo fread($this->zipFile, $this->streamChunkSize); | ||
590 | } | ||
591 | } | ||
592 | } | ||
593 | return TRUE; | ||
594 | } | ||
595 | return FALSE; | ||
596 | } | ||
597 | |||
598 | /** | ||
599 | * Return the current size of the archive | ||
600 | * | ||
601 | * @return $size Size of the archive | ||
602 | */ | ||
603 | public function getArchiveSize() { | ||
604 | if (!is_resource($this->zipFile)) { | ||
605 | return strlen($this->zipData); | ||
606 | } | ||
607 | $filestat = fstat($this->zipFile); | ||
608 | |||
609 | return $filestat['size']; | ||
610 | } | ||
611 | |||
612 | /** | ||
613 | * Calculate the 2 byte dostime used in the zip entries. | ||
614 | * | ||
615 | * @param int $timestamp | ||
616 | * @return 2-byte encoded DOS Date | ||
617 | */ | ||
618 | private function getDosTime($timestamp = 0) { | ||
619 | $timestamp = (int)$timestamp; | ||
620 | $oldTZ = @date_default_timezone_get(); | ||
621 | date_default_timezone_set('UTC'); | ||
622 | $date = ($timestamp == 0 ? getdate() : getdate($timestamp)); | ||
623 | date_default_timezone_set($oldTZ); | ||
624 | if ($date["year"] >= 1980) { | ||
625 | return pack("V", (($date["mday"] + ($date["mon"] << 5) + (($date["year"]-1980) << 9)) << 16) | | ||
626 | (($date["seconds"] >> 1) + ($date["minutes"] << 5) + ($date["hours"] << 11))); | ||
627 | } | ||
628 | return "\x00\x00\x00\x00"; | ||
629 | } | ||
630 | |||
631 | /** | ||
632 | * Build the Zip file structures | ||
633 | * | ||
634 | * @param string $filePath | ||
635 | * @param string $fileComment | ||
636 | * @param string $gpFlags | ||
637 | * @param string $gzType | ||
638 | * @param int $timestamp | ||
639 | * @param string $fileCRC32 | ||
640 | * @param int $gzLength | ||
641 | * @param int $dataLength | ||
642 | * @param int $extFileAttr Use self::EXT_FILE_ATTR_FILE for files, self::EXT_FILE_ATTR_DIR for Directories. | ||
643 | */ | ||
644 | private function buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr) { | ||
645 | $filePath = str_replace("\\", "/", $filePath); | ||
646 | $fileCommentLength = (empty($fileComment) ? 0 : strlen($fileComment)); | ||
647 | $timestamp = (int)$timestamp; | ||
648 | $timestamp = ($timestamp == 0 ? time() : $timestamp); | ||
649 | |||
650 | $dosTime = $this->getDosTime($timestamp); | ||
651 | $tsPack = pack("V", $timestamp); | ||
652 | |||
653 | $ux = "\x75\x78\x0B\x00\x01\x04\xE8\x03\x00\x00\x04\x00\x00\x00\x00"; | ||
654 | |||
655 | if (!isset($gpFlags) || strlen($gpFlags) != 2) { | ||
656 | $gpFlags = "\x00\x00"; | ||
657 | } | ||
658 | |||
659 | $isFileUTF8 = mb_check_encoding($filePath, "UTF-8") && !mb_check_encoding($filePath, "ASCII"); | ||
660 | $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, "UTF-8") && !mb_check_encoding($fileComment, "ASCII"); | ||
661 | if ($isFileUTF8 || $isCommentUTF8) { | ||
662 | $flag = 0; | ||
663 | $gpFlagsV = unpack("vflags", $gpFlags); | ||
664 | if (isset($gpFlagsV['flags'])) { | ||
665 | $flag = $gpFlagsV['flags']; | ||
666 | } | ||
667 | $gpFlags = pack("v", $flag | (1 << 11)); | ||
668 | } | ||
669 | |||
670 | $header = $gpFlags . $gzType . $dosTime. $fileCRC32 | ||
671 | . pack("VVv", $gzLength, $dataLength, strlen($filePath)); // File name length | ||
672 | |||
673 | $zipEntry = self::ZIP_LOCAL_FILE_HEADER; | ||
674 | $zipEntry .= self::ATTR_VERSION_TO_EXTRACT; | ||
675 | $zipEntry .= $header; | ||
676 | $zipEntry .= pack("v", ($this->addExtraField ? 28 : 0)); // Extra field length | ||
677 | $zipEntry .= $filePath; // FileName | ||
678 | // Extra fields | ||
679 | if ($this->addExtraField) { | ||
680 | $zipEntry .= "\x55\x54\x09\x00\x03" . $tsPack . $tsPack . $ux; | ||
681 | } | ||
682 | $this->zipwrite($zipEntry); | ||
683 | |||
684 | $cdEntry = self::ZIP_CENTRAL_FILE_HEADER; | ||
685 | $cdEntry .= self::ATTR_MADE_BY_VERSION; | ||
686 | $cdEntry .= ($dataLength === 0 ? "\x0A\x00" : self::ATTR_VERSION_TO_EXTRACT); | ||
687 | $cdEntry .= $header; | ||
688 | $cdEntry .= pack("v", ($this->addExtraField ? 24 : 0)); // Extra field length | ||
689 | $cdEntry .= pack("v", $fileCommentLength); // File comment length | ||
690 | $cdEntry .= "\x00\x00"; // Disk number start | ||
691 | $cdEntry .= "\x00\x00"; // internal file attributes | ||
692 | $cdEntry .= pack("V", $extFileAttr); // External file attributes | ||
693 | $cdEntry .= pack("V", $this->offset); // Relative offset of local header | ||
694 | $cdEntry .= $filePath; // FileName | ||
695 | // Extra fields | ||
696 | if ($this->addExtraField) { | ||
697 | $cdEntry .= "\x55\x54\x05\x00\x03" . $tsPack . $ux; | ||
698 | } | ||
699 | if (!empty($fileComment)) { | ||
700 | $cdEntry .= $fileComment; // Comment | ||
701 | } | ||
702 | |||
703 | $this->cdRec[] = $cdEntry; | ||
704 | $this->offset += strlen($zipEntry) + $gzLength; | ||
705 | } | ||
706 | |||
707 | private function zipwrite($data) { | ||
708 | if (!is_resource($this->zipFile)) { | ||
709 | $this->zipData .= $data; | ||
710 | } else { | ||
711 | fwrite($this->zipFile, $data); | ||
712 | fflush($this->zipFile); | ||
713 | } | ||
714 | } | ||
715 | |||
716 | private function zipflush() { | ||
717 | if (!is_resource($this->zipFile)) { | ||
718 | $this->zipFile = tmpfile(); | ||
719 | fwrite($this->zipFile, $this->zipData); | ||
720 | $this->zipData = NULL; | ||
721 | } | ||
722 | } | ||
723 | |||
724 | /** | ||
725 | * Join $file to $dir path, and clean up any excess slashes. | ||
726 | * | ||
727 | * @param string $dir | ||
728 | * @param string $file | ||
729 | */ | ||
730 | public static function pathJoin($dir, $file) { | ||
731 | if (empty($dir) || empty($file)) { | ||
732 | return self::getRelativePath($dir . $file); | ||
733 | } | ||
734 | return self::getRelativePath($dir . '/' . $file); | ||
735 | } | ||
736 | |||
737 | /** | ||
738 | * Clean up a path, removing any unnecessary elements such as /./, // or redundant ../ segments. | ||
739 | * If the path starts with a "/", it is deemed an absolute path and any /../ in the beginning is stripped off. | ||
740 | * The returned path will not end in a "/". | ||
741 | * | ||
742 | * Sometimes, when a path is generated from multiple fragments, | ||
743 | * you can get something like "../data/html/../images/image.jpeg" | ||
744 | * This will normalize that example path to "../data/images/image.jpeg" | ||
745 | * | ||
746 | * @param string $path The path to clean up | ||
747 | * @return string the clean path | ||
748 | */ | ||
749 | public static function getRelativePath($path) { | ||
750 | $path = preg_replace("#/+\.?/+#", "/", str_replace("\\", "/", $path)); | ||
751 | $dirs = explode("/", rtrim(preg_replace('#^(?:\./)+#', '', $path), '/')); | ||
752 | |||
753 | $offset = 0; | ||
754 | $sub = 0; | ||
755 | $subOffset = 0; | ||
756 | $root = ""; | ||
757 | |||
758 | if (empty($dirs[0])) { | ||
759 | $root = "/"; | ||
760 | $dirs = array_splice($dirs, 1); | ||
761 | } else if (preg_match("#[A-Za-z]:#", $dirs[0])) { | ||
762 | $root = strtoupper($dirs[0]) . "/"; | ||
763 | $dirs = array_splice($dirs, 1); | ||
764 | } | ||
765 | |||
766 | $newDirs = array(); | ||
767 | foreach ($dirs as $dir) { | ||
768 | if ($dir !== "..") { | ||
769 | $subOffset--; | ||
770 | $newDirs[++$offset] = $dir; | ||
771 | } else { | ||
772 | $subOffset++; | ||
773 | if (--$offset < 0) { | ||
774 | $offset = 0; | ||
775 | if ($subOffset > $sub) { | ||
776 | $sub++; | ||
777 | } | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | |||
782 | if (empty($root)) { | ||
783 | $root = str_repeat("../", $sub); | ||
784 | } | ||
785 | return $root . implode("/", array_slice($newDirs, 0, $offset)); | ||
786 | } | ||
787 | |||
788 | /** | ||
789 | * Create the file permissions for a file or directory, for use in the extFileAttr parameters. | ||
790 | * | ||
791 | * @param int $owner Unix permisions for owner (octal from 00 to 07) | ||
792 | * @param int $group Unix permisions for group (octal from 00 to 07) | ||
793 | * @param int $other Unix permisions for others (octal from 00 to 07) | ||
794 | * @param bool $isFile | ||
795 | * @return EXTRERNAL_REF field. | ||
796 | */ | ||
797 | public static function generateExtAttr($owner = 07, $group = 05, $other = 05, $isFile = true) { | ||
798 | $fp = $isFile ? self::S_IFREG : self::S_IFDIR; | ||
799 | $fp |= (($owner & 07) << 6) | (($group & 07) << 3) | ($other & 07); | ||
800 | |||
801 | return ($fp << 16) | ($isFile ? self::S_DOS_A : self::S_DOS_D); | ||
802 | } | ||
803 | |||
804 | /** | ||
805 | * Get the file permissions for a file or directory, for use in the extFileAttr parameters. | ||
806 | * | ||
807 | * @param string $filename | ||
808 | * @return external ref field, or FALSE if the file is not found. | ||
809 | */ | ||
810 | public static function getFileExtAttr($filename) { | ||
811 | if (file_exists($filename)) { | ||
812 | $fp = fileperms($filename) << 16; | ||
813 | return $fp | (is_dir($filename) ? self::S_DOS_D : self::S_DOS_A); | ||
814 | } | ||
815 | return FALSE; | ||
816 | } | ||
817 | } | ||
818 | ?> | ||
diff --git a/inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt b/inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt new file mode 100644 index 00000000..9424a83e --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt | |||
@@ -0,0 +1,31 @@ | |||
1 | DrUUID RFC4122 library for PHP5 | ||
2 | by J. King (http://jkingweb.ca/) | ||
3 | Licensed under MIT license | ||
4 | |||
5 | See http://jkingweb.ca/code/php/lib.uuid/ | ||
6 | for documentation | ||
7 | |||
8 | Last revised 2010-02-15 | ||
9 | |||
10 | Copyright (c) 2009 J. King | ||
11 | |||
12 | Permission is hereby granted, free of charge, to any person | ||
13 | obtaining a copy of this software and associated documentation | ||
14 | files (the "Software"), to deal in the Software without | ||
15 | restriction, including without limitation the rights to use, | ||
16 | copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
17 | copies of the Software, and to permit persons to whom the | ||
18 | Software is furnished to do so, subject to the following | ||
19 | conditions: | ||
20 | |||
21 | The above copyright notice and this permission notice shall be | ||
22 | included in all copies or substantial portions of the Software. | ||
23 | |||
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
26 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
28 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
29 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
31 | OTHER DEALINGS IN THE SOFTWARE. | ||
diff --git a/inc/3rdparty/libraries/PHPePub/lib.uuid.php b/inc/3rdparty/libraries/PHPePub/lib.uuid.php new file mode 100644 index 00000000..c6a8de52 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/lib.uuid.php | |||
@@ -0,0 +1,314 @@ | |||
1 | <?php | ||
2 | /* | ||
3 | DrUUID RFC4122 library for PHP5 | ||
4 | by J. King (http://jkingweb.ca/) | ||
5 | Licensed under MIT license | ||
6 | |||
7 | See http://jkingweb.ca/code/php/lib.uuid/ | ||
8 | for documentation | ||
9 | |||
10 | Last revised 2010-02-15 | ||
11 | */ | ||
12 | |||
13 | /* | ||
14 | Copyright (c) 2009 J. King | ||
15 | |||
16 | Permission is hereby granted, free of charge, to any person | ||
17 | obtaining a copy of this software and associated documentation | ||
18 | files (the "Software"), to deal in the Software without | ||
19 | restriction, including without limitation the rights to use, | ||
20 | copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
21 | copies of the Software, and to permit persons to whom the | ||
22 | Software is furnished to do so, subject to the following | ||
23 | conditions: | ||
24 | |||
25 | The above copyright notice and this permission notice shall be | ||
26 | included in all copies or substantial portions of the Software. | ||
27 | |||
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
29 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
30 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
31 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
32 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
33 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
34 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
35 | OTHER DEALINGS IN THE SOFTWARE. | ||
36 | */ | ||
37 | |||
38 | |||
39 | class UUID { | ||
40 | const MD5 = 3; | ||
41 | const SHA1 = 5; | ||
42 | const clearVer = 15; // 00001111 Clears all bits of version byte with AND | ||
43 | const clearVar = 63; // 00111111 Clears all relevant bits of variant byte with AND | ||
44 | const varRes = 224; // 11100000 Variant reserved for future use | ||
45 | const varMS = 192; // 11000000 Microsft GUID variant | ||
46 | const varRFC = 128; // 10000000 The RFC 4122 variant (this variant) | ||
47 | const varNCS = 0; // 00000000 The NCS compatibility variant | ||
48 | const version1 = 16; // 00010000 | ||
49 | const version3 = 48; // 00110000 | ||
50 | const version4 = 64; // 01000000 | ||
51 | const version5 = 80; // 01010000 | ||
52 | const interval = 0x01b21dd213814000; // Time (in 100ns steps) between the start of the UTC and Unix epochs | ||
53 | const nsDNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; | ||
54 | const nsURL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; | ||
55 | const nsOID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; | ||
56 | const nsX500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; | ||
57 | protected static $randomFunc = 'randomTwister'; | ||
58 | protected static $randomSource = NULL; | ||
59 | //instance properties | ||
60 | protected $bytes; | ||
61 | protected $hex; | ||
62 | protected $string; | ||
63 | protected $urn; | ||
64 | protected $version; | ||
65 | protected $variant; | ||
66 | protected $node; | ||
67 | protected $time; | ||
68 | |||
69 | public static function mint($ver = 1, $node = NULL, $ns = NULL) { | ||
70 | /* Create a new UUID based on provided data. */ | ||
71 | switch((int) $ver) { | ||
72 | case 1: | ||
73 | return new self(self::mintTime($node)); | ||
74 | case 2: | ||
75 | // Version 2 is not supported | ||
76 | throw new UUIDException("Version 2 is unsupported."); | ||
77 | case 3: | ||
78 | return new self(self::mintName(self::MD5, $node, $ns)); | ||
79 | case 4: | ||
80 | return new self(self::mintRand()); | ||
81 | case 5: | ||
82 | return new self(self::mintName(self::SHA1, $node, $ns)); | ||
83 | default: | ||
84 | throw new UUIDException("Selected version is invalid or unsupported."); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | public static function import($uuid) { | ||
89 | /* Import an existing UUID. */ | ||
90 | return new self(self::makeBin($uuid, 16)); | ||
91 | } | ||
92 | |||
93 | public static function compare($a, $b) { | ||
94 | /* Compares the binary representations of two UUIDs. | ||
95 | The comparison will return true if they are bit-exact, | ||
96 | or if neither is valid. */ | ||
97 | if (self::makeBin($a, 16)==self::makeBin($b, 16)) { | ||
98 | return TRUE; | ||
99 | } else { | ||
100 | return FALSE; | ||
101 | } | ||
102 | } | ||
103 | |||
104 | public function __toString() { | ||
105 | return $this->string; | ||
106 | } | ||
107 | |||
108 | public function __get($var) { | ||
109 | switch($var) { | ||
110 | case "bytes": | ||
111 | return $this->bytes; | ||
112 | case "hex": | ||
113 | return bin2hex($this->bytes); | ||
114 | case "string": | ||
115 | return $this->__toString(); | ||
116 | case "urn": | ||
117 | return "urn:uuid:".$this->__toString(); | ||
118 | case "version": | ||
119 | return ord($this->bytes[6]) >> 4; | ||
120 | case "variant": | ||
121 | $byte = ord($this->bytes[8]); | ||
122 | if ($byte >= self::varRes) { | ||
123 | return 3; | ||
124 | } | ||
125 | if ($byte >= self::varMS) { | ||
126 | return 2; | ||
127 | } | ||
128 | if ($byte >= self::varRFC) { | ||
129 | return 1; | ||
130 | } | ||
131 | return 0; | ||
132 | case "node": | ||
133 | if (ord($this->bytes[6])>>4==1) { | ||
134 | return bin2hex(substr($this->bytes,10)); | ||
135 | } else { | ||
136 | return NULL; | ||
137 | } | ||
138 | case "time": | ||
139 | if (ord($this->bytes[6])>>4==1) { | ||
140 | // Restore contiguous big-endian byte order | ||
141 | $time = bin2hex($this->bytes[6].$this->bytes[7].$this->bytes[4].$this->bytes[5].$this->bytes[0].$this->bytes[1].$this->bytes[2].$this->bytes[3]); | ||
142 | // Clear version flag | ||
143 | $time[0] = "0"; | ||
144 | // Do some reverse arithmetic to get a Unix timestamp | ||
145 | $time = (hexdec($time) - self::interval) / 10000000; | ||
146 | return $time; | ||
147 | } else { | ||
148 | return NULL; | ||
149 | } | ||
150 | default: | ||
151 | return NULL; | ||
152 | } | ||
153 | } | ||
154 | |||
155 | protected function __construct($uuid) { | ||
156 | if (strlen($uuid) != 16) { | ||
157 | throw new UUIDException("Input must be a 128-bit integer."); | ||
158 | } | ||
159 | $this->bytes = $uuid; | ||
160 | // Optimize the most common use | ||
161 | $this->string = | ||
162 | bin2hex(substr($uuid,0,4))."-". | ||
163 | bin2hex(substr($uuid,4,2))."-". | ||
164 | bin2hex(substr($uuid,6,2))."-". | ||
165 | bin2hex(substr($uuid,8,2))."-". | ||
166 | bin2hex(substr($uuid,10,6)); | ||
167 | } | ||
168 | |||
169 | protected static function mintTime($node = NULL) { | ||
170 | /* Generates a Version 1 UUID. | ||
171 | These are derived from the time at which they were generated. */ | ||
172 | // Get time since Gregorian calendar reform in 100ns intervals | ||
173 | // This is exceedingly difficult because of PHP's (and pack()'s) | ||
174 | // integer size limits. | ||
175 | // Note that this will never be more accurate than to the microsecond. | ||
176 | $time = microtime(1) * 10000000 + self::interval; | ||
177 | // Convert to a string representation | ||
178 | $time = sprintf("%F", $time); | ||
179 | preg_match("/^\d+/", $time, $time); //strip decimal point | ||
180 | // And now to a 64-bit binary representation | ||
181 | $time = base_convert($time[0], 10, 16); | ||
182 | $time = pack("H*", str_pad($time, 16, "0", STR_PAD_LEFT)); | ||
183 | // Reorder bytes to their proper locations in the UUID | ||
184 | $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; | ||
185 | // Generate a random clock sequence | ||
186 | $uuid .= self::randomBytes(2); | ||
187 | // set variant | ||
188 | $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); | ||
189 | // set version | ||
190 | $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version1); | ||
191 | // Set the final 'node' parameter, a MAC address | ||
192 | if ($node) { | ||
193 | $node = self::makeBin($node, 6); | ||
194 | } | ||
195 | if (!$node) { | ||
196 | // If no node was provided or if the node was invalid, | ||
197 | // generate a random MAC address and set the multicast bit | ||
198 | $node = self::randomBytes(6); | ||
199 | $node[0] = pack("C", ord($node[0]) | 1); | ||
200 | } | ||
201 | $uuid .= $node; | ||
202 | return $uuid; | ||
203 | } | ||
204 | |||
205 | protected static function mintRand() { | ||
206 | /* Generate a Version 4 UUID. | ||
207 | These are derived soly from random numbers. */ | ||
208 | // generate random fields | ||
209 | $uuid = self::randomBytes(16); | ||
210 | // set variant | ||
211 | $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); | ||
212 | // set version | ||
213 | $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version4); | ||
214 | return $uuid; | ||
215 | } | ||
216 | |||
217 | protected static function mintName($ver, $node, $ns) { | ||
218 | /* Generates a Version 3 or Version 5 UUID. | ||
219 | These are derived from a hash of a name and its namespace, in binary form. */ | ||
220 | if (!$node) { | ||
221 | throw new UUIDException("A name-string is required for Version 3 or 5 UUIDs."); | ||
222 | } | ||
223 | // if the namespace UUID isn't binary, make it so | ||
224 | $ns = self::makeBin($ns, 16); | ||
225 | if (!$ns) { | ||
226 | throw new UUIDException("A binary namespace is required for Version 3 or 5 UUIDs."); | ||
227 | } | ||
228 | $uuid = null; | ||
229 | $version = self::version3; | ||
230 | switch($ver) { | ||
231 | case self::MD5: | ||
232 | $version = self::version3; | ||
233 | $uuid = md5($ns.$node,1); | ||
234 | break; | ||
235 | case self::SHA1: | ||
236 | $version = self::version5; | ||
237 | $uuid = substr(sha1($ns.$node,1),0, 16); | ||
238 | break; | ||
239 | } | ||
240 | // set variant | ||
241 | $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); | ||
242 | // set version | ||
243 | $uuid[6] = chr(ord($uuid[6]) & self::clearVer | $version); | ||
244 | return ($uuid); | ||
245 | } | ||
246 | |||
247 | protected static function makeBin($str, $len) { | ||
248 | /* Insure that an input string is either binary or hexadecimal. | ||
249 | Returns binary representation, or false on failure. */ | ||
250 | if ($str instanceof self) { | ||
251 | return $str->bytes; | ||
252 | } | ||
253 | if (strlen($str)==$len) { | ||
254 | return $str; | ||
255 | } else { | ||
256 | $str = preg_replace("/^urn:uuid:/is", "", $str); // strip URN scheme and namespace | ||
257 | } | ||
258 | $str = preg_replace("/[^a-f0-9]/is", "", $str); // strip non-hex characters | ||
259 | if (strlen($str) != ($len * 2)) { | ||
260 | return FALSE; | ||
261 | } else { | ||
262 | return pack("H*", $str); | ||
263 | } | ||
264 | } | ||
265 | |||
266 | public static function initRandom() { | ||
267 | /* Look for a system-provided source of randomness, which is usually crytographically secure. | ||
268 | /dev/urandom is tried first simply out of bias for Linux systems. */ | ||
269 | if (is_readable('/dev/urandom')) { | ||
270 | self::$randomSource = fopen('/dev/urandom', 'rb'); | ||
271 | self::$randomFunc = 'randomFRead'; | ||
272 | } | ||
273 | else if (class_exists('COM', 0)) { | ||
274 | try { | ||
275 | self::$randomSource = new COM('CAPICOM.Utilities.1'); // See http://msdn.microsoft.com/en-us/library/aa388182(VS.85).aspx | ||
276 | self::$randomFunc = 'randomCOM'; | ||
277 | } | ||
278 | catch(Exception $e) { | ||
279 | } | ||
280 | } | ||
281 | return self::$randomFunc; | ||
282 | } | ||
283 | |||
284 | public static function randomBytes($bytes) { | ||
285 | return call_user_func(array('self', self::$randomFunc), $bytes); | ||
286 | } | ||
287 | |||
288 | protected static function randomTwister($bytes) { | ||
289 | /* Get the specified number of random bytes, using mt_rand(). | ||
290 | Randomness is returned as a string of bytes. */ | ||
291 | $rand = ""; | ||
292 | for ($a = 0; $a < $bytes; $a++) { | ||
293 | $rand .= chr(mt_rand(0, 255)); | ||
294 | } | ||
295 | return $rand; | ||
296 | } | ||
297 | |||
298 | protected static function randomFRead($bytes) { | ||
299 | /* Get the specified number of random bytes using a file handle | ||
300 | previously opened with UUID::initRandom(). | ||
301 | Randomness is returned as a string of bytes. */ | ||
302 | return fread(self::$randomSource, $bytes); | ||
303 | } | ||
304 | |||
305 | protected static function randomCOM($bytes) { | ||
306 | /* Get the specified number of random bytes using Windows' | ||
307 | randomness source via a COM object previously created by UUID::initRandom(). | ||
308 | Randomness is returned as a string of bytes. */ | ||
309 | return base64_decode(self::$randomSource->GetRandom($bytes,0)); // straight binary mysteriously doesn't work, hence the base64 | ||
310 | } | ||
311 | } | ||
312 | |||
313 | class UUIDException extends Exception { | ||
314 | } | ||
diff --git a/inc/3rdparty/libraries/content-extractor/ContentExtractor.php b/inc/3rdparty/libraries/content-extractor/ContentExtractor.php index ddd33bb5..21e693e7 100644 --- a/inc/3rdparty/libraries/content-extractor/ContentExtractor.php +++ b/inc/3rdparty/libraries/content-extractor/ContentExtractor.php | |||
@@ -1,728 +1,727 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Content Extractor | 3 | * Content Extractor |
4 | * | 4 | * |
5 | * Uses patterns specified in site config files and auto detection (hNews/PHP Readability) | 5 | * Uses patterns specified in site config files and auto detection (hNews/PHP Readability) |
6 | * to extract content from HTML files. | 6 | * to extract content from HTML files. |
7 | * | 7 | * |
8 | * @version 1.0 | 8 | * @version 1.0 |
9 | * @date 2013-02-05 | 9 | * @date 2013-02-05 |
10 | * @author Keyvan Minoukadeh | 10 | * @author Keyvan Minoukadeh |
11 | * @copyright 2013 Keyvan Minoukadeh | 11 | * @copyright 2013 Keyvan Minoukadeh |
12 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 | 12 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 |
13 | */ | 13 | */ |
14 | 14 | ||
15 | class ContentExtractor | 15 | class ContentExtractor |
16 | { | 16 | { |
17 | protected static $tidy_config = array( | 17 | protected static $tidy_config = array( |
18 | 'clean' => true, | 18 | 'clean' => true, |
19 | 'output-xhtml' => true, | 19 | 'output-xhtml' => true, |
20 | 'logical-emphasis' => true, | 20 | 'logical-emphasis' => true, |
21 | 'show-body-only' => false, | 21 | 'show-body-only' => false, |
22 | 'new-blocklevel-tags' => 'article, aside, footer, header, hgroup, menu, nav, section, details, datagrid', | 22 | 'new-blocklevel-tags' => 'article, aside, footer, header, hgroup, menu, nav, section, details, datagrid', |
23 | 'new-inline-tags' => 'mark, time, meter, progress, data', | 23 | 'new-inline-tags' => 'mark, time, meter, progress, data', |
24 | 'wrap' => 0, | 24 | 'wrap' => 0, |
25 | 'drop-empty-paras' => true, | 25 | 'drop-empty-paras' => true, |
26 | 'drop-proprietary-attributes' => false, | 26 | 'drop-proprietary-attributes' => false, |
27 | 'enclose-text' => true, | 27 | 'enclose-text' => true, |
28 | 'enclose-block-text' => true, | 28 | 'enclose-block-text' => true, |
29 | 'merge-divs' => true, | 29 | 'merge-divs' => true, |
30 | 'merge-spans' => true, | 30 | 'merge-spans' => true, |
31 | 'char-encoding' => 'utf8', | 31 | 'char-encoding' => 'utf8', |
32 | 'hide-comments' => true | 32 | 'hide-comments' => true |
33 | ); | 33 | ); |
34 | protected $html; | 34 | protected $html; |
35 | protected $config; | 35 | protected $config; |
36 | protected $title; | 36 | protected $title; |
37 | protected $author = array(); | 37 | protected $author = array(); |
38 | protected $language; | 38 | protected $language; |
39 | protected $date; | 39 | protected $date; |
40 | protected $body; | 40 | protected $body; |
41 | protected $success = false; | 41 | protected $success = false; |
42 | protected $nextPageUrl; | 42 | protected $nextPageUrl; |
43 | public $allowedParsers = array('libxml', 'html5lib'); | 43 | public $allowedParsers = array('libxml', 'html5lib'); |
44 | public $fingerprints = array(); | 44 | public $fingerprints = array(); |
45 | public $readability; | 45 | public $readability; |
46 | public $debug = false; | 46 | public $debug = false; |
47 | public $debugVerbose = false; | 47 | public $debugVerbose = false; |
48 | 48 | ||
49 | function __construct($path, $fallback=null) { | 49 | function __construct($path, $fallback=null) { |
50 | SiteConfig::set_config_path($path, $fallback); | 50 | SiteConfig::set_config_path($path, $fallback); |
51 | } | 51 | } |
52 | 52 | ||
53 | protected function debug($msg) { | 53 | protected function debug($msg) { |
54 | if ($this->debug) { | 54 | if ($this->debug) { |
55 | $mem = round(memory_get_usage()/1024, 2); | 55 | $mem = round(memory_get_usage()/1024, 2); |
56 | $memPeak = round(memory_get_peak_usage()/1024, 2); | 56 | $memPeak = round(memory_get_peak_usage()/1024, 2); |
57 | echo '* ',$msg; | 57 | echo '* ',$msg; |
58 | if ($this->debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)"; | 58 | if ($this->debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)"; |
59 | echo "\n"; | 59 | echo "\n"; |
60 | ob_flush(); | 60 | ob_flush(); |
61 | flush(); | 61 | flush(); |
62 | } | 62 | } |
63 | } | 63 | } |
64 | 64 | ||
65 | public function reset() { | 65 | public function reset() { |
66 | $this->html = null; | 66 | $this->html = null; |
67 | $this->readability = null; | 67 | $this->readability = null; |
68 | $this->config = null; | 68 | $this->config = null; |
69 | $this->title = null; | 69 | $this->title = null; |
70 | $this->body = null; | 70 | $this->body = null; |
71 | $this->author = array(); | 71 | $this->author = array(); |
72 | $this->language = null; | 72 | $this->language = null; |
73 | $this->date = null; | 73 | $this->date = null; |
74 | $this->nextPageUrl = null; | 74 | $this->nextPageUrl = null; |
75 | $this->success = false; | 75 | $this->success = false; |
76 | } | 76 | } |
77 | 77 | ||
78 | public function findHostUsingFingerprints($html) { | 78 | public function findHostUsingFingerprints($html) { |
79 | $this->debug('Checking fingerprints...'); | 79 | $this->debug('Checking fingerprints...'); |
80 | $head = substr($html, 0, 8000); | 80 | $head = substr($html, 0, 8000); |
81 | foreach ($this->fingerprints as $_fp => $_fphost) { | 81 | foreach ($this->fingerprints as $_fp => $_fphost) { |
82 | $lookin = 'html'; | 82 | $lookin = 'html'; |
83 | if (is_array($_fphost)) { | 83 | if (is_array($_fphost)) { |
84 | if (isset($_fphost['head']) && $_fphost['head']) { | 84 | if (isset($_fphost['head']) && $_fphost['head']) { |
85 | $lookin = 'head'; | 85 | $lookin = 'head'; |
86 | } | 86 | } |
87 | $_fphost = $_fphost['hostname']; | 87 | $_fphost = $_fphost['hostname']; |
88 | } | 88 | } |
89 | if (strpos($$lookin, $_fp) !== false) { | 89 | if (strpos($$lookin, $_fp) !== false) { |
90 | $this->debug("Found match: $_fphost"); | 90 | $this->debug("Found match: $_fphost"); |
91 | return $_fphost; | 91 | return $_fphost; |
92 | } | 92 | } |
93 | } | 93 | } |
94 | $this->debug('No fingerprint matches'); | 94 | $this->debug('No fingerprint matches'); |
95 | return false; | 95 | return false; |
96 | } | 96 | } |
97 | 97 | ||
98 | // returns SiteConfig instance (joined in order: exact match, wildcard, fingerprint, global, default) | 98 | // returns SiteConfig instance (joined in order: exact match, wildcard, fingerprint, global, default) |
99 | public function buildSiteConfig($url, $html='', $add_to_cache=true) { | 99 | public function buildSiteConfig($url, $html='', $add_to_cache=true) { |
100 | // extract host name | 100 | // extract host name |
101 | $host = @parse_url($url, PHP_URL_HOST); | 101 | $host = @parse_url($url, PHP_URL_HOST); |
102 | $host = strtolower($host); | 102 | $host = strtolower($host); |
103 | if (substr($host, 0, 4) == 'www.') $host = substr($host, 4); | 103 | if (substr($host, 0, 4) == 'www.') $host = substr($host, 4); |
104 | // is merged version already cached? | 104 | // is merged version already cached? |
105 | if (SiteConfig::is_cached("$host.merged")) { | 105 | if (SiteConfig::is_cached("$host.merged")) { |
106 | $this->debug("Returning cached and merged site config for $host"); | 106 | $this->debug("Returning cached and merged site config for $host"); |
107 | return SiteConfig::build("$host.merged"); | 107 | return SiteConfig::build("$host.merged"); |
108 | } | 108 | } |
109 | // let's build from site_config/custom/ and standard/ | 109 | // let's build from site_config/custom/ and standard/ |
110 | $config = SiteConfig::build($host); | 110 | $config = SiteConfig::build($host); |
111 | if ($add_to_cache && $config && !SiteConfig::is_cached("$host")) { | 111 | if ($add_to_cache && $config && !SiteConfig::is_cached("$host")) { |
112 | SiteConfig::add_to_cache($host, $config); | 112 | SiteConfig::add_to_cache($host, $config); |
113 | } | 113 | } |
114 | // if no match, use defaults | 114 | // if no match, use defaults |
115 | if (!$config) $config = new SiteConfig(); | 115 | if (!$config) $config = new SiteConfig(); |
116 | // load fingerprint config? | 116 | // load fingerprint config? |
117 | if ($config->autodetect_on_failure()) { | 117 | if ($config->autodetect_on_failure()) { |
118 | // check HTML for fingerprints | 118 | // check HTML for fingerprints |
119 | if (!empty($this->fingerprints) && ($_fphost = $this->findHostUsingFingerprints($html))) { | 119 | if (!empty($this->fingerprints) && ($_fphost = $this->findHostUsingFingerprints($html))) { |
120 | if ($config_fingerprint = SiteConfig::build($_fphost)) { | 120 | if ($config_fingerprint = SiteConfig::build($_fphost)) { |
121 | $this->debug("Appending site config settings from $_fphost (fingerprint match)"); | 121 | $this->debug("Appending site config settings from $_fphost (fingerprint match)"); |
122 | $config->append($config_fingerprint); | 122 | $config->append($config_fingerprint); |
123 | if ($add_to_cache && !SiteConfig::is_cached($_fphost)) { | 123 | if ($add_to_cache && !SiteConfig::is_cached($_fphost)) { |
124 | //$config_fingerprint->cache_in_apc = true; | 124 | //$config_fingerprint->cache_in_apc = true; |
125 | SiteConfig::add_to_cache($_fphost, $config_fingerprint); | 125 | SiteConfig::add_to_cache($_fphost, $config_fingerprint); |
126 | } | 126 | } |
127 | } | 127 | } |
128 | } | 128 | } |
129 | } | 129 | } |
130 | // load global config? | 130 | // load global config? |
131 | if ($config->autodetect_on_failure()) { | 131 | if ($config->autodetect_on_failure()) { |
132 | if ($config_global = SiteConfig::build('global', true)) { | 132 | if ($config_global = SiteConfig::build('global', true)) { |
133 | $this->debug('Appending site config settings from global.txt'); | 133 | $this->debug('Appending site config settings from global.txt'); |
134 | $config->append($config_global); | 134 | $config->append($config_global); |
135 | if ($add_to_cache && !SiteConfig::is_cached('global')) { | 135 | if ($add_to_cache && !SiteConfig::is_cached('global')) { |
136 | //$config_global->cache_in_apc = true; | 136 | //$config_global->cache_in_apc = true; |
137 | SiteConfig::add_to_cache('global', $config_global); | 137 | SiteConfig::add_to_cache('global', $config_global); |
138 | } | 138 | } |
139 | } | 139 | } |
140 | } | 140 | } |
141 | // store copy of merged config | 141 | // store copy of merged config |
142 | if ($add_to_cache) { | 142 | if ($add_to_cache) { |
143 | // do not store in APC if wildcard match | 143 | // do not store in APC if wildcard match |
144 | $use_apc = ($host == $config->cache_key); | 144 | $use_apc = ($host == $config->cache_key); |
145 | $config->cache_key = null; | 145 | $config->cache_key = null; |
146 | SiteConfig::add_to_cache("$host.merged", $config, $use_apc); | 146 | SiteConfig::add_to_cache("$host.merged", $config, $use_apc); |
147 | } | 147 | } |
148 | return $config; | 148 | return $config; |
149 | } | 149 | } |
150 | 150 | ||
151 | // returns true on success, false on failure | 151 | // returns true on success, false on failure |
152 | // $smart_tidy indicates that if tidy is used and no results are produced, we will | 152 | // $smart_tidy indicates that if tidy is used and no results are produced, we will |
153 | // try again without it. Tidy helps us deal with PHP's patchy HTML parsing most of the time | 153 | // try again without it. Tidy helps us deal with PHP's patchy HTML parsing most of the time |
154 | // but it has problems of its own which we try to avoid with this option. | 154 | // but it has problems of its own which we try to avoid with this option. |
155 | public function process($html, $url, $smart_tidy=true) { | 155 | public function process($html, $url, $smart_tidy=true) { |
156 | $this->reset(); | 156 | $this->reset(); |
157 | $this->config = $this->buildSiteConfig($url, $html); | 157 | $this->config = $this->buildSiteConfig($url, $html); |
158 | 158 | ||
159 | // do string replacements | 159 | // do string replacements |
160 | if (!empty($this->config->find_string)) { | 160 | if (!empty($this->config->find_string)) { |
161 | if (count($this->config->find_string) == count($this->config->replace_string)) { | 161 | if (count($this->config->find_string) == count($this->config->replace_string)) { |
162 | $html = str_replace($this->config->find_string, $this->config->replace_string, $html, $_count); | 162 | $html = str_replace($this->config->find_string, $this->config->replace_string, $html, $_count); |
163 | $this->debug("Strings replaced: $_count (find_string and/or replace_string)"); | 163 | $this->debug("Strings replaced: $_count (find_string and/or replace_string)"); |
164 | } else { | 164 | } else { |
165 | $this->debug('Skipped string replacement - incorrect number of find-replace strings in site config'); | 165 | $this->debug('Skipped string replacement - incorrect number of find-replace strings in site config'); |
166 | } | 166 | } |
167 | unset($_count); | 167 | unset($_count); |
168 | } | 168 | } |
169 | 169 | ||
170 | // use tidy (if it exists)? | 170 | // use tidy (if it exists)? |
171 | // This fixes problems with some sites which would otherwise | 171 | // This fixes problems with some sites which would otherwise |
172 | // trouble DOMDocument's HTML parsing. (Although sometimes it | 172 | // trouble DOMDocument's HTML parsing. (Although sometimes it |
173 | // makes matters worse, which is why you can override it in site config files.) | 173 | // makes matters worse, which is why you can override it in site config files.) |
174 | $tidied = false; | 174 | $tidied = false; |
175 | if ($this->config->tidy() && function_exists('tidy_parse_string') && $smart_tidy) { | 175 | if ($this->config->tidy() && function_exists('tidy_parse_string') && $smart_tidy) { |
176 | $this->debug('Using Tidy'); | 176 | $this->debug('Using Tidy'); |
177 | $tidy = tidy_parse_string($html, self::$tidy_config, 'UTF8'); | 177 | $tidy = tidy_parse_string($html, self::$tidy_config, 'UTF8'); |
178 | if (tidy_clean_repair($tidy)) { | 178 | if (tidy_clean_repair($tidy)) { |
179 | $original_html = $html; | 179 | $original_html = $html; |
180 | $tidied = true; | 180 | $tidied = true; |
181 | $html = $tidy->value; | 181 | $html = $tidy->value; |
182 | } | 182 | } |
183 | unset($tidy); | 183 | unset($tidy); |
184 | } | 184 | } |
185 | 185 | ||
186 | // load and parse html | 186 | // load and parse html |
187 | $_parser = $this->config->parser(); | 187 | $_parser = $this->config->parser(); |
188 | if (!in_array($_parser, $this->allowedParsers)) { | 188 | if (!in_array($_parser, $this->allowedParsers)) { |
189 | $this->debug("HTML parser $_parser not listed, using libxml instead"); | 189 | $this->debug("HTML parser $_parser not listed, using libxml instead"); |
190 | $_parser = 'libxml'; | 190 | $_parser = 'libxml'; |
191 | } | 191 | } |
192 | $this->debug("Attempting to parse HTML with $_parser"); | 192 | $this->debug("Attempting to parse HTML with $_parser"); |
193 | $this->readability = new Readability($html, $url, $_parser); | 193 | $this->readability = new Readability($html, $url, $_parser); |
194 | 194 | ||
195 | // we use xpath to find elements in the given HTML document | 195 | // we use xpath to find elements in the given HTML document |
196 | // see http://en.wikipedia.org/wiki/XPath_1.0 | 196 | // see http://en.wikipedia.org/wiki/XPath_1.0 |
197 | $xpath = new DOMXPath($this->readability->dom); | 197 | $xpath = new DOMXPath($this->readability->dom); |
198 | 198 | ||
199 | // try to get next page link | 199 | // try to get next page link |
200 | foreach ($this->config->next_page_link as $pattern) { | 200 | foreach ($this->config->next_page_link as $pattern) { |
201 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); | 201 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); |
202 | if (is_string($elems)) { | 202 | if (is_string($elems)) { |
203 | $this->nextPageUrl = trim($elems); | 203 | $this->nextPageUrl = trim($elems); |
204 | break; | 204 | break; |
205 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | 205 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { |
206 | foreach ($elems as $item) { | 206 | foreach ($elems as $item) { |
207 | if ($item instanceof DOMElement && $item->hasAttribute('href')) { | 207 | if ($item instanceof DOMElement && $item->hasAttribute('href')) { |
208 | $this->nextPageUrl = $item->getAttribute('href'); | 208 | $this->nextPageUrl = $item->getAttribute('href'); |
209 | break 2; | 209 | break 2; |
210 | } elseif ($item instanceof DOMAttr && $item->value) { | 210 | } elseif ($item instanceof DOMAttr && $item->value) { |
211 | $this->nextPageUrl = $item->value; | 211 | $this->nextPageUrl = $item->value; |
212 | break 2; | 212 | break 2; |
213 | } | 213 | } |
214 | } | 214 | } |
215 | } | 215 | } |
216 | } | 216 | } |
217 | 217 | ||
218 | // try to get title | 218 | // try to get title |
219 | foreach ($this->config->title as $pattern) { | 219 | foreach ($this->config->title as $pattern) { |
220 | // $this->debug("Trying $pattern"); | 220 | // $this->debug("Trying $pattern"); |
221 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); | 221 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); |
222 | if (is_string($elems)) { | 222 | if (is_string($elems)) { |
223 | $this->title = trim($elems); | 223 | $this->title = trim($elems); |
224 | $this->debug('Title expression evaluated as string: '.$this->title); | 224 | $this->debug('Title expression evaluated as string: '.$this->title); |
225 | $this->debug("...XPath match: $pattern"); | 225 | $this->debug("...XPath match: $pattern"); |
226 | break; | 226 | break; |
227 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | 227 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { |
228 | $this->title = $elems->item(0)->textContent; | 228 | $this->title = $elems->item(0)->textContent; |
229 | $this->debug('Title matched: '.$this->title); | 229 | $this->debug('Title matched: '.$this->title); |
230 | $this->debug("...XPath match: $pattern"); | 230 | $this->debug("...XPath match: $pattern"); |
231 | // remove title from document | 231 | // remove title from document |
232 | try { | 232 | try { |
233 | $elems->item(0)->parentNode->removeChild($elems->item(0)); | 233 | @$elems->item(0)->parentNode->removeChild($elems->item(0)); |
234 | } catch (DOMException $e) { | 234 | } catch (DOMException $e) { |
235 | // do nothing | 235 | // do nothing |
236 | } | 236 | } |
237 | break; | 237 | break; |
238 | } | 238 | } |
239 | } | 239 | } |
240 | 240 | ||
241 | // try to get author (if it hasn't already been set) | 241 | // try to get author (if it hasn't already been set) |
242 | if (empty($this->author)) { | 242 | if (empty($this->author)) { |
243 | foreach ($this->config->author as $pattern) { | 243 | foreach ($this->config->author as $pattern) { |
244 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); | 244 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); |
245 | if (is_string($elems)) { | 245 | if (is_string($elems)) { |
246 | if (trim($elems) != '') { | 246 | if (trim($elems) != '') { |
247 | $this->author[] = trim($elems); | 247 | $this->author[] = trim($elems); |
248 | $this->debug('Author expression evaluated as string: '.trim($elems)); | 248 | $this->debug('Author expression evaluated as string: '.trim($elems)); |
249 | $this->debug("...XPath match: $pattern"); | 249 | $this->debug("...XPath match: $pattern"); |
250 | break; | 250 | break; |
251 | } | 251 | } |
252 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | 252 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { |
253 | foreach ($elems as $elem) { | 253 | foreach ($elems as $elem) { |
254 | if (!isset($elem->parentNode)) continue; | 254 | if (!isset($elem->parentNode)) continue; |
255 | $this->author[] = trim($elem->textContent); | 255 | $this->author[] = trim($elem->textContent); |
256 | $this->debug('Author matched: '.trim($elem->textContent)); | 256 | $this->debug('Author matched: '.trim($elem->textContent)); |
257 | } | 257 | } |
258 | if (!empty($this->author)) { | 258 | if (!empty($this->author)) { |
259 | $this->debug("...XPath match: $pattern"); | 259 | $this->debug("...XPath match: $pattern"); |
260 | break; | 260 | break; |
261 | } | 261 | } |
262 | } | 262 | } |
263 | } | 263 | } |
264 | } | 264 | } |
265 | 265 | ||
266 | // try to get language | 266 | // try to get language |
267 | $_lang_xpath = array('//html[@lang]/@lang', '//meta[@name="DC.language"]/@content'); | 267 | $_lang_xpath = array('//html[@lang]/@lang', '//meta[@name="DC.language"]/@content'); |
268 | foreach ($_lang_xpath as $pattern) { | 268 | foreach ($_lang_xpath as $pattern) { |
269 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); | 269 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); |
270 | if (is_string($elems)) { | 270 | if (is_string($elems)) { |
271 | if (trim($elems) != '') { | 271 | if (trim($elems) != '') { |
272 | $this->language = trim($elems); | 272 | $this->language = trim($elems); |
273 | $this->debug('Language matched: '.$this->language); | 273 | $this->debug('Language matched: '.$this->language); |
274 | break; | 274 | break; |
275 | } | 275 | } |
276 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | 276 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { |
277 | foreach ($elems as $elem) { | 277 | foreach ($elems as $elem) { |
278 | if (!isset($elem->parentNode)) continue; | 278 | if (!isset($elem->parentNode)) continue; |
279 | $this->language = trim($elem->textContent); | 279 | $this->language = trim($elem->textContent); |
280 | $this->debug('Language matched: '.$this->language); | 280 | $this->debug('Language matched: '.$this->language); |
281 | } | 281 | } |
282 | if ($this->language) break; | 282 | if ($this->language) break; |
283 | } | 283 | } |
284 | } | 284 | } |
285 | 285 | ||
286 | // try to get date | 286 | // try to get date |
287 | foreach ($this->config->date as $pattern) { | 287 | foreach ($this->config->date as $pattern) { |
288 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); | 288 | $elems = @$xpath->evaluate($pattern, $this->readability->dom); |
289 | if (is_string($elems)) { | 289 | if (is_string($elems)) { |
290 | $this->date = strtotime(trim($elems, "; \t\n\r\0\x0B")); | 290 | $this->date = strtotime(trim($elems, "; \t\n\r\0\x0B")); |
291 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | 291 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { |
292 | $this->date = $elems->item(0)->textContent; | 292 | $this->date = $elems->item(0)->textContent; |
293 | $this->date = strtotime(trim($this->date, "; \t\n\r\0\x0B")); | 293 | $this->date = strtotime(trim($this->date, "; \t\n\r\0\x0B")); |
294 | // remove date from document | 294 | // remove date from document |
295 | // $elems->item(0)->parentNode->removeChild($elems->item(0)); | 295 | // $elems->item(0)->parentNode->removeChild($elems->item(0)); |
296 | } | 296 | } |
297 | if (!$this->date) { | 297 | if (!$this->date) { |
298 | $this->date = null; | 298 | $this->date = null; |
299 | } else { | 299 | } else { |
300 | $this->debug('Date matched: '.date('Y-m-d H:i:s', $this->date)); | 300 | $this->debug('Date matched: '.date('Y-m-d H:i:s', $this->date)); |
301 | $this->debug("...XPath match: $pattern"); | 301 | $this->debug("...XPath match: $pattern"); |
302 | break; | 302 | break; |
303 | } | 303 | } |
304 | } | 304 | } |
305 | 305 | ||
306 | // strip elements (using xpath expressions) | 306 | // strip elements (using xpath expressions) |
307 | foreach ($this->config->strip as $pattern) { | 307 | foreach ($this->config->strip as $pattern) { |
308 | $elems = @$xpath->query($pattern, $this->readability->dom); | 308 | $elems = @$xpath->query($pattern, $this->readability->dom); |
309 | // check for matches | 309 | // check for matches |
310 | if ($elems && $elems->length > 0) { | 310 | if ($elems && $elems->length > 0) { |
311 | $this->debug('Stripping '.$elems->length.' elements (strip)'); | 311 | $this->debug('Stripping '.$elems->length.' elements (strip)'); |
312 | for ($i=$elems->length-1; $i >= 0; $i--) { | 312 | for ($i=$elems->length-1; $i >= 0; $i--) { |
313 | $elems->item($i)->parentNode->removeChild($elems->item($i)); | 313 | $elems->item($i)->parentNode->removeChild($elems->item($i)); |
314 | } | 314 | } |
315 | } | 315 | } |
316 | } | 316 | } |
317 | 317 | ||
318 | // strip elements (using id and class attribute values) | 318 | // strip elements (using id and class attribute values) |
319 | foreach ($this->config->strip_id_or_class as $string) { | 319 | foreach ($this->config->strip_id_or_class as $string) { |
320 | $string = strtr($string, array("'"=>'', '"'=>'')); | 320 | $string = strtr($string, array("'"=>'', '"'=>'')); |
321 | $elems = @$xpath->query("//*[contains(@class, '$string') or contains(@id, '$string')]", $this->readability->dom); | 321 | $elems = @$xpath->query("//*[contains(@class, '$string') or contains(@id, '$string')]", $this->readability->dom); |
322 | // check for matches | 322 | // check for matches |
323 | if ($elems && $elems->length > 0) { | 323 | if ($elems && $elems->length > 0) { |
324 | $this->debug('Stripping '.$elems->length.' elements (strip_id_or_class)'); | 324 | $this->debug('Stripping '.$elems->length.' elements (strip_id_or_class)'); |
325 | for ($i=$elems->length-1; $i >= 0; $i--) { | 325 | for ($i=$elems->length-1; $i >= 0; $i--) { |
326 | $elems->item($i)->parentNode->removeChild($elems->item($i)); | 326 | $elems->item($i)->parentNode->removeChild($elems->item($i)); |
327 | } | 327 | } |
328 | } | 328 | } |
329 | } | 329 | } |
330 | 330 | ||
331 | // strip images (using src attribute values) | 331 | // strip images (using src attribute values) |
332 | foreach ($this->config->strip_image_src as $string) { | 332 | foreach ($this->config->strip_image_src as $string) { |
333 | $string = strtr($string, array("'"=>'', '"'=>'')); | 333 | $string = strtr($string, array("'"=>'', '"'=>'')); |
334 | $elems = @$xpath->query("//img[contains(@src, '$string')]", $this->readability->dom); | 334 | $elems = @$xpath->query("//img[contains(@src, '$string')]", $this->readability->dom); |
335 | // check for matches | 335 | // check for matches |
336 | if ($elems && $elems->length > 0) { | 336 | if ($elems && $elems->length > 0) { |
337 | $this->debug('Stripping '.$elems->length.' image elements'); | 337 | $this->debug('Stripping '.$elems->length.' image elements'); |
338 | for ($i=$elems->length-1; $i >= 0; $i--) { | 338 | for ($i=$elems->length-1; $i >= 0; $i--) { |
339 | $elems->item($i)->parentNode->removeChild($elems->item($i)); | 339 | $elems->item($i)->parentNode->removeChild($elems->item($i)); |
340 | } | 340 | } |
341 | } | 341 | } |
342 | } | 342 | } |
343 | // strip elements using Readability.com and Instapaper.com ignore class names | 343 | // strip elements using Readability.com and Instapaper.com ignore class names |
344 | // .entry-unrelated and .instapaper_ignore | 344 | // .entry-unrelated and .instapaper_ignore |
345 | // See https://www.readability.com/publishers/guidelines/#view-plainGuidelines | 345 | // See https://www.readability.com/publishers/guidelines/#view-plainGuidelines |
346 | // and http://blog.instapaper.com/post/730281947 | 346 | // and http://blog.instapaper.com/post/730281947 |
347 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' entry-unrelated ') or contains(concat(' ',normalize-space(@class),' '),' instapaper_ignore ')]", $this->readability->dom); | 347 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' entry-unrelated ') or contains(concat(' ',normalize-space(@class),' '),' instapaper_ignore ')]", $this->readability->dom); |
348 | // check for matches | 348 | // check for matches |
349 | if ($elems && $elems->length > 0) { | 349 | if ($elems && $elems->length > 0) { |
350 | $this->debug('Stripping '.$elems->length.' .entry-unrelated,.instapaper_ignore elements'); | 350 | $this->debug('Stripping '.$elems->length.' .entry-unrelated,.instapaper_ignore elements'); |
351 | for ($i=$elems->length-1; $i >= 0; $i--) { | 351 | for ($i=$elems->length-1; $i >= 0; $i--) { |
352 | $elems->item($i)->parentNode->removeChild($elems->item($i)); | 352 | $elems->item($i)->parentNode->removeChild($elems->item($i)); |
353 | } | 353 | } |
354 | } | 354 | } |
355 | 355 | ||
356 | // strip elements that contain style="display: none;" | 356 | // strip elements that contain style="display: none;" |
357 | $elems = @$xpath->query("//*[contains(@style,'display:none')]", $this->readability->dom); | 357 | $elems = @$xpath->query("//*[contains(@style,'display:none')]", $this->readability->dom); |
358 | // check for matches | 358 | // check for matches |
359 | if ($elems && $elems->length > 0) { | 359 | if ($elems && $elems->length > 0) { |
360 | $this->debug('Stripping '.$elems->length.' elements with inline display:none style'); | 360 | $this->debug('Stripping '.$elems->length.' elements with inline display:none style'); |
361 | for ($i=$elems->length-1; $i >= 0; $i--) { | 361 | for ($i=$elems->length-1; $i >= 0; $i--) { |
362 | $elems->item($i)->parentNode->removeChild($elems->item($i)); | 362 | $elems->item($i)->parentNode->removeChild($elems->item($i)); |
363 | } | 363 | } |
364 | } | 364 | } |
365 | 365 | ||
366 | // try to get body | 366 | // try to get body |
367 | foreach ($this->config->body as $pattern) { | 367 | foreach ($this->config->body as $pattern) { |
368 | $elems = @$xpath->query($pattern, $this->readability->dom); | 368 | $elems = @$xpath->query($pattern, $this->readability->dom); |
369 | // check for matches | 369 | // check for matches |
370 | if ($elems && $elems->length > 0) { | 370 | if ($elems && $elems->length > 0) { |
371 | $this->debug('Body matched'); | 371 | $this->debug('Body matched'); |
372 | $this->debug("...XPath match: $pattern"); | 372 | $this->debug("...XPath match: $pattern"); |
373 | if ($elems->length == 1) { | 373 | if ($elems->length == 1) { |
374 | $this->body = $elems->item(0); | 374 | $this->body = $elems->item(0); |
375 | // prune (clean up elements that may not be content) | 375 | // prune (clean up elements that may not be content) |
376 | if ($this->config->prune()) { | 376 | if ($this->config->prune()) { |
377 | $this->debug('...pruning content'); | 377 | $this->debug('...pruning content'); |
378 | $this->readability->prepArticle($this->body); | 378 | $this->readability->prepArticle($this->body); |
379 | } | 379 | } |
380 | break; | 380 | break; |
381 | } else { | 381 | } else { |
382 | $this->body = $this->readability->dom->createElement('div'); | 382 | $this->body = $this->readability->dom->createElement('div'); |
383 | $this->debug($elems->length.' body elems found'); | 383 | $this->debug($elems->length.' body elems found'); |
384 | foreach ($elems as $elem) { | 384 | foreach ($elems as $elem) { |
385 | if (!isset($elem->parentNode)) continue; | 385 | if (!isset($elem->parentNode)) continue; |
386 | $isDescendant = false; | 386 | $isDescendant = false; |
387 | foreach ($this->body->childNodes as $parent) { | 387 | foreach ($this->body->childNodes as $parent) { |
388 | if ($this->isDescendant($parent, $elem)) { | 388 | if ($this->isDescendant($parent, $elem)) { |
389 | $isDescendant = true; | 389 | $isDescendant = true; |
390 | break; | 390 | break; |
391 | } | 391 | } |
392 | } | 392 | } |
393 | if ($isDescendant) { | 393 | if ($isDescendant) { |
394 | $this->debug('...element is child of another body element, skipping.'); | 394 | $this->debug('...element is child of another body element, skipping.'); |
395 | } else { | 395 | } else { |
396 | // prune (clean up elements that may not be content) | 396 | // prune (clean up elements that may not be content) |
397 | if ($this->config->prune()) { | 397 | if ($this->config->prune()) { |
398 | $this->debug('Pruning content'); | 398 | $this->debug('Pruning content'); |
399 | $this->readability->prepArticle($elem); | 399 | $this->readability->prepArticle($elem); |
400 | } | 400 | } |
401 | $this->debug('...element added to body'); | 401 | $this->debug('...element added to body'); |
402 | $this->body->appendChild($elem); | 402 | $this->body->appendChild($elem); |
403 | } | 403 | } |
404 | } | 404 | } |
405 | if ($this->body->hasChildNodes()) break; | 405 | if ($this->body->hasChildNodes()) break; |
406 | } | 406 | } |
407 | } | 407 | } |
408 | } | 408 | } |
409 | 409 | ||
410 | // auto detect? | 410 | // auto detect? |
411 | $detect_title = $detect_body = $detect_author = $detect_date = false; | 411 | $detect_title = $detect_body = $detect_author = $detect_date = false; |
412 | // detect title? | 412 | // detect title? |
413 | if (!isset($this->title)) { | 413 | if (!isset($this->title)) { |
414 | if (empty($this->config->title) || $this->config->autodetect_on_failure()) { | 414 | if (empty($this->config->title) || $this->config->autodetect_on_failure()) { |
415 | $detect_title = true; | 415 | $detect_title = true; |
416 | } | 416 | } |
417 | } | 417 | } |
418 | // detect body? | 418 | // detect body? |
419 | if (!isset($this->body)) { | 419 | if (!isset($this->body)) { |
420 | if (empty($this->config->body) || $this->config->autodetect_on_failure()) { | 420 | if (empty($this->config->body) || $this->config->autodetect_on_failure()) { |
421 | $detect_body = true; | 421 | $detect_body = true; |
422 | } | 422 | } |
423 | } | 423 | } |
424 | // detect author? | 424 | // detect author? |
425 | if (empty($this->author)) { | 425 | if (empty($this->author)) { |
426 | if (empty($this->config->author) || $this->config->autodetect_on_failure()) { | 426 | if (empty($this->config->author) || $this->config->autodetect_on_failure()) { |
427 | $detect_author = true; | 427 | $detect_author = true; |
428 | } | 428 | } |
429 | } | 429 | } |
430 | // detect date? | 430 | // detect date? |
431 | if (!isset($this->date)) { | 431 | if (!isset($this->date)) { |
432 | if (empty($this->config->date) || $this->config->autodetect_on_failure()) { | 432 | if (empty($this->config->date) || $this->config->autodetect_on_failure()) { |
433 | $detect_date = true; | 433 | $detect_date = true; |
434 | } | 434 | } |
435 | } | 435 | } |
436 | 436 | ||
437 | // check for hNews | 437 | // check for hNews |
438 | if ($detect_title || $detect_body) { | 438 | if ($detect_title || $detect_body) { |
439 | // check for hentry | 439 | // check for hentry |
440 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' hentry ')]", $this->readability->dom); | 440 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' hentry ')]", $this->readability->dom); |
441 | if ($elems && $elems->length > 0) { | 441 | if ($elems && $elems->length > 0) { |
442 | $this->debug('hNews: found hentry'); | 442 | $this->debug('hNews: found hentry'); |
443 | $hentry = $elems->item(0); | 443 | $hentry = $elems->item(0); |
444 | 444 | ||
445 | if ($detect_title) { | 445 | if ($detect_title) { |
446 | // check for entry-title | 446 | // check for entry-title |
447 | $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' entry-title ')]", $hentry); | 447 | $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' entry-title ')]", $hentry); |
448 | if ($elems && $elems->length > 0) { | 448 | if ($elems && $elems->length > 0) { |
449 | $this->title = $elems->item(0)->textContent; | 449 | $this->title = $elems->item(0)->textContent; |
450 | $this->debug('hNews: found entry-title: '.$this->title); | 450 | $this->debug('hNews: found entry-title: '.$this->title); |
451 | // remove title from document | 451 | // remove title from document |
452 | $elems->item(0)->parentNode->removeChild($elems->item(0)); | 452 | $elems->item(0)->parentNode->removeChild($elems->item(0)); |
453 | $detect_title = false; | 453 | $detect_title = false; |
454 | } | 454 | } |
455 | } | 455 | } |
456 | 456 | ||
457 | if ($detect_date) { | 457 | if ($detect_date) { |
458 | // check for time element with pubdate attribute | 458 | // check for time element with pubdate attribute |
459 | $elems = @$xpath->query(".//time[@pubdate] | .//abbr[contains(concat(' ',normalize-space(@class),' '),' published ')]", $hentry); | 459 | $elems = @$xpath->query(".//time[@pubdate] | .//abbr[contains(concat(' ',normalize-space(@class),' '),' published ')]", $hentry); |
460 | if ($elems && $elems->length > 0) { | 460 | if ($elems && $elems->length > 0) { |
461 | $this->date = strtotime(trim($elems->item(0)->textContent)); | 461 | $this->date = strtotime(trim($elems->item(0)->textContent)); |
462 | // remove date from document | 462 | // remove date from document |
463 | //$elems->item(0)->parentNode->removeChild($elems->item(0)); | 463 | //$elems->item(0)->parentNode->removeChild($elems->item(0)); |
464 | if ($this->date) { | 464 | if ($this->date) { |
465 | $this->debug('hNews: found publication date: '.date('Y-m-d H:i:s', $this->date)); | 465 | $this->debug('hNews: found publication date: '.date('Y-m-d H:i:s', $this->date)); |
466 | $detect_date = false; | 466 | $detect_date = false; |
467 | } else { | 467 | } else { |
468 | $this->date = null; | 468 | $this->date = null; |
469 | } | 469 | } |
470 | } | 470 | } |
471 | } | 471 | } |
472 | 472 | ||
473 | if ($detect_author) { | 473 | if ($detect_author) { |
474 | // check for time element with pubdate attribute | 474 | // check for time element with pubdate attribute |
475 | $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' vcard ') and (contains(concat(' ',normalize-space(@class),' '),' author ') or contains(concat(' ',normalize-space(@class),' '),' byline '))]", $hentry); | 475 | $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' vcard ') and (contains(concat(' ',normalize-space(@class),' '),' author ') or contains(concat(' ',normalize-space(@class),' '),' byline '))]", $hentry); |
476 | if ($elems && $elems->length > 0) { | 476 | if ($elems && $elems->length > 0) { |
477 | $author = $elems->item(0); | 477 | $author = $elems->item(0); |
478 | $fn = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' fn ')]", $author); | 478 | $fn = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' fn ')]", $author); |
479 | if ($fn && $fn->length > 0) { | 479 | if ($fn && $fn->length > 0) { |
480 | foreach ($fn as $_fn) { | 480 | foreach ($fn as $_fn) { |
481 | if (trim($_fn->textContent) != '') { | 481 | if (trim($_fn->textContent) != '') { |
482 | $this->author[] = trim($_fn->textContent); | 482 | $this->author[] = trim($_fn->textContent); |
483 | $this->debug('hNews: found author: '.trim($_fn->textContent)); | 483 | $this->debug('hNews: found author: '.trim($_fn->textContent)); |
484 | } | 484 | } |
485 | } | 485 | } |
486 | } else { | 486 | } else { |
487 | if (trim($author->textContent) != '') { | 487 | if (trim($author->textContent) != '') { |
488 | $this->author[] = trim($author->textContent); | 488 | $this->author[] = trim($author->textContent); |
489 | $this->debug('hNews: found author: '.trim($author->textContent)); | 489 | $this->debug('hNews: found author: '.trim($author->textContent)); |
490 | } | 490 | } |
491 | } | 491 | } |
492 | $detect_author = empty($this->author); | 492 | $detect_author = empty($this->author); |
493 | } | 493 | } |
494 | } | 494 | } |
495 | 495 | ||
496 | // check for entry-content. | 496 | // check for entry-content. |
497 | // according to hAtom spec, if there are multiple elements marked entry-content, | 497 | // according to hAtom spec, if there are multiple elements marked entry-content, |
498 | // we include all of these in the order they appear - see http://microformats.org/wiki/hatom#Entry_Content | 498 | // we include all of these in the order they appear - see http://microformats.org/wiki/hatom#Entry_Content |
499 | if ($detect_body) { | 499 | if ($detect_body) { |
500 | $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' entry-content ')]", $hentry); | 500 | $elems = @$xpath->query(".//*[contains(concat(' ',normalize-space(@class),' '),' entry-content ')]", $hentry); |
501 | if ($elems && $elems->length > 0) { | 501 | if ($elems && $elems->length > 0) { |
502 | $this->debug('hNews: found entry-content'); | 502 | $this->debug('hNews: found entry-content'); |
503 | if ($elems->length == 1) { | 503 | if ($elems->length == 1) { |
504 | // what if it's empty? (some sites misuse hNews - place their content outside an empty entry-content element) | 504 | // what if it's empty? (some sites misuse hNews - place their content outside an empty entry-content element) |
505 | $e = $elems->item(0); | 505 | $e = $elems->item(0); |
506 | if (($e->tagName == 'img') || (trim($e->textContent) != '')) { | 506 | if (($e->tagName == 'img') || (trim($e->textContent) != '')) { |
507 | $this->body = $elems->item(0); | 507 | $this->body = $elems->item(0); |
508 | // prune (clean up elements that may not be content) | 508 | // prune (clean up elements that may not be content) |
509 | if ($this->config->prune()) { | 509 | if ($this->config->prune()) { |
510 | $this->debug('Pruning content'); | 510 | $this->debug('Pruning content'); |
511 | $this->readability->prepArticle($this->body); | 511 | $this->readability->prepArticle($this->body); |
512 | } | 512 | } |
513 | $detect_body = false; | 513 | $detect_body = false; |
514 | } else { | 514 | } else { |
515 | $this->debug('hNews: skipping entry-content - appears not to contain content'); | 515 | $this->debug('hNews: skipping entry-content - appears not to contain content'); |
516 | } | 516 | } |
517 | unset($e); | 517 | unset($e); |
518 | } else { | 518 | } else { |
519 | $this->body = $this->readability->dom->createElement('div'); | 519 | $this->body = $this->readability->dom->createElement('div'); |
520 | $this->debug($elems->length.' entry-content elems found'); | 520 | $this->debug($elems->length.' entry-content elems found'); |
521 | foreach ($elems as $elem) { | 521 | foreach ($elems as $elem) { |
522 | if (!isset($elem->parentNode)) continue; | 522 | if (!isset($elem->parentNode)) continue; |
523 | $isDescendant = false; | 523 | $isDescendant = false; |
524 | foreach ($this->body->childNodes as $parent) { | 524 | foreach ($this->body->childNodes as $parent) { |
525 | if ($this->isDescendant($parent, $elem)) { | 525 | if ($this->isDescendant($parent, $elem)) { |
526 | $isDescendant = true; | 526 | $isDescendant = true; |
527 | break; | 527 | break; |
528 | } | 528 | } |
529 | } | 529 | } |
530 | if ($isDescendant) { | 530 | if ($isDescendant) { |
531 | $this->debug('Element is child of another body element, skipping.'); | 531 | $this->debug('Element is child of another body element, skipping.'); |
532 | } else { | 532 | } else { |
533 | // prune (clean up elements that may not be content) | 533 | // prune (clean up elements that may not be content) |
534 | if ($this->config->prune()) { | 534 | if ($this->config->prune()) { |
535 | $this->debug('Pruning content'); | 535 | $this->debug('Pruning content'); |
536 | $this->readability->prepArticle($elem); | 536 | $this->readability->prepArticle($elem); |
537 | } | 537 | } |
538 | $this->debug('Element added to body'); | 538 | $this->debug('Element added to body'); |
539 | $this->body->appendChild($elem); | 539 | $this->body->appendChild($elem); |
540 | } | 540 | } |
541 | } | 541 | } |
542 | $detect_body = false; | 542 | $detect_body = false; |
543 | } | 543 | } |
544 | } | 544 | } |
545 | } | 545 | } |
546 | } | 546 | } |
547 | } | 547 | } |
548 | 548 | ||
549 | // check for elements marked with instapaper_title | 549 | // check for elements marked with instapaper_title |
550 | if ($detect_title) { | 550 | if ($detect_title) { |
551 | // check for instapaper_title | 551 | // check for instapaper_title |
552 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' instapaper_title ')]", $this->readability->dom); | 552 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' instapaper_title ')]", $this->readability->dom); |
553 | if ($elems && $elems->length > 0) { | 553 | if ($elems && $elems->length > 0) { |
554 | $this->title = $elems->item(0)->textContent; | 554 | $this->title = $elems->item(0)->textContent; |
555 | $this->debug('Title found (.instapaper_title): '.$this->title); | 555 | $this->debug('Title found (.instapaper_title): '.$this->title); |
556 | // remove title from document | 556 | // remove title from document |
557 | $elems->item(0)->parentNode->removeChild($elems->item(0)); | 557 | $elems->item(0)->parentNode->removeChild($elems->item(0)); |
558 | $detect_title = false; | 558 | $detect_title = false; |
559 | } | 559 | } |
560 | } | 560 | } |
561 | // check for elements marked with instapaper_body | 561 | // check for elements marked with instapaper_body |
562 | if ($detect_body) { | 562 | if ($detect_body) { |
563 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' instapaper_body ')]", $this->readability->dom); | 563 | $elems = @$xpath->query("//*[contains(concat(' ',normalize-space(@class),' '),' instapaper_body ')]", $this->readability->dom); |
564 | if ($elems && $elems->length > 0) { | 564 | if ($elems && $elems->length > 0) { |
565 | $this->debug('body found (.instapaper_body)'); | 565 | $this->debug('body found (.instapaper_body)'); |
566 | $this->body = $elems->item(0); | 566 | $this->body = $elems->item(0); |
567 | // prune (clean up elements that may not be content) | 567 | // prune (clean up elements that may not be content) |
568 | if ($this->config->prune()) { | 568 | if ($this->config->prune()) { |
569 | $this->debug('Pruning content'); | 569 | $this->debug('Pruning content'); |
570 | $this->readability->prepArticle($this->body); | 570 | $this->readability->prepArticle($this->body); |
571 | } | 571 | } |
572 | $detect_body = false; | 572 | $detect_body = false; |
573 | } | 573 | } |
574 | } | 574 | } |
575 | 575 | ||
576 | // Find author in rel="author" marked element | 576 | // Find author in rel="author" marked element |
577 | // We only use this if there's exactly one. | 577 | // We only use this if there's exactly one. |
578 | // If there's more than one, it could indicate more than | 578 | // If there's more than one, it could indicate more than |
579 | // one author, but it could also indicate that we're processing | 579 | // one author, but it could also indicate that we're processing |
580 | // a page listing different articles with different authors. | 580 | // a page listing different articles with different authors. |
581 | if ($detect_author) { | 581 | if ($detect_author) { |
582 | $elems = @$xpath->query("//a[contains(concat(' ',normalize-space(@rel),' '),' author ')]", $this->readability->dom); | 582 | $elems = @$xpath->query("//a[contains(concat(' ',normalize-space(@rel),' '),' author ')]", $this->readability->dom); |
583 | if ($elems && $elems->length == 1) { | 583 | if ($elems && $elems->length == 1) { |
584 | $author = trim($elems->item(0)->textContent); | 584 | $author = trim($elems->item(0)->textContent); |
585 | if ($author != '') { | 585 | if ($author != '') { |
586 | $this->debug("Author found (rel=\"author\"): $author"); | 586 | $this->debug("Author found (rel=\"author\"): $author"); |
587 | $this->author[] = $author; | 587 | $this->author[] = $author; |
588 | $detect_author = false; | 588 | $detect_author = false; |
589 | } | 589 | } |
590 | } | 590 | } |
591 | } | 591 | } |
592 | 592 | ||
593 | // Find date in pubdate marked time element | 593 | // Find date in pubdate marked time element |
594 | // For the same reason given above, we only use this | 594 | // For the same reason given above, we only use this |
595 | // if there's exactly one element. | 595 | // if there's exactly one element. |
596 | if ($detect_date) { | 596 | if ($detect_date) { |
597 | $elems = @$xpath->query("//time[@pubdate]", $this->readability->dom); | 597 | $elems = @$xpath->query("//time[@pubdate]", $this->readability->dom); |
598 | if ($elems && $elems->length == 1) { | 598 | if ($elems && $elems->length == 1) { |
599 | $this->date = strtotime(trim($elems->item(0)->textContent)); | 599 | $this->date = strtotime(trim($elems->item(0)->textContent)); |
600 | // remove date from document | 600 | // remove date from document |
601 | //$elems->item(0)->parentNode->removeChild($elems->item(0)); | 601 | //$elems->item(0)->parentNode->removeChild($elems->item(0)); |
602 | if ($this->date) { | 602 | if ($this->date) { |
603 | $this->debug('Date found (pubdate marked time element): '.date('Y-m-d H:i:s', $this->date)); | 603 | $this->debug('Date found (pubdate marked time element): '.date('Y-m-d H:i:s', $this->date)); |
604 | $detect_date = false; | 604 | $detect_date = false; |
605 | } else { | 605 | } else { |
606 | $this->date = null; | 606 | $this->date = null; |
607 | } | 607 | } |
608 | } | 608 | } |
609 | } | 609 | } |
610 | 610 | ||
611 | // still missing title or body, so we detect using Readability | 611 | // still missing title or body, so we detect using Readability |
612 | if ($detect_title || $detect_body) { | 612 | if ($detect_title || $detect_body) { |
613 | $this->debug('Using Readability'); | 613 | $this->debug('Using Readability'); |
614 | // clone body if we're only using Readability for title (otherwise it may interfere with body element) | 614 | // clone body if we're only using Readability for title (otherwise it may interfere with body element) |
615 | if (isset($this->body)) $this->body = $this->body->cloneNode(true); | 615 | if (isset($this->body)) $this->body = $this->body->cloneNode(true); |
616 | $success = $this->readability->init(); | 616 | $success = $this->readability->init(); |
617 | } | 617 | } |
618 | if ($detect_title) { | 618 | if ($detect_title) { |
619 | $this->debug('Detecting title'); | 619 | $this->debug('Detecting title'); |
620 | $this->title = $this->readability->getTitle()->textContent; | 620 | $this->title = $this->readability->getTitle()->textContent; |
621 | } | 621 | } |
622 | if ($detect_body && $success) { | 622 | if ($detect_body && $success) { |
623 | $this->debug('Detecting body'); | 623 | $this->debug('Detecting body'); |
624 | $this->body = $this->readability->getContent(); | 624 | $this->body = $this->readability->getContent(); |
625 | if ($this->body->childNodes->length == 1 && $this->body->firstChild->nodeType === XML_ELEMENT_NODE) { | 625 | if ($this->body->childNodes->length == 1 && $this->body->firstChild->nodeType === XML_ELEMENT_NODE) { |
626 | $this->body = $this->body->firstChild; | 626 | $this->body = $this->body->firstChild; |
627 | } | 627 | } |
628 | // prune (clean up elements that may not be content) | 628 | // prune (clean up elements that may not be content) |
629 | if ($this->config->prune()) { | 629 | if ($this->config->prune()) { |
630 | $this->debug('Pruning content'); | 630 | $this->debug('Pruning content'); |
631 | $this->readability->prepArticle($this->body); | 631 | $this->readability->prepArticle($this->body); |
632 | } | 632 | } |
633 | } | 633 | } |
634 | if (isset($this->body)) { | 634 | if (isset($this->body)) { |
635 | // remove scripts | 635 | // remove scripts |
636 | $this->readability->removeScripts($this->body); | 636 | $this->readability->removeScripts($this->body); |
637 | // remove any h1-h6 elements that appear as first thing in the body | 637 | // remove any h1-h6 elements that appear as first thing in the body |
638 | // and which match our title | 638 | // and which match our title |
639 | if (isset($this->title) && ($this->title != '')) { | 639 | if (isset($this->title) && ($this->title != '')) { |
640 | $firstChild = $this->body->firstChild; | 640 | $firstChild = $this->body->firstChild; |
641 | while ($firstChild->nodeType && ($firstChild->nodeType !== XML_ELEMENT_NODE)) { | 641 | while ($firstChild->nodeType && ($firstChild->nodeType !== XML_ELEMENT_NODE)) { |
642 | $firstChild = $firstChild->nextSibling; | 642 | $firstChild = $firstChild->nextSibling; |
643 | } | 643 | } |
644 | if (($firstChild->nodeType === XML_ELEMENT_NODE) | 644 | if (($firstChild->nodeType === XML_ELEMENT_NODE) |
645 | && in_array(strtolower($firstChild->tagName), array('h1', 'h2', 'h3', 'h4', 'h5', 'h6')) | 645 | && in_array(strtolower($firstChild->tagName), array('h1', 'h2', 'h3', 'h4', 'h5', 'h6')) |
646 | && (strtolower(trim($firstChild->textContent)) == strtolower(trim($this->title)))) { | 646 | && (strtolower(trim($firstChild->textContent)) == strtolower(trim($this->title)))) { |
647 | $this->body->removeChild($firstChild); | 647 | $this->body->removeChild($firstChild); |
648 | } | 648 | } |
649 | } | 649 | } |
650 | // prevent self-closing iframes | 650 | // prevent self-closing iframes |
651 | $elems = $this->body->getElementsByTagName('iframe'); | 651 | $elems = $this->body->getElementsByTagName('iframe'); |
652 | for ($i = $elems->length-1; $i >= 0; $i--) { | 652 | for ($i = $elems->length-1; $i >= 0; $i--) { |
653 | $e = $elems->item($i); | 653 | $e = $elems->item($i); |
654 | if (!$e->hasChildNodes()) { | 654 | if (!$e->hasChildNodes()) { |
655 | $e->appendChild($this->body->ownerDocument->createTextNode('[embedded content]')); | 655 | $e->appendChild($this->body->ownerDocument->createTextNode('[embedded content]')); |
656 | } | 656 | } |
657 | } | 657 | } |
658 | // remove image lazy loading - WordPress plugin http://wordpress.org/extend/plugins/lazy-load/ | 658 | // remove image lazy loading - WordPress plugin http://wordpress.org/extend/plugins/lazy-load/ |
659 | // the plugin replaces the src attribute to point to a 1x1 gif and puts the original src | 659 | // the plugin replaces the src attribute to point to a 1x1 gif and puts the original src |
660 | // inside the data-lazy-src attribute. It also places the original image inside a noscript element | 660 | // inside the data-lazy-src attribute. It also places the original image inside a noscript element |
661 | // next to the amended one. | 661 | // next to the amended one. |
662 | $elems = @$xpath->query("//img[@data-lazy-src]", $this->body); | 662 | $elems = @$xpath->query("//img[@data-lazy-src]", $this->body); |
663 | for ($i = $elems->length-1; $i >= 0; $i--) { | 663 | for ($i = $elems->length-1; $i >= 0; $i--) { |
664 | $e = $elems->item($i); | 664 | $e = $elems->item($i); |
665 | // let's see if we can grab image from noscript | 665 | // let's see if we can grab image from noscript |
666 | if ($e->nextSibling !== null && $e->nextSibling->nodeName === 'noscript') { | 666 | if ($e->nextSibling !== null && $e->nextSibling->nodeName === 'noscript') { |
667 | $_new_elem = $e->ownerDocument->createDocumentFragment(); | 667 | $_new_elem = $e->ownerDocument->createDocumentFragment(); |
668 | @$_new_elem->appendXML($e->nextSibling->innerHTML); | 668 | @$_new_elem->appendXML($e->nextSibling->innerHTML); |
669 | $e->nextSibling->parentNode->replaceChild($_new_elem, $e->nextSibling); | 669 | $e->nextSibling->parentNode->replaceChild($_new_elem, $e->nextSibling); |
670 | $e->parentNode->removeChild($e); | 670 | $e->parentNode->removeChild($e); |
671 | } else { | 671 | } else { |
672 | // Use data-lazy-src as src value | 672 | // Use data-lazy-src as src value |
673 | $e->setAttribute('src', $e->getAttribute('data-lazy-src')); | 673 | $e->setAttribute('src', $e->getAttribute('data-lazy-src')); |
674 | $e->removeAttribute('data-lazy-src'); | 674 | $e->removeAttribute('data-lazy-src'); |
675 | } | 675 | } |
676 | } | 676 | } |
677 | 677 | ||
678 | $this->success = true; | 678 | $this->success = true; |
679 | } | 679 | } |
680 | 680 | ||
681 | // if we've had no success and we've used tidy, there's a chance | 681 | // if we've had no success and we've used tidy, there's a chance |
682 | // that tidy has messed up. So let's try again without tidy... | 682 | // that tidy has messed up. So let's try again without tidy... |
683 | if (!$this->success && $tidied && $smart_tidy) { | 683 | if (!$this->success && $tidied && $smart_tidy) { |
684 | $this->debug('Trying again without tidy'); | 684 | $this->debug('Trying again without tidy'); |
685 | $this->process($original_html, $url, false); | 685 | $this->process($original_html, $url, false); |
686 | } | 686 | } |
687 | 687 | ||
688 | return $this->success; | 688 | return $this->success; |
689 | } | 689 | } |
690 | 690 | ||
691 | private function isDescendant(DOMElement $parent, DOMElement $child) { | 691 | private function isDescendant(DOMElement $parent, DOMElement $child) { |
692 | $node = $child->parentNode; | 692 | $node = $child->parentNode; |
693 | while ($node != null) { | 693 | while ($node != null) { |
694 | if ($node->isSameNode($parent)) return true; | 694 | if ($node->isSameNode($parent)) return true; |
695 | $node = $node->parentNode; | 695 | $node = $node->parentNode; |
696 | } | 696 | } |
697 | return false; | 697 | return false; |
698 | } | 698 | } |
699 | 699 | ||
700 | public function getContent() { | 700 | public function getContent() { |
701 | return $this->body; | 701 | return $this->body; |
702 | } | 702 | } |
703 | 703 | ||
704 | public function getTitle() { | 704 | public function getTitle() { |
705 | return $this->title; | 705 | return $this->title; |
706 | } | 706 | } |
707 | 707 | ||
708 | public function getAuthors() { | 708 | public function getAuthors() { |
709 | return $this->author; | 709 | return $this->author; |
710 | } | 710 | } |
711 | 711 | ||
712 | public function getLanguage() { | 712 | public function getLanguage() { |
713 | return $this->language; | 713 | return $this->language; |
714 | } | 714 | } |
715 | 715 | ||
716 | public function getDate() { | 716 | public function getDate() { |
717 | return $this->date; | 717 | return $this->date; |
718 | } | 718 | } |
719 | 719 | ||
720 | public function getSiteConfig() { | 720 | public function getSiteConfig() { |
721 | return $this->config; | 721 | return $this->config; |
722 | } | 722 | } |
723 | 723 | ||
724 | public function getNextPageUrl() { | 724 | public function getNextPageUrl() { |
725 | return $this->nextPageUrl; | 725 | return $this->nextPageUrl; |
726 | } | 726 | } |
727 | } | 727 | } \ No newline at end of file |
728 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/content-extractor/SiteConfig.php b/inc/3rdparty/libraries/content-extractor/SiteConfig.php index c5e300d7..1f6a7603 100644 --- a/inc/3rdparty/libraries/content-extractor/SiteConfig.php +++ b/inc/3rdparty/libraries/content-extractor/SiteConfig.php | |||
@@ -1,338 +1,343 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Site Config | 3 | * Site Config |
4 | * | 4 | * |
5 | * Each instance of this class should hold extraction patterns and other directives | 5 | * Each instance of this class should hold extraction patterns and other directives |
6 | * for a website. See ContentExtractor class to see how it's used. | 6 | * for a website. See ContentExtractor class to see how it's used. |
7 | * | 7 | * |
8 | * @version 0.7 | 8 | * @version 0.8 |
9 | * @date 2012-08-27 | 9 | * @date 2013-04-16 |
10 | * @author Keyvan Minoukadeh | 10 | * @author Keyvan Minoukadeh |
11 | * @copyright 2012 Keyvan Minoukadeh | 11 | * @copyright 2013 Keyvan Minoukadeh |
12 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 | 12 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 |
13 | */ | 13 | */ |
14 | 14 | ||
15 | class SiteConfig | 15 | class SiteConfig |
16 | { | 16 | { |
17 | // Use first matching element as title (0 or more xpath expressions) | 17 | // Use first matching element as title (0 or more xpath expressions) |
18 | public $title = array(); | 18 | public $title = array(); |
19 | 19 | ||
20 | // Use first matching element as body (0 or more xpath expressions) | 20 | // Use first matching element as body (0 or more xpath expressions) |
21 | public $body = array(); | 21 | public $body = array(); |
22 | 22 | ||
23 | // Use first matching element as author (0 or more xpath expressions) | 23 | // Use first matching element as author (0 or more xpath expressions) |
24 | public $author = array(); | 24 | public $author = array(); |
25 | 25 | ||
26 | // Use first matching element as date (0 or more xpath expressions) | 26 | // Use first matching element as date (0 or more xpath expressions) |
27 | public $date = array(); | 27 | public $date = array(); |
28 | 28 | ||
29 | // Strip elements matching these xpath expressions (0 or more) | 29 | // Strip elements matching these xpath expressions (0 or more) |
30 | public $strip = array(); | 30 | public $strip = array(); |
31 | 31 | ||
32 | // Strip elements which contain these strings (0 or more) in the id or class attribute | 32 | // Strip elements which contain these strings (0 or more) in the id or class attribute |
33 | public $strip_id_or_class = array(); | 33 | public $strip_id_or_class = array(); |
34 | 34 | ||
35 | // Strip images which contain these strings (0 or more) in the src attribute | 35 | // Strip images which contain these strings (0 or more) in the src attribute |
36 | public $strip_image_src = array(); | 36 | public $strip_image_src = array(); |
37 | 37 | ||
38 | // Additional HTTP headers to send | 38 | // Additional HTTP headers to send |
39 | // NOT YET USED | 39 | // NOT YET USED |
40 | public $http_header = array(); | 40 | public $http_header = array(); |
41 | 41 | ||
42 | // Process HTML with tidy before creating DOM (bool or null if undeclared) | 42 | // Process HTML with tidy before creating DOM (bool or null if undeclared) |
43 | public $tidy = null; | 43 | public $tidy = null; |
44 | 44 | ||
45 | protected $default_tidy = true; // used if undeclared | 45 | protected $default_tidy = true; // used if undeclared |
46 | 46 | ||
47 | // Autodetect title/body if xpath expressions fail to produce results. | 47 | // Autodetect title/body if xpath expressions fail to produce results. |
48 | // Note that this applies to title and body separately, ie. | 48 | // Note that this applies to title and body separately, ie. |
49 | // * if we get a body match but no title match, this option will determine whether we autodetect title | 49 | // * if we get a body match but no title match, this option will determine whether we autodetect title |
50 | // * if neither match, this determines whether we autodetect title and body. | 50 | // * if neither match, this determines whether we autodetect title and body. |
51 | // Also note that this only applies when there is at least one xpath expression in title or body, ie. | 51 | // Also note that this only applies when there is at least one xpath expression in title or body, ie. |
52 | // * if title and body are both empty (no xpath expressions), this option has no effect (both title and body will be auto-detected) | 52 | // * if title and body are both empty (no xpath expressions), this option has no effect (both title and body will be auto-detected) |
53 | // * if there's an xpath expression for title and none for body, body will be auto-detected and this option will determine whether we auto-detect title if the xpath expression for it fails to produce results. | 53 | // * if there's an xpath expression for title and none for body, body will be auto-detected and this option will determine whether we auto-detect title if the xpath expression for it fails to produce results. |
54 | // Usage scenario: you want to extract something specific from a set of URLs, e.g. a table, and if the table is not found, you want to ignore the entry completely. Auto-detection is unlikely to succeed here, so you construct your patterns and set this option to false. Another scenario may be a site where auto-detection has proven to fail (or worse, picked up the wrong content). | 54 | // Usage scenario: you want to extract something specific from a set of URLs, e.g. a table, and if the table is not found, you want to ignore the entry completely. Auto-detection is unlikely to succeed here, so you construct your patterns and set this option to false. Another scenario may be a site where auto-detection has proven to fail (or worse, picked up the wrong content). |
55 | // bool or null if undeclared | 55 | // bool or null if undeclared |
56 | public $autodetect_on_failure = null; | 56 | public $autodetect_on_failure = null; |
57 | protected $default_autodetect_on_failure = true; // used if undeclared | 57 | protected $default_autodetect_on_failure = true; // used if undeclared |
58 | 58 | ||
59 | // Clean up content block - attempt to remove elements that appear to be superfluous | 59 | // Clean up content block - attempt to remove elements that appear to be superfluous |
60 | // bool or null if undeclared | 60 | // bool or null if undeclared |
61 | public $prune = null; | 61 | public $prune = null; |
62 | protected $default_prune = true; // used if undeclared | 62 | protected $default_prune = true; // used if undeclared |
63 | 63 | ||
64 | // Test URL - if present, can be used to test the config above | 64 | // Test URL - if present, can be used to test the config above |
65 | public $test_url = array(); | 65 | public $test_url = array(); |
66 | 66 | ||
67 | // Single-page link - should identify a link element or URL pointing to the page holding the entire article | 67 | // Single-page link - should identify a link element or URL pointing to the page holding the entire article |
68 | // This is useful for sites which split their articles across multiple pages. Links to such pages tend to | 68 | // This is useful for sites which split their articles across multiple pages. Links to such pages tend to |
69 | // display the first page with links to the other pages at the bottom. Often there is also a link to a page | 69 | // display the first page with links to the other pages at the bottom. Often there is also a link to a page |
70 | // which displays the entire article on one page (e.g. 'print view'). | 70 | // which displays the entire article on one page (e.g. 'print view'). |
71 | // This should be an XPath expression identifying the link to that page. If present and we find a match, | 71 | // This should be an XPath expression identifying the link to that page. If present and we find a match, |
72 | // we will retrieve that page and the rest of the options in this config will be applied to the new page. | 72 | // we will retrieve that page and the rest of the options in this config will be applied to the new page. |
73 | public $single_page_link = array(); | 73 | public $single_page_link = array(); |
74 | 74 | ||
75 | public $next_page_link = array(); | 75 | public $next_page_link = array(); |
76 | 76 | ||
77 | // Single-page link in feed? - same as above, but patterns applied to item description HTML taken from feed | 77 | // Single-page link in feed? - same as above, but patterns applied to item description HTML taken from feed |
78 | public $single_page_link_in_feed = array(); | 78 | public $single_page_link_in_feed = array(); |
79 | 79 | ||
80 | // Which parser to use for turning raw HTML into a DOMDocument (either 'libxml' or 'html5lib') | 80 | // Which parser to use for turning raw HTML into a DOMDocument (either 'libxml' or 'html5lib') |
81 | // string or null if undeclared | 81 | // string or null if undeclared |
82 | public $parser = null; | 82 | public $parser = null; |
83 | protected $default_parser = 'libxml'; // used if undeclared | 83 | protected $default_parser = 'libxml'; // used if undeclared |
84 | 84 | ||
85 | // Strings to search for in HTML before processing begins (used with $replace_string) | 85 | // Strings to search for in HTML before processing begins (used with $replace_string) |
86 | public $find_string = array(); | 86 | public $find_string = array(); |
87 | // Strings to replace those found in $find_string before HTML processing begins | 87 | // Strings to replace those found in $find_string before HTML processing begins |
88 | public $replace_string = array(); | 88 | public $replace_string = array(); |
89 | 89 | ||
90 | // the options below cannot be set in the config files which this class represents | 90 | // the options below cannot be set in the config files which this class represents |
91 | 91 | ||
92 | //public $cache_in_apc = false; // used to decide if we should cache in apc or not | 92 | //public $cache_in_apc = false; // used to decide if we should cache in apc or not |
93 | public $cache_key = null; | 93 | public $cache_key = null; |
94 | public static $debug = false; | 94 | public static $debug = false; |
95 | protected static $apc = false; | 95 | protected static $apc = false; |
96 | protected static $config_path; | 96 | protected static $config_path; |
97 | protected static $config_path_fallback; | 97 | protected static $config_path_fallback; |
98 | protected static $config_cache = array(); | 98 | protected static $config_cache = array(); |
99 | const HOSTNAME_REGEX = '/^(([a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9-]*[A-Za-z0-9])$/'; | 99 | const HOSTNAME_REGEX = '/^(([a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9-]*[A-Za-z0-9])$/'; |
100 | 100 | ||
101 | protected static function debug($msg) { | 101 | protected static function debug($msg) { |
102 | if (self::$debug) { | 102 | if (self::$debug) { |
103 | //$mem = round(memory_get_usage()/1024, 2); | 103 | //$mem = round(memory_get_usage()/1024, 2); |
104 | //$memPeak = round(memory_get_peak_usage()/1024, 2); | 104 | //$memPeak = round(memory_get_peak_usage()/1024, 2); |
105 | echo '* ',$msg; | 105 | echo '* ',$msg; |
106 | //echo ' - mem used: ',$mem," (peak: $memPeak)\n"; | 106 | //echo ' - mem used: ',$mem," (peak: $memPeak)\n"; |
107 | echo "\n"; | 107 | echo "\n"; |
108 | ob_flush(); | 108 | ob_flush(); |
109 | flush(); | 109 | flush(); |
110 | } | 110 | } |
111 | } | 111 | } |
112 | 112 | ||
113 | // enable APC caching of certain site config files? | 113 | // enable APC caching of certain site config files? |
114 | // If enabled the following site config files will be | 114 | // If enabled the following site config files will be |
115 | // cached in APC cache (when requested for first time): | 115 | // cached in APC cache (when requested for first time): |
116 | // * anything in site_config/custom/ and its corresponding file in site_config/standard/ | 116 | // * anything in site_config/custom/ and its corresponding file in site_config/standard/ |
117 | // * the site config files associated with HTML fingerprints | 117 | // * the site config files associated with HTML fingerprints |
118 | // * the global site config file | 118 | // * the global site config file |
119 | // returns true if enabled, false otherwise | 119 | // returns true if enabled, false otherwise |
120 | public static function use_apc($apc=true) { | 120 | public static function use_apc($apc=true) { |
121 | if (!function_exists('apc_add')) { | 121 | if (!function_exists('apc_add')) { |
122 | if ($apc) self::debug('APC will not be used (function apc_add does not exist)'); | 122 | if ($apc) self::debug('APC will not be used (function apc_add does not exist)'); |
123 | return false; | 123 | return false; |
124 | } | 124 | } |
125 | self::$apc = $apc; | 125 | self::$apc = $apc; |
126 | return $apc; | 126 | return $apc; |
127 | } | 127 | } |
128 | 128 | ||
129 | // return bool or null | 129 | // return bool or null |
130 | public function tidy($use_default=true) { | 130 | public function tidy($use_default=true) { |
131 | if ($use_default) return (isset($this->tidy)) ? $this->tidy : $this->default_tidy; | 131 | if ($use_default) return (isset($this->tidy)) ? $this->tidy : $this->default_tidy; |
132 | return $this->tidy; | 132 | return $this->tidy; |
133 | } | 133 | } |
134 | 134 | ||
135 | // return bool or null | 135 | // return bool or null |
136 | public function prune($use_default=true) { | 136 | public function prune($use_default=true) { |
137 | if ($use_default) return (isset($this->prune)) ? $this->prune : $this->default_prune; | 137 | if ($use_default) return (isset($this->prune)) ? $this->prune : $this->default_prune; |
138 | return $this->prune; | 138 | return $this->prune; |
139 | } | 139 | } |
140 | 140 | ||
141 | // return string or null | 141 | // return string or null |
142 | public function parser($use_default=true) { | 142 | public function parser($use_default=true) { |
143 | if ($use_default) return (isset($this->parser)) ? $this->parser : $this->default_parser; | 143 | if ($use_default) return (isset($this->parser)) ? $this->parser : $this->default_parser; |
144 | return $this->parser; | 144 | return $this->parser; |
145 | } | 145 | } |
146 | 146 | ||
147 | // return bool or null | 147 | // return bool or null |
148 | public function autodetect_on_failure($use_default=true) { | 148 | public function autodetect_on_failure($use_default=true) { |
149 | if ($use_default) return (isset($this->autodetect_on_failure)) ? $this->autodetect_on_failure : $this->default_autodetect_on_failure; | 149 | if ($use_default) return (isset($this->autodetect_on_failure)) ? $this->autodetect_on_failure : $this->default_autodetect_on_failure; |
150 | return $this->autodetect_on_failure; | 150 | return $this->autodetect_on_failure; |
151 | } | 151 | } |
152 | 152 | ||
153 | public static function set_config_path($path, $fallback=null) { | 153 | public static function set_config_path($path, $fallback=null) { |
154 | self::$config_path = $path; | 154 | self::$config_path = $path; |
155 | self::$config_path_fallback = $fallback; | 155 | self::$config_path_fallback = $fallback; |
156 | } | 156 | } |
157 | 157 | ||
158 | public static function add_to_cache($key, SiteConfig $config, $use_apc=true) { | 158 | public static function add_to_cache($key, SiteConfig $config, $use_apc=true) { |
159 | $key = strtolower($key); | 159 | $key = strtolower($key); |
160 | if (substr($key, 0, 4) == 'www.') $key = substr($key, 4); | 160 | if (substr($key, 0, 4) == 'www.') $key = substr($key, 4); |
161 | if ($config->cache_key) $key = $config->cache_key; | 161 | if ($config->cache_key) $key = $config->cache_key; |
162 | self::$config_cache[$key] = $config; | 162 | self::$config_cache[$key] = $config; |
163 | if (self::$apc && $use_apc) { | 163 | if (self::$apc && $use_apc) { |
164 | self::debug("Adding site config to APC cache with key sc.$key"); | 164 | self::debug("Adding site config to APC cache with key sc.$key"); |
165 | apc_add("sc.$key", $config); | 165 | apc_add("sc.$key", $config); |
166 | } | 166 | } |
167 | self::debug("Cached site config with key $key"); | 167 | self::debug("Cached site config with key $key"); |
168 | } | 168 | } |
169 | 169 | ||
170 | public static function is_cached($key) { | 170 | public static function is_cached($key) { |
171 | $key = strtolower($key); | 171 | $key = strtolower($key); |
172 | if (substr($key, 0, 4) == 'www.') $key = substr($key, 4); | 172 | if (substr($key, 0, 4) == 'www.') $key = substr($key, 4); |
173 | if (array_key_exists($key, self::$config_cache)) { | 173 | if (array_key_exists($key, self::$config_cache)) { |
174 | return true; | 174 | return true; |
175 | } elseif (self::$apc && (bool)apc_fetch("sc.$key")) { | 175 | } elseif (self::$apc && (bool)apc_fetch("sc.$key")) { |
176 | return true; | 176 | return true; |
177 | } | 177 | } |
178 | return false; | 178 | return false; |
179 | } | 179 | } |
180 | 180 | ||
181 | public function append(SiteConfig $newconfig) { | 181 | public function append(SiteConfig $newconfig) { |
182 | // check for commands where we accept multiple statements (no test_url) | 182 | // check for commands where we accept multiple statements (no test_url) |
183 | foreach (array('title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'http_header', 'find_string', 'replace_string') as $var) { | 183 | foreach (array('title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'http_header') as $var) { |
184 | // append array elements for this config variable from $newconfig to this config | 184 | // append array elements for this config variable from $newconfig to this config |
185 | //$this->$var = $this->$var + $newconfig->$var; | 185 | //$this->$var = $this->$var + $newconfig->$var; |
186 | $this->$var = array_unique(array_merge($this->$var, $newconfig->$var)); | 186 | $this->$var = array_unique(array_merge($this->$var, $newconfig->$var)); |
187 | } | 187 | } |
188 | // check for single statement commands | 188 | // check for single statement commands |
189 | // we do not overwrite existing non null values | 189 | // we do not overwrite existing non null values |
190 | foreach (array('tidy', 'prune', 'parser', 'autodetect_on_failure') as $var) { | 190 | foreach (array('tidy', 'prune', 'parser', 'autodetect_on_failure') as $var) { |
191 | if ($this->$var === null) $this->$var = $newconfig->$var; | 191 | if ($this->$var === null) $this->$var = $newconfig->$var; |
192 | } | 192 | } |
193 | } | 193 | // treat find_string and replace_string separately (don't apply array_unique) (thanks fabrizio!) |
194 | 194 | foreach (array('find_string', 'replace_string') as $var) { | |
195 | // returns SiteConfig instance if an appropriate one is found, false otherwise | 195 | // append array elements for this config variable from $newconfig to this config |
196 | // if $exact_host_match is true, we will not look for wildcard config matches | 196 | //$this->$var = $this->$var + $newconfig->$var; |
197 | // by default if host is 'test.example.org' we will look for and load '.example.org.txt' if it exists | 197 | $this->$var = array_merge($this->$var, $newconfig->$var); |
198 | public static function build($host, $exact_host_match=false) { | 198 | } |
199 | $host = strtolower($host); | 199 | } |
200 | if (substr($host, 0, 4) == 'www.') $host = substr($host, 4); | 200 | |
201 | if (!$host || (strlen($host) > 200) || !preg_match(self::HOSTNAME_REGEX, ltrim($host, '.'))) return false; | 201 | // returns SiteConfig instance if an appropriate one is found, false otherwise |
202 | // check for site configuration | 202 | // if $exact_host_match is true, we will not look for wildcard config matches |
203 | $try = array($host); | 203 | // by default if host is 'test.example.org' we will look for and load '.example.org.txt' if it exists |
204 | // should we look for wildcard matches | 204 | public static function build($host, $exact_host_match=false) { |
205 | if (!$exact_host_match) { | 205 | $host = strtolower($host); |
206 | $split = explode('.', $host); | 206 | if (substr($host, 0, 4) == 'www.') $host = substr($host, 4); |
207 | if (count($split) > 1) { | 207 | if (!$host || (strlen($host) > 200) || !preg_match(self::HOSTNAME_REGEX, ltrim($host, '.'))) return false; |
208 | array_shift($split); | 208 | // check for site configuration |
209 | $try[] = '.'.implode('.', $split); | 209 | $try = array($host); |
210 | } | 210 | // should we look for wildcard matches |
211 | } | 211 | if (!$exact_host_match) { |
212 | 212 | $split = explode('.', $host); | |
213 | // look for site config file in primary folder | 213 | if (count($split) > 1) { |
214 | self::debug(". looking for site config for $host in primary folder"); | 214 | array_shift($split); |
215 | foreach ($try as $h) { | 215 | $try[] = '.'.implode('.', $split); |
216 | if (array_key_exists($h, self::$config_cache)) { | 216 | } |
217 | self::debug("... site config for $h already loaded in this request"); | 217 | } |
218 | return self::$config_cache[$h]; | 218 | |
219 | } elseif (self::$apc && ($sconfig = apc_fetch("sc.$h"))) { | 219 | // look for site config file in primary folder |
220 | self::debug("... site config for $h in APC cache"); | 220 | self::debug(". looking for site config for $host in primary folder"); |
221 | return $sconfig; | 221 | foreach ($try as $h) { |
222 | } elseif (file_exists(self::$config_path."/$h.txt")) { | 222 | if (array_key_exists($h, self::$config_cache)) { |
223 | self::debug("... found site config ($h.txt)"); | 223 | self::debug("... site config for $h already loaded in this request"); |
224 | $file_primary = self::$config_path."/$h.txt"; | 224 | return self::$config_cache[$h]; |
225 | $matched_name = $h; | 225 | } elseif (self::$apc && ($sconfig = apc_fetch("sc.$h"))) { |
226 | break; | 226 | self::debug("... site config for $h in APC cache"); |
227 | } | 227 | return $sconfig; |
228 | } | 228 | } elseif (file_exists(self::$config_path."/$h.txt")) { |
229 | 229 | self::debug("... found site config ($h.txt)"); | |
230 | // if we found site config, process it | 230 | $file_primary = self::$config_path."/$h.txt"; |
231 | if (isset($file_primary)) { | 231 | $matched_name = $h; |
232 | $config_lines = file($file_primary, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); | 232 | break; |
233 | if (!$config_lines || !is_array($config_lines)) return false; | 233 | } |
234 | $config = self::build_from_array($config_lines); | 234 | } |
235 | // if APC caching is available and enabled, mark this for cache | 235 | |
236 | //$config->cache_in_apc = true; | 236 | // if we found site config, process it |
237 | $config->cache_key = $matched_name; | 237 | if (isset($file_primary)) { |
238 | 238 | $config_lines = file($file_primary, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); | |
239 | // if autodetec on failure is off (on by default) we do not need to look | 239 | if (!$config_lines || !is_array($config_lines)) return false; |
240 | // in secondary folder | 240 | $config = self::build_from_array($config_lines); |
241 | if (!$config->autodetect_on_failure()) { | 241 | // if APC caching is available and enabled, mark this for cache |
242 | self::debug('... autodetect on failure is disabled (no other site config files will be loaded)'); | 242 | //$config->cache_in_apc = true; |
243 | return $config; | 243 | $config->cache_key = $matched_name; |
244 | } | 244 | |
245 | } | 245 | // if autodetec on failure is off (on by default) we do not need to look |
246 | 246 | // in secondary folder | |
247 | // look for site config file in secondary folder | 247 | if (!$config->autodetect_on_failure()) { |
248 | if (isset(self::$config_path_fallback)) { | 248 | self::debug('... autodetect on failure is disabled (no other site config files will be loaded)'); |
249 | self::debug(". looking for site config for $host in secondary folder"); | 249 | return $config; |
250 | foreach ($try as $h) { | 250 | } |
251 | if (file_exists(self::$config_path_fallback."/$h.txt")) { | 251 | } |
252 | self::debug("... found site config in secondary folder ($h.txt)"); | 252 | |
253 | $file_secondary = self::$config_path_fallback."/$h.txt"; | 253 | // look for site config file in secondary folder |
254 | $matched_name = $h; | 254 | if (isset(self::$config_path_fallback)) { |
255 | break; | 255 | self::debug(". looking for site config for $host in secondary folder"); |
256 | } | 256 | foreach ($try as $h) { |
257 | } | 257 | if (file_exists(self::$config_path_fallback."/$h.txt")) { |
258 | if (!isset($file_secondary)) { | 258 | self::debug("... found site config in secondary folder ($h.txt)"); |
259 | self::debug("... no site config match in secondary folder"); | 259 | $file_secondary = self::$config_path_fallback."/$h.txt"; |
260 | } | 260 | $matched_name = $h; |
261 | } | 261 | break; |
262 | 262 | } | |
263 | // return false if no config file found | 263 | } |
264 | if (!isset($file_primary) && !isset($file_secondary)) { | 264 | if (!isset($file_secondary)) { |
265 | self::debug("... no site config match for $host"); | 265 | self::debug("... no site config match in secondary folder"); |
266 | return false; | 266 | } |
267 | } | 267 | } |
268 | 268 | ||
269 | // return primary config if secondary not found | 269 | // return false if no config file found |
270 | if (!isset($file_secondary) && isset($config)) { | 270 | if (!isset($file_primary) && !isset($file_secondary)) { |
271 | return $config; | 271 | self::debug("... no site config match for $host"); |
272 | } | 272 | return false; |
273 | 273 | } | |
274 | // process secondary config file | 274 | |
275 | $config_lines = file($file_secondary, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); | 275 | // return primary config if secondary not found |
276 | if (!$config_lines || !is_array($config_lines)) { | 276 | if (!isset($file_secondary) && isset($config)) { |
277 | // failed to process secondary | 277 | return $config; |
278 | if (isset($config)) { | 278 | } |
279 | // return primary config | 279 | |
280 | return $config; | 280 | // process secondary config file |
281 | } else { | 281 | $config_lines = file($file_secondary, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); |
282 | return false; | 282 | if (!$config_lines || !is_array($config_lines)) { |
283 | } | 283 | // failed to process secondary |
284 | } | 284 | if (isset($config)) { |
285 | 285 | // return primary config | |
286 | // merge with primary and return | 286 | return $config; |
287 | if (isset($config)) { | 287 | } else { |
288 | self::debug('. merging config files'); | 288 | return false; |
289 | $config->append(self::build_from_array($config_lines)); | 289 | } |
290 | return $config; | 290 | } |
291 | } else { | 291 | |
292 | // return just secondary | 292 | // merge with primary and return |
293 | $config = self::build_from_array($config_lines); | 293 | if (isset($config)) { |
294 | // if APC caching is available and enabled, mark this for cache | 294 | self::debug('. merging config files'); |
295 | //$config->cache_in_apc = true; | 295 | $config->append(self::build_from_array($config_lines)); |
296 | $config->cache_key = $matched_name; | 296 | return $config; |
297 | return $config; | 297 | } else { |
298 | } | 298 | // return just secondary |
299 | } | 299 | $config = self::build_from_array($config_lines); |
300 | 300 | // if APC caching is available and enabled, mark this for cache | |
301 | public static function build_from_array(array $lines) { | 301 | //$config->cache_in_apc = true; |
302 | $config = new SiteConfig(); | 302 | $config->cache_key = $matched_name; |
303 | foreach ($lines as $line) { | 303 | return $config; |
304 | $line = trim($line); | 304 | } |
305 | 305 | } | |
306 | // skip comments, empty lines | 306 | |
307 | if ($line == '' || $line[0] == '#') continue; | 307 | public static function build_from_array(array $lines) { |
308 | 308 | $config = new SiteConfig(); | |
309 | // get command | 309 | foreach ($lines as $line) { |
310 | $command = explode(':', $line, 2); | 310 | $line = trim($line); |
311 | // if there's no colon ':', skip this line | 311 | |
312 | if (count($command) != 2) continue; | 312 | // skip comments, empty lines |
313 | $val = trim($command[1]); | 313 | if ($line == '' || $line[0] == '#') continue; |
314 | $command = trim($command[0]); | 314 | |
315 | if ($command == '' || $val == '') continue; | 315 | // get command |
316 | 316 | $command = explode(':', $line, 2); | |
317 | // check for commands where we accept multiple statements | 317 | // if there's no colon ':', skip this line |
318 | if (in_array($command, array('title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'http_header', 'test_url', 'find_string', 'replace_string'))) { | 318 | if (count($command) != 2) continue; |
319 | array_push($config->$command, $val); | 319 | $val = trim($command[1]); |
320 | // check for single statement commands that evaluate to true or false | 320 | $command = trim($command[0]); |
321 | } elseif (in_array($command, array('tidy', 'prune', 'autodetect_on_failure'))) { | 321 | if ($command == '' || $val == '') continue; |
322 | $config->$command = ($val == 'yes'); | 322 | |
323 | // check for single statement commands stored as strings | 323 | // check for commands where we accept multiple statements |
324 | } elseif (in_array($command, array('parser'))) { | 324 | if (in_array($command, array('title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'http_header', 'test_url', 'find_string', 'replace_string'))) { |
325 | $config->$command = $val; | 325 | array_push($config->$command, $val); |
326 | // check for replace_string(find): replace | 326 | // check for single statement commands that evaluate to true or false |
327 | } elseif ((substr($command, -1) == ')') && preg_match('!^([a-z0-9_]+)\((.*?)\)$!i', $command, $match)) { | 327 | } elseif (in_array($command, array('tidy', 'prune', 'autodetect_on_failure'))) { |
328 | if (in_array($match[1], array('replace_string'))) { | 328 | $config->$command = ($val == 'yes'); |
329 | $command = $match[1]; | 329 | // check for single statement commands stored as strings |
330 | array_push($config->find_string, $match[2]); | 330 | } elseif (in_array($command, array('parser'))) { |
331 | array_push($config->$command, $val); | 331 | $config->$command = $val; |
332 | } | 332 | // check for replace_string(find): replace |
333 | } | 333 | } elseif ((substr($command, -1) == ')') && preg_match('!^([a-z0-9_]+)\((.*?)\)$!i', $command, $match)) { |
334 | } | 334 | if (in_array($match[1], array('replace_string'))) { |
335 | return $config; | 335 | $command = $match[1]; |
336 | } | 336 | array_push($config->find_string, $match[2]); |
337 | } | 337 | array_push($config->$command, $val); |
338 | ?> \ No newline at end of file | 338 | } |
339 | } | ||
340 | } | ||
341 | return $config; | ||
342 | } | ||
343 | } \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/feedwriter/FeedItem.php b/inc/3rdparty/libraries/feedwriter/FeedItem.php index 9373deeb..40786598 100644..100755 --- a/inc/3rdparty/libraries/feedwriter/FeedItem.php +++ b/inc/3rdparty/libraries/feedwriter/FeedItem.php | |||
@@ -1,7 +1,7 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Univarsel Feed Writer | 3 | * Univarsel Feed Writer |
4 | * | 4 | * |
5 | * FeedItem class - Used as feed element in FeedWriter class | 5 | * FeedItem class - Used as feed element in FeedWriter class |
6 | * | 6 | * |
7 | * @package UnivarselFeedWriter | 7 | * @package UnivarselFeedWriter |
@@ -10,176 +10,195 @@ | |||
10 | */ | 10 | */ |
11 | class FeedItem | 11 | class FeedItem |
12 | { | 12 | { |
13 | private $elements = array(); //Collection of feed elements | 13 | private $elements = array(); //Collection of feed elements |
14 | private $version; | 14 | private $version; |
15 | 15 | ||
16 | /** | 16 | /** |
17 | * Constructor | 17 | * Constructor |
18 | * | 18 | * |
19 | * @param contant (RSS1/RSS2/ATOM) RSS2 is default. | 19 | * @param contant (RSS1/RSS2/ATOM) RSS2 is default. |
20 | */ | 20 | */ |
21 | function __construct($version = RSS2) | 21 | function __construct($version = RSS2) |
22 | { | 22 | { |
23 | $this->version = $version; | 23 | $this->version = $version; |
24 | } | 24 | } |
25 | 25 | ||
26 | /** | 26 | /** |
27 | * Set element (overwrites existing elements with $elementName) | 27 | * Set element (overwrites existing elements with $elementName) |
28 | * | 28 | * |
29 | * @access public | 29 | * @access public |
30 | * @param srting The tag name of an element | 30 | * @param srting The tag name of an element |
31 | * @param srting The content of tag | 31 | * @param srting The content of tag |
32 | * @param array Attributes(if any) in 'attrName' => 'attrValue' format | 32 | * @param array Attributes(if any) in 'attrName' => 'attrValue' format |
33 | * @return void | 33 | * @return void |
34 | */ | 34 | */ |
35 | public function setElement($elementName, $content, $attributes = null) | 35 | public function setElement($elementName, $content, $attributes = null) |
36 | { | 36 | { |
37 | if (isset($this->elements[$elementName])) { | 37 | if (isset($this->elements[$elementName])) { |
38 | unset($this->elements[$elementName]); | 38 | unset($this->elements[$elementName]); |
39 | } | 39 | } |
40 | $this->addElement($elementName, $content, $attributes); | 40 | $this->addElement($elementName, $content, $attributes); |
41 | } | 41 | } |
42 | 42 | ||
43 | /** | 43 | /** |
44 | * Add an element to elements array | 44 | * Add an element to elements array |
45 | * | 45 | * |
46 | * @access public | 46 | * @access public |
47 | * @param srting The tag name of an element | 47 | * @param srting The tag name of an element |
48 | * @param srting The content of tag | 48 | * @param srting The content of tag |
49 | * @param array Attributes(if any) in 'attrName' => 'attrValue' format | 49 | * @param array Attributes(if any) in 'attrName' => 'attrValue' format |
50 | * @return void | 50 | * @return void |
51 | */ | 51 | */ |
52 | public function addElement($elementName, $content, $attributes = null) | 52 | public function addElement($elementName, $content, $attributes = null) |
53 | { | 53 | { |
54 | $i = 0; | 54 | $i = 0; |
55 | if (isset($this->elements[$elementName])) { | 55 | if (isset($this->elements[$elementName])) { |
56 | $i = count($this->elements[$elementName]); | 56 | $i = count($this->elements[$elementName]); |
57 | } else { | 57 | } else { |
58 | $this->elements[$elementName] = array(); | 58 | $this->elements[$elementName] = array(); |
59 | } | 59 | } |
60 | $this->elements[$elementName][$i]['name'] = $elementName; | 60 | $this->elements[$elementName][$i]['name'] = $elementName; |
61 | $this->elements[$elementName][$i]['content'] = $content; | 61 | $this->elements[$elementName][$i]['content'] = $content; |
62 | $this->elements[$elementName][$i]['attributes'] = $attributes; | 62 | $this->elements[$elementName][$i]['attributes'] = $attributes; |
63 | } | 63 | } |
64 | 64 | ||
65 | /** | 65 | /** |
66 | * Set multiple feed elements from an array. | 66 | * Set multiple feed elements from an array. |
67 | * Elements which have attributes cannot be added by this method | 67 | * Elements which have attributes cannot be added by this method |
68 | * | 68 | * |
69 | * @access public | 69 | * @access public |
70 | * @param array array of elements in 'tagName' => 'tagContent' format. | 70 | * @param array array of elements in 'tagName' => 'tagContent' format. |
71 | * @return void | 71 | * @return void |
72 | */ | 72 | */ |
73 | public function addElementArray($elementArray) | 73 | public function addElementArray($elementArray) |
74 | { | 74 | { |
75 | if(! is_array($elementArray)) return; | 75 | if(! is_array($elementArray)) return; |
76 | foreach ($elementArray as $elementName => $content) | 76 | foreach ($elementArray as $elementName => $content) |
77 | { | 77 | { |
78 | $this->addElement($elementName, $content); | 78 | $this->addElement($elementName, $content); |
79 | } | 79 | } |
80 | } | 80 | } |
81 | 81 | ||
82 | /** | 82 | /** |
83 | * Return the collection of elements in this feed item | 83 | * Return the collection of elements in this feed item |
84 | * | 84 | * |
85 | * @access public | 85 | * @access public |
86 | * @return array | 86 | * @return array |
87 | */ | 87 | */ |
88 | public function getElements() | 88 | public function getElements() |
89 | { | 89 | { |
90 | return $this->elements; | 90 | return $this->elements; |
91 | } | 91 | } |
92 | 92 | ||
93 | // Wrapper functions ------------------------------------------------------ | 93 | // Wrapper functions ------------------------------------------------------ |
94 | 94 | ||
95 | /** | 95 | /** |
96 | * Set the 'dscription' element of feed item | 96 | * Set the 'dscription' element of feed item |
97 | * | 97 | * |
98 | * @access public | 98 | * @access public |
99 | * @param string The content of 'description' element | 99 | * @param string The content of 'description' element |
100 | * @return void | 100 | * @return void |
101 | */ | 101 | */ |
102 | public function setDescription($description) | 102 | public function setDescription($description) |
103 | { | 103 | { |
104 | $tag = 'description'; | 104 | $tag = ($this->version == ATOM)? 'summary' : 'description'; |
105 | $this->setElement($tag, $description); | 105 | $this->setElement($tag, $description); |
106 | } | 106 | } |
107 | 107 | ||
108 | /** | 108 | /** |
109 | * @desc Set the 'title' element of feed item | 109 | * @desc Set the 'title' element of feed item |
110 | * @access public | 110 | * @access public |
111 | * @param string The content of 'title' element | 111 | * @param string The content of 'title' element |
112 | * @return void | 112 | * @return void |
113 | */ | 113 | */ |
114 | public function setTitle($title) | 114 | public function setTitle($title) |
115 | { | 115 | { |
116 | $this->setElement('title', $title); | 116 | $this->setElement('title', $title); |
117 | } | 117 | } |
118 | 118 | ||
119 | /** | 119 | /** |
120 | * Set the 'date' element of feed item | 120 | * Set the 'date' element of feed item |
121 | * | 121 | * |
122 | * @access public | 122 | * @access public |
123 | * @param string The content of 'date' element | 123 | * @param string The content of 'date' element |
124 | * @return void | 124 | * @return void |
125 | */ | 125 | */ |
126 | public function setDate($date) | 126 | public function setDate($date) |
127 | { | 127 | { |
128 | if(! is_numeric($date)) | 128 | if(! is_numeric($date)) |
129 | { | 129 | { |
130 | $date = strtotime($date); | 130 | $date = strtotime($date); |
131 | } | 131 | } |
132 | 132 | ||
133 | if($this->version == RSS2) | 133 | if($this->version == ATOM) |
134 | { | 134 | { |
135 | $tag = 'pubDate'; | 135 | $tag = 'updated'; |
136 | $value = date(DATE_RSS, $date); | 136 | $value = date(DATE_ATOM, $date); |
137 | } | 137 | } |
138 | else | 138 | elseif($this->version == RSS2) |
139 | { | 139 | { |
140 | $tag = 'dc:date'; | 140 | $tag = 'pubDate'; |
141 | $value = date("Y-m-d", $date); | 141 | $value = date(DATE_RSS, $date); |
142 | } | 142 | } |
143 | 143 | else | |
144 | $this->setElement($tag, $value); | 144 | { |
145 | } | 145 | $tag = 'dc:date'; |
146 | 146 | $value = date("Y-m-d", $date); | |
147 | /** | 147 | } |
148 | * Set the 'link' element of feed item | 148 | |
149 | * | 149 | $this->setElement($tag, $value); |
150 | * @access public | 150 | } |
151 | * @param string The content of 'link' element | 151 | |
152 | * @return void | 152 | /** |
153 | */ | 153 | * Set the 'link' element of feed item |
154 | public function setLink($link) | 154 | * |
155 | { | 155 | * @access public |
156 | if($this->version == RSS2 || $this->version == RSS1) | 156 | * @param string The content of 'link' element |
157 | { | 157 | * @return void |
158 | $this->setElement('link', $link); | 158 | */ |
159 | } | 159 | public function setLink($link) |
160 | else | 160 | { |
161 | { | 161 | if($this->version == RSS2 || $this->version == RSS1) |
162 | $this->setElement('link','',array('href'=>$link)); | 162 | { |
163 | $this->setElement('id', FeedWriter::uuid($link,'urn:uuid:')); | 163 | $this->setElement('link', $link); |
164 | } | 164 | $this->setElement('guid', $link); |
165 | 165 | } | |
166 | } | 166 | else |
167 | 167 | { | |
168 | /** | 168 | $this->setElement('link','',array('href'=>$link)); |
169 | * Set the 'encloser' element of feed item | 169 | $this->setElement('id', FeedWriter::uuid($link,'urn:uuid:')); |
170 | * For RSS 2.0 only | 170 | } |
171 | * | 171 | |
172 | * @access public | 172 | } |
173 | * @param string The url attribute of encloser tag | 173 | |
174 | * @param string The length attribute of encloser tag | 174 | /** |
175 | * @param string The type attribute of encloser tag | 175 | * Set the 'source' element of feed item |
176 | * @return void | 176 | * |
177 | */ | 177 | * @access public |
178 | public function setEncloser($url, $length, $type) | 178 | * @param string The content of 'source' element |
179 | { | 179 | * @return void |
180 | $attributes = array('url'=>$url, 'length'=>$length, 'type'=>$type); | 180 | */ |
181 | $this->setElement('enclosure','',$attributes); | 181 | public function setSource($link) |
182 | } | 182 | { |
183 | 183 | $attributes = array('url'=>$link); | |
184 | $this->setElement('source', "wallabag",$attributes); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Set the 'encloser' element of feed item | ||
189 | * For RSS 2.0 only | ||
190 | * | ||
191 | * @access public | ||
192 | * @param string The url attribute of encloser tag | ||
193 | * @param string The length attribute of encloser tag | ||
194 | * @param string The type attribute of encloser tag | ||
195 | * @return void | ||
196 | */ | ||
197 | public function setEncloser($url, $length, $type) | ||
198 | { | ||
199 | $attributes = array('url'=>$url, 'length'=>$length, 'type'=>$type); | ||
200 | $this->setElement('enclosure','',$attributes); | ||
201 | } | ||
202 | |||
184 | } // end of class FeedItem | 203 | } // end of class FeedItem |
185 | ?> | 204 | ?> \ No newline at end of file |
diff --git a/inc/3rdparty/libraries/feedwriter/FeedWriter.php b/inc/3rdparty/libraries/feedwriter/FeedWriter.php index adb2526c..9446cddf 100644..100755 --- a/inc/3rdparty/libraries/feedwriter/FeedWriter.php +++ b/inc/3rdparty/libraries/feedwriter/FeedWriter.php | |||
@@ -2,6 +2,7 @@ | |||
2 | define('RSS2', 1, true); | 2 | define('RSS2', 1, true); |
3 | define('JSON', 2, true); | 3 | define('JSON', 2, true); |
4 | define('JSONP', 3, true); | 4 | define('JSONP', 3, true); |
5 | define('ATOM', 4, true); | ||
5 | 6 | ||
6 | /** | 7 | /** |
7 | * Univarsel Feed Writer class | 8 | * Univarsel Feed Writer class |
@@ -9,433 +10,444 @@ define('JSONP', 3, true); | |||
9 | * Genarate RSS2 or JSON (original: RSS 1.0, RSS2.0 and ATOM Feed) | 10 | * Genarate RSS2 or JSON (original: RSS 1.0, RSS2.0 and ATOM Feed) |
10 | * | 11 | * |
11 | * Modified for FiveFilters.org's Full-Text RSS project | 12 | * Modified for FiveFilters.org's Full-Text RSS project |
12 | * to allow for inclusion of hubs, JSON output. | 13 | * to allow for inclusion of hubs, JSON output. |
13 | * Stripped RSS1 and ATOM support. | 14 | * Stripped RSS1 and ATOM support. |
14 | * | 15 | * |
15 | * @package UnivarselFeedWriter | 16 | * @package UnivarselFeedWriter |
16 | * @author Anis uddin Ahmad <anisniit@gmail.com> | 17 | * @author Anis uddin Ahmad <anisniit@gmail.com> |
17 | * @link http://www.ajaxray.com/projects/rss | 18 | * @link http://www.ajaxray.com/projects/rss |
18 | */ | 19 | */ |
19 | class FeedWriter | 20 | class FeedWriter |
20 | { | 21 | { |
21 | private $self = null; // self URL - http://feed2.w3.org/docs/warning/MissingAtomSelfLink.html | 22 | private $self = null; // self URL - http://feed2.w3.org/docs/warning/MissingAtomSelfLink.html |
22 | private $hubs = array(); // PubSubHubbub hubs | 23 | private $hubs = array(); // PubSubHubbub hubs |
23 | private $channels = array(); // Collection of channel elements | 24 | private $channels = array(); // Collection of channel elements |
24 | private $items = array(); // Collection of items as object of FeedItem class. | 25 | private $items = array(); // Collection of items as object of FeedItem class. |
25 | private $data = array(); // Store some other version wise data | 26 | private $data = array(); // Store some other version wise data |
26 | private $CDATAEncoding = array(); // The tag names which have to encoded as CDATA | 27 | private $CDATAEncoding = array(); // The tag names which have to encoded as CDATA |
27 | private $xsl = null; // stylesheet to render RSS (used by Chrome) | 28 | private $xsl = null; // stylesheet to render RSS (used by Chrome) |
28 | private $json = null; // JSON object | 29 | private $json = null; // JSON object |
29 | 30 | ||
30 | private $version = null; | 31 | private $version = null; |
31 | 32 | ||
32 | /** | 33 | /** |
33 | * Constructor | 34 | * Constructor |
34 | * | 35 | * |
35 | * @param constant the version constant (RSS2 or JSON). | 36 | * @param constant the version constant (RSS2 or JSON). |
36 | */ | 37 | */ |
37 | function __construct($version = RSS2) | 38 | function __construct($version = RSS2) |
38 | { | 39 | { |
39 | $this->version = $version; | 40 | $this->version = $version; |
40 | 41 | ||
41 | // Setting default value for assential channel elements | 42 | // Setting default value for assential channel elements |
42 | $this->channels['title'] = $version . ' Feed'; | 43 | $this->channels['title'] = $version . ' Feed'; |
43 | $this->channels['link'] = 'http://www.ajaxray.com/blog'; | 44 | $this->channels['link'] = 'http://www.ajaxray.com/blog'; |
44 | 45 | ||
45 | //Tag names to encode in CDATA | 46 | //Tag names to encode in CDATA |
46 | $this->CDATAEncoding = array('description', 'content:encoded', 'content', 'subtitle', 'summary'); | 47 | $this->CDATAEncoding = array('description', 'content:encoded', 'content', 'subtitle', 'summary'); |
47 | } | 48 | } |
48 | 49 | ||
49 | public function setFormat($format) { | 50 | public function setFormat($format) { |
50 | $this->version = $format; | 51 | $this->version = $format; |
51 | } | 52 | } |
52 | 53 | ||
53 | // Start # public functions --------------------------------------------- | 54 | // Start # public functions --------------------------------------------- |
54 | 55 | ||
55 | /** | 56 | /** |
56 | * Set a channel element | 57 | * Set a channel element |
57 | * @access public | 58 | * @access public |
58 | * @param srting name of the channel tag | 59 | * @param srting name of the channel tag |
59 | * @param string content of the channel tag | 60 | * @param string content of the channel tag |
60 | * @return void | 61 | * @return void |
61 | */ | 62 | */ |
62 | public function setChannelElement($elementName, $content) | 63 | public function setChannelElement($elementName, $content) |
63 | { | 64 | { |
64 | $this->channels[$elementName] = $content ; | 65 | $this->channels[$elementName] = $content ; |
65 | } | 66 | } |
66 | 67 | ||
67 | /** | 68 | /** |
68 | * Set multiple channel elements from an array. Array elements | 69 | * Set multiple channel elements from an array. Array elements |
69 | * should be 'channelName' => 'channelContent' format. | 70 | * should be 'channelName' => 'channelContent' format. |
70 | * | 71 | * |
71 | * @access public | 72 | * @access public |
72 | * @param array array of channels | 73 | * @param array array of channels |
73 | * @return void | 74 | * @return void |
74 | */ | 75 | */ |
75 | public function setChannelElementsFromArray($elementArray) | 76 | public function setChannelElementsFromArray($elementArray) |
76 | { | 77 | { |
77 | if(! is_array($elementArray)) return; | 78 | if(! is_array($elementArray)) return; |
78 | foreach ($elementArray as $elementName => $content) | 79 | foreach ($elementArray as $elementName => $content) |
79 | { | 80 | { |
80 | $this->setChannelElement($elementName, $content); | 81 | $this->setChannelElement($elementName, $content); |
81 | } | 82 | } |
82 | } | 83 | } |
83 | 84 | ||
84 | /** | 85 | /** |
85 | * Genarate the actual RSS/JSON file | 86 | * Genarate the actual RSS/JSON file |
86 | * | 87 | * |
87 | * @access public | 88 | * @access public |
88 | * @return void | 89 | * @return void |
89 | */ | 90 | */ |
90 | public function genarateFeed() | 91 | public function genarateFeed($withHeaders = true) |
91 | { | 92 | { |
92 | if ($this->version == RSS2) { | 93 | if ($withHeaders) { |
93 | header('Content-type: text/xml; charset=UTF-8'); | 94 | if ($this->version == RSS2) { |
94 | // this line prevents Chrome 20 from prompting download | 95 | header('Content-type: text/xml; charset=UTF-8'); |
95 | // used by Google: https://news.google.com/news/feeds?ned=us&topic=b&output=rss | 96 | // this line prevents Chrome 20 from prompting download |
96 | header('X-content-type-options: nosniff'); | 97 | // used by Google: https://news.google.com/news/feeds?ned=us&topic=b&output=rss |
97 | } elseif ($this->version == JSON) { | 98 | header('X-content-type-options: nosniff'); |
98 | header('Content-type: application/json; charset=UTF-8'); | 99 | } elseif ($this->version == JSON) { |
99 | $this->json = new stdClass(); | 100 | header('Content-type: application/json; charset=UTF-8'); |
100 | } elseif ($this->version == JSONP) { | 101 | } elseif ($this->version == JSONP) { |
101 | header('Content-type: application/javascript; charset=UTF-8'); | 102 | header('Content-type: application/javascript; charset=UTF-8'); |
102 | $this->json = new stdClass(); | 103 | } |
103 | } | 104 | } |
104 | $this->printHead(); | 105 | |
105 | $this->printChannels(); | 106 | if ($this->version == JSON || $this->version == JSONP) { |
106 | $this->printItems(); | 107 | $this->json = new stdClass(); |
107 | $this->printTale(); | 108 | } |
108 | if ($this->version == JSON || $this->version == JSONP) { | 109 | |
109 | echo json_encode($this->json); | 110 | |
110 | } | 111 | $this->printHead(); |
111 | } | 112 | $this->printChannels(); |
112 | 113 | $this->printItems(); | |
113 | /** | 114 | $this->printTale(); |
114 | * Create a new FeedItem. | 115 | if ($this->version == JSON || $this->version == JSONP) { |
115 | * | 116 | echo json_encode($this->json); |
116 | * @access public | 117 | } |
117 | * @return object instance of FeedItem class | 118 | } |
118 | */ | 119 | |
119 | public function createNewItem() | 120 | public function &getItems() |
120 | { | 121 | { |
121 | $Item = new FeedItem($this->version); | 122 | return $this->items; |
122 | return $Item; | 123 | } |
123 | } | 124 | |
124 | 125 | /** | |
125 | /** | 126 | * Create a new FeedItem. |
126 | * Add a FeedItem to the main class | 127 | * |
127 | * | 128 | * @access public |
128 | * @access public | 129 | * @return object instance of FeedItem class |
129 | * @param object instance of FeedItem class | 130 | */ |
130 | * @return void | 131 | public function createNewItem() |
131 | */ | 132 | { |
132 | public function addItem($feedItem) | 133 | $Item = new FeedItem($this->version); |
133 | { | 134 | return $Item; |
134 | $this->items[] = $feedItem; | 135 | } |
135 | } | 136 | |
136 | 137 | /** | |
137 | // Wrapper functions ------------------------------------------------------------------- | 138 | * Add a FeedItem to the main class |
138 | 139 | * | |
139 | /** | 140 | * @access public |
140 | * Set the 'title' channel element | 141 | * @param object instance of FeedItem class |
141 | * | 142 | * @return void |
142 | * @access public | 143 | */ |
143 | * @param srting value of 'title' channel tag | 144 | public function addItem($feedItem) |
144 | * @return void | 145 | { |
145 | */ | 146 | $this->items[] = $feedItem; |
146 | public function setTitle($title) | 147 | } |
147 | { | 148 | |
148 | $this->setChannelElement('title', $title); | 149 | // Wrapper functions ------------------------------------------------------------------- |
149 | } | 150 | |
150 | 151 | /** | |
151 | /** | 152 | * Set the 'title' channel element |
152 | * Add a hub to the channel element | 153 | * |
153 | * | 154 | * @access public |
154 | * @access public | 155 | * @param srting value of 'title' channel tag |
155 | * @param string URL | 156 | * @return void |
156 | * @return void | 157 | */ |
157 | */ | 158 | public function setTitle($title) |
158 | public function addHub($hub) | 159 | { |
159 | { | 160 | $this->setChannelElement('title', $title); |
160 | $this->hubs[] = $hub; | 161 | } |
161 | } | 162 | |
162 | 163 | /** | |
163 | /** | 164 | * Add a hub to the channel element |
164 | * Set XSL URL | 165 | * |
165 | * | 166 | * @access public |
166 | * @access public | 167 | * @param string URL |
167 | * @param string URL | 168 | * @return void |
168 | * @return void | 169 | */ |
169 | */ | 170 | public function addHub($hub) |
170 | public function setXsl($xsl) | 171 | { |
171 | { | 172 | $this->hubs[] = $hub; |
172 | $this->xsl = $xsl; | 173 | } |
173 | } | 174 | |
174 | 175 | /** | |
175 | /** | 176 | * Set XSL URL |
176 | * Set self URL | 177 | * |
177 | * | 178 | * @access public |
178 | * @access public | 179 | * @param string URL |
179 | * @param string URL | 180 | * @return void |
180 | * @return void | 181 | */ |
181 | */ | 182 | public function setXsl($xsl) |
182 | public function setSelf($self) | 183 | { |
183 | { | 184 | $this->xsl = $xsl; |
184 | $this->self = $self; | 185 | } |
185 | } | 186 | |
186 | 187 | /** | |
187 | /** | 188 | * Set self URL |
188 | * Set the 'description' channel element | 189 | * |
189 | * | 190 | * @access public |
190 | * @access public | 191 | * @param string URL |
191 | * @param srting value of 'description' channel tag | 192 | * @return void |
192 | * @return void | 193 | */ |
193 | */ | 194 | public function setSelf($self) |
194 | public function setDescription($desciption) | 195 | { |
195 | { | 196 | $this->self = $self; |
196 | $tag = ($this->version == ATOM)? 'subtitle' : 'description'; | 197 | } |
197 | $this->setChannelElement($tag, $desciption); | 198 | |
198 | } | 199 | /** |
199 | 200 | * Set the 'description' channel element | |
200 | /** | 201 | * |
201 | * Set the 'link' channel element | 202 | * @access public |
202 | * | 203 | * @param srting value of 'description' channel tag |
203 | * @access public | 204 | * @return void |
204 | * @param srting value of 'link' channel tag | 205 | */ |
205 | * @return void | 206 | public function setDescription($description) |
206 | */ | 207 | { |
207 | public function setLink($link) | 208 | $tag = ($this->version == ATOM)? 'subtitle' : 'description'; |
208 | { | 209 | $this->setChannelElement($tag, $description); |
209 | $this->setChannelElement('link', $link); | 210 | } |
210 | } | 211 | |
211 | 212 | /** | |
212 | /** | 213 | * Set the 'link' channel element |
213 | * Set the 'image' channel element | 214 | * |
214 | * | 215 | * @access public |
215 | * @access public | 216 | * @param srting value of 'link' channel tag |
216 | * @param srting title of image | 217 | * @return void |
217 | * @param srting link url of the imahe | 218 | */ |
218 | * @param srting path url of the image | 219 | public function setLink($link) |
219 | * @return void | 220 | { |
220 | */ | 221 | $this->setChannelElement('link', $link); |
221 | public function setImage($title, $link, $url) | 222 | } |
222 | { | 223 | |
223 | $this->setChannelElement('image', array('title'=>$title, 'link'=>$link, 'url'=>$url)); | 224 | /** |
224 | } | 225 | * Set the 'image' channel element |
225 | 226 | * | |
226 | // End # public functions ---------------------------------------------- | 227 | * @access public |
227 | 228 | * @param srting title of image | |
228 | // Start # private functions ---------------------------------------------- | 229 | * @param srting link url of the imahe |
229 | 230 | * @param srting path url of the image | |
230 | /** | 231 | * @return void |
231 | * Prints the xml and rss namespace | 232 | */ |
232 | * | 233 | public function setImage($title, $link, $url) |
233 | * @access private | 234 | { |
234 | * @return void | 235 | $this->setChannelElement('image', array('title'=>$title, 'link'=>$link, 'url'=>$url)); |
235 | */ | 236 | } |
236 | private function printHead() | 237 | |
237 | { | 238 | // End # public functions ---------------------------------------------- |
238 | if ($this->version == RSS2) | 239 | |
239 | { | 240 | // Start # private functions ---------------------------------------------- |
240 | $out = '<?xml version="1.0" encoding="utf-8"?>'."\n"; | 241 | |
241 | if ($this->xsl) $out .= '<?xml-stylesheet type="text/xsl" href="'.htmlspecialchars($this->xsl).'"?>' . PHP_EOL; | 242 | /** |
242 | $out .= '<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">' . PHP_EOL; | 243 | * Prints the xml and rss namespace |
243 | echo $out; | 244 | * |
244 | } | 245 | * @access private |
245 | elseif ($this->version == JSON || $this->version == JSONP) | 246 | * @return void |
246 | { | 247 | */ |
247 | $this->json->rss = array('@attributes' => array('version' => '2.0')); | 248 | private function printHead() |
248 | } | 249 | { |
249 | } | 250 | if ($this->version == RSS2) |
250 | 251 | { | |
251 | /** | 252 | $out = '<?xml version="1.0" encoding="utf-8"?>'."\n"; |
252 | * Closes the open tags at the end of file | 253 | if ($this->xsl) $out .= '<?xml-stylesheet type="text/xsl" href="'.htmlspecialchars($this->xsl).'"?>' . PHP_EOL; |
253 | * | 254 | $out .= '<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">' . PHP_EOL; |
254 | * @access private | 255 | echo $out; |
255 | * @return void | 256 | } |
256 | */ | 257 | elseif ($this->version == JSON || $this->version == JSONP) |
257 | private function printTale() | 258 | { |
258 | { | 259 | $this->json->rss = array('@attributes' => array('version' => '2.0')); |
259 | if ($this->version == RSS2) | 260 | } |
260 | { | 261 | } |
261 | echo '</channel>',PHP_EOL,'</rss>'; | 262 | |
262 | } | 263 | /** |
263 | // do nothing for JSON | 264 | * Closes the open tags at the end of file |
264 | } | 265 | * |
265 | 266 | * @access private | |
266 | /** | 267 | * @return void |
267 | * Creates a single node as xml format | 268 | */ |
268 | * | 269 | private function printTale() |
269 | * @access private | 270 | { |
270 | * @param string name of the tag | 271 | if ($this->version == RSS2) |
271 | * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format | 272 | { |
272 | * @param array Attributes(if any) in 'attrName' => 'attrValue' format | 273 | echo '</channel>',PHP_EOL,'</rss>'; |
273 | * @return string formatted xml tag | 274 | } |
274 | */ | 275 | // do nothing for JSON |
275 | private function makeNode($tagName, $tagContent, $attributes = null) | 276 | } |
276 | { | 277 | |
277 | if ($this->version == RSS2) | 278 | /** |
278 | { | 279 | * Creates a single node as xml format |
279 | $nodeText = ''; | 280 | * |
280 | $attrText = ''; | 281 | * @access private |
281 | if (is_array($attributes)) | 282 | * @param string name of the tag |
282 | { | 283 | * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format |
283 | foreach ($attributes as $key => $value) | 284 | * @param array Attributes(if any) in 'attrName' => 'attrValue' format |
284 | { | 285 | * @return string formatted xml tag |
285 | $attrText .= " $key=\"$value\" "; | 286 | */ |
286 | } | 287 | private function makeNode($tagName, $tagContent, $attributes = null) |
287 | } | 288 | { |
288 | $nodeText .= "<{$tagName}{$attrText}>"; | 289 | if ($this->version == RSS2) |
289 | if (is_array($tagContent)) | 290 | { |
290 | { | 291 | $nodeText = ''; |
291 | foreach ($tagContent as $key => $value) | 292 | $attrText = ''; |
292 | { | 293 | if (is_array($attributes)) |
293 | $nodeText .= $this->makeNode($key, $value); | 294 | { |
294 | } | 295 | foreach ($attributes as $key => $value) |
295 | } | 296 | { |
296 | else | 297 | $attrText .= " $key=\"$value\" "; |
297 | { | 298 | } |
298 | //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? $tagContent : htmlentities($tagContent); | 299 | } |
299 | $nodeText .= htmlspecialchars($tagContent); | 300 | $nodeText .= "<{$tagName}{$attrText}>"; |
300 | } | 301 | if (is_array($tagContent)) |
301 | //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? "]]></$tagName>" : "</$tagName>"; | 302 | { |
302 | $nodeText .= "</$tagName>"; | 303 | foreach ($tagContent as $key => $value) |
303 | return $nodeText . PHP_EOL; | 304 | { |
304 | } | 305 | $nodeText .= $this->makeNode($key, $value); |
305 | elseif ($this->version == JSON || $this->version == JSONP) | 306 | } |
306 | { | 307 | } |
307 | $tagName = (string)$tagName; | 308 | else |
308 | $tagName = strtr($tagName, ':', '_'); | 309 | { |
309 | $node = null; | 310 | //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? $tagContent : htmlentities($tagContent); |
310 | if (!$tagContent && is_array($attributes) && count($attributes)) | 311 | $nodeText .= htmlspecialchars($tagContent); |
311 | { | 312 | } |
312 | $node = array('@attributes' => $this->json_keys($attributes)); | 313 | //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? "]]></$tagName>" : "</$tagName>"; |
313 | } else { | 314 | $nodeText .= "</$tagName>"; |
314 | if (is_array($tagContent)) { | 315 | return $nodeText . PHP_EOL; |
315 | $node = $this->json_keys($tagContent); | 316 | } |
316 | } else { | 317 | elseif ($this->version == JSON || $this->version == JSONP) |
317 | $node = $tagContent; | 318 | { |
318 | } | 319 | $tagName = (string)$tagName; |
319 | } | 320 | $tagName = strtr($tagName, ':', '_'); |
320 | return $node; | 321 | $node = null; |
321 | } | 322 | if (!$tagContent && is_array($attributes) && count($attributes)) |
322 | return ''; // should not get here | 323 | { |
323 | } | 324 | $node = array('@attributes' => $this->json_keys($attributes)); |
324 | 325 | } else { | |
325 | private function json_keys(array $array) { | 326 | if (is_array($tagContent)) { |
326 | $new = array(); | 327 | $node = $this->json_keys($tagContent); |
327 | foreach ($array as $key => $val) { | 328 | } else { |
328 | if (is_string($key)) $key = strtr($key, ':', '_'); | 329 | $node = $tagContent; |
329 | if (is_array($val)) { | 330 | } |
330 | $new[$key] = $this->json_keys($val); | 331 | } |
331 | } else { | 332 | return $node; |
332 | $new[$key] = $val; | 333 | } |
333 | } | 334 | return ''; // should not get here |
334 | } | 335 | } |
335 | return $new; | 336 | |
336 | } | 337 | private function json_keys(array $array) { |
337 | 338 | $new = array(); | |
338 | /** | 339 | foreach ($array as $key => $val) { |
339 | * @desc Print channels | 340 | if (is_string($key)) $key = strtr($key, ':', '_'); |
340 | * @access private | 341 | if (is_array($val)) { |
341 | * @return void | 342 | $new[$key] = $this->json_keys($val); |
342 | */ | 343 | } else { |
343 | private function printChannels() | 344 | $new[$key] = $val; |
344 | { | 345 | } |
345 | //Start channel tag | 346 | } |
346 | if ($this->version == RSS2) { | 347 | return $new; |
347 | echo '<channel>' . PHP_EOL; | 348 | } |
348 | // add hubs | 349 | |
349 | foreach ($this->hubs as $hub) { | 350 | /** |
350 | //echo $this->makeNode('link', '', array('rel'=>'hub', 'href'=>$hub, 'xmlns'=>'http://www.w3.org/2005/Atom')); | 351 | * @desc Print channels |
351 | echo '<link rel="hub" href="'.htmlspecialchars($hub).'" xmlns="http://www.w3.org/2005/Atom" />' . PHP_EOL; | 352 | * @access private |
352 | } | 353 | * @return void |
353 | // add self | 354 | */ |
354 | if (isset($this->self)) { | 355 | private function printChannels() |
355 | //echo $this->makeNode('link', '', array('rel'=>'self', 'href'=>$this->self, 'xmlns'=>'http://www.w3.org/2005/Atom')); | 356 | { |
356 | echo '<link rel="self" href="'.htmlspecialchars($this->self).'" xmlns="http://www.w3.org/2005/Atom" />' . PHP_EOL; | 357 | //Start channel tag |
357 | } | 358 | if ($this->version == RSS2) { |
358 | //Print Items of channel | 359 | echo '<channel>' . PHP_EOL; |
359 | foreach ($this->channels as $key => $value) | 360 | // add hubs |
360 | { | 361 | foreach ($this->hubs as $hub) { |
361 | echo $this->makeNode($key, $value); | 362 | //echo $this->makeNode('link', '', array('rel'=>'hub', 'href'=>$hub, 'xmlns'=>'http://www.w3.org/2005/Atom')); |
362 | } | 363 | echo '<link rel="hub" href="'.htmlspecialchars($hub).'" xmlns="http://www.w3.org/2005/Atom" />' . PHP_EOL; |
363 | } elseif ($this->version == JSON || $this->version == JSONP) { | 364 | } |
364 | $this->json->rss['channel'] = (object)$this->json_keys($this->channels); | 365 | // add self |
365 | } | 366 | if (isset($this->self)) { |
366 | } | 367 | //echo $this->makeNode('link', '', array('rel'=>'self', 'href'=>$this->self, 'xmlns'=>'http://www.w3.org/2005/Atom')); |
367 | 368 | echo '<link rel="self" href="'.htmlspecialchars($this->self).'" xmlns="http://www.w3.org/2005/Atom" />' . PHP_EOL; | |
368 | /** | 369 | } |
369 | * Prints formatted feed items | 370 | //Print Items of channel |
370 | * | 371 | foreach ($this->channels as $key => $value) |
371 | * @access private | 372 | { |
372 | * @return void | 373 | echo $this->makeNode($key, $value); |
373 | */ | 374 | } |
374 | private function printItems() | 375 | } elseif ($this->version == JSON || $this->version == JSONP) { |
375 | { | 376 | $this->json->rss['channel'] = (object)$this->json_keys($this->channels); |
376 | foreach ($this->items as $item) { | 377 | } |
377 | $itemElements = $item->getElements(); | 378 | } |
378 | 379 | ||
379 | echo $this->startItem(); | 380 | /** |
380 | 381 | * Prints formatted feed items | |
381 | if ($this->version == JSON || $this->version == JSONP) { | 382 | * |
382 | $json_item = array(); | 383 | * @access private |
383 | } | 384 | * @return void |
384 | 385 | */ | |
385 | foreach ($itemElements as $thisElement) { | 386 | private function printItems() |
386 | foreach ($thisElement as $instance) { | 387 | { |
387 | if ($this->version == RSS2) { | 388 | foreach ($this->items as $item) { |
388 | echo $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); | 389 | $itemElements = $item->getElements(); |
389 | } elseif ($this->version == JSON || $this->version == JSONP) { | 390 | |
390 | $_json_node = $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); | 391 | echo $this->startItem(); |
391 | if (count($thisElement) > 1) { | 392 | |
392 | $json_item[strtr($instance['name'], ':', '_')][] = $_json_node; | 393 | if ($this->version == JSON || $this->version == JSONP) { |
393 | } else { | 394 | $json_item = array(); |
394 | $json_item[strtr($instance['name'], ':', '_')] = $_json_node; | 395 | } |
395 | } | 396 | |
396 | } | 397 | foreach ($itemElements as $thisElement) { |
397 | } | 398 | foreach ($thisElement as $instance) { |
398 | } | 399 | if ($this->version == RSS2) { |
399 | echo $this->endItem(); | 400 | echo $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); |
400 | if ($this->version == JSON || $this->version == JSONP) { | 401 | } elseif ($this->version == JSON || $this->version == JSONP) { |
401 | if (count($this->items) > 1) { | 402 | $_json_node = $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); |
402 | $this->json->rss['channel']->item[] = $json_item; | 403 | if (count($thisElement) > 1) { |
403 | } else { | 404 | $json_item[strtr($instance['name'], ':', '_')][] = $_json_node; |
404 | $this->json->rss['channel']->item = $json_item; | 405 | } else { |
405 | } | 406 | $json_item[strtr($instance['name'], ':', '_')] = $_json_node; |
406 | } | 407 | } |
407 | } | 408 | } |
408 | } | 409 | } |
409 | 410 | } | |
410 | /** | 411 | echo $this->endItem(); |
411 | * Make the starting tag of channels | 412 | if ($this->version == JSON || $this->version == JSONP) { |
412 | * | 413 | if (count($this->items) > 1) { |
413 | * @access private | 414 | $this->json->rss['channel']->item[] = $json_item; |
414 | * @return void | 415 | } else { |
415 | */ | 416 | $this->json->rss['channel']->item = $json_item; |
416 | private function startItem() | 417 | } |
417 | { | 418 | } |
418 | if ($this->version == RSS2) | 419 | } |
419 | { | 420 | } |
420 | echo '<item>' . PHP_EOL; | 421 | |
421 | } | 422 | /** |
422 | // nothing for JSON | 423 | * Make the starting tag of channels |
423 | } | 424 | * |
424 | 425 | * @access private | |
425 | /** | 426 | * @return void |
426 | * Closes feed item tag | 427 | */ |
427 | * | 428 | private function startItem() |
428 | * @access private | 429 | { |
429 | * @return void | 430 | if ($this->version == RSS2) |
430 | */ | 431 | { |
431 | private function endItem() | 432 | echo '<item>' . PHP_EOL; |
432 | { | 433 | } |
433 | if ($this->version == RSS2) | 434 | // nothing for JSON |
434 | { | 435 | } |
435 | echo '</item>' . PHP_EOL; | 436 | |
436 | } | 437 | /** |
437 | // nothing for JSON | 438 | * Closes feed item tag |
438 | } | 439 | * |
439 | 440 | * @access private | |
440 | // End # private functions ---------------------------------------------- | 441 | * @return void |
442 | */ | ||
443 | private function endItem() | ||
444 | { | ||
445 | if ($this->version == RSS2) | ||
446 | { | ||
447 | echo '</item>' . PHP_EOL; | ||
448 | } | ||
449 | // nothing for JSON | ||
450 | } | ||
451 | |||
452 | // End # private functions ---------------------------------------------- | ||
441 | } \ No newline at end of file | 453 | } \ No newline at end of file |
diff --git a/inc/3rdparty/libraries/html5/TreeBuilder.php b/inc/3rdparty/libraries/html5/TreeBuilder.php index 2f5244f9..c4a48b21 100644 --- a/inc/3rdparty/libraries/html5/TreeBuilder.php +++ b/inc/3rdparty/libraries/html5/TreeBuilder.php | |||
@@ -134,6 +134,7 @@ class HTML5_TreeBuilder { | |||
134 | 134 | ||
135 | // Namespaces for foreign content | 135 | // Namespaces for foreign content |
136 | const NS_HTML = null; // to prevent DOM from requiring NS on everything | 136 | const NS_HTML = null; // to prevent DOM from requiring NS on everything |
137 | const NS_XHTML = 'http://www.w3.org/1999/xhtml'; | ||
137 | const NS_MATHML = 'http://www.w3.org/1998/Math/MathML'; | 138 | const NS_MATHML = 'http://www.w3.org/1998/Math/MathML'; |
138 | const NS_SVG = 'http://www.w3.org/2000/svg'; | 139 | const NS_SVG = 'http://www.w3.org/2000/svg'; |
139 | const NS_XLINK = 'http://www.w3.org/1999/xlink'; | 140 | const NS_XLINK = 'http://www.w3.org/1999/xlink'; |
@@ -3157,11 +3158,19 @@ class HTML5_TreeBuilder { | |||
3157 | } | 3158 | } |
3158 | 3159 | ||
3159 | private function insertElement($token, $append = true) { | 3160 | private function insertElement($token, $append = true) { |
3160 | $el = $this->dom->createElementNS(self::NS_HTML, $token['name']); | 3161 | //$el = $this->dom->createElementNS(self::NS_HTML, $token['name']); |
3162 | $namespaceURI = strpos($token['name'], ':') ? self::NS_XHTML : self::NS_HTML; | ||
3163 | $el = $this->dom->createElementNS($namespaceURI, $token['name']); | ||
3161 | 3164 | ||
3162 | if (!empty($token['attr'])) { | 3165 | if (!empty($token['attr'])) { |
3163 | foreach($token['attr'] as $attr) { | 3166 | foreach($token['attr'] as $attr) { |
3164 | if(!$el->hasAttribute($attr['name'])) { | 3167 | |
3168 | // mike@macgirvin.com 2011-11-17, check attribute name for | ||
3169 | // validity (ignoring extenders and combiners) as illegal chars in names | ||
3170 | // causes everything to abort | ||
3171 | |||
3172 | $valid = preg_match('/^[a-zA-Z\_\:]([\-a-zA-Z0-9\_\:\.]+$)/',$attr['name']); | ||
3173 | if($attr['name'] && (!$el->hasAttribute($attr['name'])) && ($valid)) { | ||
3165 | $el->setAttribute($attr['name'], $attr['value']); | 3174 | $el->setAttribute($attr['name'], $attr['value']); |
3166 | } | 3175 | } |
3167 | } | 3176 | } |
diff --git a/inc/3rdparty/libraries/humble-http-agent/CookieJar.php b/inc/3rdparty/libraries/humble-http-agent/CookieJar.php index 83e94f14..e4d5f495 100644 --- a/inc/3rdparty/libraries/humble-http-agent/CookieJar.php +++ b/inc/3rdparty/libraries/humble-http-agent/CookieJar.php | |||
@@ -1,404 +1,403 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Cookie Jar | 3 | * Cookie Jar |
4 | * | 4 | * |
5 | * PHP class for handling cookies, as defined by the Netscape spec: | 5 | * PHP class for handling cookies, as defined by the Netscape spec: |
6 | * <http://curl.haxx.se/rfc/cookie_spec.html> | 6 | * <http://curl.haxx.se/rfc/cookie_spec.html> |
7 | * | 7 | * |
8 | * This class should be used to handle cookies (storing cookies from HTTP response messages, and | 8 | * This class should be used to handle cookies (storing cookies from HTTP response messages, and |
9 | * sending out cookies in HTTP request messages). This has been adapted for FiveFilters.org | 9 | * sending out cookies in HTTP request messages). This has been adapted for FiveFilters.org |
10 | * from the original version used in HTTP Navigator. See http://www.keyvan.net/code/http-navigator/ | 10 | * from the original version used in HTTP Navigator. See http://www.keyvan.net/code/http-navigator/ |
11 | * | 11 | * |
12 | * This class is mainly based on Cookies.pm <http://search.cpan.org/author/GAAS/libwww-perl-5.65/ | 12 | * This class is mainly based on Cookies.pm <http://search.cpan.org/author/GAAS/libwww-perl-5.65/ |
13 | * lib/HTTP/Cookies.pm> from the libwww-perl collection <http://www.linpro.no/lwp/>. | 13 | * lib/HTTP/Cookies.pm> from the libwww-perl collection <http://www.linpro.no/lwp/>. |
14 | * Unlike Cookies.pm, this class only supports the Netscape cookie spec, not RFC 2965. | 14 | * Unlike Cookies.pm, this class only supports the Netscape cookie spec, not RFC 2965. |
15 | * | 15 | * |
16 | * @version 0.5 | 16 | * @version 0.5 |
17 | * @date 2011-03-15 | 17 | * @date 2011-03-15 |
18 | * @see http://php.net/HttpRequestPool | 18 | * @see http://php.net/HttpRequestPool |
19 | * @author Keyvan Minoukadeh | 19 | * @author Keyvan Minoukadeh |
20 | * @copyright 2011 Keyvan Minoukadeh | 20 | * @copyright 2011 Keyvan Minoukadeh |
21 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 | 21 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 |
22 | */ | 22 | */ |
23 | 23 | ||
24 | class CookieJar | 24 | class CookieJar |
25 | { | 25 | { |
26 | /** | 26 | /** |
27 | * Cookies - array containing all cookies. | 27 | * Cookies - array containing all cookies. |
28 | * | 28 | * |
29 | * <pre> | 29 | * <pre> |
30 | * Cookies are stored like this: | 30 | * Cookies are stored like this: |
31 | * [domain][path][name] = array | 31 | * [domain][path][name] = array |
32 | * where array is: | 32 | * where array is: |
33 | * 0 => value, 1 => secure, 2 => expires | 33 | * 0 => value, 1 => secure, 2 => expires |
34 | * </pre> | 34 | * </pre> |
35 | * @var array | 35 | * @var array |
36 | * @access private | 36 | * @access private |
37 | */ | 37 | */ |
38 | public $cookies = array(); | 38 | public $cookies = array(); |
39 | public $debug = false; | 39 | public $debug = false; |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * Constructor | 42 | * Constructor |
43 | */ | 43 | */ |
44 | function __construct() { | 44 | function __construct() { |
45 | } | 45 | } |
46 | 46 | ||
47 | protected function debug($msg, $file=null, $line=null) { | 47 | protected function debug($msg, $file=null, $line=null) { |
48 | if ($this->debug) { | 48 | if ($this->debug) { |
49 | $mem = round(memory_get_usage()/1024, 2); | 49 | $mem = round(memory_get_usage()/1024, 2); |
50 | $memPeak = round(memory_get_peak_usage()/1024, 2); | 50 | $memPeak = round(memory_get_peak_usage()/1024, 2); |
51 | echo '* ',$msg; | 51 | echo '* ',$msg; |
52 | if (isset($file, $line)) echo " ($file line $line)"; | 52 | if (isset($file, $line)) echo " ($file line $line)"; |
53 | echo ' - mem used: ',$mem," (peak: $memPeak)\n"; | 53 | echo ' - mem used: ',$mem," (peak: $memPeak)\n"; |
54 | ob_flush(); | 54 | ob_flush(); |
55 | flush(); | 55 | flush(); |
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | /** | 59 | /** |
60 | * Get matching cookies | 60 | * Get matching cookies |
61 | * | 61 | * |
62 | * Only use this method if you cannot use add_cookie_header(), for example, if you want to use | 62 | * Only use this method if you cannot use add_cookie_header(), for example, if you want to use |
63 | * this cookie jar class without using the request class. | 63 | * this cookie jar class without using the request class. |
64 | * | 64 | * |
65 | * @param array $param associative array containing 'domain', 'path', 'secure' keys | 65 | * @param array $param associative array containing 'domain', 'path', 'secure' keys |
66 | * @return string | 66 | * @return string |
67 | * @see add_cookie_header() | 67 | * @see add_cookie_header() |
68 | */ | 68 | */ |
69 | public function getMatchingCookies($url) | 69 | public function getMatchingCookies($url) |
70 | { | 70 | { |
71 | if (($parts = @parse_url($url)) && isset($parts['scheme'], $parts['host'], $parts['path'])) { | 71 | if (($parts = @parse_url($url)) && isset($parts['scheme'], $parts['host'], $parts['path'])) { |
72 | $param['domain'] = $parts['host']; | 72 | $param['domain'] = $parts['host']; |
73 | $param['path'] = $parts['path']; | 73 | $param['path'] = $parts['path']; |
74 | $param['secure'] = (strtolower($parts['scheme']) == 'https'); | 74 | $param['secure'] = (strtolower($parts['scheme']) == 'https'); |
75 | unset($parts); | 75 | unset($parts); |
76 | } else { | 76 | } else { |
77 | return false; | 77 | return false; |
78 | } | 78 | } |
79 | // RFC 2965 notes: | 79 | // RFC 2965 notes: |
80 | // If multiple cookies satisfy the criteria above, they are ordered in | 80 | // If multiple cookies satisfy the criteria above, they are ordered in |
81 | // the Cookie header such that those with more specific Path attributes | 81 | // the Cookie header such that those with more specific Path attributes |
82 | // precede those with less specific. Ordering with respect to other | 82 | // precede those with less specific. Ordering with respect to other |
83 | // attributes (e.g., Domain) is unspecified. | 83 | // attributes (e.g., Domain) is unspecified. |
84 | $domain = $param['domain']; | 84 | $domain = $param['domain']; |
85 | if (strpos($domain, '.') === false) $domain .= '.local'; | 85 | if (strpos($domain, '.') === false) $domain .= '.local'; |
86 | $request_path = $param['path']; | 86 | $request_path = $param['path']; |
87 | if ($request_path == '') $request_path = '/'; | 87 | if ($request_path == '') $request_path = '/'; |
88 | $request_secure = $param['secure']; | 88 | $request_secure = $param['secure']; |
89 | $now = time(); | 89 | $now = time(); |
90 | $matched_cookies = array(); | 90 | $matched_cookies = array(); |
91 | // domain - find matching domains | 91 | // domain - find matching domains |
92 | $this->debug('Finding matching domains for '.$domain, __FILE__, __LINE__); | 92 | $this->debug('Finding matching domains for '.$domain, __FILE__, __LINE__); |
93 | while (strpos($domain, '.') !== false) { | 93 | while (strpos($domain, '.') !== false) { |
94 | if (isset($this->cookies[$domain])) { | 94 | if (isset($this->cookies[$domain])) { |
95 | $this->debug(' domain match found: '.$domain); | 95 | $this->debug(' domain match found: '.$domain); |
96 | $cookies =& $this->cookies[$domain]; | 96 | $cookies =& $this->cookies[$domain]; |
97 | } else { | 97 | } else { |
98 | $domain = $this->_reduce_domain($domain); | 98 | $domain = $this->_reduce_domain($domain); |
99 | continue; | 99 | continue; |
100 | } | 100 | } |
101 | // paths - find matching paths starting from most specific | 101 | // paths - find matching paths starting from most specific |
102 | $this->debug(' - Finding matching paths for '.$request_path); | 102 | $this->debug(' - Finding matching paths for '.$request_path); |
103 | $paths = array_keys($cookies); | 103 | $paths = array_keys($cookies); |
104 | usort($paths, array($this, '_cmp_length')); | 104 | usort($paths, array($this, '_cmp_length')); |
105 | foreach ($paths as $path) { | 105 | foreach ($paths as $path) { |
106 | // continue to next cookie if request path does not path-match cookie path | 106 | // continue to next cookie if request path does not path-match cookie path |
107 | if (!$this->_path_match($request_path, $path)) continue; | 107 | if (!$this->_path_match($request_path, $path)) continue; |
108 | // loop through cookie names | 108 | // loop through cookie names |
109 | $this->debug(' path match found: '.$path); | 109 | $this->debug(' path match found: '.$path); |
110 | foreach ($cookies[$path] as $name => $values) { | 110 | foreach ($cookies[$path] as $name => $values) { |
111 | // if this cookie is secure but request isn't, continue to next cookie | 111 | // if this cookie is secure but request isn't, continue to next cookie |
112 | if ($values[1] && !$request_secure) continue; | 112 | if ($values[1] && !$request_secure) continue; |
113 | // if cookie is not a session cookie and has expired, continue to next cookie | 113 | // if cookie is not a session cookie and has expired, continue to next cookie |
114 | if (is_int($values[2]) && ($values[2] < $now)) continue; | 114 | if (is_int($values[2]) && ($values[2] < $now)) continue; |
115 | // cookie matches request | 115 | // cookie matches request |
116 | $this->debug(' cookie match: '.$name.'='.$values[0]); | 116 | $this->debug(' cookie match: '.$name.'='.$values[0]); |
117 | $matched_cookies[] = $name.'='.$values[0]; | 117 | $matched_cookies[] = $name.'='.$values[0]; |
118 | } | 118 | } |
119 | } | 119 | } |
120 | $domain = $this->_reduce_domain($domain); | 120 | $domain = $this->_reduce_domain($domain); |
121 | } | 121 | } |
122 | // return cookies | 122 | // return cookies |
123 | return implode('; ', $matched_cookies); | 123 | return implode('; ', $matched_cookies); |
124 | } | 124 | } |
125 | 125 | ||
126 | /** | 126 | /** |
127 | * Parse Set-Cookie values. | 127 | * Parse Set-Cookie values. |
128 | * | 128 | * |
129 | * Only use this method if you cannot use extract_cookies(), for example, if you want to use | 129 | * Only use this method if you cannot use extract_cookies(), for example, if you want to use |
130 | * this cookie jar class without using the response class. | 130 | * this cookie jar class without using the response class. |
131 | * | 131 | * |
132 | * @param array $set_cookies array holding 1 or more "Set-Cookie" header values | 132 | * @param array $set_cookies array holding 1 or more "Set-Cookie" header values |
133 | * @param array $param associative array containing 'host', 'path' keys | 133 | * @param array $param associative array containing 'host', 'path' keys |
134 | * @return void | 134 | * @return void |
135 | * @see extract_cookies() | 135 | * @see extract_cookies() |
136 | */ | 136 | */ |
137 | public function storeCookies($url, $set_cookies) | 137 | public function storeCookies($url, $set_cookies) |
138 | { | 138 | { |
139 | if (count($set_cookies) == 0) return; | 139 | if (count($set_cookies) == 0) return; |
140 | $param = @parse_url($url); | 140 | $param = @parse_url($url); |
141 | if (!is_array($param) || !isset($param['host'])) return; | 141 | if (!is_array($param) || !isset($param['host'])) return; |
142 | $request_host = $param['host']; | 142 | $request_host = $param['host']; |
143 | if (strpos($request_host, '.') === false) $request_host .= '.local'; | 143 | if (strpos($request_host, '.') === false) $request_host .= '.local'; |
144 | $request_path = @$param['path']; | 144 | $request_path = @$param['path']; |
145 | if ($request_path == '') $request_path = '/'; | 145 | if ($request_path == '') $request_path = '/'; |
146 | // | 146 | // |
147 | // loop through set-cookie headers | 147 | // loop through set-cookie headers |
148 | // | 148 | // |
149 | foreach ($set_cookies as $set_cookie) { | 149 | foreach ($set_cookies as $set_cookie) { |
150 | $this->debug('Parsing: '.$set_cookie); | 150 | $this->debug('Parsing: '.$set_cookie); |
151 | // temporary cookie store (before adding to jar) | 151 | // temporary cookie store (before adding to jar) |
152 | $tmp_cookie = array(); | 152 | $tmp_cookie = array(); |
153 | $param = explode(';', $set_cookie); | 153 | $param = explode(';', $set_cookie); |
154 | // loop through params | 154 | // loop through params |
155 | for ($x=0; $x<count($param); $x++) { | 155 | for ($x=0; $x<count($param); $x++) { |
156 | $key_val = explode('=', $param[$x], 2); | 156 | $key_val = explode('=', $param[$x], 2); |
157 | if (count($key_val) != 2) { | 157 | if (count($key_val) != 2) { |
158 | // if the first param isn't a name=value pair, continue to the next set-cookie | 158 | // if the first param isn't a name=value pair, continue to the next set-cookie |
159 | // header | 159 | // header |
160 | if ($x == 0) continue 2; | 160 | if ($x == 0) continue 2; |
161 | // check for secure flag | 161 | // check for secure flag |
162 | if (strtolower(trim($key_val[0])) == 'secure') $tmp_cookie['secure'] = true; | 162 | if (strtolower(trim($key_val[0])) == 'secure') $tmp_cookie['secure'] = true; |
163 | // continue to next param | 163 | // continue to next param |
164 | continue; | 164 | continue; |
165 | } | 165 | } |
166 | list($key, $val) = array_map('trim', $key_val); | 166 | list($key, $val) = array_map('trim', $key_val); |
167 | // first name=value pair is the cookie name and value | 167 | // first name=value pair is the cookie name and value |
168 | // the name and value are stored under 'name' and 'value' to avoid conflicts | 168 | // the name and value are stored under 'name' and 'value' to avoid conflicts |
169 | // with later parameters. | 169 | // with later parameters. |
170 | if ($x == 0) { | 170 | if ($x == 0) { |
171 | $tmp_cookie = array('name'=>$key, 'value'=>$val); | 171 | $tmp_cookie = array('name'=>$key, 'value'=>$val); |
172 | continue; | 172 | continue; |
173 | } | 173 | } |
174 | $key = strtolower($key); | 174 | $key = strtolower($key); |
175 | if (in_array($key, array('expires', 'path', 'domain', 'secure'))) { | 175 | if (in_array($key, array('expires', 'path', 'domain', 'secure'))) { |
176 | $tmp_cookie[$key] = $val; | 176 | $tmp_cookie[$key] = $val; |
177 | } | 177 | } |
178 | } | 178 | } |
179 | // | 179 | // |
180 | // set cookie | 180 | // set cookie |
181 | // | 181 | // |
182 | // check domain | 182 | // check domain |
183 | if (isset($tmp_cookie['domain']) && ($tmp_cookie['domain'] != $request_host) && | 183 | if (isset($tmp_cookie['domain']) && ($tmp_cookie['domain'] != $request_host) && |
184 | ($tmp_cookie['domain'] != ".$request_host")) { | 184 | ($tmp_cookie['domain'] != ".$request_host")) { |
185 | $domain = $tmp_cookie['domain']; | 185 | $domain = $tmp_cookie['domain']; |
186 | if ((strpos($domain, '.') === false) && ($domain != 'local')) { | 186 | if ((strpos($domain, '.') === false) && ($domain != 'local')) { |
187 | $this->debug(' - domain "'.$domain.'" has no dot and is not a local domain'); | 187 | $this->debug(' - domain "'.$domain.'" has no dot and is not a local domain'); |
188 | continue; | 188 | continue; |
189 | } | 189 | } |
190 | if (preg_match('/\.[0-9]+$/', $domain)) { | 190 | if (preg_match('/\.[0-9]+$/', $domain)) { |
191 | $this->debug(' - domain "'.$domain.'" appears to be an ip address'); | 191 | $this->debug(' - domain "'.$domain.'" appears to be an ip address'); |
192 | continue; | 192 | continue; |
193 | } | 193 | } |
194 | if (substr($domain, 0, 1) != '.') $domain = ".$domain"; | 194 | if (substr($domain, 0, 1) != '.') $domain = ".$domain"; |
195 | if (!$this->_domain_match($request_host, $domain)) { | 195 | if (!$this->_domain_match($request_host, $domain)) { |
196 | $this->debug(' - request host "'.$request_host.'" does not domain-match "'.$domain.'"'); | 196 | $this->debug(' - request host "'.$request_host.'" does not domain-match "'.$domain.'"'); |
197 | continue; | 197 | continue; |
198 | } | 198 | } |
199 | } else { | 199 | } else { |
200 | // if domain is not specified in the set-cookie header, domain will default to | 200 | // if domain is not specified in the set-cookie header, domain will default to |
201 | // the request host | 201 | // the request host |
202 | $domain = $request_host; | 202 | $domain = $request_host; |
203 | } | 203 | } |
204 | // check path | 204 | // check path |
205 | if (isset($tmp_cookie['path']) && ($tmp_cookie['path'] != '')) { | 205 | if (isset($tmp_cookie['path']) && ($tmp_cookie['path'] != '')) { |
206 | $path = urldecode($tmp_cookie['path']); | 206 | $path = urldecode($tmp_cookie['path']); |
207 | if (!$this->_path_match($request_path, $path)) { | 207 | if (!$this->_path_match($request_path, $path)) { |
208 | $this->debug(' - request path "'.$request_path.'" does not path-match "'.$path.'"'); | 208 | $this->debug(' - request path "'.$request_path.'" does not path-match "'.$path.'"'); |
209 | continue; | 209 | continue; |
210 | } | 210 | } |
211 | } else { | 211 | } else { |
212 | $path = $request_path; | 212 | $path = $request_path; |
213 | $path = substr($path, 0, strrpos($path, '/')); | 213 | $path = substr($path, 0, strrpos($path, '/')); |
214 | if ($path == '') $path = '/'; | 214 | if ($path == '') $path = '/'; |
215 | } | 215 | } |
216 | // check if secure | 216 | // check if secure |
217 | $secure = (isset($tmp_cookie['secure'])) ? true : false; | 217 | $secure = (isset($tmp_cookie['secure'])) ? true : false; |
218 | // check expiry | 218 | // check expiry |
219 | if (isset($tmp_cookie['expires'])) { | 219 | if (isset($tmp_cookie['expires'])) { |
220 | if (($expires = strtotime($tmp_cookie['expires'])) < 0) { | 220 | if (($expires = strtotime($tmp_cookie['expires'])) < 0) { |
221 | $expires = null; | 221 | $expires = null; |
222 | } | 222 | } |
223 | } else { | 223 | } else { |
224 | $expires = null; | 224 | $expires = null; |
225 | } | 225 | } |
226 | // set cookie | 226 | // set cookie |
227 | $this->set_cookie($domain, $path, $tmp_cookie['name'], $tmp_cookie['value'], $secure, $expires); | 227 | $this->set_cookie($domain, $path, $tmp_cookie['name'], $tmp_cookie['value'], $secure, $expires); |
228 | } | 228 | } |
229 | } | 229 | } |
230 | 230 | ||
231 | // return array of set-cookie values extracted from HTTP response headers (string $h) | 231 | // return array of set-cookie values extracted from HTTP response headers (string $h) |
232 | public function extractCookies($h) { | 232 | public function extractCookies($h) { |
233 | $x = 0; | 233 | $x = 0; |
234 | $lines = 0; | 234 | $lines = 0; |
235 | $headers = array(); | 235 | $headers = array(); |
236 | $last_match = false; | 236 | $last_match = false; |
237 | $h = explode("\n", $h); | 237 | $h = explode("\n", $h); |
238 | foreach ($h as $line) { | 238 | foreach ($h as $line) { |
239 | $line = rtrim($line); | 239 | $line = rtrim($line); |
240 | $lines++; | 240 | $lines++; |
241 | 241 | ||
242 | $trimmed_line = trim($line); | 242 | $trimmed_line = trim($line); |
243 | if (isset($line_last)) { | 243 | if (isset($line_last)) { |
244 | // check if we have \r\n\r\n (indicating the end of headers) | 244 | // check if we have \r\n\r\n (indicating the end of headers) |
245 | // some servers will not use CRLF (\r\n), so we make CR (\r) optional. | 245 | // some servers will not use CRLF (\r\n), so we make CR (\r) optional. |
246 | // if (preg_match('/\015?\012\015?\012/', $line_last.$line)) { | 246 | // if (preg_match('/\015?\012\015?\012/', $line_last.$line)) { |
247 | // break; | 247 | // break; |
248 | // } | 248 | // } |
249 | // As an alternative, we can check if the current trimmed line is empty | 249 | // As an alternative, we can check if the current trimmed line is empty |
250 | if ($trimmed_line == '') { | 250 | if ($trimmed_line == '') { |
251 | break; | 251 | break; |
252 | } | 252 | } |
253 | 253 | ||
254 | // check for continuation line... | 254 | // check for continuation line... |
255 | // RFC 2616 Section 2.2 "Basic Rules": | 255 | // RFC 2616 Section 2.2 "Basic Rules": |
256 | // HTTP/1.1 header field values can be folded onto multiple lines if the | 256 | // HTTP/1.1 header field values can be folded onto multiple lines if the |
257 | // continuation line begins with a space or horizontal tab. All linear | 257 | // continuation line begins with a space or horizontal tab. All linear |
258 | // white space, including folding, has the same semantics as SP. A | 258 | // white space, including folding, has the same semantics as SP. A |
259 | // recipient MAY replace any linear white space with a single SP before | 259 | // recipient MAY replace any linear white space with a single SP before |
260 | // interpreting the field value or forwarding the message downstream. | 260 | // interpreting the field value or forwarding the message downstream. |
261 | if ($last_match && preg_match('/^\s+(.*)/', $line, $match)) { | 261 | if ($last_match && preg_match('/^\s+(.*)/', $line, $match)) { |
262 | // append to previous header value | 262 | // append to previous header value |
263 | $headers[$x-1] .= ' '.rtrim($match[1]); | 263 | $headers[$x-1] .= ' '.rtrim($match[1]); |
264 | continue; | 264 | continue; |
265 | } | 265 | } |
266 | } | 266 | } |
267 | $line_last = $line; | 267 | $line_last = $line; |
268 | 268 | ||
269 | // split header name and value | 269 | // split header name and value |
270 | if (preg_match('/^Set-Cookie\s*:\s*(.*)/i', $line, $match)) { | 270 | if (preg_match('/^Set-Cookie\s*:\s*(.*)/i', $line, $match)) { |
271 | $headers[$x++] = rtrim($match[1]); | 271 | $headers[$x++] = rtrim($match[1]); |
272 | $last_match = true; | 272 | $last_match = true; |
273 | } else { | 273 | } else { |
274 | $last_match = false; | 274 | $last_match = false; |
275 | } | 275 | } |
276 | } | 276 | } |
277 | return $headers; | 277 | return $headers; |
278 | } | 278 | } |
279 | 279 | ||
280 | /** | 280 | /** |
281 | * Set Cookie | 281 | * Set Cookie |
282 | * @param string $domain | 282 | * @param string $domain |
283 | * @param string $path | 283 | * @param string $path |
284 | * @param string $name cookie name | 284 | * @param string $name cookie name |
285 | * @param string $value cookie value | 285 | * @param string $value cookie value |
286 | * @param bool $secure | 286 | * @param bool $secure |
287 | * @param int $expires expiry time (null if session cookie, <= 0 will delete cookie) | 287 | * @param int $expires expiry time (null if session cookie, <= 0 will delete cookie) |
288 | * @return void | 288 | * @return void |
289 | */ | 289 | */ |
290 | function set_cookie($domain, $path, $name, $value, $secure=false, $expires=null) | 290 | function set_cookie($domain, $path, $name, $value, $secure=false, $expires=null) |
291 | { | 291 | { |
292 | if ($domain == '') return; | 292 | if ($domain == '') return; |
293 | if ($path == '') return; | 293 | if ($path == '') return; |
294 | if ($name == '') return; | 294 | if ($name == '') return; |
295 | // check if cookie needs to go | 295 | // check if cookie needs to go |
296 | if (isset($expires) && ($expires <= 0)) { | 296 | if (isset($expires) && ($expires <= 0)) { |
297 | if (isset($this->cookies[$domain][$path][$name])) unset($this->cookies[$domain][$path][$name]); | 297 | if (isset($this->cookies[$domain][$path][$name])) unset($this->cookies[$domain][$path][$name]); |
298 | return; | 298 | return; |
299 | } | 299 | } |
300 | if ($value == '') return; | 300 | if ($value == '') return; |
301 | $this->cookies[$domain][$path][$name] = array($value, $secure, $expires); | 301 | $this->cookies[$domain][$path][$name] = array($value, $secure, $expires); |
302 | return; | 302 | return; |
303 | } | 303 | } |
304 | 304 | ||
305 | /** | 305 | /** |
306 | * Clear cookies - [domain [,path [,name]]] - call method with no arguments to clear all cookies. | 306 | * Clear cookies - [domain [,path [,name]]] - call method with no arguments to clear all cookies. |
307 | * @param string $domain | 307 | * @param string $domain |
308 | * @param string $path | 308 | * @param string $path |
309 | * @param string $name | 309 | * @param string $name |
310 | * @return void | 310 | * @return void |
311 | */ | 311 | */ |
312 | function clear($domain=null, $path=null, $name=null) | 312 | function clear($domain=null, $path=null, $name=null) |
313 | { | 313 | { |
314 | if (!isset($domain)) { | 314 | if (!isset($domain)) { |
315 | $this->cookies = array(); | 315 | $this->cookies = array(); |
316 | } elseif (!isset($path)) { | 316 | } elseif (!isset($path)) { |
317 | if (isset($this->cookies[$domain])) unset($this->cookies[$domain]); | 317 | if (isset($this->cookies[$domain])) unset($this->cookies[$domain]); |
318 | } elseif (!isset($name)) { | 318 | } elseif (!isset($name)) { |
319 | if (isset($this->cookies[$domain][$path])) unset($this->cookies[$domain][$path]); | 319 | if (isset($this->cookies[$domain][$path])) unset($this->cookies[$domain][$path]); |
320 | } elseif (isset($name)) { | 320 | } elseif (isset($name)) { |
321 | if (isset($this->cookies[$domain][$path][$name])) unset($this->cookies[$domain][$path][$name]); | 321 | if (isset($this->cookies[$domain][$path][$name])) unset($this->cookies[$domain][$path][$name]); |
322 | } | 322 | } |
323 | } | 323 | } |
324 | 324 | ||
325 | /** | 325 | /** |
326 | * Compare string length - used for sorting | 326 | * Compare string length - used for sorting |
327 | * @access private | 327 | * @access private |
328 | * @return int | 328 | * @return int |
329 | */ | 329 | */ |
330 | function _cmp_length($a, $b) | 330 | function _cmp_length($a, $b) |
331 | { | 331 | { |
332 | $la = strlen($a); $lb = strlen($b); | 332 | $la = strlen($a); $lb = strlen($b); |
333 | if ($la == $lb) return 0; | 333 | if ($la == $lb) return 0; |
334 | return ($la > $lb) ? -1 : 1; | 334 | return ($la > $lb) ? -1 : 1; |
335 | } | 335 | } |
336 | 336 | ||
337 | /** | 337 | /** |
338 | * Reduce domain | 338 | * Reduce domain |
339 | * @param string $domain | 339 | * @param string $domain |
340 | * @return string | 340 | * @return string |
341 | * @access private | 341 | * @access private |
342 | */ | 342 | */ |
343 | function _reduce_domain($domain) | 343 | function _reduce_domain($domain) |
344 | { | 344 | { |
345 | if ($domain == '') return ''; | 345 | if ($domain == '') return ''; |
346 | if (substr($domain, 0, 1) == '.') return substr($domain, 1); | 346 | if (substr($domain, 0, 1) == '.') return substr($domain, 1); |
347 | return substr($domain, strpos($domain, '.')); | 347 | return substr($domain, strpos($domain, '.')); |
348 | } | 348 | } |
349 | 349 | ||
350 | /** | 350 | /** |
351 | * Path match - check if path1 path-matches path2 | 351 | * Path match - check if path1 path-matches path2 |
352 | * | 352 | * |
353 | * From RFC 2965: | 353 | * From RFC 2965: |
354 | * <i>For two strings that represent paths, P1 and P2, P1 path-matches P2 | 354 | * <i>For two strings that represent paths, P1 and P2, P1 path-matches P2 |
355 | * if P2 is a prefix of P1 (including the case where P1 and P2 string- | 355 | * if P2 is a prefix of P1 (including the case where P1 and P2 string- |
356 | * compare equal). Thus, the string /tec/waldo path-matches /tec.</i> | 356 | * compare equal). Thus, the string /tec/waldo path-matches /tec.</i> |
357 | * @param string $path1 | 357 | * @param string $path1 |
358 | * @param string $path2 | 358 | * @param string $path2 |
359 | * @return bool | 359 | * @return bool |
360 | * @access private | 360 | * @access private |
361 | */ | 361 | */ |
362 | function _path_match($path1, $path2) | 362 | function _path_match($path1, $path2) |
363 | { | 363 | { |
364 | return (substr($path1, 0, strlen($path2)) == $path2); | 364 | return (substr($path1, 0, strlen($path2)) == $path2); |
365 | } | 365 | } |
366 | 366 | ||
367 | /** | 367 | /** |
368 | * Domain match - check if domain1 domain-matches domain2 | 368 | * Domain match - check if domain1 domain-matches domain2 |
369 | * | 369 | * |
370 | * A few extracts from RFC 2965: | 370 | * A few extracts from RFC 2965: |
371 | * - A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com | 371 | * - A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com |
372 | * would be rejected, because H is y.x and contains a dot. | 372 | * would be rejected, because H is y.x and contains a dot. |
373 | * | 373 | * |
374 | * - A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com | 374 | * - A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com |
375 | * would be accepted. | 375 | * would be accepted. |
376 | * | 376 | * |
377 | * - A Set-Cookie2 with Domain=.com or Domain=.com., will always be | 377 | * - A Set-Cookie2 with Domain=.com or Domain=.com., will always be |
378 | * rejected, because there is no embedded dot. | 378 | * rejected, because there is no embedded dot. |
379 | * | 379 | * |
380 | * - A Set-Cookie2 from request-host example for Domain=.local will | 380 | * - A Set-Cookie2 from request-host example for Domain=.local will |
381 | * be accepted, because the effective host name for the request- | 381 | * be accepted, because the effective host name for the request- |
382 | * host is example.local, and example.local domain-matches .local. | 382 | * host is example.local, and example.local domain-matches .local. |
383 | * | 383 | * |
384 | * I'm ignoring the first point for now (must check to see how other browsers handle | 384 | * I'm ignoring the first point for now (must check to see how other browsers handle |
385 | * this rule for Set-Cookie headers) | 385 | * this rule for Set-Cookie headers) |
386 | * | 386 | * |
387 | * @param string $domain1 | 387 | * @param string $domain1 |
388 | * @param string $domain2 | 388 | * @param string $domain2 |
389 | * @return bool | 389 | * @return bool |
390 | * @access private | 390 | * @access private |
391 | */ | 391 | */ |
392 | function _domain_match($domain1, $domain2) | 392 | function _domain_match($domain1, $domain2) |
393 | { | 393 | { |
394 | $domain1 = strtolower($domain1); | 394 | $domain1 = strtolower($domain1); |
395 | $domain2 = strtolower($domain2); | 395 | $domain2 = strtolower($domain2); |
396 | while (strpos($domain1, '.') !== false) { | 396 | while (strpos($domain1, '.') !== false) { |
397 | if ($domain1 == $domain2) return true; | 397 | if ($domain1 == $domain2) return true; |
398 | $domain1 = $this->_reduce_domain($domain1); | 398 | $domain1 = $this->_reduce_domain($domain1); |
399 | continue; | 399 | continue; |
400 | } | 400 | } |
401 | return false; | 401 | return false; |
402 | } | 402 | } |
403 | } | 403 | } \ No newline at end of file |
404 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/humble-http-agent/HumbleHttpAgent.php b/inc/3rdparty/libraries/humble-http-agent/HumbleHttpAgent.php index e4f1b3b3..963f0c05 100644 --- a/inc/3rdparty/libraries/humble-http-agent/HumbleHttpAgent.php +++ b/inc/3rdparty/libraries/humble-http-agent/HumbleHttpAgent.php | |||
@@ -1,779 +1,810 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Humble HTTP Agent | 3 | * Humble HTTP Agent |
4 | * | 4 | * |
5 | * This class is designed to take advantage of parallel HTTP requests | 5 | * This class is designed to take advantage of parallel HTTP requests |
6 | * offered by PHP's PECL HTTP extension or the curl_multi_* functions. | 6 | * offered by PHP's PECL HTTP extension or the curl_multi_* functions. |
7 | * For environments which do not have these options, it reverts to standard sequential | 7 | * For environments which do not have these options, it reverts to standard sequential |
8 | * requests (using file_get_contents()) | 8 | * requests (using file_get_contents()) |
9 | * | 9 | * |
10 | * @version 1.1 | 10 | * @version 1.4 |
11 | * @date 2012-08-20 | 11 | * @date 2013-05-10 |
12 | * @see http://php.net/HttpRequestPool | 12 | * @see http://php.net/HttpRequestPool |
13 | * @author Keyvan Minoukadeh | 13 | * @author Keyvan Minoukadeh |
14 | * @copyright 2011-2012 Keyvan Minoukadeh | 14 | * @copyright 2011-2013 Keyvan Minoukadeh |
15 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 | 15 | * @license http://www.gnu.org/licenses/agpl-3.0.html AGPL v3 |
16 | */ | 16 | */ |
17 | 17 | ||
18 | class HumbleHttpAgent | 18 | class HumbleHttpAgent |
19 | { | 19 | { |
20 | const METHOD_REQUEST_POOL = 1; | 20 | const METHOD_REQUEST_POOL = 1; |
21 | const METHOD_CURL_MULTI = 2; | 21 | const METHOD_CURL_MULTI = 2; |
22 | const METHOD_FILE_GET_CONTENTS = 4; | 22 | const METHOD_FILE_GET_CONTENTS = 4; |
23 | //const UA_BROWSER = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'; | 23 | //const UA_BROWSER = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'; |
24 | const UA_BROWSER = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.92 Safari/535.2'; | 24 | const UA_BROWSER = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.92 Safari/535.2'; |
25 | const UA_PHP = 'PHP/5.2'; | 25 | const UA_PHP = 'PHP/5.4'; |
26 | const REF_GOOGLE = 'http://www.google.co.uk/url?sa=t&source=web&cd=1'; | 26 | const REF_GOOGLE = 'http://www.google.co.uk/url?sa=t&source=web&cd=1'; |
27 | 27 | ||
28 | protected $requests = array(); | 28 | protected $requests = array(); |
29 | protected $redirectQueue = array(); | 29 | protected $redirectQueue = array(); |
30 | protected $requestOptions; | 30 | protected $requestOptions; |
31 | protected $maxParallelRequests = 5; | 31 | protected $maxParallelRequests = 5; |
32 | protected $cache = null; //TODO | 32 | protected $cache = null; //TODO |
33 | protected $httpContext; | 33 | protected $httpContext; |
34 | protected $minimiseMemoryUse = false; //TODO | 34 | protected $minimiseMemoryUse = false; //TODO |
35 | protected $method; | 35 | protected $method; |
36 | protected $cookieJar; | 36 | protected $cookieJar; |
37 | public $debug = false; | 37 | public $debug = false; |
38 | public $debugVerbose = false; | 38 | public $debugVerbose = false; |
39 | public $rewriteHashbangFragment = true; // see http://code.google.com/web/ajaxcrawling/docs/specification.html | 39 | public $rewriteHashbangFragment = true; // see http://code.google.com/web/ajaxcrawling/docs/specification.html |
40 | public $maxRedirects = 5; | 40 | public $maxRedirects = 5; |
41 | public $userAgentMap = array(); | 41 | public $userAgentMap = array(); |
42 | public $rewriteUrls = array(); | 42 | public $rewriteUrls = array(); |
43 | public $userAgentDefault; | 43 | public $userAgentDefault; |
44 | public $referer; | 44 | public $referer; |
45 | //public $userAgent = 'Mozilla/5.0'; | 45 | //public $userAgent = 'Mozilla/5.0'; |
46 | 46 | ||
47 | // Prevent certain file/mime types | 47 | // Prevent certain file/mime types |
48 | // HTTP responses which match these content types will | 48 | // HTTP responses which match these content types will |
49 | // be returned without body. | 49 | // be returned without body. |
50 | public $headerOnlyTypes = array(); | 50 | public $headerOnlyTypes = array(); |
51 | // URLs ending with one of these extensions will | 51 | // URLs ending with one of these extensions will |
52 | // prompt Humble HTTP Agent to send a HEAD request first | 52 | // prompt Humble HTTP Agent to send a HEAD request first |
53 | // to see if returned content type matches $headerOnlyTypes. | 53 | // to see if returned content type matches $headerOnlyTypes. |
54 | public $headerOnlyClues = array('pdf','mp3','zip','exe','gif','gzip','gz','jpeg','jpg','mpg','mpeg','png','ppt','mov'); | 54 | public $headerOnlyClues = array('pdf','mp3','zip','exe','gif','gzip','gz','jpeg','jpg','mpg','mpeg','png','ppt','mov'); |
55 | // AJAX triggers to search for. | 55 | // AJAX triggers to search for. |
56 | // for AJAX sites, e.g. Blogger with its dynamic views templates. | 56 | // for AJAX sites, e.g. Blogger with its dynamic views templates. |
57 | public $ajaxTriggers = array("<meta name='fragment' content='!'",'<meta name="fragment" content="!"',"<meta content='!' name='fragment'",'<meta content="!" name="fragment"'); | 57 | public $ajaxTriggers = array("<meta name='fragment' content='!'",'<meta name="fragment" content="!"',"<meta content='!' name='fragment'",'<meta content="!" name="fragment"'); |
58 | 58 | ||
59 | //TODO: set max file size | 59 | //TODO: set max file size |
60 | //TODO: normalise headers | 60 | //TODO: normalise headers |
61 | 61 | ||
62 | function __construct($requestOptions=null, $method=null) { | 62 | function __construct($requestOptions=null, $method=null) { |
63 | $this->userAgentDefault = self::UA_BROWSER; | 63 | $this->userAgentDefault = self::UA_BROWSER; |
64 | $this->referer = self::REF_GOOGLE; | 64 | $this->referer = self::REF_GOOGLE; |
65 | // set the request method | 65 | // set the request method |
66 | if (in_array($method, array(1,2,4))) { | 66 | if (in_array($method, array(1,2,4))) { |
67 | $this->method = $method; | 67 | $this->method = $method; |
68 | } else { | 68 | } else { |
69 | if (class_exists('HttpRequestPool')) { | 69 | if (class_exists('HttpRequestPool')) { |
70 | $this->method = self::METHOD_REQUEST_POOL; | 70 | $this->method = self::METHOD_REQUEST_POOL; |
71 | } elseif (function_exists('curl_multi_init')) { | 71 | } elseif (function_exists('curl_multi_init')) { |
72 | $this->method = self::METHOD_CURL_MULTI; | 72 | $this->method = self::METHOD_CURL_MULTI; |
73 | } else { | 73 | } else { |
74 | $this->method = self::METHOD_FILE_GET_CONTENTS; | 74 | $this->method = self::METHOD_FILE_GET_CONTENTS; |
75 | } | 75 | } |
76 | } | 76 | } |
77 | if ($this->method == self::METHOD_CURL_MULTI) { | 77 | if ($this->method == self::METHOD_CURL_MULTI) { |
78 | require_once(dirname(__FILE__).'/RollingCurl.php'); | 78 | require_once(dirname(__FILE__).'/RollingCurl.php'); |
79 | } | 79 | } |
80 | // create cookie jar | 80 | // create cookie jar |
81 | $this->cookieJar = new CookieJar(); | 81 | $this->cookieJar = new CookieJar(); |
82 | // set request options (redirect must be 0) | 82 | // set request options (redirect must be 0) |
83 | $this->requestOptions = array( | 83 | $this->requestOptions = array( |
84 | 'timeout' => 15, | 84 | 'timeout' => 15, |
85 | 'redirect' => 0 // we handle redirects manually so we can rewrite the new hashbang URLs that are creeping up over the web | 85 | 'connecttimeout' => 15, |
86 | // TODO: test onprogress? | 86 | 'dns_cache_timeout' => 300, |
87 | ); | 87 | 'redirect' => 0 // we handle redirects manually so we can rewrite the new hashbang URLs that are creeping up over the web |
88 | if (is_array($requestOptions)) { | 88 | // TODO: test onprogress? |
89 | $this->requestOptions = array_merge($this->requestOptions, $requestOptions); | 89 | ); |
90 | } | 90 | if (is_array($requestOptions)) { |
91 | $this->httpContext = array( | 91 | $this->requestOptions = array_merge($this->requestOptions, $requestOptions); |
92 | 'http' => array( | 92 | } |
93 | 'ignore_errors' => true, | 93 | $this->httpContext = array( |
94 | 'timeout' => $this->requestOptions['timeout'], | 94 | 'http' => array( |
95 | 'max_redirects' => $this->requestOptions['redirect'], | 95 | 'ignore_errors' => true, |
96 | 'header' => "Accept: */*\r\n" | 96 | 'timeout' => $this->requestOptions['timeout'], |
97 | ) | 97 | 'max_redirects' => $this->requestOptions['redirect'], |
98 | ); | 98 | 'header' => "Accept: */*\r\n" |
99 | } | 99 | ) |
100 | 100 | ); | |
101 | protected function debug($msg) { | 101 | } |
102 | if ($this->debug) { | 102 | |
103 | $mem = round(memory_get_usage()/1024, 2); | 103 | protected function debug($msg) { |
104 | $memPeak = round(memory_get_peak_usage()/1024, 2); | 104 | if ($this->debug) { |
105 | echo '* ',$msg; | 105 | $mem = round(memory_get_usage()/1024, 2); |
106 | if ($this->debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)"; | 106 | $memPeak = round(memory_get_peak_usage()/1024, 2); |
107 | echo "\n"; | 107 | echo '* ',$msg; |
108 | ob_flush(); | 108 | if ($this->debugVerbose) echo ' - mem used: ',$mem," (peak: $memPeak)"; |
109 | flush(); | 109 | echo "\n"; |
110 | } | 110 | ob_flush(); |
111 | } | 111 | flush(); |
112 | 112 | } | |
113 | protected function getUserAgent($url, $asArray=false) { | 113 | } |
114 | $host = @parse_url($url, PHP_URL_HOST); | 114 | |
115 | if (strtolower(substr($host, 0, 4)) == 'www.') { | 115 | protected function getUserAgent($url, $asArray=false) { |
116 | $host = substr($host, 4); | 116 | $host = @parse_url($url, PHP_URL_HOST); |
117 | } | 117 | if (strtolower(substr($host, 0, 4)) == 'www.') { |
118 | if ($host) { | 118 | $host = substr($host, 4); |
119 | $try = array($host); | 119 | } |
120 | $split = explode('.', $host); | 120 | if ($host) { |
121 | if (count($split) > 1) { | 121 | $try = array($host); |
122 | array_shift($split); | 122 | $split = explode('.', $host); |
123 | $try[] = '.'.implode('.', $split); | 123 | if (count($split) > 1) { |
124 | } | 124 | array_shift($split); |
125 | foreach ($try as $h) { | 125 | $try[] = '.'.implode('.', $split); |
126 | if (isset($this->userAgentMap[$h])) { | 126 | } |
127 | $ua = $this->userAgentMap[$h]; | 127 | foreach ($try as $h) { |
128 | break; | 128 | if (isset($this->userAgentMap[$h])) { |
129 | } | 129 | $ua = $this->userAgentMap[$h]; |
130 | } | 130 | break; |
131 | } | 131 | } |
132 | if (!isset($ua)) $ua = $this->userAgentDefault; | 132 | } |
133 | if ($asArray) { | 133 | } |
134 | return array('User-Agent' => $ua); | 134 | if (!isset($ua)) $ua = $this->userAgentDefault; |
135 | } else { | 135 | if ($asArray) { |
136 | return 'User-Agent: '.$ua; | 136 | return array('User-Agent' => $ua); |
137 | } | 137 | } else { |
138 | } | 138 | return 'User-Agent: '.$ua; |
139 | 139 | } | |
140 | public function rewriteHashbangFragment($url) { | 140 | } |
141 | // return $url if there's no '#!' | 141 | |
142 | if (strpos($url, '#!') === false) return $url; | 142 | public function rewriteHashbangFragment($url) { |
143 | // split $url and rewrite | 143 | // return $url if there's no '#!' |
144 | // TODO: is SimplePie_IRI included? | 144 | if (strpos($url, '#!') === false) return $url; |
145 | $iri = new SimplePie_IRI($url); | 145 | // split $url and rewrite |
146 | $fragment = substr($iri->fragment, 1); // strip '!' | 146 | // TODO: is SimplePie_IRI included? |
147 | $iri->fragment = null; | 147 | $iri = new SimplePie_IRI($url); |
148 | if (isset($iri->query)) { | 148 | $fragment = substr($iri->fragment, 1); // strip '!' |
149 | parse_str($iri->query, $query); | 149 | $iri->fragment = null; |
150 | } else { | 150 | if (isset($iri->query)) { |
151 | $query = array(); | 151 | parse_str($iri->query, $query); |
152 | } | 152 | } else { |
153 | $query['_escaped_fragment_'] = (string)$fragment; | 153 | $query = array(); |
154 | $iri->query = str_replace('%2F', '/', http_build_query($query)); // needed for some sites | 154 | } |
155 | return $iri->get_iri(); | 155 | $query['_escaped_fragment_'] = (string)$fragment; |
156 | } | 156 | $iri->query = str_replace('%2F', '/', http_build_query($query)); // needed for some sites |
157 | 157 | return $iri->get_iri(); | |
158 | public function getUglyURL($url, $html) { | 158 | } |
159 | if ($html == '') return false; | 159 | |
160 | $found = false; | 160 | public function getRedirectURLfromHTML($url, $html) { |
161 | foreach ($this->ajaxTriggers as $string) { | 161 | $redirect_url = $this->getMetaRefreshURL($url, $html); |
162 | if (stripos($html, $string)) { | 162 | if (!$redirect_url) { |
163 | $found = true; | 163 | $redirect_url = $this->getUglyURL($url, $html); |
164 | break; | 164 | } |
165 | } | 165 | return $redirect_url; |
166 | } | 166 | } |
167 | if (!$found) return false; | 167 | |
168 | $iri = new SimplePie_IRI($url); | 168 | public function getMetaRefreshURL($url, $html) { |
169 | if (isset($iri->query)) { | 169 | if ($html == '') return false; |
170 | parse_str($iri->query, $query); | 170 | // <meta HTTP-EQUIV="REFRESH" content="0; url=http://www.bernama.com/bernama/v6/newsindex.php?id=943513"> |
171 | } else { | 171 | if (!preg_match('!<meta http-equiv=["\']?refresh["\']? content=["\']?[0-9];\s*url=["\']?([^"\'>]+)["\']*>!i', $html, $match)) { |
172 | $query = array(); | 172 | return false; |
173 | } | 173 | } |
174 | $query['_escaped_fragment_'] = ''; | 174 | $redirect_url = $match[1]; |
175 | $iri->query = str_replace('%2F', '/', http_build_query($query)); // needed for some sites | 175 | if (preg_match('!^https?://!i', $redirect_url)) { |
176 | return $iri->get_iri(); | 176 | // already absolute |
177 | } | 177 | $this->debug('Meta refresh redirect found (http-equiv="refresh"), new URL: '.$redirect_url); |
178 | 178 | return $redirect_url; | |
179 | public function removeFragment($url) { | 179 | } |
180 | $pos = strpos($url, '#'); | 180 | // absolutize redirect URL |
181 | if ($pos === false) { | 181 | $base = new SimplePie_IRI($url); |
182 | return $url; | 182 | // remove '//' in URL path (causes URLs not to resolve properly) |
183 | } else { | 183 | if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); |
184 | return substr($url, 0, $pos); | 184 | if ($absolute = SimplePie_IRI::absolutize($base, $redirect_url)) { |
185 | } | 185 | $this->debug('Meta refresh redirect found (http-equiv="refresh"), new URL: '.$absolute); |
186 | } | 186 | return $absolute; |
187 | 187 | } | |
188 | public function rewriteUrls($url) { | 188 | return false; |
189 | foreach ($this->rewriteUrls as $find => $action) { | 189 | } |
190 | if (strpos($url, $find) !== false) { | 190 | |
191 | if (is_array($action)) { | 191 | public function getUglyURL($url, $html) { |
192 | return strtr($url, $action); | 192 | if ($html == '') return false; |
193 | } | 193 | $found = false; |
194 | } | 194 | foreach ($this->ajaxTriggers as $string) { |
195 | } | 195 | if (stripos($html, $string)) { |
196 | return $url; | 196 | $found = true; |
197 | } | 197 | break; |
198 | 198 | } | |
199 | public function enableDebug($bool=true) { | 199 | } |
200 | $this->debug = (bool)$bool; | 200 | if (!$found) return false; |
201 | } | 201 | $iri = new SimplePie_IRI($url); |
202 | 202 | if (isset($iri->query)) { | |
203 | public function minimiseMemoryUse($bool = true) { | 203 | parse_str($iri->query, $query); |
204 | $this->minimiseMemoryUse = $bool; | 204 | } else { |
205 | } | 205 | $query = array(); |
206 | 206 | } | |
207 | public function setMaxParallelRequests($max) { | 207 | $query['_escaped_fragment_'] = ''; |
208 | $this->maxParallelRequests = $max; | 208 | $iri->query = str_replace('%2F', '/', http_build_query($query)); // needed for some sites |
209 | } | 209 | $ugly_url = $iri->get_iri(); |
210 | 210 | $this->debug('AJAX trigger (meta name="fragment" content="!") found, new URL: '.$ugly_url); | |
211 | public function validateUrl($url) { | 211 | return $ugly_url; |
212 | $url = filter_var($url, FILTER_SANITIZE_URL); | 212 | } |
213 | $test = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); | 213 | |
214 | // deal with bug http://bugs.php.net/51192 (present in PHP 5.2.13 and PHP 5.3.2) | 214 | public function removeFragment($url) { |
215 | if ($test === false) { | 215 | $pos = strpos($url, '#'); |
216 | $test = filter_var(strtr($url, '-', '_'), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); | 216 | if ($pos === false) { |
217 | } | 217 | return $url; |
218 | if ($test !== false && $test !== null && preg_match('!^https?://!', $url)) { | 218 | } else { |
219 | return $url; | 219 | return substr($url, 0, $pos); |
220 | } else { | 220 | } |
221 | return false; | 221 | } |
222 | } | 222 | |
223 | } | 223 | public function rewriteUrls($url) { |
224 | 224 | foreach ($this->rewriteUrls as $find => $action) { | |
225 | public function fetchAll(array $urls) { | 225 | if (strpos($url, $find) !== false) { |
226 | $this->fetchAllOnce($urls, $isRedirect=false); | 226 | if (is_array($action)) { |
227 | $redirects = 0; | 227 | return strtr($url, $action); |
228 | while (!empty($this->redirectQueue) && ++$redirects <= $this->maxRedirects) { | 228 | } |
229 | $this->debug("Following redirects #$redirects..."); | 229 | } |
230 | $this->fetchAllOnce($this->redirectQueue, $isRedirect=true); | 230 | } |
231 | } | 231 | return $url; |
232 | } | 232 | } |
233 | 233 | ||
234 | // fetch all URLs without following redirects | 234 | public function enableDebug($bool=true) { |
235 | public function fetchAllOnce(array $urls, $isRedirect=false) { | 235 | $this->debug = (bool)$bool; |
236 | if (!$isRedirect) $urls = array_unique($urls); | 236 | } |
237 | if (empty($urls)) return; | 237 | |
238 | 238 | public function minimiseMemoryUse($bool = true) { | |
239 | ////////////////////////////////////////////////////// | 239 | $this->minimiseMemoryUse = $bool; |
240 | // parallel (HttpRequestPool) | 240 | } |
241 | if ($this->method == self::METHOD_REQUEST_POOL) { | 241 | |
242 | $this->debug('Starting parallel fetch (HttpRequestPool)'); | 242 | public function setMaxParallelRequests($max) { |
243 | try { | 243 | $this->maxParallelRequests = $max; |
244 | while (count($urls) > 0) { | 244 | } |
245 | $this->debug('Processing set of '.min($this->maxParallelRequests, count($urls))); | 245 | |
246 | $subset = array_splice($urls, 0, $this->maxParallelRequests); | 246 | public function validateUrl($url) { |
247 | $pool = new HttpRequestPool(); | 247 | $url = filter_var($url, FILTER_SANITIZE_URL); |
248 | foreach ($subset as $orig => $url) { | 248 | $test = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); |
249 | if (!$isRedirect) $orig = $url; | 249 | // deal with bug http://bugs.php.net/51192 (present in PHP 5.2.13 and PHP 5.3.2) |
250 | unset($this->redirectQueue[$orig]); | 250 | if ($test === false) { |
251 | $this->debug("...$url"); | 251 | $test = filter_var(strtr($url, '-', '_'), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); |
252 | if (!$isRedirect && isset($this->requests[$url])) { | 252 | } |
253 | $this->debug("......in memory"); | 253 | if ($test !== false && $test !== null && preg_match('!^https?://!', $url)) { |
254 | /* | 254 | return $url; |
255 | } elseif ($this->isCached($url)) { | 255 | } else { |
256 | $this->debug("......is cached"); | 256 | return false; |
257 | if (!$this->minimiseMemoryUse) { | 257 | } |
258 | $this->requests[$url] = $this->getCached($url); | 258 | } |
259 | } | 259 | |
260 | */ | 260 | public function fetchAll(array $urls) { |
261 | } else { | 261 | $this->fetchAllOnce($urls, $isRedirect=false); |
262 | $this->debug("......adding to pool"); | 262 | $redirects = 0; |
263 | $req_url = $this->rewriteUrls($url); | 263 | while (!empty($this->redirectQueue) && ++$redirects <= $this->maxRedirects) { |
264 | $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; | 264 | $this->debug("Following redirects #$redirects..."); |
265 | $req_url = $this->removeFragment($req_url); | 265 | $this->fetchAllOnce($this->redirectQueue, $isRedirect=true); |
266 | if (!empty($this->headerOnlyTypes) && !isset($this->requests[$orig]['wrongGuess']) && $this->possibleUnsupportedType($req_url)) { | 266 | } |
267 | $_meth = HttpRequest::METH_HEAD; | 267 | } |
268 | } else { | 268 | |
269 | $_meth = HttpRequest::METH_GET; | 269 | // fetch all URLs without following redirects |
270 | unset($this->requests[$orig]['wrongGuess']); | 270 | public function fetchAllOnce(array $urls, $isRedirect=false) { |
271 | } | 271 | if (!$isRedirect) $urls = array_unique($urls); |
272 | $httpRequest = new HttpRequest($req_url, $_meth, $this->requestOptions); | 272 | if (empty($urls)) return; |
273 | // send cookies, if we have any | 273 | |
274 | if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { | 274 | ////////////////////////////////////////////////////// |
275 | $this->debug("......sending cookies: $cookies"); | 275 | // parallel (HttpRequestPool) |
276 | $httpRequest->addHeaders(array('Cookie' => $cookies)); | 276 | if ($this->method == self::METHOD_REQUEST_POOL) { |
277 | } | 277 | $this->debug('Starting parallel fetch (HttpRequestPool)'); |
278 | //$httpRequest->addHeaders(array('User-Agent' => $this->userAgent)); | 278 | try { |
279 | $httpRequest->addHeaders($this->getUserAgent($req_url, true)); | 279 | while (count($urls) > 0) { |
280 | // add referer for picky sites | 280 | $this->debug('Processing set of '.min($this->maxParallelRequests, count($urls))); |
281 | $httpRequest->addheaders(array('Referer' => $this->referer)); | 281 | $subset = array_splice($urls, 0, $this->maxParallelRequests); |
282 | $this->requests[$orig] = array('headers'=>null, 'body'=>null, 'httpRequest'=>$httpRequest); | 282 | $pool = new HttpRequestPool(); |
283 | $this->requests[$orig]['original_url'] = $orig; | 283 | foreach ($subset as $orig => $url) { |
284 | $pool->attach($httpRequest); | 284 | if (!$isRedirect) $orig = $url; |
285 | } | 285 | unset($this->redirectQueue[$orig]); |
286 | } | 286 | $this->debug("...$url"); |
287 | // did we get anything into the pool? | 287 | if (!$isRedirect && isset($this->requests[$url])) { |
288 | if (count($pool) > 0) { | 288 | $this->debug("......in memory"); |
289 | $this->debug('Sending request...'); | 289 | /* |
290 | try { | 290 | } elseif ($this->isCached($url)) { |
291 | $pool->send(); | 291 | $this->debug("......is cached"); |
292 | } catch (HttpRequestPoolException $e) { | 292 | if (!$this->minimiseMemoryUse) { |
293 | // do nothing | 293 | $this->requests[$url] = $this->getCached($url); |
294 | } | 294 | } |
295 | $this->debug('Received responses'); | 295 | */ |
296 | foreach($subset as $orig => $url) { | 296 | } else { |
297 | if (!$isRedirect) $orig = $url; | 297 | $this->debug("......adding to pool"); |
298 | $request = $this->requests[$orig]['httpRequest']; | 298 | $req_url = $this->rewriteUrls($url); |
299 | //$this->requests[$orig]['headers'] = $this->headersToString($request->getResponseHeader()); | 299 | $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; |
300 | // getResponseHeader() doesn't return status line, so, for consistency... | 300 | $req_url = $this->removeFragment($req_url); |
301 | $this->requests[$orig]['headers'] = substr($request->getRawResponseMessage(), 0, $request->getResponseInfo('header_size')); | 301 | if (!empty($this->headerOnlyTypes) && !isset($this->requests[$orig]['wrongGuess']) && $this->possibleUnsupportedType($req_url)) { |
302 | // check content type | 302 | $_meth = HttpRequest::METH_HEAD; |
303 | // TODO: use getResponseHeader('content-type') or getResponseInfo() | 303 | } else { |
304 | if ($this->headerOnlyType($this->requests[$orig]['headers'])) { | 304 | $_meth = HttpRequest::METH_GET; |
305 | $this->requests[$orig]['body'] = ''; | 305 | unset($this->requests[$orig]['wrongGuess']); |
306 | $_header_only_type = true; | 306 | } |
307 | $this->debug('Header only type returned'); | 307 | $httpRequest = new HttpRequest($req_url, $_meth, $this->requestOptions); |
308 | } else { | 308 | // send cookies, if we have any |
309 | $this->requests[$orig]['body'] = $request->getResponseBody(); | 309 | if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { |
310 | $_header_only_type = false; | 310 | $this->debug("......sending cookies: $cookies"); |
311 | } | 311 | $httpRequest->addHeaders(array('Cookie' => $cookies)); |
312 | $this->requests[$orig]['effective_url'] = $request->getResponseInfo('effective_url'); | 312 | } |
313 | $this->requests[$orig]['status_code'] = $status_code = $request->getResponseCode(); | 313 | //$httpRequest->addHeaders(array('User-Agent' => $this->userAgent)); |
314 | // is redirect? | 314 | $httpRequest->addHeaders($this->getUserAgent($req_url, true)); |
315 | if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && $request->getResponseHeader('location')) { | 315 | // add referer for picky sites |
316 | $redirectURL = $request->getResponseHeader('location'); | 316 | $httpRequest->addheaders(array('Referer' => $this->referer)); |
317 | if (!preg_match('!^https?://!i', $redirectURL)) { | 317 | $this->requests[$orig] = array('headers'=>null, 'body'=>null, 'httpRequest'=>$httpRequest); |
318 | $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); | 318 | $this->requests[$orig]['original_url'] = $orig; |
319 | } | 319 | $pool->attach($httpRequest); |
320 | if ($this->validateURL($redirectURL)) { | 320 | } |
321 | $this->debug('Redirect detected. Valid URL: '.$redirectURL); | 321 | } |
322 | // store any cookies | 322 | // did we get anything into the pool? |
323 | $cookies = $request->getResponseHeader('set-cookie'); | 323 | if (count($pool) > 0) { |
324 | if ($cookies && !is_array($cookies)) $cookies = array($cookies); | 324 | $this->debug('Sending request...'); |
325 | if ($cookies) $this->cookieJar->storeCookies($url, $cookies); | 325 | try { |
326 | $this->redirectQueue[$orig] = $redirectURL; | 326 | $pool->send(); |
327 | } else { | 327 | } catch (HttpRequestPoolException $e) { |
328 | $this->debug('Redirect detected. Invalid URL: '.$redirectURL); | 328 | // do nothing |
329 | } | 329 | } |
330 | } elseif (!$_header_only_type && $request->getMethod() === HttpRequest::METH_HEAD) { | 330 | $this->debug('Received responses'); |
331 | // the response content-type did not match our 'header only' types, | 331 | foreach($subset as $orig => $url) { |
332 | // but we'd issues a HEAD request because we assumed it would. So | 332 | if (!$isRedirect) $orig = $url; |
333 | // let's queue a proper GET request for this item... | 333 | $request = $this->requests[$orig]['httpRequest']; |
334 | $this->debug('Wrong guess at content-type, queing GET request'); | 334 | //$this->requests[$orig]['headers'] = $this->headersToString($request->getResponseHeader()); |
335 | $this->requests[$orig]['wrongGuess'] = true; | 335 | // getResponseHeader() doesn't return status line, so, for consistency... |
336 | $this->redirectQueue[$orig] = $this->requests[$orig]['effective_url']; | 336 | $this->requests[$orig]['headers'] = substr($request->getRawResponseMessage(), 0, $request->getResponseInfo('header_size')); |
337 | } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) { | 337 | // check content type |
338 | // check for <meta name='fragment' content='!'/> | 338 | // TODO: use getResponseHeader('content-type') or getResponseInfo() |
339 | // for AJAX sites, e.g. Blogger with its dynamic views templates. | 339 | if ($this->headerOnlyType($this->requests[$orig]['headers'])) { |
340 | // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification | 340 | $this->requests[$orig]['body'] = ''; |
341 | if (isset($this->requests[$orig]['body'])) { | 341 | $_header_only_type = true; |
342 | $redirectURL = $this->getUglyURL($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); | 342 | $this->debug('Header only type returned'); |
343 | if ($redirectURL) { | 343 | } else { |
344 | $this->debug('AJAX trigger (meta name="fragment" content="!") found. Queueing '.$redirectURL); | 344 | $this->requests[$orig]['body'] = $request->getResponseBody(); |
345 | $this->redirectQueue[$orig] = $redirectURL; | 345 | $_header_only_type = false; |
346 | } | 346 | } |
347 | } | 347 | $this->requests[$orig]['effective_url'] = $request->getResponseInfo('effective_url'); |
348 | } | 348 | $this->requests[$orig]['status_code'] = $status_code = $request->getResponseCode(); |
349 | //die($url.' -multi- '.$request->getResponseInfo('effective_url')); | 349 | // is redirect? |
350 | $pool->detach($request); | 350 | if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && $request->getResponseHeader('location')) { |
351 | unset($this->requests[$orig]['httpRequest'], $request); | 351 | $redirectURL = $request->getResponseHeader('location'); |
352 | /* | 352 | if (!preg_match('!^https?://!i', $redirectURL)) { |
353 | if ($this->minimiseMemoryUse) { | 353 | $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); |
354 | if ($this->cache($url)) { | 354 | } |
355 | unset($this->requests[$url]); | 355 | if ($this->validateURL($redirectURL)) { |
356 | } | 356 | $this->debug('Redirect detected. Valid URL: '.$redirectURL); |
357 | } | 357 | // store any cookies |
358 | */ | 358 | $cookies = $request->getResponseHeader('set-cookie'); |
359 | } | 359 | if ($cookies && !is_array($cookies)) $cookies = array($cookies); |
360 | } | 360 | if ($cookies) $this->cookieJar->storeCookies($url, $cookies); |
361 | } | 361 | $this->redirectQueue[$orig] = $redirectURL; |
362 | } catch (HttpException $e) { | 362 | } else { |
363 | $this->debug($e); | 363 | $this->debug('Redirect detected. Invalid URL: '.$redirectURL); |
364 | return false; | 364 | } |
365 | } | 365 | } elseif (!$_header_only_type && $request->getMethod() === HttpRequest::METH_HEAD) { |
366 | } | 366 | // the response content-type did not match our 'header only' types, |
367 | 367 | // but we'd issues a HEAD request because we assumed it would. So | |
368 | ////////////////////////////////////////////////////////// | 368 | // let's queue a proper GET request for this item... |
369 | // parallel (curl_multi_*) | 369 | $this->debug('Wrong guess at content-type, queing GET request'); |
370 | elseif ($this->method == self::METHOD_CURL_MULTI) { | 370 | $this->requests[$orig]['wrongGuess'] = true; |
371 | $this->debug('Starting parallel fetch (curl_multi_*)'); | 371 | $this->redirectQueue[$orig] = $this->requests[$orig]['effective_url']; |
372 | while (count($urls) > 0) { | 372 | } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) { |
373 | $this->debug('Processing set of '.min($this->maxParallelRequests, count($urls))); | 373 | // check for <meta name='fragment' content='!'/> |
374 | $subset = array_splice($urls, 0, $this->maxParallelRequests); | 374 | // for AJAX sites, e.g. Blogger with its dynamic views templates. |
375 | $pool = new RollingCurl(array($this, 'handleCurlResponse')); | 375 | // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification |
376 | $pool->window_size = count($subset); | 376 | if (isset($this->requests[$orig]['body'])) { |
377 | 377 | $redirectURL = $this->getRedirectURLfromHTML($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); | |
378 | foreach ($subset as $orig => $url) { | 378 | if ($redirectURL) { |
379 | if (!$isRedirect) $orig = $url; | 379 | $this->redirectQueue[$orig] = $redirectURL; |
380 | unset($this->redirectQueue[$orig]); | 380 | } |
381 | $this->debug("...$url"); | 381 | } |
382 | if (!$isRedirect && isset($this->requests[$url])) { | 382 | } |
383 | $this->debug("......in memory"); | 383 | //die($url.' -multi- '.$request->getResponseInfo('effective_url')); |
384 | /* | 384 | $pool->detach($request); |
385 | } elseif ($this->isCached($url)) { | 385 | unset($this->requests[$orig]['httpRequest'], $request); |
386 | $this->debug("......is cached"); | 386 | /* |
387 | if (!$this->minimiseMemoryUse) { | 387 | if ($this->minimiseMemoryUse) { |
388 | $this->requests[$url] = $this->getCached($url); | 388 | if ($this->cache($url)) { |
389 | } | 389 | unset($this->requests[$url]); |
390 | */ | 390 | } |
391 | } else { | 391 | } |
392 | $this->debug("......adding to pool"); | 392 | */ |
393 | $req_url = $this->rewriteUrls($url); | 393 | } |
394 | $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; | 394 | } |
395 | $req_url = $this->removeFragment($req_url); | 395 | } |
396 | if (!empty($this->headerOnlyTypes) && !isset($this->requests[$orig]['wrongGuess']) && $this->possibleUnsupportedType($req_url)) { | 396 | } catch (HttpException $e) { |
397 | $_meth = 'HEAD'; | 397 | $this->debug($e); |
398 | } else { | 398 | return false; |
399 | $_meth = 'GET'; | 399 | } |
400 | unset($this->requests[$orig]['wrongGuess']); | 400 | } |
401 | } | 401 | |
402 | $headers = array(); | 402 | ////////////////////////////////////////////////////////// |
403 | //$headers[] = 'User-Agent: '.$this->userAgent; | 403 | // parallel (curl_multi_*) |
404 | $headers[] = $this->getUserAgent($req_url); | 404 | elseif ($this->method == self::METHOD_CURL_MULTI) { |
405 | // add referer for picky sites | 405 | $this->debug('Starting parallel fetch (curl_multi_*)'); |
406 | $headers[] = 'Referer: '.$this->referer; | 406 | while (count($urls) > 0) { |
407 | // send cookies, if we have any | 407 | $this->debug('Processing set of '.min($this->maxParallelRequests, count($urls))); |
408 | if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { | 408 | $subset = array_splice($urls, 0, $this->maxParallelRequests); |
409 | $this->debug("......sending cookies: $cookies"); | 409 | $pool = new RollingCurl(array($this, 'handleCurlResponse')); |
410 | $headers[] = 'Cookie: '.$cookies; | 410 | $pool->window_size = count($subset); |
411 | } | 411 | |
412 | $httpRequest = new RollingCurlRequest($req_url, $_meth, null, $headers, array( | 412 | foreach ($subset as $orig => $url) { |
413 | CURLOPT_CONNECTTIMEOUT => $this->requestOptions['timeout'], | 413 | if (!$isRedirect) $orig = $url; |
414 | CURLOPT_TIMEOUT => $this->requestOptions['timeout'] | 414 | unset($this->redirectQueue[$orig]); |
415 | )); | 415 | $this->debug("...$url"); |
416 | $httpRequest->set_original_url($orig); | 416 | if (!$isRedirect && isset($this->requests[$url])) { |
417 | $this->requests[$orig] = array('headers'=>null, 'body'=>null, 'httpRequest'=>$httpRequest); | 417 | $this->debug("......in memory"); |
418 | $this->requests[$orig]['original_url'] = $orig; // TODO: is this needed anymore? | 418 | /* |
419 | $pool->add($httpRequest); | 419 | } elseif ($this->isCached($url)) { |
420 | } | 420 | $this->debug("......is cached"); |
421 | } | 421 | if (!$this->minimiseMemoryUse) { |
422 | // did we get anything into the pool? | 422 | $this->requests[$url] = $this->getCached($url); |
423 | if (count($pool) > 0) { | 423 | } |
424 | $this->debug('Sending request...'); | 424 | */ |
425 | $pool->execute(); // this will call handleCurlResponse() and populate $this->requests[$orig] | 425 | } else { |
426 | $this->debug('Received responses'); | 426 | $this->debug("......adding to pool"); |
427 | foreach($subset as $orig => $url) { | 427 | $req_url = $this->rewriteUrls($url); |
428 | if (!$isRedirect) $orig = $url; | 428 | $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; |
429 | // $this->requests[$orig]['headers'] | 429 | $req_url = $this->removeFragment($req_url); |
430 | // $this->requests[$orig]['body'] | 430 | if (!empty($this->headerOnlyTypes) && !isset($this->requests[$orig]['wrongGuess']) && $this->possibleUnsupportedType($req_url)) { |
431 | // $this->requests[$orig]['effective_url'] | 431 | $_meth = 'HEAD'; |
432 | // check content type | 432 | } else { |
433 | if ($this->headerOnlyType($this->requests[$orig]['headers'])) { | 433 | $_meth = 'GET'; |
434 | $this->requests[$orig]['body'] = ''; | 434 | unset($this->requests[$orig]['wrongGuess']); |
435 | $_header_only_type = true; | 435 | } |
436 | $this->debug('Header only type returned'); | 436 | $headers = array(); |
437 | } else { | 437 | //$headers[] = 'User-Agent: '.$this->userAgent; |
438 | $_header_only_type = false; | 438 | $headers[] = $this->getUserAgent($req_url); |
439 | } | 439 | // add referer for picky sites |
440 | $status_code = $this->requests[$orig]['status_code']; | 440 | $headers[] = 'Referer: '.$this->referer; |
441 | if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && isset($this->requests[$orig]['location'])) { | 441 | // send cookies, if we have any |
442 | $redirectURL = $this->requests[$orig]['location']; | 442 | if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { |
443 | if (!preg_match('!^https?://!i', $redirectURL)) { | 443 | $this->debug("......sending cookies: $cookies"); |
444 | $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); | 444 | $headers[] = 'Cookie: '.$cookies; |
445 | } | 445 | } |
446 | if ($this->validateURL($redirectURL)) { | 446 | $httpRequest = new RollingCurlRequest($req_url, $_meth, null, $headers, array( |
447 | $this->debug('Redirect detected. Valid URL: '.$redirectURL); | 447 | CURLOPT_CONNECTTIMEOUT => $this->requestOptions['timeout'], |
448 | // store any cookies | 448 | CURLOPT_TIMEOUT => $this->requestOptions['timeout'] |
449 | $cookies = $this->cookieJar->extractCookies($this->requests[$orig]['headers']); | 449 | )); |
450 | if (!empty($cookies)) $this->cookieJar->storeCookies($url, $cookies); | 450 | $httpRequest->set_original_url($orig); |
451 | $this->redirectQueue[$orig] = $redirectURL; | 451 | $this->requests[$orig] = array('headers'=>null, 'body'=>null, 'httpRequest'=>$httpRequest); |
452 | } else { | 452 | $this->requests[$orig]['original_url'] = $orig; // TODO: is this needed anymore? |
453 | $this->debug('Redirect detected. Invalid URL: '.$redirectURL); | 453 | $pool->add($httpRequest); |
454 | } | 454 | } |
455 | } elseif (!$_header_only_type && $this->requests[$orig]['method'] == 'HEAD') { | 455 | } |
456 | // the response content-type did not match our 'header only' types, | 456 | // did we get anything into the pool? |
457 | // but we'd issues a HEAD request because we assumed it would. So | 457 | if (count($pool) > 0) { |
458 | // let's queue a proper GET request for this item... | 458 | $this->debug('Sending request...'); |
459 | $this->debug('Wrong guess at content-type, queing GET request'); | 459 | $pool->execute(); // this will call handleCurlResponse() and populate $this->requests[$orig] |
460 | $this->requests[$orig]['wrongGuess'] = true; | 460 | $this->debug('Received responses'); |
461 | $this->redirectQueue[$orig] = $this->requests[$orig]['effective_url']; | 461 | foreach($subset as $orig => $url) { |
462 | } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) { | 462 | if (!$isRedirect) $orig = $url; |
463 | // check for <meta name='fragment' content='!'/> | 463 | // $this->requests[$orig]['headers'] |
464 | // for AJAX sites, e.g. Blogger with its dynamic views templates. | 464 | // $this->requests[$orig]['body'] |
465 | // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification | 465 | // $this->requests[$orig]['effective_url'] |
466 | if (isset($this->requests[$orig]['body'])) { | 466 | // check content type |
467 | $redirectURL = $this->getUglyURL($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); | 467 | if ($this->headerOnlyType($this->requests[$orig]['headers'])) { |
468 | if ($redirectURL) { | 468 | $this->requests[$orig]['body'] = ''; |
469 | $this->debug('AJAX trigger (meta name="fragment" content="!") found. Queueing '.$redirectURL); | 469 | $_header_only_type = true; |
470 | $this->redirectQueue[$orig] = $redirectURL; | 470 | $this->debug('Header only type returned'); |
471 | } | 471 | } else { |
472 | } | 472 | $_header_only_type = false; |
473 | } | 473 | } |
474 | // die($url.' -multi- '.$request->getResponseInfo('effective_url')); | 474 | $status_code = $this->requests[$orig]['status_code']; |
475 | unset($this->requests[$orig]['httpRequest'], $this->requests[$orig]['method']); | 475 | if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && isset($this->requests[$orig]['location'])) { |
476 | } | 476 | $redirectURL = $this->requests[$orig]['location']; |
477 | } | 477 | if (!preg_match('!^https?://!i', $redirectURL)) { |
478 | } | 478 | $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); |
479 | } | 479 | } |
480 | 480 | if ($this->validateURL($redirectURL)) { | |
481 | ////////////////////////////////////////////////////// | 481 | $this->debug('Redirect detected. Valid URL: '.$redirectURL); |
482 | // sequential (file_get_contents) | 482 | // store any cookies |
483 | else { | 483 | $cookies = $this->cookieJar->extractCookies($this->requests[$orig]['headers']); |
484 | $this->debug('Starting sequential fetch (file_get_contents)'); | 484 | if (!empty($cookies)) $this->cookieJar->storeCookies($url, $cookies); |
485 | $this->debug('Processing set of '.count($urls)); | 485 | $this->redirectQueue[$orig] = $redirectURL; |
486 | foreach ($urls as $orig => $url) { | 486 | } else { |
487 | if (!$isRedirect) $orig = $url; | 487 | $this->debug('Redirect detected. Invalid URL: '.$redirectURL); |
488 | unset($this->redirectQueue[$orig]); | 488 | } |
489 | $this->debug("...$url"); | 489 | } elseif (!$_header_only_type && $this->requests[$orig]['method'] == 'HEAD') { |
490 | if (!$isRedirect && isset($this->requests[$url])) { | 490 | // the response content-type did not match our 'header only' types, |
491 | $this->debug("......in memory"); | 491 | // but we'd issues a HEAD request because we assumed it would. So |
492 | /* | 492 | // let's queue a proper GET request for this item... |
493 | } elseif ($this->isCached($url)) { | 493 | $this->debug('Wrong guess at content-type, queing GET request'); |
494 | $this->debug("......is cached"); | 494 | $this->requests[$orig]['wrongGuess'] = true; |
495 | if (!$this->minimiseMemoryUse) { | 495 | $this->redirectQueue[$orig] = $this->requests[$orig]['effective_url']; |
496 | $this->requests[$url] = $this->getCached($url); | 496 | } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) { |
497 | } | 497 | // check for <meta name='fragment' content='!'/> |
498 | */ | 498 | // for AJAX sites, e.g. Blogger with its dynamic views templates. |
499 | } else { | 499 | // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification |
500 | $this->debug("Sending request for $url"); | 500 | if (isset($this->requests[$orig]['body'])) { |
501 | $this->requests[$orig]['original_url'] = $orig; | 501 | $redirectURL = $this->getRedirectURLfromHTML($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); |
502 | $req_url = $this->rewriteUrls($url); | 502 | if ($redirectURL) { |
503 | $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; | 503 | $this->redirectQueue[$orig] = $redirectURL; |
504 | $req_url = $this->removeFragment($req_url); | 504 | } |
505 | // send cookies, if we have any | 505 | } |
506 | $httpContext = $this->httpContext; | 506 | } |
507 | $httpContext['http']['header'] .= $this->getUserAgent($req_url)."\r\n"; | 507 | // die($url.' -multi- '.$request->getResponseInfo('effective_url')); |
508 | // add referer for picky sites | 508 | unset($this->requests[$orig]['httpRequest'], $this->requests[$orig]['method']); |
509 | $httpContext['http']['header'] .= 'Referer: '.$this->referer."\r\n"; | 509 | } |
510 | if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { | 510 | } |
511 | $this->debug("......sending cookies: $cookies"); | 511 | } |
512 | $httpContext['http']['header'] .= 'Cookie: '.$cookies."\r\n"; | 512 | } |
513 | } | 513 | |
514 | if (false !== ($html = @file_get_contents($req_url, false, stream_context_create($httpContext)))) { | 514 | ////////////////////////////////////////////////////// |
515 | $this->debug('Received response'); | 515 | // sequential (file_get_contents) |
516 | // get status code | 516 | else { |
517 | if (!isset($http_response_header[0]) || !preg_match('!^HTTP/\d+\.\d+\s+(\d+)!', trim($http_response_header[0]), $match)) { | 517 | $this->debug('Starting sequential fetch (file_get_contents)'); |
518 | $this->debug('Error: no status code found'); | 518 | $this->debug('Processing set of '.count($urls)); |
519 | // TODO: handle error - no status code | 519 | foreach ($urls as $orig => $url) { |
520 | } else { | 520 | if (!$isRedirect) $orig = $url; |
521 | $this->requests[$orig]['headers'] = $this->headersToString($http_response_header, false); | 521 | unset($this->redirectQueue[$orig]); |
522 | // check content type | 522 | $this->debug("...$url"); |
523 | if ($this->headerOnlyType($this->requests[$orig]['headers'])) { | 523 | if (!$isRedirect && isset($this->requests[$url])) { |
524 | $this->requests[$orig]['body'] = ''; | 524 | $this->debug("......in memory"); |
525 | } else { | 525 | /* |
526 | $this->requests[$orig]['body'] = $html; | 526 | } elseif ($this->isCached($url)) { |
527 | } | 527 | $this->debug("......is cached"); |
528 | $this->requests[$orig]['effective_url'] = $req_url; | 528 | if (!$this->minimiseMemoryUse) { |
529 | $this->requests[$orig]['status_code'] = $status_code = (int)$match[1]; | 529 | $this->requests[$url] = $this->getCached($url); |
530 | unset($match); | 530 | } |
531 | // handle redirect | 531 | */ |
532 | if (preg_match('/^Location:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) { | 532 | } else { |
533 | $this->requests[$orig]['location'] = trim($match[1]); | 533 | $this->debug("Sending request for $url"); |
534 | } | 534 | $this->requests[$orig]['original_url'] = $orig; |
535 | if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && isset($this->requests[$orig]['location'])) { | 535 | $req_url = $this->rewriteUrls($url); |
536 | $redirectURL = $this->requests[$orig]['location']; | 536 | $req_url = ($this->rewriteHashbangFragment) ? $this->rewriteHashbangFragment($req_url) : $req_url; |
537 | if (!preg_match('!^https?://!i', $redirectURL)) { | 537 | $req_url = $this->removeFragment($req_url); |
538 | $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); | 538 | // send cookies, if we have any |
539 | } | 539 | $httpContext = $this->httpContext; |
540 | if ($this->validateURL($redirectURL)) { | 540 | $httpContext['http']['header'] .= $this->getUserAgent($req_url)."\r\n"; |
541 | $this->debug('Redirect detected. Valid URL: '.$redirectURL); | 541 | // add referer for picky sites |
542 | // store any cookies | 542 | $httpContext['http']['header'] .= 'Referer: '.$this->referer."\r\n"; |
543 | $cookies = $this->cookieJar->extractCookies($this->requests[$orig]['headers']); | 543 | if ($cookies = $this->cookieJar->getMatchingCookies($req_url)) { |
544 | if (!empty($cookies)) $this->cookieJar->storeCookies($url, $cookies); | 544 | $this->debug("......sending cookies: $cookies"); |
545 | $this->redirectQueue[$orig] = $redirectURL; | 545 | $httpContext['http']['header'] .= 'Cookie: '.$cookies."\r\n"; |
546 | } else { | 546 | } |
547 | $this->debug('Redirect detected. Invalid URL: '.$redirectURL); | 547 | if (false !== ($html = @file_get_contents($req_url, false, stream_context_create($httpContext)))) { |
548 | } | 548 | $this->debug('Received response'); |
549 | } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) { | 549 | // get status code |
550 | // check for <meta name='fragment' content='!'/> | 550 | if (!isset($http_response_header[0]) || !preg_match('!^HTTP/\d+\.\d+\s+(\d+)!', trim($http_response_header[0]), $match)) { |
551 | // for AJAX sites, e.g. Blogger with its dynamic views templates. | 551 | $this->debug('Error: no status code found'); |
552 | // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification | 552 | // TODO: handle error - no status code |
553 | if (isset($this->requests[$orig]['body'])) { | 553 | } else { |
554 | $redirectURL = $this->getUglyURL($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); | 554 | $this->requests[$orig]['headers'] = $this->headersToString($http_response_header, false); |
555 | if ($redirectURL) { | 555 | // check content type |
556 | $this->debug('AJAX trigger (meta name="fragment" content="!") found. Queueing '.$redirectURL); | 556 | if ($this->headerOnlyType($this->requests[$orig]['headers'])) { |
557 | $this->redirectQueue[$orig] = $redirectURL; | 557 | $this->requests[$orig]['body'] = ''; |
558 | } | 558 | } else { |
559 | } | 559 | $this->requests[$orig]['body'] = $html; |
560 | } | 560 | } |
561 | } | 561 | $this->requests[$orig]['effective_url'] = $req_url; |
562 | } else { | 562 | $this->requests[$orig]['status_code'] = $status_code = (int)$match[1]; |
563 | $this->debug('Error retrieving URL'); | 563 | unset($match); |
564 | //print_r($req_url); | 564 | // handle redirect |
565 | //print_r($http_response_header); | 565 | if (preg_match('/^Location:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) { |
566 | //print_r($html); | 566 | $this->requests[$orig]['location'] = trim($match[1]); |
567 | 567 | } | |
568 | // TODO: handle error - failed to retrieve URL | 568 | if ((in_array($status_code, array(300, 301, 302, 303, 307)) || $status_code > 307 && $status_code < 400) && isset($this->requests[$orig]['location'])) { |
569 | } | 569 | $redirectURL = $this->requests[$orig]['location']; |
570 | } | 570 | if (!preg_match('!^https?://!i', $redirectURL)) { |
571 | } | 571 | $redirectURL = SimplePie_Misc::absolutize_url($redirectURL, $url); |
572 | } | 572 | } |
573 | } | 573 | if ($this->validateURL($redirectURL)) { |
574 | 574 | $this->debug('Redirect detected. Valid URL: '.$redirectURL); | |
575 | public function handleCurlResponse($response, $info, $request) { | 575 | // store any cookies |
576 | $orig = $request->url_original; | 576 | $cookies = $this->cookieJar->extractCookies($this->requests[$orig]['headers']); |
577 | $this->requests[$orig]['headers'] = substr($response, 0, $info['header_size']); | 577 | if (!empty($cookies)) $this->cookieJar->storeCookies($url, $cookies); |
578 | $this->requests[$orig]['body'] = substr($response, $info['header_size']); | 578 | $this->redirectQueue[$orig] = $redirectURL; |
579 | $this->requests[$orig]['method'] = $request->method; | 579 | } else { |
580 | $this->requests[$orig]['effective_url'] = $info['url']; | 580 | $this->debug('Redirect detected. Invalid URL: '.$redirectURL); |
581 | $this->requests[$orig]['status_code'] = (int)$info['http_code']; | 581 | } |
582 | if (preg_match('/^Location:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) { | 582 | } elseif (strpos($this->requests[$orig]['effective_url'], '_escaped_fragment_') === false) { |
583 | $this->requests[$orig]['location'] = trim($match[1]); | 583 | // check for <meta name='fragment' content='!'/> |
584 | } | 584 | // for AJAX sites, e.g. Blogger with its dynamic views templates. |
585 | } | 585 | // Based on Google's spec: https://developers.google.com/webmasters/ajax-crawling/docs/specification |
586 | 586 | if (isset($this->requests[$orig]['body'])) { | |
587 | protected function headersToString(array $headers, $associative=true) { | 587 | $redirectURL = $this->getRedirectURLfromHTML($this->requests[$orig]['effective_url'], substr($this->requests[$orig]['body'], 0, 4000)); |
588 | if (!$associative) { | 588 | if ($redirectURL) { |
589 | return implode("\n", $headers); | 589 | $this->redirectQueue[$orig] = $redirectURL; |
590 | } else { | 590 | } |
591 | $str = ''; | 591 | } |
592 | foreach ($headers as $key => $val) { | 592 | } |
593 | if (is_array($val)) { | 593 | } |
594 | foreach ($val as $v) $str .= "$key: $v\n"; | 594 | } else { |
595 | } else { | 595 | $this->debug('Error retrieving URL'); |
596 | $str .= "$key: $val\n"; | 596 | //print_r($req_url); |
597 | } | 597 | //print_r($http_response_header); |
598 | } | 598 | //print_r($html); |
599 | return rtrim($str); | 599 | |
600 | } | 600 | // TODO: handle error - failed to retrieve URL |
601 | } | 601 | } |
602 | 602 | } | |
603 | public function get($url, $remove=false, $gzdecode=true) { | 603 | } |
604 | $url = "$url"; | 604 | } |
605 | if (isset($this->requests[$url]) && isset($this->requests[$url]['body'])) { | 605 | } |
606 | $this->debug("URL already fetched - in memory ($url, effective: {$this->requests[$url]['effective_url']})"); | 606 | |
607 | $response = $this->requests[$url]; | 607 | public function handleCurlResponse($response, $info, $request) { |
608 | /* | 608 | $orig = $request->url_original; |
609 | } elseif ($this->isCached($url)) { | 609 | $this->requests[$orig]['headers'] = substr($response, 0, $info['header_size']); |
610 | $this->debug("URL already fetched - in disk cache ($url)"); | 610 | $this->requests[$orig]['body'] = substr($response, $info['header_size']); |
611 | $response = $this->getCached($url); | 611 | $this->requests[$orig]['method'] = $request->method; |
612 | $this->requests[$url] = $response; | 612 | $this->requests[$orig]['effective_url'] = $info['url']; |
613 | */ | 613 | $this->requests[$orig]['status_code'] = (int)$info['http_code']; |
614 | } else { | 614 | if (preg_match('/^Location:(.*?)$/mi', $this->requests[$orig]['headers'], $match)) { |
615 | $this->debug("Fetching URL ($url)"); | 615 | $this->requests[$orig]['location'] = trim($match[1]); |
616 | $this->fetchAll(array($url)); | 616 | } |
617 | if (isset($this->requests[$url]) && isset($this->requests[$url]['body'])) { | 617 | } |
618 | $response = $this->requests[$url]; | 618 | |
619 | } else { | 619 | protected function headersToString(array $headers, $associative=true) { |
620 | $this->debug("Request failed"); | 620 | if (!$associative) { |
621 | $response = false; | 621 | return implode("\n", $headers); |
622 | } | 622 | } else { |
623 | } | 623 | $str = ''; |
624 | /* | 624 | foreach ($headers as $key => $val) { |
625 | if ($this->minimiseMemoryUse && $response) { | 625 | if (is_array($val)) { |
626 | $this->cache($url); | 626 | foreach ($val as $v) $str .= "$key: $v\n"; |
627 | unset($this->requests[$url]); | 627 | } else { |
628 | } | 628 | $str .= "$key: $val\n"; |
629 | */ | 629 | } |
630 | if ($remove && $response) unset($this->requests[$url]); | 630 | } |
631 | if ($gzdecode && stripos($response['headers'], 'Content-Encoding: gzip')) { | 631 | return rtrim($str); |
632 | if ($html = gzdecode($response['body'])) { | 632 | } |
633 | $response['body'] = $html; | 633 | } |
634 | } | 634 | |
635 | } | 635 | public function get($url, $remove=false, $gzdecode=true) { |
636 | return $response; | 636 | $url = "$url"; |
637 | } | 637 | if (isset($this->requests[$url]) && isset($this->requests[$url]['body'])) { |
638 | 638 | $this->debug("URL already fetched - in memory ($url, effective: {$this->requests[$url]['effective_url']})"); | |
639 | public function parallelSupport() { | 639 | $response = $this->requests[$url]; |
640 | return class_exists('HttpRequestPool') || function_exists('curl_multi_init'); | 640 | /* |
641 | } | 641 | } elseif ($this->isCached($url)) { |
642 | 642 | $this->debug("URL already fetched - in disk cache ($url)"); | |
643 | private function headerOnlyType($headers) { | 643 | $response = $this->getCached($url); |
644 | if (preg_match('!^Content-Type:\s*(([a-z-]+)/([^;\r\n ]+))!im', $headers, $match)) { | 644 | $this->requests[$url] = $response; |
645 | // look for full mime type (e.g. image/jpeg) or just type (e.g. image) | 645 | */ |
646 | $match[1] = strtolower(trim($match[1])); | 646 | } else { |
647 | $match[2] = strtolower(trim($match[2])); | 647 | $this->debug("Fetching URL ($url)"); |
648 | foreach (array($match[1], $match[2]) as $mime) { | 648 | $this->fetchAll(array($url)); |
649 | if (in_array($mime, $this->headerOnlyTypes)) return true; | 649 | if (isset($this->requests[$url]) && isset($this->requests[$url]['body'])) { |
650 | } | 650 | $response = $this->requests[$url]; |
651 | } | 651 | } else { |
652 | return false; | 652 | $this->debug("Request failed"); |
653 | } | 653 | $response = false; |
654 | 654 | } | |
655 | private function possibleUnsupportedType($url) { | 655 | } |
656 | $path = @parse_url($url, PHP_URL_PATH); | 656 | /* |
657 | if ($path && strpos($path, '.') !== false) { | 657 | if ($this->minimiseMemoryUse && $response) { |
658 | $ext = strtolower(trim(pathinfo($path, PATHINFO_EXTENSION))); | 658 | $this->cache($url); |
659 | return in_array($ext, $this->headerOnlyClues); | 659 | unset($this->requests[$url]); |
660 | } | 660 | } |
661 | return false; | 661 | */ |
662 | } | 662 | if ($remove && $response) unset($this->requests[$url]); |
663 | } | 663 | if ($gzdecode && stripos($response['headers'], 'Content-Encoding: gzip')) { |
664 | 664 | if ($html = gzdecode($response['body'])) { | |
665 | // gzdecode from http://www.php.net/manual/en/function.gzdecode.php#82930 | 665 | $response['body'] = $html; |
666 | if (!function_exists('gzdecode')) { | 666 | } |
667 | function gzdecode($data,&$filename='',&$error='',$maxlength=null) | 667 | } |
668 | { | 668 | return $response; |
669 | $len = strlen($data); | 669 | } |
670 | if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) { | 670 | |
671 | $error = "Not in GZIP format."; | 671 | public function parallelSupport() { |
672 | return null; // Not GZIP format (See RFC 1952) | 672 | return class_exists('HttpRequestPool') || function_exists('curl_multi_init'); |
673 | } | 673 | } |
674 | $method = ord(substr($data,2,1)); // Compression method | 674 | |
675 | $flags = ord(substr($data,3,1)); // Flags | 675 | private function headerOnlyType($headers) { |
676 | if ($flags & 31 != $flags) { | 676 | if (preg_match('!^Content-Type:\s*(([a-z-]+)/([^;\r\n ]+))!im', $headers, $match)) { |
677 | $error = "Reserved bits not allowed."; | 677 | // look for full mime type (e.g. image/jpeg) or just type (e.g. image) |
678 | return null; | 678 | $match[1] = strtolower(trim($match[1])); |
679 | } | 679 | $match[2] = strtolower(trim($match[2])); |
680 | // NOTE: $mtime may be negative (PHP integer limitations) | 680 | foreach (array($match[1], $match[2]) as $mime) { |
681 | $mtime = unpack("V", substr($data,4,4)); | 681 | if (in_array($mime, $this->headerOnlyTypes)) return true; |
682 | $mtime = $mtime[1]; | 682 | } |
683 | $xfl = substr($data,8,1); | 683 | } |
684 | $os = substr($data,8,1); | 684 | return false; |
685 | $headerlen = 10; | 685 | } |
686 | $extralen = 0; | 686 | |
687 | $extra = ""; | 687 | private function possibleUnsupportedType($url) { |
688 | if ($flags & 4) { | 688 | $path = @parse_url($url, PHP_URL_PATH); |
689 | // 2-byte length prefixed EXTRA data in header | 689 | if ($path && strpos($path, '.') !== false) { |
690 | if ($len - $headerlen - 2 < 8) { | 690 | $ext = strtolower(trim(pathinfo($path, PATHINFO_EXTENSION))); |
691 | return false; // invalid | 691 | return in_array($ext, $this->headerOnlyClues); |
692 | } | 692 | } |
693 | $extralen = unpack("v",substr($data,8,2)); | 693 | return false; |
694 | $extralen = $extralen[1]; | 694 | } |
695 | if ($len - $headerlen - 2 - $extralen < 8) { | 695 | } |
696 | return false; // invalid | 696 | |
697 | } | 697 | // gzdecode from http://www.php.net/manual/en/function.gzdecode.php#82930 |
698 | $extra = substr($data,10,$extralen); | 698 | if (!function_exists('gzdecode')) { |
699 | $headerlen += 2 + $extralen; | 699 | function gzdecode($data,&$filename='',&$error='',$maxlength=null) |
700 | } | 700 | { |
701 | $filenamelen = 0; | 701 | $len = strlen($data); |
702 | $filename = ""; | 702 | if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) { |
703 | if ($flags & 8) { | 703 | $error = "Not in GZIP format."; |
704 | // C-style string | 704 | return null; // Not GZIP format (See RFC 1952) |
705 | if ($len - $headerlen - 1 < 8) { | 705 | } |
706 | return false; // invalid | 706 | $method = ord(substr($data,2,1)); // Compression method |
707 | } | 707 | $flags = ord(substr($data,3,1)); // Flags |
708 | $filenamelen = strpos(substr($data,$headerlen),chr(0)); | 708 | if ($flags & 31 != $flags) { |
709 | if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { | 709 | $error = "Reserved bits not allowed."; |
710 | return false; // invalid | 710 | return null; |
711 | } | 711 | } |
712 | $filename = substr($data,$headerlen,$filenamelen); | 712 | // NOTE: $mtime may be negative (PHP integer limitations) |
713 | $headerlen += $filenamelen + 1; | 713 | $mtime = unpack("V", substr($data,4,4)); |
714 | } | 714 | $mtime = $mtime[1]; |
715 | $commentlen = 0; | 715 | $xfl = substr($data,8,1); |
716 | $comment = ""; | 716 | $os = substr($data,8,1); |
717 | if ($flags & 16) { | 717 | $headerlen = 10; |
718 | // C-style string COMMENT data in header | 718 | $extralen = 0; |
719 | if ($len - $headerlen - 1 < 8) { | 719 | $extra = ""; |
720 | return false; // invalid | 720 | if ($flags & 4) { |
721 | } | 721 | // 2-byte length prefixed EXTRA data in header |
722 | $commentlen = strpos(substr($data,$headerlen),chr(0)); | 722 | if ($len - $headerlen - 2 < 8) { |
723 | if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { | 723 | return false; // invalid |
724 | return false; // Invalid header format | 724 | } |
725 | } | 725 | $extralen = unpack("v",substr($data,8,2)); |
726 | $comment = substr($data,$headerlen,$commentlen); | 726 | $extralen = $extralen[1]; |
727 | $headerlen += $commentlen + 1; | 727 | if ($len - $headerlen - 2 - $extralen < 8) { |
728 | } | 728 | return false; // invalid |
729 | $headercrc = ""; | 729 | } |
730 | if ($flags & 2) { | 730 | $extra = substr($data,10,$extralen); |
731 | // 2-bytes (lowest order) of CRC32 on header present | 731 | $headerlen += 2 + $extralen; |
732 | if ($len - $headerlen - 2 < 8) { | 732 | } |
733 | return false; // invalid | 733 | $filenamelen = 0; |
734 | } | 734 | $filename = ""; |
735 | $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff; | 735 | if ($flags & 8) { |
736 | $headercrc = unpack("v", substr($data,$headerlen,2)); | 736 | // C-style string |
737 | $headercrc = $headercrc[1]; | 737 | if ($len - $headerlen - 1 < 8) { |
738 | if ($headercrc != $calccrc) { | 738 | return false; // invalid |
739 | $error = "Header checksum failed."; | 739 | } |
740 | return false; // Bad header CRC | 740 | $filenamelen = strpos(substr($data,$headerlen),chr(0)); |
741 | } | 741 | if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { |
742 | $headerlen += 2; | 742 | return false; // invalid |
743 | } | 743 | } |
744 | // GZIP FOOTER | 744 | $filename = substr($data,$headerlen,$filenamelen); |
745 | $datacrc = unpack("V",substr($data,-8,4)); | 745 | $headerlen += $filenamelen + 1; |
746 | $datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF); | 746 | } |
747 | $isize = unpack("V",substr($data,-4)); | 747 | $commentlen = 0; |
748 | $isize = $isize[1]; | 748 | $comment = ""; |
749 | // decompression: | 749 | if ($flags & 16) { |
750 | $bodylen = $len-$headerlen-8; | 750 | // C-style string COMMENT data in header |
751 | if ($bodylen < 1) { | 751 | if ($len - $headerlen - 1 < 8) { |
752 | // IMPLEMENTATION BUG! | 752 | return false; // invalid |
753 | return null; | 753 | } |
754 | } | 754 | $commentlen = strpos(substr($data,$headerlen),chr(0)); |
755 | $body = substr($data,$headerlen,$bodylen); | 755 | if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { |
756 | $data = ""; | 756 | return false; // Invalid header format |
757 | if ($bodylen > 0) { | 757 | } |
758 | switch ($method) { | 758 | $comment = substr($data,$headerlen,$commentlen); |
759 | case 8: | 759 | $headerlen += $commentlen + 1; |
760 | // Currently the only supported compression method: | 760 | } |
761 | $data = gzinflate($body,$maxlength); | 761 | $headercrc = ""; |
762 | break; | 762 | if ($flags & 2) { |
763 | default: | 763 | // 2-bytes (lowest order) of CRC32 on header present |
764 | $error = "Unknown compression method."; | 764 | if ($len - $headerlen - 2 < 8) { |
765 | return false; | 765 | return false; // invalid |
766 | } | 766 | } |
767 | } // zero-byte body content is allowed | 767 | $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff; |
768 | // Verifiy CRC32 | 768 | $headercrc = unpack("v", substr($data,$headerlen,2)); |
769 | $crc = sprintf("%u",crc32($data)); | 769 | $headercrc = $headercrc[1]; |
770 | $crcOK = $crc == $datacrc; | 770 | if ($headercrc != $calccrc) { |
771 | $lenOK = $isize == strlen($data); | 771 | $error = "Header checksum failed."; |
772 | if (!$lenOK || !$crcOK) { | 772 | return false; // Bad header CRC |
773 | $error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.'); | 773 | } |
774 | return false; | 774 | $headerlen += 2; |
775 | } | 775 | } |
776 | return $data; | 776 | // GZIP FOOTER |
777 | } | 777 | $datacrc = unpack("V",substr($data,-8,4)); |
778 | } | 778 | $datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF); |
779 | ?> \ No newline at end of file | 779 | $isize = unpack("V",substr($data,-4)); |
780 | $isize = $isize[1]; | ||
781 | // decompression: | ||
782 | $bodylen = $len-$headerlen-8; | ||
783 | if ($bodylen < 1) { | ||
784 | // IMPLEMENTATION BUG! | ||
785 | return null; | ||
786 | } | ||
787 | $body = substr($data,$headerlen,$bodylen); | ||
788 | $data = ""; | ||
789 | if ($bodylen > 0) { | ||
790 | switch ($method) { | ||
791 | case 8: | ||
792 | // Currently the only supported compression method: | ||
793 | $data = gzinflate($body,$maxlength); | ||
794 | break; | ||
795 | default: | ||
796 | $error = "Unknown compression method."; | ||
797 | return false; | ||
798 | } | ||
799 | } // zero-byte body content is allowed | ||
800 | // Verifiy CRC32 | ||
801 | $crc = sprintf("%u",crc32($data)); | ||
802 | $crcOK = $crc == $datacrc; | ||
803 | $lenOK = $isize == strlen($data); | ||
804 | if (!$lenOK || !$crcOK) { | ||
805 | $error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.'); | ||
806 | return false; | ||
807 | } | ||
808 | return $data; | ||
809 | } | ||
810 | } \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/humble-http-agent/SimplePie_HumbleHttpAgent.php b/inc/3rdparty/libraries/humble-http-agent/SimplePie_HumbleHttpAgent.php index ecd46d5f..c524a1ee 100644 --- a/inc/3rdparty/libraries/humble-http-agent/SimplePie_HumbleHttpAgent.php +++ b/inc/3rdparty/libraries/humble-http-agent/SimplePie_HumbleHttpAgent.php | |||
@@ -1,79 +1,78 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Humble HTTP Agent extension for SimplePie_File | 3 | * Humble HTTP Agent extension for SimplePie_File |
4 | * | 4 | * |
5 | * This class is designed to extend and override SimplePie_File | 5 | * This class is designed to extend and override SimplePie_File |
6 | * in order to prevent duplicate HTTP requests being sent out. | 6 | * in order to prevent duplicate HTTP requests being sent out. |
7 | * The idea is to initialise an instance of Humble HTTP Agent | 7 | * The idea is to initialise an instance of Humble HTTP Agent |
8 | * and attach it, to a static class variable, of this class. | 8 | * and attach it, to a static class variable, of this class. |
9 | * SimplePie will then automatically initialise this class | 9 | * SimplePie will then automatically initialise this class |
10 | * | 10 | * |
11 | * @date 2011-02-28 | 11 | * @date 2011-02-28 |
12 | */ | 12 | */ |
13 | 13 | ||
14 | class SimplePie_HumbleHttpAgent extends SimplePie_File | 14 | class SimplePie_HumbleHttpAgent extends SimplePie_File |
15 | { | 15 | { |
16 | protected static $agent; | 16 | protected static $agent; |
17 | var $url; | 17 | var $url; |
18 | var $useragent; | 18 | var $useragent; |
19 | var $success = true; | 19 | var $success = true; |
20 | var $headers = array(); | 20 | var $headers = array(); |
21 | var $body; | 21 | var $body; |
22 | var $status_code; | 22 | var $status_code; |
23 | var $redirects = 0; | 23 | var $redirects = 0; |
24 | var $error; | 24 | var $error; |
25 | var $method = SIMPLEPIE_FILE_SOURCE_NONE; | 25 | var $method = SIMPLEPIE_FILE_SOURCE_NONE; |
26 | 26 | ||
27 | public static function set_agent(HumbleHttpAgent $agent) { | 27 | public static function set_agent(HumbleHttpAgent $agent) { |
28 | self::$agent = $agent; | 28 | self::$agent = $agent; |
29 | } | 29 | } |
30 | 30 | ||
31 | public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) { | 31 | public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) { |
32 | if (class_exists('idna_convert')) | 32 | if (class_exists('idna_convert')) |
33 | { | 33 | { |
34 | $idn = new idna_convert(); | 34 | $idn = new idna_convert(); |
35 | $parsed = SimplePie_Misc::parse_url($url); | 35 | $parsed = SimplePie_Misc::parse_url($url); |
36 | $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); | 36 | $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); |
37 | } | 37 | } |
38 | $this->url = $url; | 38 | $this->url = $url; |
39 | $this->useragent = $useragent; | 39 | $this->useragent = $useragent; |
40 | if (preg_match('/^http(s)?:\/\//i', $url)) | 40 | if (preg_match('/^http(s)?:\/\//i', $url)) |
41 | { | 41 | { |
42 | if (!is_array($headers)) | 42 | if (!is_array($headers)) |
43 | { | 43 | { |
44 | $headers = array(); | 44 | $headers = array(); |
45 | } | 45 | } |
46 | $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; | 46 | $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; |
47 | $headers2 = array(); | 47 | $headers2 = array(); |
48 | foreach ($headers as $key => $value) { | 48 | foreach ($headers as $key => $value) { |
49 | $headers2[] = "$key: $value"; | 49 | $headers2[] = "$key: $value"; |
50 | } | 50 | } |
51 | //TODO: allow for HTTP headers | 51 | //TODO: allow for HTTP headers |
52 | // curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); | 52 | // curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); |
53 | 53 | ||
54 | $response = self::$agent->get($url); | 54 | $response = self::$agent->get($url); |
55 | 55 | ||
56 | if ($response === false || !isset($response['status_code'])) { | 56 | if ($response === false || !isset($response['status_code'])) { |
57 | $this->error = 'failed to fetch URL'; | 57 | $this->error = 'failed to fetch URL'; |
58 | $this->success = false; | 58 | $this->success = false; |
59 | } else { | 59 | } else { |
60 | // The extra lines at the end are there to satisfy SimplePie's HTTP parser. | 60 | // The extra lines at the end are there to satisfy SimplePie's HTTP parser. |
61 | // The class expects a full HTTP message, whereas we're giving it only | 61 | // The class expects a full HTTP message, whereas we're giving it only |
62 | // headers - the new lines indicate the start of the body. | 62 | // headers - the new lines indicate the start of the body. |
63 | $parser = new SimplePie_HTTP_Parser($response['headers']."\r\n\r\n"); | 63 | $parser = new SimplePie_HTTP_Parser($response['headers']."\r\n\r\n"); |
64 | if ($parser->parse()) { | 64 | if ($parser->parse()) { |
65 | $this->headers = $parser->headers; | 65 | $this->headers = $parser->headers; |
66 | //$this->body = $parser->body; | 66 | //$this->body = $parser->body; |
67 | $this->body = $response['body']; | 67 | $this->body = $response['body']; |
68 | $this->status_code = $parser->status_code; | 68 | $this->status_code = $parser->status_code; |
69 | } | 69 | } |
70 | } | 70 | } |
71 | } | 71 | } |
72 | else | 72 | else |
73 | { | 73 | { |
74 | $this->error = 'invalid URL'; | 74 | $this->error = 'invalid URL'; |
75 | $this->success = false; | 75 | $this->success = false; |
76 | } | 76 | } |
77 | } | 77 | } |
78 | } | 78 | } \ No newline at end of file |
79 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/language-detect/LanguageDetect.php b/inc/3rdparty/libraries/language-detect/LanguageDetect.php index 09b11546..382d869c 100644 --- a/inc/3rdparty/libraries/language-detect/LanguageDetect.php +++ b/inc/3rdparty/libraries/language-detect/LanguageDetect.php | |||
@@ -6,23 +6,24 @@ | |||
6 | * Attempts to detect the language of a sample of text by correlating ranked | 6 | * Attempts to detect the language of a sample of text by correlating ranked |
7 | * 3-gram frequencies to a table of 3-gram frequencies of known languages. | 7 | * 3-gram frequencies to a table of 3-gram frequencies of known languages. |
8 | * | 8 | * |
9 | * Implements a version of a technique originally proposed by Cavnar & Trenkle | 9 | * Implements a version of a technique originally proposed by Cavnar & Trenkle |
10 | * (1994): "N-Gram-Based Text Categorization" | 10 | * (1994): "N-Gram-Based Text Categorization" |
11 | * | 11 | * |
12 | * PHP versions 4 and 5 | 12 | * PHP version 5 |
13 | * | 13 | * |
14 | * @category Text | 14 | * @category Text |
15 | * @package Text_LanguageDetect | 15 | * @package Text_LanguageDetect |
16 | * @author Nicholas Pisarro <infinityminusnine+pear@gmail.com> | 16 | * @author Nicholas Pisarro <infinityminusnine+pear@gmail.com> |
17 | * @copyright 2005-2006 Nicholas Pisarro | 17 | * @copyright 2005-2006 Nicholas Pisarro |
18 | * @license http://www.debian.org/misc/bsd.license BSD | 18 | * @license http://www.debian.org/misc/bsd.license BSD |
19 | * @version CVS: $Id: LanguageDetect.php,v 1.20 2008/07/01 02:09:15 taak Exp $ | 19 | * @version SVN: $Id: LanguageDetect.php 322353 2012-01-16 08:41:43Z cweiske $ |
20 | * @link http://pear.php.net/package/Text_LanguageDetect/ | 20 | * @link http://pear.php.net/package/Text_LanguageDetect/ |
21 | * @link http://langdetect.blogspot.com/ | 21 | * @link http://langdetect.blogspot.com/ |
22 | */ | 22 | */ |
23 | 23 | ||
24 | //require_once 'PEAR.php'; | 24 | require_once 'LanguageDetect/Exception.php'; |
25 | require_once 'Parser.php'; | 25 | require_once 'LanguageDetect/Parser.php'; |
26 | require_once 'LanguageDetect/ISO639.php'; | ||
26 | 27 | ||
27 | /** | 28 | /** |
28 | * Language detection class | 29 | * Language detection class |
@@ -41,9 +42,10 @@ require_once 'Parser.php'; | |||
41 | * | 42 | * |
42 | * echo "Supported languages:\n"; | 43 | * echo "Supported languages:\n"; |
43 | * | 44 | * |
44 | * $langs = $l->getLanguages(); | 45 | * try { |
45 | * if (PEAR::isError($langs)) { | 46 | * $langs = $l->getLanguages(); |
46 | * die($langs->getMessage()); | 47 | * } catch (Text_LanguageDetect_Exception $e) { |
48 | * die($e->getMessage()); | ||
47 | * } | 49 | * } |
48 | * | 50 | * |
49 | * sort($langs); | 51 | * sort($langs); |
@@ -54,38 +56,38 @@ require_once 'Parser.php'; | |||
54 | * } | 56 | * } |
55 | * </code> | 57 | * </code> |
56 | * | 58 | * |
57 | * @category Text | 59 | * @category Text |
58 | * @package Text_LanguageDetect | 60 | * @package Text_LanguageDetect |
59 | * @author Nicholas Pisarro <infinityminusnine+pear@gmail.com> | 61 | * @author Nicholas Pisarro <infinityminusnine+pear@gmail.com> |
60 | * @copyright 2005 Nicholas Pisarro | 62 | * @copyright 2005 Nicholas Pisarro |
61 | * @license http://www.debian.org/misc/bsd.license BSD | 63 | * @license http://www.debian.org/misc/bsd.license BSD |
62 | * @version Release: @package_version@ | 64 | * @version Release: @package_version@ |
63 | * @todo allow users to generate their own language models | 65 | * @link http://pear.php.net/package/Text_LanguageDetect/ |
66 | * @todo allow users to generate their own language models | ||
64 | */ | 67 | */ |
65 | |||
66 | class Text_LanguageDetect | 68 | class Text_LanguageDetect |
67 | { | 69 | { |
68 | /** | 70 | /** |
69 | * The filename that stores the trigram data for the detector | 71 | * The filename that stores the trigram data for the detector |
70 | * | 72 | * |
71 | * If this value starts with a slash (/) or a dot (.) the value of | 73 | * If this value starts with a slash (/) or a dot (.) the value of |
72 | * $this->_data_dir will be ignored | 74 | * $this->_data_dir will be ignored |
73 | * | 75 | * |
74 | * @var string | 76 | * @var string |
75 | * @access private | 77 | * @access private |
76 | */ | 78 | */ |
77 | var $_db_filename = './lang.dat'; | 79 | var $_db_filename = 'lang.dat'; |
78 | 80 | ||
79 | /** | 81 | /** |
80 | * The filename that stores the unicode block definitions | 82 | * The filename that stores the unicode block definitions |
81 | * | 83 | * |
82 | * If this value starts with a slash (/) or a dot (.) the value of | 84 | * If this value starts with a slash (/) or a dot (.) the value of |
83 | * $this->_data_dir will be ignored | 85 | * $this->_data_dir will be ignored |
84 | * | 86 | * |
85 | * @var string | 87 | * @var string |
86 | * @access private | 88 | * @access private |
87 | */ | 89 | */ |
88 | var $_unicode_db_filename = './unicode_blocks.dat'; | 90 | var $_unicode_db_filename = 'unicode_blocks.dat'; |
89 | 91 | ||
90 | /** | 92 | /** |
91 | * The data directory | 93 | * The data directory |
@@ -99,11 +101,8 @@ class Text_LanguageDetect | |||
99 | 101 | ||
100 | /** | 102 | /** |
101 | * The trigram data for comparison | 103 | * The trigram data for comparison |
102 | * | ||
103 | * Will be loaded on start from $this->_db_filename | ||
104 | * | 104 | * |
105 | * May be set to a PEAR_Error object if there is an error during its | 105 | * Will be loaded on start from $this->_db_filename |
106 | * initialization | ||
107 | * | 106 | * |
108 | * @var array | 107 | * @var array |
109 | * @access private | 108 | * @access private |
@@ -120,7 +119,7 @@ class Text_LanguageDetect | |||
120 | 119 | ||
121 | /** | 120 | /** |
122 | * The size of the trigram data arrays | 121 | * The size of the trigram data arrays |
123 | * | 122 | * |
124 | * @var int | 123 | * @var int |
125 | * @access private | 124 | * @access private |
126 | */ | 125 | */ |
@@ -140,7 +139,7 @@ class Text_LanguageDetect | |||
140 | 139 | ||
141 | /** | 140 | /** |
142 | * Whether or not to simulate perl's Language::Guess exactly | 141 | * Whether or not to simulate perl's Language::Guess exactly |
143 | * | 142 | * |
144 | * @access private | 143 | * @access private |
145 | * @var bool | 144 | * @var bool |
146 | * @see setPerlCompatible() | 145 | * @see setPerlCompatible() |
@@ -165,18 +164,24 @@ class Text_LanguageDetect | |||
165 | var $_clusters; | 164 | var $_clusters; |
166 | 165 | ||
167 | /** | 166 | /** |
167 | * Which type of "language names" are accepted and returned: | ||
168 | * | ||
169 | * 0 - language name ("english") | ||
170 | * 2 - 2-letter ISO 639-1 code ("en") | ||
171 | * 3 - 3-letter ISO 639-2 code ("eng") | ||
172 | */ | ||
173 | var $_name_mode = 0; | ||
174 | |||
175 | /** | ||
168 | * Constructor | 176 | * Constructor |
169 | * | 177 | * |
170 | * Will attempt to load the language database. If it fails, you will get | 178 | * Will attempt to load the language database. If it fails, you will get |
171 | * a PEAR_Error object returned when you try to use detect() | 179 | * an exception. |
172 | * | ||
173 | */ | 180 | */ |
174 | function Text_LanguageDetect($db=null, $unicode_db=null) | 181 | function __construct() |
175 | { | 182 | { |
176 | if (isset($db)) $this->_db_filename = $db; | ||
177 | if (isset($unicode_db)) $this->_unicode_db_filename = $unicode_db; | ||
178 | |||
179 | $data = $this->_readdb($this->_db_filename); | 183 | $data = $this->_readdb($this->_db_filename); |
184 | $this->_checkTrigram($data['trigram']); | ||
180 | $this->_lang_db = $data['trigram']; | 185 | $this->_lang_db = $data['trigram']; |
181 | 186 | ||
182 | if (isset($data['trigram-unicodemap'])) { | 187 | if (isset($data['trigram-unicodemap'])) { |
@@ -186,29 +191,32 @@ class Text_LanguageDetect | |||
186 | // Not yet implemented: | 191 | // Not yet implemented: |
187 | if (isset($data['trigram-clusters'])) { | 192 | if (isset($data['trigram-clusters'])) { |
188 | $this->_clusters = $data['trigram-clusters']; | 193 | $this->_clusters = $data['trigram-clusters']; |
189 | } | 194 | } |
190 | } | 195 | } |
191 | 196 | ||
192 | /** | 197 | /** |
193 | * Returns the path to the location of the database | 198 | * Returns the path to the location of the database |
194 | * | 199 | * |
195 | * @access private | 200 | * @param string $fname File name to load |
196 | * @return string expected path to the language model database | 201 | * |
202 | * @return string expected path to the language model database | ||
203 | * @access private | ||
197 | */ | 204 | */ |
198 | function _get_data_loc($fname) | 205 | function _get_data_loc($fname) |
199 | { | 206 | { |
200 | return $fname; | 207 | return dirname(__FILE__).'/'.$fname; |
201 | } | 208 | } |
202 | 209 | ||
203 | /** | 210 | /** |
204 | * Loads the language trigram database from filename | 211 | * Loads the language trigram database from filename |
205 | * | 212 | * |
206 | * Trigram datbase should be a serialize()'d array | 213 | * Trigram datbase should be a serialize()'d array |
207 | * | 214 | * |
208 | * @access private | 215 | * @param string $fname the filename where the data is stored |
209 | * @param string $fname the filename where the data is stored | 216 | * |
210 | * @return array the language model data | 217 | * @return array the language model data |
211 | * @throws PEAR_Error | 218 | * @throws Text_LanguageDetect_Exception |
219 | * @access private | ||
212 | */ | 220 | */ |
213 | function _readdb($fname) | 221 | function _readdb($fname) |
214 | { | 222 | { |
@@ -217,79 +225,74 @@ class Text_LanguageDetect | |||
217 | 225 | ||
218 | // input check | 226 | // input check |
219 | if (!file_exists($fname)) { | 227 | if (!file_exists($fname)) { |
220 | throw new Exception('Language database does not exist.'); | 228 | throw new Text_LanguageDetect_Exception( |
229 | 'Language database does not exist: ' . $fname, | ||
230 | Text_LanguageDetect_Exception::DB_NOT_FOUND | ||
231 | ); | ||
221 | } elseif (!is_readable($fname)) { | 232 | } elseif (!is_readable($fname)) { |
222 | throw new Exception('Language database is not readable.'); | 233 | throw new Text_LanguageDetect_Exception( |
234 | 'Language database is not readable: ' . $fname, | ||
235 | Text_LanguageDetect_Exception::DB_NOT_READABLE | ||
236 | ); | ||
223 | } | 237 | } |
224 | 238 | ||
225 | if (function_exists('file_get_contents')) { | 239 | return unserialize(file_get_contents($fname)); |
226 | return unserialize(file_get_contents($fname)); | ||
227 | } else { | ||
228 | // if you don't have file_get_contents(), | ||
229 | // then this is the next fastest way | ||
230 | ob_start(); | ||
231 | readfile($fname); | ||
232 | $contents = ob_get_contents(); | ||
233 | ob_end_clean(); | ||
234 | return unserialize($contents); | ||
235 | } | ||
236 | } | 240 | } |
237 | 241 | ||
238 | 242 | ||
239 | /** | 243 | /** |
240 | * Checks if this object is ready to detect languages | 244 | * Checks if this object is ready to detect languages |
241 | * | 245 | * |
242 | * @access private | 246 | * @param array $trigram Trigram data from database |
243 | * @param mixed &$err error object to be returned by reference, if any | 247 | * |
244 | * @return bool true if no errors | 248 | * @return void |
249 | * @access private | ||
245 | */ | 250 | */ |
246 | function _setup_ok(&$err) | 251 | function _checkTrigram($trigram) |
247 | { | 252 | { |
248 | if (!is_array($this->_lang_db)) { | 253 | if (!is_array($trigram)) { |
249 | if (ini_get('magic_quotes_runtime')) { | 254 | if (ini_get('magic_quotes_runtime')) { |
250 | throw new Exception('Error loading database. Try turning magic_quotes_runtime off.'); | 255 | throw new Text_LanguageDetect_Exception( |
251 | } else { | 256 | 'Error loading database. Try turning magic_quotes_runtime off.', |
252 | throw new Exception('Language database is not an array.'); | 257 | Text_LanguageDetect_Exception::MAGIC_QUOTES |
258 | ); | ||
253 | } | 259 | } |
254 | return false; | 260 | throw new Text_LanguageDetect_Exception( |
255 | 261 | 'Language database is not an array.', | |
256 | } elseif (empty($this->_lang_db)) { | 262 | Text_LanguageDetect_Exception::DB_NOT_ARRAY |
257 | throw new Exception('Language database has no elements.'); | 263 | ); |
258 | return false; | 264 | } elseif (empty($trigram)) { |
259 | 265 | throw new Text_LanguageDetect_Exception( | |
260 | } else { | 266 | 'Language database has no elements.', |
261 | return true; | 267 | Text_LanguageDetect_Exception::DB_EMPTY |
268 | ); | ||
262 | } | 269 | } |
263 | } | 270 | } |
264 | 271 | ||
265 | /** | 272 | /** |
266 | * Omits languages | 273 | * Omits languages |
267 | * | 274 | * |
268 | * Pass this function the name of or an array of names of | 275 | * Pass this function the name of or an array of names of |
269 | * languages that you don't want considered | 276 | * languages that you don't want considered |
270 | * | 277 | * |
271 | * If you're only expecting a limited set of languages, this can greatly | 278 | * If you're only expecting a limited set of languages, this can greatly |
272 | * speed up processing | 279 | * speed up processing |
273 | * | 280 | * |
274 | * @access public | 281 | * @param mixed $omit_list language name or array of names to omit |
275 | * @param mixed $omit_list language name or array of names to omit | 282 | * @param bool $include_only if true will include (rather than |
276 | * @param bool $include_only if true will include (rather than | 283 | * exclude) only those in the list |
277 | * exclude) only those in the list | 284 | * |
278 | * @return int number of languages successfully deleted | 285 | * @return int number of languages successfully deleted |
279 | * @throws PEAR_Error | 286 | * @throws Text_LanguageDetect_Exception |
280 | */ | 287 | */ |
281 | function omitLanguages($omit_list, $include_only = false) | 288 | public function omitLanguages($omit_list, $include_only = false) |
282 | { | 289 | { |
283 | |||
284 | // setup check | ||
285 | if (!$this->_setup_ok($err)) { | ||
286 | return $err; | ||
287 | } | ||
288 | |||
289 | $deleted = 0; | 290 | $deleted = 0; |
290 | 291 | ||
291 | // deleting the given languages | 292 | $omit_list = $this->_convertFromNameMode($omit_list); |
293 | |||
292 | if (!$include_only) { | 294 | if (!$include_only) { |
295 | // deleting the given languages | ||
293 | if (!is_array($omit_list)) { | 296 | if (!is_array($omit_list)) { |
294 | $omit_list = strtolower($omit_list); // case desensitize | 297 | $omit_list = strtolower($omit_list); // case desensitize |
295 | if (isset($this->_lang_db[$omit_list])) { | 298 | if (isset($this->_lang_db[$omit_list])) { |
@@ -301,12 +304,12 @@ class Text_LanguageDetect | |||
301 | if (isset($this->_lang_db[$omit_lang])) { | 304 | if (isset($this->_lang_db[$omit_lang])) { |
302 | unset($this->_lang_db[$omit_lang]); | 305 | unset($this->_lang_db[$omit_lang]); |
303 | $deleted++; | 306 | $deleted++; |
304 | } | 307 | } |
305 | } | 308 | } |
306 | } | 309 | } |
307 | 310 | ||
308 | // deleting all except the given languages | ||
309 | } else { | 311 | } else { |
312 | // deleting all except the given languages | ||
310 | if (!is_array($omit_list)) { | 313 | if (!is_array($omit_list)) { |
311 | $omit_list = array($omit_list); | 314 | $omit_list = array($omit_list); |
312 | } | 315 | } |
@@ -327,7 +330,7 @@ class Text_LanguageDetect | |||
327 | // reset the cluster cache if the number of languages changes | 330 | // reset the cluster cache if the number of languages changes |
328 | // this will then have to be recalculated | 331 | // this will then have to be recalculated |
329 | if (isset($this->_clusters) && $deleted > 0) { | 332 | if (isset($this->_clusters) && $deleted > 0) { |
330 | unset($this->_clusters); | 333 | $this->_clusters = null; |
331 | } | 334 | } |
332 | 335 | ||
333 | return $deleted; | 336 | return $deleted; |
@@ -339,49 +342,40 @@ class Text_LanguageDetect | |||
339 | * | 342 | * |
340 | * @access public | 343 | * @access public |
341 | * @return int the number of languages | 344 | * @return int the number of languages |
342 | * @throws PEAR_Error | 345 | * @throws Text_LanguageDetect_Exception |
343 | */ | 346 | */ |
344 | function getLanguageCount() | 347 | function getLanguageCount() |
345 | { | 348 | { |
346 | if (!$this->_setup_ok($err)) { | 349 | return count($this->_lang_db); |
347 | return $err; | ||
348 | } else { | ||
349 | return count($this->_lang_db); | ||
350 | } | ||
351 | } | 350 | } |
352 | 351 | ||
353 | /** | 352 | /** |
354 | * Returns true if a given language exists | 353 | * Checks if the language with the given name exists in the database |
355 | * | 354 | * |
356 | * If passed an array of names, will return true only if all exist | 355 | * @param mixed $lang Language name or array of language names |
357 | * | 356 | * |
358 | * @access public | 357 | * @return bool true if language model exists |
359 | * @param mixed $lang language name or array of language names | ||
360 | * @return bool true if language model exists | ||
361 | * @throws PEAR_Error | ||
362 | */ | 358 | */ |
363 | function languageExists($lang) | 359 | public function languageExists($lang) |
364 | { | 360 | { |
365 | if (!$this->_setup_ok($err)) { | 361 | $lang = $this->_convertFromNameMode($lang); |
366 | return $err; | ||
367 | } else { | ||
368 | // string | ||
369 | if (is_string($lang)) { | ||
370 | return isset($this->_lang_db[strtolower($lang)]); | ||
371 | |||
372 | // array | ||
373 | } elseif (is_array($lang)) { | ||
374 | foreach ($lang as $test_lang) { | ||
375 | if (!isset($this->_lang_db[strtolower($test_lang)])) { | ||
376 | return false; | ||
377 | } | ||
378 | } | ||
379 | return true; | ||
380 | 362 | ||
381 | // other (error) | 363 | if (is_string($lang)) { |
382 | } else { | 364 | return isset($this->_lang_db[strtolower($lang)]); |
383 | throw new Exception('Unknown type passed to languageExists()'); | 365 | |
366 | } elseif (is_array($lang)) { | ||
367 | foreach ($lang as $test_lang) { | ||
368 | if (!isset($this->_lang_db[strtolower($test_lang)])) { | ||
369 | return false; | ||
370 | } | ||
384 | } | 371 | } |
372 | return true; | ||
373 | |||
374 | } else { | ||
375 | throw new Text_LanguageDetect_Exception( | ||
376 | 'Unsupported parameter type passed to languageExists()', | ||
377 | Text_LanguageDetect_Exception::PARAM_TYPE | ||
378 | ); | ||
385 | } | 379 | } |
386 | } | 380 | } |
387 | 381 | ||
@@ -389,25 +383,24 @@ class Text_LanguageDetect | |||
389 | * Returns the list of detectable languages | 383 | * Returns the list of detectable languages |
390 | * | 384 | * |
391 | * @access public | 385 | * @access public |
392 | * @return array the names of the languages known to this object | 386 | * @return array the names of the languages known to this object<<<<<<< |
393 | * @throws PEAR_Error | 387 | * @throws Text_LanguageDetect_Exception |
394 | */ | 388 | */ |
395 | function getLanguages() | 389 | function getLanguages() |
396 | { | 390 | { |
397 | if (!$this->_setup_ok($err)) { | 391 | return $this->_convertToNameMode( |
398 | return $err; | 392 | array_keys($this->_lang_db) |
399 | } else { | 393 | ); |
400 | return array_keys($this->_lang_db); | ||
401 | } | ||
402 | } | 394 | } |
403 | 395 | ||
404 | /** | 396 | /** |
405 | * Make this object behave like Language::Guess | 397 | * Make this object behave like Language::Guess |
406 | * | 398 | * |
407 | * @access public | 399 | * @param bool $setting false to turn off perl compatibility |
408 | * @param bool $setting false to turn off perl compatibility | 400 | * |
401 | * @return void | ||
409 | */ | 402 | */ |
410 | function setPerlCompatible($setting = true) | 403 | public function setPerlCompatible($setting = true) |
411 | { | 404 | { |
412 | if (is_bool($setting)) { // input check | 405 | if (is_bool($setting)) { // input check |
413 | $this->_perl_compatible = $setting; | 406 | $this->_perl_compatible = $setting; |
@@ -422,6 +415,21 @@ class Text_LanguageDetect | |||
422 | } | 415 | } |
423 | 416 | ||
424 | /** | 417 | /** |
418 | * Sets the way how language names are accepted and returned. | ||
419 | * | ||
420 | * @param integer $name_mode One of the following modes: | ||
421 | * 0 - language name ("english") | ||
422 | * 2 - 2-letter ISO 639-1 code ("en") | ||
423 | * 3 - 3-letter ISO 639-2 code ("eng") | ||
424 | * | ||
425 | * @return void | ||
426 | */ | ||
427 | function setNameMode($name_mode) | ||
428 | { | ||
429 | $this->_name_mode = $name_mode; | ||
430 | } | ||
431 | |||
432 | /** | ||
425 | * Whether to use unicode block ranges in detection | 433 | * Whether to use unicode block ranges in detection |
426 | * | 434 | * |
427 | * Should speed up most detections if turned on (detault is on). In some | 435 | * Should speed up most detections if turned on (detault is on). In some |
@@ -429,10 +437,11 @@ class Text_LanguageDetect | |||
429 | * in languages that use latin scripts. In other cases it should speed up | 437 | * in languages that use latin scripts. In other cases it should speed up |
430 | * detection noticeably. | 438 | * detection noticeably. |
431 | * | 439 | * |
432 | * @access public | 440 | * @param bool $setting false to turn off |
433 | * @param bool $setting false to turn off | 441 | * |
442 | * @return void | ||
434 | */ | 443 | */ |
435 | function useUnicodeBlocks($setting = true) | 444 | public function useUnicodeBlocks($setting = true) |
436 | { | 445 | { |
437 | if (is_bool($setting)) { | 446 | if (is_bool($setting)) { |
438 | $this->_use_unicode_narrowing = $setting; | 447 | $this->_use_unicode_narrowing = $setting; |
@@ -442,15 +451,15 @@ class Text_LanguageDetect | |||
442 | /** | 451 | /** |
443 | * Converts a piece of text into trigrams | 452 | * Converts a piece of text into trigrams |
444 | * | 453 | * |
445 | * Superceded by the Text_LanguageDetect_Parser class | 454 | * @param string $text text to convert |
446 | * | 455 | * |
447 | * @access private | 456 | * @return array array of trigram frequencies |
448 | * @param string $text text to convert | 457 | * @access private |
449 | * @return array array of trigram frequencies | 458 | * @deprecated Superceded by the Text_LanguageDetect_Parser class |
450 | */ | 459 | */ |
451 | function _trigram($text) | 460 | function _trigram($text) |
452 | { | 461 | { |
453 | $s = new Text_LanguageDetect_Parser($text, $this->_db_filename, $this->_unicode_db_filename); | 462 | $s = new Text_LanguageDetect_Parser($text); |
454 | $s->prepareTrigram(); | 463 | $s->prepareTrigram(); |
455 | $s->prepareUnicode(false); | 464 | $s->prepareUnicode(false); |
456 | $s->setPadStart(!$this->_perl_compatible); | 465 | $s->setPadStart(!$this->_perl_compatible); |
@@ -463,11 +472,12 @@ class Text_LanguageDetect | |||
463 | * | 472 | * |
464 | * Thresholds (cuts off) the list at $this->_threshold | 473 | * Thresholds (cuts off) the list at $this->_threshold |
465 | * | 474 | * |
466 | * @access protected | 475 | * @param array $arr array of trigram |
467 | * @param array $arr array of trgram | 476 | * |
468 | * @return array ranks of trigrams | 477 | * @return array ranks of trigrams |
478 | * @access protected | ||
469 | */ | 479 | */ |
470 | function _arr_rank(&$arr) | 480 | function _arr_rank($arr) |
471 | { | 481 | { |
472 | 482 | ||
473 | // sorts alphabetically first as a standard way of breaking rank ties | 483 | // sorts alphabetically first as a standard way of breaking rank ties |
@@ -494,14 +504,17 @@ class Text_LanguageDetect | |||
494 | 504 | ||
495 | /** | 505 | /** |
496 | * Sorts an array by value breaking ties alphabetically | 506 | * Sorts an array by value breaking ties alphabetically |
497 | * | 507 | * |
498 | * @access private | 508 | * @param array &$arr the array to sort |
499 | * @param array &$arr the array to sort | 509 | * |
510 | * @return void | ||
511 | * @access private | ||
500 | */ | 512 | */ |
501 | function _bub_sort(&$arr) | 513 | function _bub_sort(&$arr) |
502 | { | 514 | { |
503 | // should do the same as this perl statement: | 515 | // should do the same as this perl statement: |
504 | // sort { $trigrams{$b} == $trigrams{$a} ? $a cmp $b : $trigrams{$b} <=> $trigrams{$a} } | 516 | // sort { $trigrams{$b} == $trigrams{$a} |
517 | // ? $a cmp $b : $trigrams{$b} <=> $trigrams{$a} } | ||
505 | 518 | ||
506 | // needs to sort by both key and value at once | 519 | // needs to sort by both key and value at once |
507 | // using the key to break ties for the value | 520 | // using the key to break ties for the value |
@@ -528,13 +541,14 @@ class Text_LanguageDetect | |||
528 | /** | 541 | /** |
529 | * Sort function used by bubble sort | 542 | * Sort function used by bubble sort |
530 | * | 543 | * |
531 | * Callback function for usort(). | 544 | * Callback function for usort(). |
532 | * | 545 | * |
533 | * @access private | 546 | * @param array $a first param passed by usort() |
534 | * @param array first param passed by usort() | 547 | * @param array $b second param passed by usort() |
535 | * @param array second param passed by usort() | 548 | * |
536 | * @return int 1 if $a is greater, -1 if not | 549 | * @return int 1 if $a is greater, -1 if not |
537 | * @see _bub_sort() | 550 | * @see _bub_sort() |
551 | * @access private | ||
538 | */ | 552 | */ |
539 | function _sort_func($a, $b) | 553 | function _sort_func($a, $b) |
540 | { | 554 | { |
@@ -542,12 +556,12 @@ class Text_LanguageDetect | |||
542 | list($a_key, $a_value) = $a; | 556 | list($a_key, $a_value) = $a; |
543 | list($b_key, $b_value) = $b; | 557 | list($b_key, $b_value) = $b; |
544 | 558 | ||
545 | // if the values are the same, break ties using the key | ||
546 | if ($a_value == $b_value) { | 559 | if ($a_value == $b_value) { |
560 | // if the values are the same, break ties using the key | ||
547 | return strcmp($a_key, $b_key); | 561 | return strcmp($a_key, $b_key); |
548 | 562 | ||
549 | // if not, just sort normally | ||
550 | } else { | 563 | } else { |
564 | // if not, just sort normally | ||
551 | if ($a_value > $b_value) { | 565 | if ($a_value > $b_value) { |
552 | return -1; | 566 | return -1; |
553 | } else { | 567 | } else { |
@@ -559,23 +573,24 @@ class Text_LanguageDetect | |||
559 | } | 573 | } |
560 | 574 | ||
561 | /** | 575 | /** |
562 | * Calculates a linear rank-order distance statistic between two sets of | 576 | * Calculates a linear rank-order distance statistic between two sets of |
563 | * ranked trigrams | 577 | * ranked trigrams |
564 | * | 578 | * |
565 | * Sums the differences in rank for each trigram. If the trigram does not | 579 | * Sums the differences in rank for each trigram. If the trigram does not |
566 | * appear in both, consider it a difference of $this->_threshold. | 580 | * appear in both, consider it a difference of $this->_threshold. |
567 | * | 581 | * |
568 | * This distance measure was proposed by Cavnar & Trenkle (1994). Despite | 582 | * This distance measure was proposed by Cavnar & Trenkle (1994). Despite |
569 | * its simplicity it has been shown to be highly accurate for language | 583 | * its simplicity it has been shown to be highly accurate for language |
570 | * identification tasks. | 584 | * identification tasks. |
571 | * | 585 | * |
572 | * @access private | 586 | * @param array $arr1 the reference set of trigram ranks |
573 | * @param array $arr1 the reference set of trigram ranks | 587 | * @param array $arr2 the target set of trigram ranks |
574 | * @param array $arr2 the target set of trigram ranks | 588 | * |
575 | * @return int the sum of the differences between the ranks of | 589 | * @return int the sum of the differences between the ranks of |
576 | * the two trigram sets | 590 | * the two trigram sets |
591 | * @access private | ||
577 | */ | 592 | */ |
578 | function _distance(&$arr1, &$arr2) | 593 | function _distance($arr1, $arr2) |
579 | { | 594 | { |
580 | $sumdist = 0; | 595 | $sumdist = 0; |
581 | 596 | ||
@@ -598,14 +613,15 @@ class Text_LanguageDetect | |||
598 | 613 | ||
599 | /** | 614 | /** |
600 | * Normalizes the score returned by _distance() | 615 | * Normalizes the score returned by _distance() |
601 | * | 616 | * |
602 | * Different if perl compatible or not | 617 | * Different if perl compatible or not |
603 | * | 618 | * |
604 | * @access private | 619 | * @param int $score the score from _distance() |
605 | * @param int $score the score from _distance() | 620 | * @param int $base_count the number of trigrams being considered |
606 | * @param int $base_count the number of trigrams being considered | 621 | * |
607 | * @return float the normalized score | 622 | * @return float the normalized score |
608 | * @see _distance() | 623 | * @see _distance() |
624 | * @access private | ||
609 | */ | 625 | */ |
610 | function _normalize_score($score, $base_count = null) | 626 | function _normalize_score($score, $base_count = null) |
611 | { | 627 | { |
@@ -630,29 +646,24 @@ class Text_LanguageDetect | |||
630 | * | 646 | * |
631 | * If perl compatible, the score is 300-0, 0 being most similar. | 647 | * If perl compatible, the score is 300-0, 0 being most similar. |
632 | * Otherwise, it's 0-1 with 1 being most similar. | 648 | * Otherwise, it's 0-1 with 1 being most similar. |
633 | * | 649 | * |
634 | * The $sample text should be at least a few sentences in length; | 650 | * The $sample text should be at least a few sentences in length; |
635 | * should be ascii-7 or utf8 encoded, if another and the mbstring extension | 651 | * should be ascii-7 or utf8 encoded, if another and the mbstring extension |
636 | * is present it will try to detect and convert. However, experience has | 652 | * is present it will try to detect and convert. However, experience has |
637 | * shown that mb_detect_encoding() *does not work very well* with at least | 653 | * shown that mb_detect_encoding() *does not work very well* with at least |
638 | * some types of encoding. | 654 | * some types of encoding. |
639 | * | 655 | * |
640 | * @access public | 656 | * @param string $sample a sample of text to compare. |
641 | * @param string $sample a sample of text to compare. | 657 | * @param int $limit if specified, return an array of the most likely |
642 | * @param int $limit if specified, return an array of the most likely | 658 | * $limit languages and their scores. |
643 | * $limit languages and their scores. | 659 | * |
644 | * @return mixed sorted array of language scores, blank array if no | 660 | * @return mixed sorted array of language scores, blank array if no |
645 | * useable text was found, or PEAR_Error if error | 661 | * useable text was found |
646 | * with the object setup | 662 | * @see _distance() |
647 | * @see _distance() | 663 | * @throws Text_LanguageDetect_Exception |
648 | * @throws PEAR_Error | ||
649 | */ | 664 | */ |
650 | function detect($sample, $limit = 0) | 665 | public function detect($sample, $limit = 0) |
651 | { | 666 | { |
652 | if (!$this->_setup_ok($err)) { | ||
653 | return $err; | ||
654 | } | ||
655 | |||
656 | // input check | 667 | // input check |
657 | if (!Text_LanguageDetect_Parser::validateString($sample)) { | 668 | if (!Text_LanguageDetect_Parser::validateString($sample)) { |
658 | return array(); | 669 | return array(); |
@@ -660,36 +671,27 @@ class Text_LanguageDetect | |||
660 | 671 | ||
661 | // check char encoding | 672 | // check char encoding |
662 | // (only if mbstring extension is compiled and PHP > 4.0.6) | 673 | // (only if mbstring extension is compiled and PHP > 4.0.6) |
663 | if (function_exists('mb_detect_encoding') | 674 | if (function_exists('mb_detect_encoding') |
664 | && function_exists('mb_convert_encoding')) { | 675 | && function_exists('mb_convert_encoding') |
665 | 676 | ) { | |
666 | // mb_detect_encoding isn't very reliable, to say the least | 677 | // mb_detect_encoding isn't very reliable, to say the least |
667 | // detection should still work with a sufficient sample of ascii characters | 678 | // detection should still work with a sufficient sample |
679 | // of ascii characters | ||
668 | $encoding = mb_detect_encoding($sample); | 680 | $encoding = mb_detect_encoding($sample); |
669 | 681 | ||
670 | // mb_detect_encoding() will return FALSE if detection fails | 682 | // mb_detect_encoding() will return FALSE if detection fails |
671 | // don't attempt conversion if that's the case | 683 | // don't attempt conversion if that's the case |
672 | if ($encoding != 'ASCII' && $encoding != 'UTF-8' && $encoding !== false) { | 684 | if ($encoding != 'ASCII' && $encoding != 'UTF-8' |
673 | 685 | && $encoding !== false | |
674 | if (function_exists('mb_list_encodings')) { | 686 | ) { |
675 | 687 | // verify the encoding exists in mb_list_encodings | |
676 | // verify the encoding exists in mb_list_encodings | 688 | if (in_array($encoding, mb_list_encodings())) { |
677 | if (in_array($encoding, mb_list_encodings())) { | 689 | $sample = mb_convert_encoding($sample, 'UTF-8', $encoding); |
678 | $sample = mb_convert_encoding($sample, 'UTF-8', $encoding); | ||
679 | } | ||
680 | |||
681 | // if the previous condition failed: | ||
682 | // somehow we detected an encoding that also we don't support | ||
683 | |||
684 | } else { | ||
685 | // php 4 doesnt have mb_list_encodings() | ||
686 | // so attempt with error suppression | ||
687 | $sample = @mb_convert_encoding($sample, 'UTF-8', $encoding); | ||
688 | } | 690 | } |
689 | } | 691 | } |
690 | } | 692 | } |
691 | 693 | ||
692 | $sample_obj = new Text_LanguageDetect_Parser($sample, $this->_db_filename, $this->_unicode_db_filename); | 694 | $sample_obj = new Text_LanguageDetect_Parser($sample); |
693 | $sample_obj->prepareTrigram(); | 695 | $sample_obj->prepareTrigram(); |
694 | if ($this->_use_unicode_narrowing) { | 696 | if ($this->_use_unicode_narrowing) { |
695 | $sample_obj->prepareUnicode(); | 697 | $sample_obj->prepareUnicode(); |
@@ -713,7 +715,10 @@ class Text_LanguageDetect | |||
713 | if (is_array($blocks)) { | 715 | if (is_array($blocks)) { |
714 | $present_blocks = array_keys($blocks); | 716 | $present_blocks = array_keys($blocks); |
715 | } else { | 717 | } else { |
716 | throw new Exception('Error during block detection'); | 718 | throw new Text_LanguageDetect_Exception( |
719 | 'Error during block detection', | ||
720 | Text_LanguageDetect_Exception::BLOCK_DETECTION | ||
721 | ); | ||
717 | } | 722 | } |
718 | 723 | ||
719 | $possible_langs = array(); | 724 | $possible_langs = array(); |
@@ -731,30 +736,30 @@ class Text_LanguageDetect | |||
731 | } | 736 | } |
732 | 737 | ||
733 | // could also try an intersect operation rather than a union | 738 | // could also try an intersect operation rather than a union |
734 | // in other words, choose languages whose trigrams contain | 739 | // in other words, choose languages whose trigrams contain |
735 | // ALL of the unicode blocks found in this sample | 740 | // ALL of the unicode blocks found in this sample |
736 | // would improve speed but would be completely thrown off by an | 741 | // would improve speed but would be completely thrown off by an |
737 | // unexpected character, like an umlaut appearing in english text | 742 | // unexpected character, like an umlaut appearing in english text |
738 | 743 | ||
739 | $possible_langs = array_intersect( | 744 | $possible_langs = array_intersect( |
740 | array_keys($this->_lang_db), | 745 | array_keys($this->_lang_db), |
741 | array_unique($possible_langs) | 746 | array_unique($possible_langs) |
742 | ); | 747 | ); |
743 | 748 | ||
744 | // needs to intersect it with the keys of _lang_db in case | 749 | // needs to intersect it with the keys of _lang_db in case |
745 | // languages have been omitted | 750 | // languages have been omitted |
746 | 751 | ||
747 | // or just try 'em all | ||
748 | } else { | 752 | } else { |
753 | // or just try 'em all | ||
749 | $possible_langs = array_keys($this->_lang_db); | 754 | $possible_langs = array_keys($this->_lang_db); |
750 | } | 755 | } |
751 | 756 | ||
752 | 757 | ||
753 | foreach ($possible_langs as $lang) { | 758 | foreach ($possible_langs as $lang) { |
754 | $scores[$lang] = | 759 | $scores[$lang] = $this->_normalize_score( |
755 | $this->_normalize_score( | 760 | $this->_distance($this->_lang_db[$lang], $trigram_freqs), |
756 | $this->_distance($this->_lang_db[$lang], $trigram_freqs), | 761 | $trigram_count |
757 | $trigram_count); | 762 | ); |
758 | } | 763 | } |
759 | 764 | ||
760 | unset($sample_obj); | 765 | unset($sample_obj); |
@@ -772,7 +777,6 @@ class Text_LanguageDetect | |||
772 | $limited_scores = array(); | 777 | $limited_scores = array(); |
773 | 778 | ||
774 | $i = 0; | 779 | $i = 0; |
775 | |||
776 | foreach ($scores as $key => $value) { | 780 | foreach ($scores as $key => $value) { |
777 | if ($i++ >= $limit) { | 781 | if ($i++ >= $limit) { |
778 | break; | 782 | break; |
@@ -781,9 +785,9 @@ class Text_LanguageDetect | |||
781 | $limited_scores[$key] = $value; | 785 | $limited_scores[$key] = $value; |
782 | } | 786 | } |
783 | 787 | ||
784 | return $limited_scores; | 788 | return $this->_convertToNameMode($limited_scores, true); |
785 | } else { | 789 | } else { |
786 | return $scores; | 790 | return $this->_convertToNameMode($scores, true); |
787 | } | 791 | } |
788 | } | 792 | } |
789 | 793 | ||
@@ -791,35 +795,33 @@ class Text_LanguageDetect | |||
791 | * Returns only the most similar language to the text sample | 795 | * Returns only the most similar language to the text sample |
792 | * | 796 | * |
793 | * Calls $this->detect() and returns only the top result | 797 | * Calls $this->detect() and returns only the top result |
794 | * | 798 | * |
795 | * @access public | 799 | * @param string $sample text to detect the language of |
796 | * @param string $sample text to detect the language of | 800 | * |
797 | * @return string the name of the most likely language | 801 | * @return string the name of the most likely language |
798 | * or null if no language is similar | 802 | * or null if no language is similar |
799 | * @see detect() | 803 | * @see detect() |
800 | * @throws PEAR_Error | 804 | * @throws Text_LanguageDetect_Exception |
801 | */ | 805 | */ |
802 | function detectSimple($sample) | 806 | public function detectSimple($sample) |
803 | { | 807 | { |
804 | $scores = $this->detect($sample, 1); | 808 | $scores = $this->detect($sample, 1); |
805 | 809 | ||
806 | // if top language has the maximum possible score, | 810 | // if top language has the maximum possible score, |
807 | // then the top score will have been picked at random | 811 | // then the top score will have been picked at random |
808 | if ( !is_array($scores) | 812 | if (!is_array($scores) || empty($scores) |
809 | || empty($scores) | 813 | || current($scores) == $this->_max_score |
810 | || current($scores) == $this->_max_score) { | 814 | ) { |
811 | |||
812 | return null; | 815 | return null; |
813 | |||
814 | } else { | 816 | } else { |
815 | return ucfirst(key($scores)); | 817 | return key($scores); |
816 | } | 818 | } |
817 | } | 819 | } |
818 | 820 | ||
819 | /** | 821 | /** |
820 | * Returns an array containing the most similar language and a confidence | 822 | * Returns an array containing the most similar language and a confidence |
821 | * rating | 823 | * rating |
822 | * | 824 | * |
823 | * Confidence is a simple measure calculated from the similarity score | 825 | * Confidence is a simple measure calculated from the similarity score |
824 | * minus the similarity score from the next most similar language | 826 | * minus the similarity score from the next most similar language |
825 | * divided by the highest possible score. Languages that have closely | 827 | * divided by the highest possible score. Languages that have closely |
@@ -827,46 +829,43 @@ class Text_LanguageDetect | |||
827 | * confidence scores. | 829 | * confidence scores. |
828 | * | 830 | * |
829 | * The similarity score answers the question "How likely is the text the | 831 | * The similarity score answers the question "How likely is the text the |
830 | * returned language regardless of the other languages considered?" The | 832 | * returned language regardless of the other languages considered?" The |
831 | * confidence score is one way of answering the question "how likely is the | 833 | * confidence score is one way of answering the question "how likely is the |
832 | * text the detected language relative to the rest of the language model | 834 | * text the detected language relative to the rest of the language model |
833 | * set?" | 835 | * set?" |
834 | * | 836 | * |
835 | * To see how similar languages are a priori, see languageSimilarity() | 837 | * To see how similar languages are a priori, see languageSimilarity() |
836 | * | 838 | * |
837 | * @access public | 839 | * @param string $sample text for which language will be detected |
838 | * @param string $sample text for which language will be detected | 840 | * |
839 | * @return array most similar language, score and confidence rating | 841 | * @return array most similar language, score and confidence rating |
840 | * or null if no language is similar | 842 | * or null if no language is similar |
841 | * @see detect() | 843 | * @see detect() |
842 | * @throws PEAR_Error | 844 | * @throws Text_LanguageDetect_Exception |
843 | */ | 845 | */ |
844 | function detectConfidence($sample) | 846 | public function detectConfidence($sample) |
845 | { | 847 | { |
846 | $scores = $this->detect($sample, 2); | 848 | $scores = $this->detect($sample, 2); |
847 | 849 | ||
848 | // if most similar language has the max score, it | 850 | // if most similar language has the max score, it |
849 | // will have been picked at random | 851 | // will have been picked at random |
850 | if ( !is_array($scores) | 852 | if (!is_array($scores) || empty($scores) |
851 | || empty($scores) | 853 | || current($scores) == $this->_max_score |
852 | || current($scores) == $this->_max_score) { | 854 | ) { |
853 | |||
854 | return null; | 855 | return null; |
855 | } | 856 | } |
856 | 857 | ||
857 | $arr['language'] = ucfirst(key($scores)); | 858 | $arr['language'] = key($scores); |
858 | $arr['similarity'] = current($scores); | 859 | $arr['similarity'] = current($scores); |
859 | if (next($scores) !== false) { // if false then no next element | 860 | if (next($scores) !== false) { // if false then no next element |
860 | // the goal is to return a higher value if the distance between | 861 | // the goal is to return a higher value if the distance between |
861 | // the similarity of the first score and the second score is high | 862 | // the similarity of the first score and the second score is high |
862 | 863 | ||
863 | if ($this->_perl_compatible) { | 864 | if ($this->_perl_compatible) { |
864 | 865 | $arr['confidence'] = (current($scores) - $arr['similarity']) | |
865 | $arr['confidence'] = | 866 | / $this->_max_score; |
866 | (current($scores) - $arr['similarity']) / $this->_max_score; | ||
867 | 867 | ||
868 | } else { | 868 | } else { |
869 | |||
870 | $arr['confidence'] = $arr['similarity'] - current($scores); | 869 | $arr['confidence'] = $arr['similarity'] - current($scores); |
871 | 870 | ||
872 | } | 871 | } |
@@ -882,32 +881,26 @@ class Text_LanguageDetect | |||
882 | * Returns the distribution of unicode blocks in a given utf8 string | 881 | * Returns the distribution of unicode blocks in a given utf8 string |
883 | * | 882 | * |
884 | * For the block name of a single char, use unicodeBlockName() | 883 | * For the block name of a single char, use unicodeBlockName() |
885 | * | 884 | * |
886 | * @access public | 885 | * @param string $str input string. Must be ascii or utf8 |
887 | * @param string $str input string. Must be ascii or utf8 | 886 | * @param bool $skip_symbols if true, skip ascii digits, symbols and |
888 | * @param bool $skip_symbols if true, skip ascii digits, symbols and | 887 | * non-printing characters. Includes spaces, |
889 | * non-printing characters. Includes spaces, | 888 | * newlines and common punctutation characters. |
890 | * newlines and common punctutation characters. | 889 | * |
891 | * @return array | 890 | * @return array |
892 | * @throws PEAR_Error | 891 | * @throws Text_LanguageDetect_Exception |
893 | */ | 892 | */ |
894 | function detectUnicodeBlocks($str, $skip_symbols) | 893 | public function detectUnicodeBlocks($str, $skip_symbols) |
895 | { | 894 | { |
896 | // input check | 895 | $skip_symbols = (bool)$skip_symbols; |
897 | if (!is_bool($skip_symbols)) { | 896 | $str = (string)$str; |
898 | throw new Exception('Second parameter must be boolean'); | ||
899 | } | ||
900 | |||
901 | if (!is_string($str)) { | ||
902 | throw new Exception('First parameter was not a string'); | ||
903 | } | ||
904 | 897 | ||
905 | $sample_obj = new Text_LanguageDetect_Parser($str, $this->_db_filename, $this->_unicode_db_filename); | 898 | $sample_obj = new Text_LanguageDetect_Parser($str); |
906 | $sample_obj->prepareUnicode(); | 899 | $sample_obj->prepareUnicode(); |
907 | $sample_obj->prepareTrigram(false); | 900 | $sample_obj->prepareTrigram(false); |
908 | $sample_obj->setUnicodeSkipSymbols($skip_symbols); | 901 | $sample_obj->setUnicodeSkipSymbols($skip_symbols); |
909 | $sample_obj->analyze(); | 902 | $sample_obj->analyze(); |
910 | $blocks =& $sample_obj->getUnicodeBlocks(); | 903 | $blocks = $sample_obj->getUnicodeBlocks(); |
911 | unset($sample_obj); | 904 | unset($sample_obj); |
912 | return $blocks; | 905 | return $blocks; |
913 | } | 906 | } |
@@ -915,38 +908,37 @@ class Text_LanguageDetect | |||
915 | /** | 908 | /** |
916 | * Returns the block name for a given unicode value | 909 | * Returns the block name for a given unicode value |
917 | * | 910 | * |
918 | * If passed a string, will assume it is being passed a UTF8-formatted | 911 | * If passed a string, will assume it is being passed a UTF8-formatted |
919 | * character and will automatically convert. Otherwise it will assume it | 912 | * character and will automatically convert. Otherwise it will assume it |
920 | * is being passed a numeric unicode value. | 913 | * is being passed a numeric unicode value. |
921 | * | 914 | * |
922 | * Make sure input is of the correct type! | 915 | * Make sure input is of the correct type! |
923 | * | 916 | * |
924 | * @access public | ||
925 | * @param mixed $unicode unicode value or utf8 char | 917 | * @param mixed $unicode unicode value or utf8 char |
918 | * | ||
926 | * @return mixed the block name string or false if not found | 919 | * @return mixed the block name string or false if not found |
927 | * @throws PEAR_Error | 920 | * @throws Text_LanguageDetect_Exception |
928 | */ | 921 | */ |
929 | function unicodeBlockName($unicode) { | 922 | public function unicodeBlockName($unicode) |
923 | { | ||
930 | if (is_string($unicode)) { | 924 | if (is_string($unicode)) { |
931 | // assume it is being passed a utf8 char, so convert it | 925 | // assume it is being passed a utf8 char, so convert it |
932 | 926 | if (self::utf8strlen($unicode) > 1) { | |
933 | // input check | 927 | throw new Text_LanguageDetect_Exception( |
934 | if ($this->utf8strlen($unicode) > 1) { | 928 | 'Pass a single char only to this method', |
935 | throw new Exception('Pass this function only a single char'); | 929 | Text_LanguageDetect_Exception::PARAM_TYPE |
930 | ); | ||
936 | } | 931 | } |
937 | |||
938 | $unicode = $this->_utf8char2unicode($unicode); | 932 | $unicode = $this->_utf8char2unicode($unicode); |
939 | 933 | ||
940 | if ($unicode == -1) { | ||
941 | throw new Exception('Malformatted char'); | ||
942 | } | ||
943 | |||
944 | // input check | ||
945 | } elseif (!is_int($unicode)) { | 934 | } elseif (!is_int($unicode)) { |
946 | throw new Exception('Input must be of type string or int.'); | 935 | throw new Text_LanguageDetect_Exception( |
936 | 'Input must be of type string or int.', | ||
937 | Text_LanguageDetect_Exception::PARAM_TYPE | ||
938 | ); | ||
947 | } | 939 | } |
948 | 940 | ||
949 | $blocks =& $this->_read_unicode_block_db(); | 941 | $blocks = $this->_read_unicode_block_db(); |
950 | 942 | ||
951 | $result = $this->_unicode_block_name($unicode, $blocks); | 943 | $result = $this->_unicode_block_name($unicode, $blocks); |
952 | 944 | ||
@@ -964,14 +956,17 @@ class Text_LanguageDetect | |||
964 | * the public interface for this function, which does input checks which | 956 | * the public interface for this function, which does input checks which |
965 | * this function omits for speed. | 957 | * this function omits for speed. |
966 | * | 958 | * |
967 | * @access protected | 959 | * @param int $unicode the unicode value |
968 | * @param int $unicode the unicode value | 960 | * @param array $blocks the block database |
969 | * @param array &$blocks the block database | 961 | * @param int $block_count the number of defined blocks in the database |
970 | * @param int $block_count the number of defined blocks in the database | 962 | * |
971 | * @see unicodeBlockName() | 963 | * @return mixed Block name, -1 if it failed |
964 | * @see unicodeBlockName() | ||
965 | * @access protected | ||
972 | */ | 966 | */ |
973 | function _unicode_block_name($unicode, &$blocks, $block_count = -1) { | 967 | function _unicode_block_name($unicode, $blocks, $block_count = -1) |
974 | // for a reference, see | 968 | { |
969 | // for a reference, see | ||
975 | // http://www.unicode.org/Public/UNIDATA/Blocks.txt | 970 | // http://www.unicode.org/Public/UNIDATA/Blocks.txt |
976 | 971 | ||
977 | // assume that ascii characters are the most common | 972 | // assume that ascii characters are the most common |
@@ -994,35 +989,36 @@ class Text_LanguageDetect | |||
994 | while ($low <= $high) { | 989 | while ($low <= $high) { |
995 | $mid = floor(($low + $high) / 2); | 990 | $mid = floor(($low + $high) / 2); |
996 | 991 | ||
997 | // if it's lower than the lower bound | ||
998 | if ($unicode < $blocks[$mid][0]) { | 992 | if ($unicode < $blocks[$mid][0]) { |
993 | // if it's lower than the lower bound | ||
999 | $high = $mid - 1; | 994 | $high = $mid - 1; |
1000 | 995 | ||
1001 | // if it's higher than the upper bound | ||
1002 | } elseif ($unicode > $blocks[$mid][1]) { | 996 | } elseif ($unicode > $blocks[$mid][1]) { |
997 | // if it's higher than the upper bound | ||
1003 | $low = $mid + 1; | 998 | $low = $mid + 1; |
1004 | 999 | ||
1005 | // found it | ||
1006 | } else { | 1000 | } else { |
1001 | // found it | ||
1007 | return $blocks[$mid]; | 1002 | return $blocks[$mid]; |
1008 | } | 1003 | } |
1009 | } | 1004 | } |
1010 | 1005 | ||
1011 | // failed to find the block | 1006 | // failed to find the block |
1012 | return -1; | 1007 | return -1; |
1013 | 1008 | ||
1014 | // todo: differentiate when it's out of range or when it falls | 1009 | // todo: differentiate when it's out of range or when it falls |
1015 | // into an unassigned range? | 1010 | // into an unassigned range? |
1016 | } | 1011 | } |
1017 | 1012 | ||
1018 | /** | 1013 | /** |
1019 | * Brings up the unicode block database | 1014 | * Brings up the unicode block database |
1020 | * | 1015 | * |
1021 | * @access protected | ||
1022 | * @return array the database of unicode block definitions | 1016 | * @return array the database of unicode block definitions |
1023 | * @throws PEAR_Error | 1017 | * @throws Text_LanguageDetect_Exception |
1018 | * @access protected | ||
1024 | */ | 1019 | */ |
1025 | function &_read_unicode_block_db() { | 1020 | function _read_unicode_block_db() |
1021 | { | ||
1026 | // since the unicode definitions are always going to be the same, | 1022 | // since the unicode definitions are always going to be the same, |
1027 | // might as well share the memory for the db with all other instances | 1023 | // might as well share the memory for the db with all other instances |
1028 | // of this class | 1024 | // of this class |
@@ -1037,29 +1033,27 @@ class Text_LanguageDetect | |||
1037 | 1033 | ||
1038 | /** | 1034 | /** |
1039 | * Calculate the similarities between the language models | 1035 | * Calculate the similarities between the language models |
1040 | * | 1036 | * |
1041 | * Use this function to see how similar languages are to each other. | 1037 | * Use this function to see how similar languages are to each other. |
1042 | * | 1038 | * |
1043 | * If passed 2 language names, will return just those languages compared. | 1039 | * If passed 2 language names, will return just those languages compared. |
1044 | * If passed 1 language name, will return that language compared to | 1040 | * If passed 1 language name, will return that language compared to |
1045 | * all others. | 1041 | * all others. |
1046 | * If passed none, will return an array of every language model compared | 1042 | * If passed none, will return an array of every language model compared |
1047 | * to every other one. | 1043 | * to every other one. |
1048 | * | 1044 | * |
1049 | * @access public | 1045 | * @param string $lang1 the name of the first language to be compared |
1050 | * @param string $lang1 the name of the first language to be compared | 1046 | * @param string $lang2 the name of the second language to be compared |
1051 | * @param string $lang2 the name of the second language to be compared | 1047 | * |
1052 | * @return array scores of every language compared | 1048 | * @return array scores of every language compared |
1053 | * or the score of just the provided languages | 1049 | * or the score of just the provided languages |
1054 | * or null if one of the supplied languages does not exist | 1050 | * or null if one of the supplied languages does not exist |
1055 | * @throws PEAR_Error | 1051 | * @throws Text_LanguageDetect_Exception |
1056 | */ | 1052 | */ |
1057 | function languageSimilarity($lang1 = null, $lang2 = null) | 1053 | public function languageSimilarity($lang1 = null, $lang2 = null) |
1058 | { | 1054 | { |
1059 | if (!$this->_setup_ok($err)) { | 1055 | $lang1 = $this->_convertFromNameMode($lang1); |
1060 | return $err; | 1056 | $lang2 = $this->_convertFromNameMode($lang2); |
1061 | } | ||
1062 | |||
1063 | if ($lang1 != null) { | 1057 | if ($lang1 != null) { |
1064 | $lang1 = strtolower($lang1); | 1058 | $lang1 = strtolower($lang1); |
1065 | 1059 | ||
@@ -1069,12 +1063,8 @@ class Text_LanguageDetect | |||
1069 | } | 1063 | } |
1070 | 1064 | ||
1071 | if ($lang2 != null) { | 1065 | if ($lang2 != null) { |
1072 | 1066 | if (!isset($this->_lang_db[$lang2])) { | |
1073 | // can't only set the second param | 1067 | // check if language model exists |
1074 | if ($lang1 == null) { | ||
1075 | return null; | ||
1076 | // check if language model exists | ||
1077 | } elseif (!isset($this->_lang_db[$lang2])) { | ||
1078 | return null; | 1068 | return null; |
1079 | } | 1069 | } |
1080 | 1070 | ||
@@ -1088,14 +1078,15 @@ class Text_LanguageDetect | |||
1088 | ) | 1078 | ) |
1089 | ); | 1079 | ); |
1090 | 1080 | ||
1091 | |||
1092 | // compare just $lang1 to all languages | ||
1093 | } else { | 1081 | } else { |
1082 | // compare just $lang1 to all languages | ||
1094 | $return_arr = array(); | 1083 | $return_arr = array(); |
1095 | foreach ($this->_lang_db as $key => $value) { | 1084 | foreach ($this->_lang_db as $key => $value) { |
1096 | if ($key != $lang1) { // don't compare a language to itself | 1085 | if ($key != $lang1) { |
1086 | // don't compare a language to itself | ||
1097 | $return_arr[$key] = $this->_normalize_score( | 1087 | $return_arr[$key] = $this->_normalize_score( |
1098 | $this->_distance($this->_lang_db[$lang1], $value)); | 1088 | $this->_distance($this->_lang_db[$lang1], $value) |
1089 | ); | ||
1099 | } | 1090 | } |
1100 | } | 1091 | } |
1101 | asort($return_arr); | 1092 | asort($return_arr); |
@@ -1104,30 +1095,27 @@ class Text_LanguageDetect | |||
1104 | } | 1095 | } |
1105 | 1096 | ||
1106 | 1097 | ||
1107 | // compare all languages to each other | ||
1108 | } else { | 1098 | } else { |
1099 | // compare all languages to each other | ||
1109 | $return_arr = array(); | 1100 | $return_arr = array(); |
1110 | foreach (array_keys($this->_lang_db) as $lang1) { | 1101 | foreach (array_keys($this->_lang_db) as $lang1) { |
1111 | foreach (array_keys($this->_lang_db) as $lang2) { | 1102 | foreach (array_keys($this->_lang_db) as $lang2) { |
1112 | |||
1113 | // skip comparing languages to themselves | 1103 | // skip comparing languages to themselves |
1114 | if ($lang1 != $lang2) { | 1104 | if ($lang1 != $lang2) { |
1115 | |||
1116 | // don't re-calculate what's already been done | ||
1117 | if (isset($return_arr[$lang2][$lang1])) { | ||
1118 | 1105 | ||
1119 | $return_arr[$lang1][$lang2] = | 1106 | if (isset($return_arr[$lang2][$lang1])) { |
1120 | $return_arr[$lang2][$lang1]; | 1107 | // don't re-calculate what's already been done |
1108 | $return_arr[$lang1][$lang2] | ||
1109 | = $return_arr[$lang2][$lang1]; | ||
1121 | 1110 | ||
1122 | // calculate | ||
1123 | } else { | 1111 | } else { |
1124 | 1112 | // calculate | |
1125 | $return_arr[$lang1][$lang2] = | 1113 | $return_arr[$lang1][$lang2] |
1126 | $this->_normalize_score( | 1114 | = $this->_normalize_score( |
1127 | $this->_distance( | 1115 | $this->_distance( |
1128 | $this->_lang_db[$lang1], | 1116 | $this->_lang_db[$lang1], |
1129 | $this->_lang_db[$lang2] | 1117 | $this->_lang_db[$lang2] |
1130 | ) | 1118 | ) |
1131 | ); | 1119 | ); |
1132 | 1120 | ||
1133 | } | 1121 | } |
@@ -1150,20 +1138,14 @@ class Text_LanguageDetect | |||
1150 | * | 1138 | * |
1151 | * @access public | 1139 | * @access public |
1152 | * @return array language cluster data | 1140 | * @return array language cluster data |
1153 | * @throws PEAR_Error | 1141 | * @throws Text_LanguageDetect_Exception |
1154 | * @see languageSimilarity() | 1142 | * @see languageSimilarity() |
1155 | * @deprecated this function will eventually be removed and placed into | 1143 | * @deprecated this function will eventually be removed and placed into |
1156 | * the model generation class | 1144 | * the model generation class |
1157 | */ | 1145 | */ |
1158 | function clusterLanguages() | 1146 | function clusterLanguages() |
1159 | { | 1147 | { |
1160 | // todo: set the maximum number of clusters | 1148 | // todo: set the maximum number of clusters |
1161 | |||
1162 | // setup check | ||
1163 | if (!$this->_setup_ok($err)) { | ||
1164 | return $err; | ||
1165 | } | ||
1166 | |||
1167 | // return cached result, if any | 1149 | // return cached result, if any |
1168 | if (isset($this->_clusters)) { | 1150 | if (isset($this->_clusters)) { |
1169 | return $this->_clusters; | 1151 | return $this->_clusters; |
@@ -1177,7 +1159,10 @@ class Text_LanguageDetect | |||
1177 | 1159 | ||
1178 | foreach ($langs as $lang) { | 1160 | foreach ($langs as $lang) { |
1179 | if (!isset($this->_lang_db[$lang])) { | 1161 | if (!isset($this->_lang_db[$lang])) { |
1180 | throw new Exception("missing $lang!\n"); | 1162 | throw new Text_LanguageDetect_Exception( |
1163 | "missing $lang!", | ||
1164 | Text_LanguageDetect_Exception::UNKNOWN_LANGUAGE | ||
1165 | ); | ||
1181 | } | 1166 | } |
1182 | } | 1167 | } |
1183 | 1168 | ||
@@ -1186,7 +1171,9 @@ class Text_LanguageDetect | |||
1186 | $langs[$lang1] = $lang1; | 1171 | $langs[$lang1] = $lang1; |
1187 | unset($langs[$old_key]); | 1172 | unset($langs[$old_key]); |
1188 | } | 1173 | } |
1189 | 1174 | ||
1175 | $result_data = $really_map = array(); | ||
1176 | |||
1190 | $i = 0; | 1177 | $i = 0; |
1191 | while (count($langs) > 2 && $i++ < 200) { | 1178 | while (count($langs) > 2 && $i++ < 200) { |
1192 | $highest_score = -1; | 1179 | $highest_score = -1; |
@@ -1194,18 +1181,22 @@ class Text_LanguageDetect | |||
1194 | $highest_key2 = ''; | 1181 | $highest_key2 = ''; |
1195 | foreach ($langs as $lang1) { | 1182 | foreach ($langs as $lang1) { |
1196 | foreach ($langs as $lang2) { | 1183 | foreach ($langs as $lang2) { |
1197 | if ( $lang1 != $lang2 | 1184 | if ($lang1 != $lang2 |
1198 | && $arr[$lang1][$lang2] > $highest_score) { | 1185 | && $arr[$lang1][$lang2] > $highest_score |
1186 | ) { | ||
1199 | $highest_score = $arr[$lang1][$lang2]; | 1187 | $highest_score = $arr[$lang1][$lang2]; |
1200 | $highest_key1 = $lang1; | 1188 | $highest_key1 = $lang1; |
1201 | $highest_key2 = $lang2; | 1189 | $highest_key2 = $lang2; |
1202 | } | 1190 | } |
1203 | } | 1191 | } |
1204 | } | 1192 | } |
1205 | 1193 | ||
1206 | if (!$highest_key1) { | 1194 | if (!$highest_key1) { |
1207 | // should not ever happen | 1195 | // should not ever happen |
1208 | throw new Exception("no highest key? (step: $i)"); | 1196 | throw new Text_LanguageDetect_Exception( |
1197 | "no highest key? (step: $i)", | ||
1198 | Text_LanguageDetect_Exception::NO_HIGHEST_KEY | ||
1199 | ); | ||
1209 | } | 1200 | } |
1210 | 1201 | ||
1211 | if ($highest_score == 0) { | 1202 | if ($highest_score == 0) { |
@@ -1217,7 +1208,7 @@ class Text_LanguageDetect | |||
1217 | $sum1 = array_sum($arr[$highest_key1]); | 1208 | $sum1 = array_sum($arr[$highest_key1]); |
1218 | $sum2 = array_sum($arr[$highest_key2]); | 1209 | $sum2 = array_sum($arr[$highest_key2]); |
1219 | 1210 | ||
1220 | // use the score for the one that is most similar to the rest of | 1211 | // use the score for the one that is most similar to the rest of |
1221 | // the field as the score for the group | 1212 | // the field as the score for the group |
1222 | // todo: could try averaging or "centroid" method instead | 1213 | // todo: could try averaging or "centroid" method instead |
1223 | // seems like that might make more sense | 1214 | // seems like that might make more sense |
@@ -1248,7 +1239,7 @@ class Text_LanguageDetect | |||
1248 | $really_lang = $replaceme; | 1239 | $really_lang = $replaceme; |
1249 | while (isset($really_map[$really_lang])) { | 1240 | while (isset($really_map[$really_lang])) { |
1250 | $really_lang = $really_map[$really_lang]; | 1241 | $really_lang = $really_map[$really_lang]; |
1251 | } | 1242 | } |
1252 | $really_map[$newkey] = $really_lang; | 1243 | $really_map[$newkey] = $really_lang; |
1253 | 1244 | ||
1254 | 1245 | ||
@@ -1259,8 +1250,8 @@ class Text_LanguageDetect | |||
1259 | $arr[$key1][$newkey] = $arr[$key1][$key2]; | 1250 | $arr[$key1][$newkey] = $arr[$key1][$key2]; |
1260 | unset($arr[$key1][$key2]); | 1251 | unset($arr[$key1][$key2]); |
1261 | // replacing $arr[$key1][$key2] with $arr[$key1][$newkey] | 1252 | // replacing $arr[$key1][$key2] with $arr[$key1][$newkey] |
1262 | } | 1253 | } |
1263 | 1254 | ||
1264 | if ($key1 == $replaceme) { | 1255 | if ($key1 == $replaceme) { |
1265 | $arr[$newkey][$key2] = $arr[$key1][$key2]; | 1256 | $arr[$newkey][$key2] = $arr[$key1][$key2]; |
1266 | unset($arr[$key1][$key2]); | 1257 | unset($arr[$key1][$key2]); |
@@ -1273,7 +1264,7 @@ class Text_LanguageDetect | |||
1273 | } | 1264 | } |
1274 | } | 1265 | } |
1275 | } | 1266 | } |
1276 | 1267 | ||
1277 | 1268 | ||
1278 | unset($langs[$highest_key1]); | 1269 | unset($langs[$highest_key1]); |
1279 | unset($langs[$highest_key2]); | 1270 | unset($langs[$highest_key2]); |
@@ -1293,7 +1284,7 @@ class Text_LanguageDetect | |||
1293 | } | 1284 | } |
1294 | 1285 | ||
1295 | $return_val = array( | 1286 | $return_val = array( |
1296 | 'open_forks' => $langs, | 1287 | 'open_forks' => $langs, |
1297 | // the top level of clusters | 1288 | // the top level of clusters |
1298 | // clusters that are mutually exclusive | 1289 | // clusters that are mutually exclusive |
1299 | // or specified by a specific maximum | 1290 | // or specified by a specific maximum |
@@ -1323,11 +1314,11 @@ class Text_LanguageDetect | |||
1323 | * use, and it may disappear or its functionality may change in future | 1314 | * use, and it may disappear or its functionality may change in future |
1324 | * releases without notice. | 1315 | * releases without notice. |
1325 | * | 1316 | * |
1326 | * This compares the sample text to top the top level of clusters. If the | 1317 | * This compares the sample text to top the top level of clusters. If the |
1327 | * sample is similar to the cluster it will drop down and compare it to the | 1318 | * sample is similar to the cluster it will drop down and compare it to the |
1328 | * languages in the cluster, and so on until it hits a leaf node. | 1319 | * languages in the cluster, and so on until it hits a leaf node. |
1329 | * | 1320 | * |
1330 | * this should find the language in considerably fewer compares | 1321 | * this should find the language in considerably fewer compares |
1331 | * (the equivalent of a binary search), however clusterLanguages() is costly | 1322 | * (the equivalent of a binary search), however clusterLanguages() is costly |
1332 | * and the loss of accuracy from this technique is significant. | 1323 | * and the loss of accuracy from this technique is significant. |
1333 | * | 1324 | * |
@@ -1337,15 +1328,14 @@ class Text_LanguageDetect | |||
1337 | * was very large, however in such cases some method of Bayesian inference | 1328 | * was very large, however in such cases some method of Bayesian inference |
1338 | * might be more helpful. | 1329 | * might be more helpful. |
1339 | * | 1330 | * |
1340 | * @see clusterLanguages() | 1331 | * @param string $str input string |
1341 | * @access public | 1332 | * |
1342 | * @param string $str input string | 1333 | * @return array language scores (only those compared) |
1343 | * @return array language scores (only those compared) | 1334 | * @throws Text_LanguageDetect_Exception |
1344 | * @throws PEAR_Error | 1335 | * @see clusterLanguages() |
1345 | */ | 1336 | */ |
1346 | function clusteredSearch($str) | 1337 | public function clusteredSearch($str) |
1347 | { | 1338 | { |
1348 | |||
1349 | // input check | 1339 | // input check |
1350 | if (!Text_LanguageDetect_Parser::validateString($str)) { | 1340 | if (!Text_LanguageDetect_Parser::validateString($str)) { |
1351 | return array(); | 1341 | return array(); |
@@ -1359,7 +1349,7 @@ class Text_LanguageDetect | |||
1359 | $dendogram_data = $result['fork_data']; | 1349 | $dendogram_data = $result['fork_data']; |
1360 | $dendogram_alias = $result['name_map']; | 1350 | $dendogram_alias = $result['name_map']; |
1361 | 1351 | ||
1362 | $sample_obj = new Text_LanguageDetect_Parser($str, $this->_db_filename, $this->_unicode_db_filename); | 1352 | $sample_obj = new Text_LanguageDetect_Parser($str); |
1363 | $sample_obj->prepareTrigram(); | 1353 | $sample_obj->prepareTrigram(); |
1364 | $sample_obj->setPadStart(!$this->_perl_compatible); | 1354 | $sample_obj->setPadStart(!$this->_perl_compatible); |
1365 | $sample_obj->analyze(); | 1355 | $sample_obj->analyze(); |
@@ -1372,7 +1362,7 @@ class Text_LanguageDetect | |||
1372 | } | 1362 | } |
1373 | 1363 | ||
1374 | $i = 0; // counts the number of steps | 1364 | $i = 0; // counts the number of steps |
1375 | 1365 | ||
1376 | foreach ($dendogram_start as $lang) { | 1366 | foreach ($dendogram_start as $lang) { |
1377 | if (isset($dendogram_alias[$lang])) { | 1367 | if (isset($dendogram_alias[$lang])) { |
1378 | $lang_key = $dendogram_alias[$lang]; | 1368 | $lang_key = $dendogram_alias[$lang]; |
@@ -1382,7 +1372,8 @@ class Text_LanguageDetect | |||
1382 | 1372 | ||
1383 | $scores[$lang] = $this->_normalize_score( | 1373 | $scores[$lang] = $this->_normalize_score( |
1384 | $this->_distance($this->_lang_db[$lang_key], $sample_result), | 1374 | $this->_distance($this->_lang_db[$lang_key], $sample_result), |
1385 | $sample_count); | 1375 | $sample_count |
1376 | ); | ||
1386 | 1377 | ||
1387 | $i++; | 1378 | $i++; |
1388 | } | 1379 | } |
@@ -1411,7 +1402,8 @@ class Text_LanguageDetect | |||
1411 | 1402 | ||
1412 | $scores[$lang] = $this->_normalize_score( | 1403 | $scores[$lang] = $this->_normalize_score( |
1413 | $this->_distance($this->_lang_db[$lang_key], $sample_result), | 1404 | $this->_distance($this->_lang_db[$lang_key], $sample_result), |
1414 | $sample_count); | 1405 | $sample_count |
1406 | ); | ||
1415 | 1407 | ||
1416 | //todo: does not need to do same comparison again | 1408 | //todo: does not need to do same comparison again |
1417 | } | 1409 | } |
@@ -1428,8 +1420,8 @@ class Text_LanguageDetect | |||
1428 | 1420 | ||
1429 | $diff = $scores[$cur_key] - $scores[$loser_key]; | 1421 | $diff = $scores[$cur_key] - $scores[$loser_key]; |
1430 | 1422 | ||
1431 | // $cur_key ({$dendogram_alias[$cur_key]}) wins | 1423 | // $cur_key ({$dendogram_alias[$cur_key]}) wins |
1432 | // over $loser_key ({$dendogram_alias[$loser_key]}) | 1424 | // over $loser_key ({$dendogram_alias[$loser_key]}) |
1433 | // with a difference of $diff | 1425 | // with a difference of $diff |
1434 | } | 1426 | } |
1435 | 1427 | ||
@@ -1439,9 +1431,9 @@ class Text_LanguageDetect | |||
1439 | // which paths the algorithm decided to take along the tree | 1431 | // which paths the algorithm decided to take along the tree |
1440 | 1432 | ||
1441 | // but sometimes the last item is only the second highest | 1433 | // but sometimes the last item is only the second highest |
1442 | if ( ($this->_perl_compatible && (end($scores) > prev($scores))) | 1434 | if (($this->_perl_compatible && (end($scores) > prev($scores))) |
1443 | || (!$this->_perl_compatible && (end($scores) < prev($scores)))) { | 1435 | || (!$this->_perl_compatible && (end($scores) < prev($scores))) |
1444 | 1436 | ) { | |
1445 | $real_last_score = current($scores); | 1437 | $real_last_score = current($scores); |
1446 | $real_last_key = key($scores); | 1438 | $real_last_key = key($scores); |
1447 | 1439 | ||
@@ -1449,7 +1441,7 @@ class Text_LanguageDetect | |||
1449 | unset($scores[$real_last_key]); | 1441 | unset($scores[$real_last_key]); |
1450 | $scores[$real_last_key] = $real_last_score; | 1442 | $scores[$real_last_key] = $real_last_score; |
1451 | } | 1443 | } |
1452 | 1444 | ||
1453 | 1445 | ||
1454 | if (!$this->_perl_compatible) { | 1446 | if (!$this->_perl_compatible) { |
1455 | $scores = array_reverse($scores, true); | 1447 | $scores = array_reverse($scores, true); |
@@ -1464,12 +1456,11 @@ class Text_LanguageDetect | |||
1464 | * | 1456 | * |
1465 | * Returns the numbers of characters (not bytes) in a utf8 string | 1457 | * Returns the numbers of characters (not bytes) in a utf8 string |
1466 | * | 1458 | * |
1467 | * @static | 1459 | * @param string $str string to get the length of |
1468 | * @access public | 1460 | * |
1469 | * @param string $str string to get the length of | 1461 | * @return int number of chars |
1470 | * @return int number of chars | ||
1471 | */ | 1462 | */ |
1472 | function utf8strlen($str) | 1463 | public static function utf8strlen($str) |
1473 | { | 1464 | { |
1474 | // utf8_decode() will convert unknown chars to '?', which is actually | 1465 | // utf8_decode() will convert unknown chars to '?', which is actually |
1475 | // ideal for counting. | 1466 | // ideal for counting. |
@@ -1482,53 +1473,45 @@ class Text_LanguageDetect | |||
1482 | /** | 1473 | /** |
1483 | * Returns the unicode value of a utf8 char | 1474 | * Returns the unicode value of a utf8 char |
1484 | * | 1475 | * |
1485 | * @access protected | 1476 | * @param string $char a utf8 (possibly multi-byte) char |
1486 | * @param string $char a utf8 (possibly multi-byte) char | 1477 | * |
1487 | * @return int unicode value or -1 if malformatted | 1478 | * @return int unicode value |
1479 | * @access protected | ||
1480 | * @link http://en.wikipedia.org/wiki/UTF-8 | ||
1488 | */ | 1481 | */ |
1489 | function _utf8char2unicode($char) { | 1482 | function _utf8char2unicode($char) |
1490 | 1483 | { | |
1491 | // strlen() here will actually get the binary length of a single char | 1484 | // strlen() here will actually get the binary length of a single char |
1492 | switch (strlen($char)) { | 1485 | switch (strlen($char)) { |
1493 | 1486 | case 1: | |
1494 | // for a reference, see http://en.wikipedia.org/wiki/UTF-8 | 1487 | // normal ASCII-7 byte |
1495 | 1488 | // 0xxxxxxx --> 0xxxxxxx | |
1496 | case 1: | 1489 | return ord($char{0}); |
1497 | // normal ASCII-7 byte | 1490 | |
1498 | // 0xxxxxxx --> 0xxxxxxx | 1491 | case 2: |
1499 | return ord($char{0}); | 1492 | // 2 byte unicode |
1500 | 1493 | // 110zzzzx 10xxxxxx --> 00000zzz zxxxxxxx | |
1501 | case 2: | 1494 | $z = (ord($char{0}) & 0x000001F) << 6; |
1502 | // 2 byte unicode | 1495 | $x = (ord($char{1}) & 0x0000003F); |
1503 | // 110zzzzx 10xxxxxx --> 00000zzz zxxxxxxx | 1496 | return ($z | $x); |
1504 | $z = (ord($char{0}) & 0x000001F) << 6; | 1497 | |
1505 | $x = (ord($char{1}) & 0x0000003F); | 1498 | case 3: |
1506 | 1499 | // 3 byte unicode | |
1507 | return ($z | $x); | 1500 | // 1110zzzz 10zxxxxx 10xxxxxx --> zzzzzxxx xxxxxxxx |
1508 | 1501 | $z = (ord($char{0}) & 0x0000000F) << 12; | |
1509 | case 3: | 1502 | $x1 = (ord($char{1}) & 0x0000003F) << 6; |
1510 | // 3 byte unicode | 1503 | $x2 = (ord($char{2}) & 0x0000003F); |
1511 | // 1110zzzz 10zxxxxx 10xxxxxx --> zzzzzxxx xxxxxxxx | 1504 | return ($z | $x1 | $x2); |
1512 | $z = (ord($char{0}) & 0x0000000F) << 12; | 1505 | |
1513 | $x1 = (ord($char{1}) & 0x0000003F) << 6; | 1506 | case 4: |
1514 | $x2 = (ord($char{2}) & 0x0000003F); | 1507 | // 4 byte unicode |
1515 | 1508 | // 11110zzz 10zzxxxx 10xxxxxx 10xxxxxx --> | |
1516 | return ($z | $x1 | $x2); | 1509 | // 000zzzzz xxxxxxxx xxxxxxxx |
1517 | 1510 | $z1 = (ord($char{0}) & 0x00000007) << 18; | |
1518 | case 4: | 1511 | $z2 = (ord($char{1}) & 0x0000003F) << 12; |
1519 | // 4 byte unicode | 1512 | $x1 = (ord($char{2}) & 0x0000003F) << 6; |
1520 | // 11110zzz 10zzxxxx 10xxxxxx 10xxxxxx --> | 1513 | $x2 = (ord($char{3}) & 0x0000003F); |
1521 | // 000zzzzz xxxxxxxx xxxxxxxx | 1514 | return ($z1 | $z2 | $x1 | $x2); |
1522 | $z1 = (ord($char{0}) & 0x00000007) << 18; | ||
1523 | $z2 = (ord($char{1}) & 0x0000003F) << 12; | ||
1524 | $x1 = (ord($char{2}) & 0x0000003F) << 6; | ||
1525 | $x2 = (ord($char{3}) & 0x0000003F); | ||
1526 | |||
1527 | return ($z1 | $z2 | $x1 | $x2); | ||
1528 | |||
1529 | default: | ||
1530 | // error: malformatted char? | ||
1531 | return -1; | ||
1532 | } | 1515 | } |
1533 | } | 1516 | } |
1534 | 1517 | ||
@@ -1536,18 +1519,18 @@ class Text_LanguageDetect | |||
1536 | * utf8-safe fast character iterator | 1519 | * utf8-safe fast character iterator |
1537 | * | 1520 | * |
1538 | * Will get the next character starting from $counter, which will then be | 1521 | * Will get the next character starting from $counter, which will then be |
1539 | * incremented. If a multi-byte char the bytes will be concatenated and | 1522 | * incremented. If a multi-byte char the bytes will be concatenated and |
1540 | * $counter will be incremeted by the number of bytes in the char. | 1523 | * $counter will be incremeted by the number of bytes in the char. |
1541 | * | 1524 | * |
1542 | * @access private | 1525 | * @param string $str the string being iterated over |
1543 | * @param string &$str the string being iterated over | 1526 | * @param int &$counter the iterator, will increment by reference |
1544 | * @param int &$counter the iterator, will increment by reference | 1527 | * @param bool $special_convert whether to do special conversions |
1545 | * @param bool $special_convert whether to do special conversions | 1528 | * |
1546 | * @return char the next (possibly multi-byte) char from $counter | 1529 | * @return char the next (possibly multi-byte) char from $counter |
1530 | * @access private | ||
1547 | */ | 1531 | */ |
1548 | function _next_char(&$str, &$counter, $special_convert = false) | 1532 | static function _next_char($str, &$counter, $special_convert = false) |
1549 | { | 1533 | { |
1550 | |||
1551 | $char = $str{$counter++}; | 1534 | $char = $str{$counter++}; |
1552 | $ord = ord($char); | 1535 | $ord = ord($char); |
1553 | 1536 | ||
@@ -1556,7 +1539,6 @@ class Text_LanguageDetect | |||
1556 | 1539 | ||
1557 | // normal ascii one byte char | 1540 | // normal ascii one byte char |
1558 | if ($ord <= 127) { | 1541 | if ($ord <= 127) { |
1559 | |||
1560 | // special conversions needed for this package | 1542 | // special conversions needed for this package |
1561 | // (that only apply to regular ascii characters) | 1543 | // (that only apply to regular ascii characters) |
1562 | // lower case, and convert all non-alphanumeric characters | 1544 | // lower case, and convert all non-alphanumeric characters |
@@ -1571,8 +1553,8 @@ class Text_LanguageDetect | |||
1571 | 1553 | ||
1572 | return $char; | 1554 | return $char; |
1573 | 1555 | ||
1574 | // multi-byte chars | ||
1575 | } elseif ($ord >> 5 == 6) { // two-byte char | 1556 | } elseif ($ord >> 5 == 6) { // two-byte char |
1557 | // multi-byte chars | ||
1576 | $nextchar = $str{$counter++}; // get next byte | 1558 | $nextchar = $str{$counter++}; // get next byte |
1577 | 1559 | ||
1578 | // lower-casing of non-ascii characters is still incomplete | 1560 | // lower-casing of non-ascii characters is still incomplete |
@@ -1582,27 +1564,27 @@ class Text_LanguageDetect | |||
1582 | if ($ord == 195) { | 1564 | if ($ord == 195) { |
1583 | $nextord = ord($nextchar); | 1565 | $nextord = ord($nextchar); |
1584 | $nextord_adj = $nextord + 64; | 1566 | $nextord_adj = $nextord + 64; |
1585 | // for a reference, see | 1567 | // for a reference, see |
1586 | // http://www.ramsch.org/martin/uni/fmi-hp/iso8859-1.html | 1568 | // http://www.ramsch.org/martin/uni/fmi-hp/iso8859-1.html |
1587 | 1569 | ||
1588 | // À - Þ but not × | 1570 | // À - Þ but not × |
1589 | if ( $nextord_adj >= 192 | 1571 | if ($nextord_adj >= 192 |
1590 | && $nextord_adj <= 222 | 1572 | && $nextord_adj <= 222 |
1591 | && $nextord_adj != 215) { | 1573 | && $nextord_adj != 215 |
1592 | 1574 | ) { | |
1593 | $nextchar = chr($nextord + 32); | 1575 | $nextchar = chr($nextord + 32); |
1594 | } | 1576 | } |
1595 | 1577 | ||
1596 | // lower case cyrillic alphabet | ||
1597 | } elseif ($ord == 208) { | 1578 | } elseif ($ord == 208) { |
1579 | // lower case cyrillic alphabet | ||
1598 | $nextord = ord($nextchar); | 1580 | $nextord = ord($nextchar); |
1599 | // if A - Pe | 1581 | // if A - Pe |
1600 | if ($nextord >= 144 && $nextord <= 159) { | 1582 | if ($nextord >= 144 && $nextord <= 159) { |
1601 | // lower case | 1583 | // lower case |
1602 | $nextchar = chr($nextord + 32); | 1584 | $nextchar = chr($nextord + 32); |
1603 | 1585 | ||
1604 | // if Er - Ya | ||
1605 | } elseif ($nextord >= 160 && $nextord <= 175) { | 1586 | } elseif ($nextord >= 160 && $nextord <= 175) { |
1587 | // if Er - Ya | ||
1606 | // lower case | 1588 | // lower case |
1607 | $char = chr(209); // == $ord++ | 1589 | $char = chr(209); // == $ord++ |
1608 | $nextchar = chr($nextord - 32); | 1590 | $nextchar = chr($nextord - 32); |
@@ -1611,12 +1593,11 @@ class Text_LanguageDetect | |||
1611 | } | 1593 | } |
1612 | 1594 | ||
1613 | // tag on next byte | 1595 | // tag on next byte |
1614 | return $char . $nextchar; | 1596 | return $char . $nextchar; |
1615 | |||
1616 | } elseif ($ord >> 4 == 14) { // three-byte char | 1597 | } elseif ($ord >> 4 == 14) { // three-byte char |
1617 | 1598 | ||
1618 | // tag on next 2 bytes | 1599 | // tag on next 2 bytes |
1619 | return $char . $str{$counter++} . $str{$counter++}; | 1600 | return $char . $str{$counter++} . $str{$counter++}; |
1620 | 1601 | ||
1621 | } elseif ($ord >> 3 == 30) { // four-byte char | 1602 | } elseif ($ord >> 3 == 30) { // four-byte char |
1622 | 1603 | ||
@@ -1628,8 +1609,85 @@ class Text_LanguageDetect | |||
1628 | } | 1609 | } |
1629 | } | 1610 | } |
1630 | 1611 | ||
1631 | } | 1612 | /** |
1613 | * Converts an $language input parameter from the configured mode | ||
1614 | * to the language name that is used internally. | ||
1615 | * | ||
1616 | * Works for strings and arrays. | ||
1617 | * | ||
1618 | * @param string|array $lang A language description ("english"/"en"/"eng") | ||
1619 | * @param boolean $convertKey If $lang is an array, setting $key | ||
1620 | * converts the keys to the language name. | ||
1621 | * | ||
1622 | * @return string|array Language name | ||
1623 | */ | ||
1624 | function _convertFromNameMode($lang, $convertKey = false) | ||
1625 | { | ||
1626 | if ($this->_name_mode == 0) { | ||
1627 | return $lang; | ||
1628 | } | ||
1629 | |||
1630 | if ($this->_name_mode == 2) { | ||
1631 | $method = 'code2ToName'; | ||
1632 | } else { | ||
1633 | $method = 'code3ToName'; | ||
1634 | } | ||
1635 | |||
1636 | if (is_string($lang)) { | ||
1637 | return (string)Text_LanguageDetect_ISO639::$method($lang); | ||
1638 | } | ||
1639 | |||
1640 | $newlang = array(); | ||
1641 | foreach ($lang as $key => $val) { | ||
1642 | if ($convertKey) { | ||
1643 | $newkey = (string)Text_LanguageDetect_ISO639::$method($key); | ||
1644 | $newlang[$newkey] = $val; | ||
1645 | } else { | ||
1646 | $newlang[$key] = (string)Text_LanguageDetect_ISO639::$method($val); | ||
1647 | } | ||
1648 | } | ||
1649 | return $newlang; | ||
1650 | } | ||
1632 | 1651 | ||
1633 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ | 1652 | /** |
1653 | * Converts an $language output parameter from the language name that is | ||
1654 | * used internally to the configured mode. | ||
1655 | * | ||
1656 | * Works for strings and arrays. | ||
1657 | * | ||
1658 | * @param string|array $lang A language description ("english"/"en"/"eng") | ||
1659 | * @param boolean $convertKey If $lang is an array, setting $key | ||
1660 | * converts the keys to the language name. | ||
1661 | * | ||
1662 | * @return string|array Language name | ||
1663 | */ | ||
1664 | function _convertToNameMode($lang, $convertKey = false) | ||
1665 | { | ||
1666 | if ($this->_name_mode == 0) { | ||
1667 | return $lang; | ||
1668 | } | ||
1669 | |||
1670 | if ($this->_name_mode == 2) { | ||
1671 | $method = 'nameToCode2'; | ||
1672 | } else { | ||
1673 | $method = 'nameToCode3'; | ||
1674 | } | ||
1675 | |||
1676 | if (is_string($lang)) { | ||
1677 | return Text_LanguageDetect_ISO639::$method($lang); | ||
1678 | } | ||
1679 | |||
1680 | $newlang = array(); | ||
1681 | foreach ($lang as $key => $val) { | ||
1682 | if ($convertKey) { | ||
1683 | $newkey = Text_LanguageDetect_ISO639::$method($key); | ||
1684 | $newlang[$newkey] = $val; | ||
1685 | } else { | ||
1686 | $newlang[$key] = Text_LanguageDetect_ISO639::$method($val); | ||
1687 | } | ||
1688 | } | ||
1689 | return $newlang; | ||
1690 | } | ||
1691 | } | ||
1634 | 1692 | ||
1635 | ?> | 1693 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ \ No newline at end of file |
diff --git a/inc/3rdparty/libraries/language-detect/LanguageDetect/Exception.php b/inc/3rdparty/libraries/language-detect/LanguageDetect/Exception.php new file mode 100644 index 00000000..196d994f --- /dev/null +++ b/inc/3rdparty/libraries/language-detect/LanguageDetect/Exception.php | |||
@@ -0,0 +1,57 @@ | |||
1 | <?php | ||
2 | class Text_LanguageDetect_Exception extends Exception | ||
3 | { | ||
4 | /** | ||
5 | * Database file could not be found | ||
6 | */ | ||
7 | const DB_NOT_FOUND = 10; | ||
8 | |||
9 | /** | ||
10 | * Database file found, but not readable | ||
11 | */ | ||
12 | const DB_NOT_READABLE = 11; | ||
13 | |||
14 | /** | ||
15 | * Database file is empty | ||
16 | */ | ||
17 | const DB_EMPTY = 12; | ||
18 | |||
19 | /** | ||
20 | * Database contents is not a PHP array | ||
21 | */ | ||
22 | const DB_NOT_ARRAY = 13; | ||
23 | |||
24 | /** | ||
25 | * Magic quotes are activated | ||
26 | */ | ||
27 | const MAGIC_QUOTES = 14; | ||
28 | |||
29 | |||
30 | /** | ||
31 | * Parameter of invalid type passed to method | ||
32 | */ | ||
33 | const PARAM_TYPE = 20; | ||
34 | |||
35 | /** | ||
36 | * Character in parameter is invalid | ||
37 | */ | ||
38 | const INVALID_CHAR = 21; | ||
39 | |||
40 | |||
41 | /** | ||
42 | * Language is not in the database | ||
43 | */ | ||
44 | const UNKNOWN_LANGUAGE = 30; | ||
45 | |||
46 | |||
47 | /** | ||
48 | * Error during block detection | ||
49 | */ | ||
50 | const BLOCK_DETECTION = 40; | ||
51 | |||
52 | |||
53 | /** | ||
54 | * Error while clustering languages | ||
55 | */ | ||
56 | const NO_HIGHEST_KEY = 50; | ||
57 | } | ||
diff --git a/inc/3rdparty/libraries/language-detect/LanguageDetect/ISO639.php b/inc/3rdparty/libraries/language-detect/LanguageDetect/ISO639.php new file mode 100644 index 00000000..05b0590d --- /dev/null +++ b/inc/3rdparty/libraries/language-detect/LanguageDetect/ISO639.php | |||
@@ -0,0 +1,339 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Part of Text_LanguageDetect | ||
4 | * | ||
5 | * PHP version 5 | ||
6 | * | ||
7 | * @category Text | ||
8 | * @package Text_LanguageDetect | ||
9 | * @author Christian Weiske <cweiske@php.net> | ||
10 | * @copyright 2011 Christian Weiske <cweiske@php.net> | ||
11 | * @license http://www.debian.org/misc/bsd.license BSD | ||
12 | * @version SVN: $Id$ | ||
13 | * @link http://pear.php.net/package/Text_LanguageDetect/ | ||
14 | */ | ||
15 | |||
16 | /** | ||
17 | * Provides a mapping between the languages from lang.dat and the | ||
18 | * ISO 639-1 and ISO-639-2 codes. | ||
19 | * | ||
20 | * Note that this class contains only languages that exist in lang.dat. | ||
21 | * | ||
22 | * @category Text | ||
23 | * @package Text_LanguageDetect | ||
24 | * @author Christian Weiske <cweiske@php.net> | ||
25 | * @copyright 2011 Christian Weiske <cweiske@php.net> | ||
26 | * @license http://www.debian.org/misc/bsd.license BSD | ||
27 | * @link http://www.loc.gov/standards/iso639-2/php/code_list.php | ||
28 | */ | ||
29 | class Text_LanguageDetect_ISO639 | ||
30 | { | ||
31 | /** | ||
32 | * Maps all language names from the language database to the | ||
33 | * ISO 639-1 2-letter language code. | ||
34 | * | ||
35 | * NULL indicates that there is no 2-letter code. | ||
36 | * | ||
37 | * @var array | ||
38 | */ | ||
39 | public static $nameToCode2 = array( | ||
40 | 'albanian' => 'sq', | ||
41 | 'arabic' => 'ar', | ||
42 | 'azeri' => 'az', | ||
43 | 'bengali' => 'bn', | ||
44 | 'bulgarian' => 'bg', | ||
45 | 'cebuano' => null, | ||
46 | 'croatian' => 'hr', | ||
47 | 'czech' => 'cs', | ||
48 | 'danish' => 'da', | ||
49 | 'dutch' => 'nl', | ||
50 | 'english' => 'en', | ||
51 | 'estonian' => 'et', | ||
52 | 'farsi' => 'fa', | ||
53 | 'finnish' => 'fi', | ||
54 | 'french' => 'fr', | ||
55 | 'german' => 'de', | ||
56 | 'hausa' => 'ha', | ||
57 | 'hawaiian' => null, | ||
58 | 'hindi' => 'hi', | ||
59 | 'hungarian' => 'hu', | ||
60 | 'icelandic' => 'is', | ||
61 | 'indonesian' => 'id', | ||
62 | 'italian' => 'it', | ||
63 | 'kazakh' => 'kk', | ||
64 | 'kyrgyz' => 'ky', | ||
65 | 'latin' => 'la', | ||
66 | 'latvian' => 'lv', | ||
67 | 'lithuanian' => 'lt', | ||
68 | 'macedonian' => 'mk', | ||
69 | 'mongolian' => 'mn', | ||
70 | 'nepali' => 'ne', | ||
71 | 'norwegian' => 'no', | ||
72 | 'pashto' => 'ps', | ||
73 | 'pidgin' => null, | ||
74 | 'polish' => 'pl', | ||
75 | 'portuguese' => 'pt', | ||
76 | 'romanian' => 'ro', | ||
77 | 'russian' => 'ru', | ||
78 | 'serbian' => 'sr', | ||
79 | 'slovak' => 'sk', | ||
80 | 'slovene' => 'sl', | ||
81 | 'somali' => 'so', | ||
82 | 'spanish' => 'es', | ||
83 | 'swahili' => 'sw', | ||
84 | 'swedish' => 'sv', | ||
85 | 'tagalog' => 'tl', | ||
86 | 'turkish' => 'tr', | ||
87 | 'ukrainian' => 'uk', | ||
88 | 'urdu' => 'ur', | ||
89 | 'uzbek' => 'uz', | ||
90 | 'vietnamese' => 'vi', | ||
91 | 'welsh' => 'cy', | ||
92 | ); | ||
93 | |||
94 | /** | ||
95 | * Maps all language names from the language database to the | ||
96 | * ISO 639-2 3-letter language code. | ||
97 | * | ||
98 | * @var array | ||
99 | */ | ||
100 | public static $nameToCode3 = array( | ||
101 | 'albanian' => 'sqi', | ||
102 | 'arabic' => 'ara', | ||
103 | 'azeri' => 'aze', | ||
104 | 'bengali' => 'ben', | ||
105 | 'bulgarian' => 'bul', | ||
106 | 'cebuano' => 'ceb', | ||
107 | 'croatian' => 'hrv', | ||
108 | 'czech' => 'ces', | ||
109 | 'danish' => 'dan', | ||
110 | 'dutch' => 'nld', | ||
111 | 'english' => 'eng', | ||
112 | 'estonian' => 'est', | ||
113 | 'farsi' => 'fas', | ||
114 | 'finnish' => 'fin', | ||
115 | 'french' => 'fra', | ||
116 | 'german' => 'deu', | ||
117 | 'hausa' => 'hau', | ||
118 | 'hawaiian' => 'haw', | ||
119 | 'hindi' => 'hin', | ||
120 | 'hungarian' => 'hun', | ||
121 | 'icelandic' => 'isl', | ||
122 | 'indonesian' => 'ind', | ||
123 | 'italian' => 'ita', | ||
124 | 'kazakh' => 'kaz', | ||
125 | 'kyrgyz' => 'kir', | ||
126 | 'latin' => 'lat', | ||
127 | 'latvian' => 'lav', | ||
128 | 'lithuanian' => 'lit', | ||
129 | 'macedonian' => 'mkd', | ||
130 | 'mongolian' => 'mon', | ||
131 | 'nepali' => 'nep', | ||
132 | 'norwegian' => 'nor', | ||
133 | 'pashto' => 'pus', | ||
134 | 'pidgin' => 'crp', | ||
135 | 'polish' => 'pol', | ||
136 | 'portuguese' => 'por', | ||
137 | 'romanian' => 'ron', | ||
138 | 'russian' => 'rus', | ||
139 | 'serbian' => 'srp', | ||
140 | 'slovak' => 'slk', | ||
141 | 'slovene' => 'slv', | ||
142 | 'somali' => 'som', | ||
143 | 'spanish' => 'spa', | ||
144 | 'swahili' => 'swa', | ||
145 | 'swedish' => 'swe', | ||
146 | 'tagalog' => 'tgl', | ||
147 | 'turkish' => 'tur', | ||
148 | 'ukrainian' => 'ukr', | ||
149 | 'urdu' => 'urd', | ||
150 | 'uzbek' => 'uzb', | ||
151 | 'vietnamese' => 'vie', | ||
152 | 'welsh' => 'cym', | ||
153 | ); | ||
154 | |||
155 | /** | ||
156 | * Maps ISO 639-1 2-letter language codes to the language names | ||
157 | * in the language database | ||
158 | * | ||
159 | * Not all languages have a 2 letter code, so some are missing | ||
160 | * | ||
161 | * @var array | ||
162 | */ | ||
163 | public static $code2ToName = array( | ||
164 | 'ar' => 'arabic', | ||
165 | 'az' => 'azeri', | ||
166 | 'bg' => 'bulgarian', | ||
167 | 'bn' => 'bengali', | ||
168 | 'cs' => 'czech', | ||
169 | 'cy' => 'welsh', | ||
170 | 'da' => 'danish', | ||
171 | 'de' => 'german', | ||
172 | 'en' => 'english', | ||
173 | 'es' => 'spanish', | ||
174 | 'et' => 'estonian', | ||
175 | 'fa' => 'farsi', | ||
176 | 'fi' => 'finnish', | ||
177 | 'fr' => 'french', | ||
178 | 'ha' => 'hausa', | ||
179 | 'hi' => 'hindi', | ||
180 | 'hr' => 'croatian', | ||
181 | 'hu' => 'hungarian', | ||
182 | 'id' => 'indonesian', | ||
183 | 'is' => 'icelandic', | ||
184 | 'it' => 'italian', | ||
185 | 'kk' => 'kazakh', | ||
186 | 'ky' => 'kyrgyz', | ||
187 | 'la' => 'latin', | ||
188 | 'lt' => 'lithuanian', | ||
189 | 'lv' => 'latvian', | ||
190 | 'mk' => 'macedonian', | ||
191 | 'mn' => 'mongolian', | ||
192 | 'ne' => 'nepali', | ||
193 | 'nl' => 'dutch', | ||
194 | 'no' => 'norwegian', | ||
195 | 'pl' => 'polish', | ||
196 | 'ps' => 'pashto', | ||
197 | 'pt' => 'portuguese', | ||
198 | 'ro' => 'romanian', | ||
199 | 'ru' => 'russian', | ||
200 | 'sk' => 'slovak', | ||
201 | 'sl' => 'slovene', | ||
202 | 'so' => 'somali', | ||
203 | 'sq' => 'albanian', | ||
204 | 'sr' => 'serbian', | ||
205 | 'sv' => 'swedish', | ||
206 | 'sw' => 'swahili', | ||
207 | 'tl' => 'tagalog', | ||
208 | 'tr' => 'turkish', | ||
209 | 'uk' => 'ukrainian', | ||
210 | 'ur' => 'urdu', | ||
211 | 'uz' => 'uzbek', | ||
212 | 'vi' => 'vietnamese', | ||
213 | ); | ||
214 | |||
215 | /** | ||
216 | * Maps ISO 639-2 3-letter language codes to the language names | ||
217 | * in the language database. | ||
218 | * | ||
219 | * @var array | ||
220 | */ | ||
221 | public static $code3ToName = array( | ||
222 | 'ara' => 'arabic', | ||
223 | 'aze' => 'azeri', | ||
224 | 'ben' => 'bengali', | ||
225 | 'bul' => 'bulgarian', | ||
226 | 'ceb' => 'cebuano', | ||
227 | 'ces' => 'czech', | ||
228 | 'crp' => 'pidgin', | ||
229 | 'cym' => 'welsh', | ||
230 | 'dan' => 'danish', | ||
231 | 'deu' => 'german', | ||
232 | 'eng' => 'english', | ||
233 | 'est' => 'estonian', | ||
234 | 'fas' => 'farsi', | ||
235 | 'fin' => 'finnish', | ||
236 | 'fra' => 'french', | ||
237 | 'hau' => 'hausa', | ||
238 | 'haw' => 'hawaiian', | ||
239 | 'hin' => 'hindi', | ||
240 | 'hrv' => 'croatian', | ||
241 | 'hun' => 'hungarian', | ||
242 | 'ind' => 'indonesian', | ||
243 | 'isl' => 'icelandic', | ||
244 | 'ita' => 'italian', | ||
245 | 'kaz' => 'kazakh', | ||
246 | 'kir' => 'kyrgyz', | ||
247 | 'lat' => 'latin', | ||
248 | 'lav' => 'latvian', | ||
249 | 'lit' => 'lithuanian', | ||
250 | 'mkd' => 'macedonian', | ||
251 | 'mon' => 'mongolian', | ||
252 | 'nep' => 'nepali', | ||
253 | 'nld' => 'dutch', | ||
254 | 'nor' => 'norwegian', | ||
255 | 'pol' => 'polish', | ||
256 | 'por' => 'portuguese', | ||
257 | 'pus' => 'pashto', | ||
258 | 'rom' => 'romanian', | ||
259 | 'rus' => 'russian', | ||
260 | 'slk' => 'slovak', | ||
261 | 'slv' => 'slovene', | ||
262 | 'som' => 'somali', | ||
263 | 'spa' => 'spanish', | ||
264 | 'sqi' => 'albanian', | ||
265 | 'srp' => 'serbian', | ||
266 | 'swa' => 'swahili', | ||
267 | 'swe' => 'swedish', | ||
268 | 'tgl' => 'tagalog', | ||
269 | 'tur' => 'turkish', | ||
270 | 'ukr' => 'ukrainian', | ||
271 | 'urd' => 'urdu', | ||
272 | 'uzb' => 'uzbek', | ||
273 | 'vie' => 'vietnamese', | ||
274 | ); | ||
275 | |||
276 | /** | ||
277 | * Returns the 2-letter ISO 639-1 code for the given language name. | ||
278 | * | ||
279 | * @param string $lang English language name like "swedish" | ||
280 | * | ||
281 | * @return string Two-letter language code (e.g. "sv") or NULL if not found | ||
282 | */ | ||
283 | public static function nameToCode2($lang) | ||
284 | { | ||
285 | $lang = strtolower($lang); | ||
286 | if (!isset(self::$nameToCode2[$lang])) { | ||
287 | return null; | ||
288 | } | ||
289 | return self::$nameToCode2[$lang]; | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * Returns the 3-letter ISO 639-2 code for the given language name. | ||
294 | * | ||
295 | * @param string $lang English language name like "swedish" | ||
296 | * | ||
297 | * @return string Three-letter language code (e.g. "swe") or NULL if not found | ||
298 | */ | ||
299 | public static function nameToCode3($lang) | ||
300 | { | ||
301 | $lang = strtolower($lang); | ||
302 | if (!isset(self::$nameToCode3[$lang])) { | ||
303 | return null; | ||
304 | } | ||
305 | return self::$nameToCode3[$lang]; | ||
306 | } | ||
307 | |||
308 | /** | ||
309 | * Returns the language name for the given 2-letter ISO 639-1 code. | ||
310 | * | ||
311 | * @param string $code Two-letter language code (e.g. "sv") | ||
312 | * | ||
313 | * @return string English language name like "swedish" | ||
314 | */ | ||
315 | public static function code2ToName($code) | ||
316 | { | ||
317 | $lang = strtolower($code); | ||
318 | if (!isset(self::$code2ToName[$code])) { | ||
319 | return null; | ||
320 | } | ||
321 | return self::$code2ToName[$code]; | ||
322 | } | ||
323 | |||
324 | /** | ||
325 | * Returns the language name for the given 3-letter ISO 639-2 code. | ||
326 | * | ||
327 | * @param string $code Three-letter language code (e.g. "swe") | ||
328 | * | ||
329 | * @return string English language name like "swedish" | ||
330 | */ | ||
331 | public static function code3ToName($code) | ||
332 | { | ||
333 | $lang = strtolower($code); | ||
334 | if (!isset(self::$code3ToName[$code])) { | ||
335 | return null; | ||
336 | } | ||
337 | return self::$code3ToName[$code]; | ||
338 | } | ||
339 | } \ No newline at end of file | ||
diff --git a/inc/3rdparty/libraries/language-detect/Parser.php b/inc/3rdparty/libraries/language-detect/LanguageDetect/Parser.php index 7f15fa98..fb0e1e20 100644 --- a/inc/3rdparty/libraries/language-detect/Parser.php +++ b/inc/3rdparty/libraries/language-detect/LanguageDetect/Parser.php | |||
@@ -8,7 +8,7 @@ | |||
8 | * @author Nicholas Pisarro | 8 | * @author Nicholas Pisarro |
9 | * @copyright 2006 | 9 | * @copyright 2006 |
10 | * @license BSD | 10 | * @license BSD |
11 | * @version CVS: $Id: Parser.php,v 1.5 2006/03/11 05:45:05 taak Exp $ | 11 | * @version CVS: $Id: Parser.php 322327 2012-01-15 17:55:59Z cweiske $ |
12 | * @link http://pear.php.net/package/Text_LanguageDetect/ | 12 | * @link http://pear.php.net/package/Text_LanguageDetect/ |
13 | * @link http://langdetect.blogspot.com/ | 13 | * @link http://langdetect.blogspot.com/ |
14 | */ | 14 | */ |
@@ -28,7 +28,7 @@ | |||
28 | * @author Nicholas Pisarro | 28 | * @author Nicholas Pisarro |
29 | * @copyright 2006 | 29 | * @copyright 2006 |
30 | * @license BSD | 30 | * @license BSD |
31 | * @version release: 0.2.3 | 31 | * @version release: 0.3.0 |
32 | */ | 32 | */ |
33 | class Text_LanguageDetect_Parser extends Text_LanguageDetect | 33 | class Text_LanguageDetect_Parser extends Text_LanguageDetect |
34 | { | 34 | { |
@@ -102,21 +102,17 @@ class Text_LanguageDetect_Parser extends Text_LanguageDetect | |||
102 | * @access private | 102 | * @access private |
103 | * @param string $string string to be parsed | 103 | * @param string $string string to be parsed |
104 | */ | 104 | */ |
105 | function Text_LanguageDetect_Parser($string, $db=null, $unicode_db=null) { | 105 | function Text_LanguageDetect_Parser($string) { |
106 | if (isset($db)) $this->_db_filename = $db; | ||
107 | if (isset($unicode_db)) $this->_unicode_db_filename = $unicode_db; | ||
108 | $this->_string = $string; | 106 | $this->_string = $string; |
109 | } | 107 | } |
110 | 108 | ||
111 | /** | 109 | /** |
112 | * Returns true if a string is suitable for parsing | 110 | * Returns true if a string is suitable for parsing |
113 | * | 111 | * |
114 | * @static | ||
115 | * @access public | ||
116 | * @param string $str input string to test | 112 | * @param string $str input string to test |
117 | * @return bool true if acceptable, false if not | 113 | * @return bool true if acceptable, false if not |
118 | */ | 114 | */ |
119 | function validateString($str) { | 115 | public static function validateString($str) { |
120 | if (!empty($str) && strlen($str) > 3 && preg_match('/\S/', $str)) { | 116 | if (!empty($str) && strlen($str) > 3 && preg_match('/\S/', $str)) { |
121 | return true; | 117 | return true; |
122 | } else { | 118 | } else { |
@@ -222,8 +218,7 @@ class Text_LanguageDetect_Parser extends Text_LanguageDetect | |||
222 | 218 | ||
223 | // unicode startup | 219 | // unicode startup |
224 | if ($this->_compile_unicode) { | 220 | if ($this->_compile_unicode) { |
225 | $blocks =& $this->_read_unicode_block_db(); | 221 | $blocks = $this->_read_unicode_block_db(); |
226 | |||
227 | $block_count = count($blocks); | 222 | $block_count = count($blocks); |
228 | 223 | ||
229 | $skipped_count = 0; | 224 | $skipped_count = 0; |
@@ -349,6 +344,4 @@ class Text_LanguageDetect_Parser extends Text_LanguageDetect | |||
349 | } | 344 | } |
350 | } | 345 | } |
351 | 346 | ||
352 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ | 347 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ \ No newline at end of file |
353 | |||
354 | ?> | ||
diff --git a/inc/3rdparty/libraries/readability/Readability.php b/inc/3rdparty/libraries/readability/Readability.php index 2e8991cc..4fa3ba63 100644..100755 --- a/inc/3rdparty/libraries/readability/Readability.php +++ b/inc/3rdparty/libraries/readability/Readability.php | |||
@@ -1,1138 +1,1151 @@ | |||
1 | <?php | 1 | <?php |
2 | /** | 2 | /** |
3 | * Arc90's Readability ported to PHP for FiveFilters.org | 3 | * Arc90's Readability ported to PHP for FiveFilters.org |
4 | * Based on readability.js version 1.7.1 (without multi-page support) | 4 | * Based on readability.js version 1.7.1 (without multi-page support) |
5 | * Updated to allow HTML5 parsing with html5lib | 5 | * Updated to allow HTML5 parsing with html5lib |
6 | * Updated with lightClean mode to preserve more images and youtube/vimeo/viddler embeds | 6 | * Updated with lightClean mode to preserve more images and youtube/vimeo/viddler embeds |
7 | * ------------------------------------------------------ | 7 | * ------------------------------------------------------ |
8 | * Original URL: http://lab.arc90.com/experiments/readability/js/readability.js | 8 | * Original URL: http://lab.arc90.com/experiments/readability/js/readability.js |
9 | * Arc90's project URL: http://lab.arc90.com/experiments/readability/ | 9 | * Arc90's project URL: http://lab.arc90.com/experiments/readability/ |
10 | * JS Source: http://code.google.com/p/arc90labs-readability | 10 | * JS Source: http://code.google.com/p/arc90labs-readability |
11 | * Ported by: Keyvan Minoukadeh, http://www.keyvan.net | 11 | * Ported by: Keyvan Minoukadeh, http://www.keyvan.net |
12 | * More information: http://fivefilters.org/content-only/ | 12 | * More information: http://fivefilters.org/content-only/ |
13 | * License: Apache License, Version 2.0 | 13 | * License: Apache License, Version 2.0 |
14 | * Requires: PHP5 | 14 | * Requires: PHP5 |
15 | * Date: 2012-09-19 | 15 | * Date: 2012-09-19 |
16 | * | 16 | * |
17 | * Differences between the PHP port and the original | 17 | * Differences between the PHP port and the original |
18 | * ------------------------------------------------------ | 18 | * ------------------------------------------------------ |
19 | * Arc90's Readability is designed to run in the browser. It works on the DOM | 19 | * Arc90's Readability is designed to run in the browser. It works on the DOM |
20 | * tree (the parsed HTML) after the page's CSS styles have been applied and | 20 | * tree (the parsed HTML) after the page's CSS styles have been applied and |
21 | * Javascript code executed. This PHP port does not run inside a browser. | 21 | * Javascript code executed. This PHP port does not run inside a browser. |
22 | * We use PHP's ability to parse HTML to build our DOM tree, but we cannot | 22 | * We use PHP's ability to parse HTML to build our DOM tree, but we cannot |
23 | * rely on CSS or Javascript support. As such, the results will not always | 23 | * rely on CSS or Javascript support. As such, the results will not always |
24 | * match Arc90's Readability. (For example, if a web page contains CSS style | 24 | * match Arc90's Readability. (For example, if a web page contains CSS style |
25 | * rules or Javascript code which hide certain HTML elements from display, | 25 | * rules or Javascript code which hide certain HTML elements from display, |
26 | * Arc90's Readability will dismiss those from consideration but our PHP port, | 26 | * Arc90's Readability will dismiss those from consideration but our PHP port, |
27 | * unable to understand CSS or Javascript, will not know any better.) | 27 | * unable to understand CSS or Javascript, will not know any better.) |
28 | * | 28 | * |
29 | * Another significant difference is that the aim of Arc90's Readability is | 29 | * Another significant difference is that the aim of Arc90's Readability is |
30 | * to re-present the main content block of a given web page so users can | 30 | * to re-present the main content block of a given web page so users can |
31 | * read it more easily in their browsers. Correct identification, clean up, | 31 | * read it more easily in their browsers. Correct identification, clean up, |
32 | * and separation of the content block is only a part of this process. | 32 | * and separation of the content block is only a part of this process. |
33 | * This PHP port is only concerned with this part, it does not include code | 33 | * This PHP port is only concerned with this part, it does not include code |
34 | * that relates to presentation in the browser - Arc90 already do | 34 | * that relates to presentation in the browser - Arc90 already do |
35 | * that extremely well, and for PDF output there's FiveFilters.org's | 35 | * that extremely well, and for PDF output there's FiveFilters.org's |
36 | * PDF Newspaper: http://fivefilters.org/pdf-newspaper/. | 36 | * PDF Newspaper: http://fivefilters.org/pdf-newspaper/. |
37 | * | 37 | * |
38 | * Finally, this class contains methods that might be useful for developers | 38 | * Finally, this class contains methods that might be useful for developers |
39 | * working on HTML document fragments. So without deviating too much from | 39 | * working on HTML document fragments. So without deviating too much from |
40 | * the original code (which I don't want to do because it makes debugging | 40 | * the original code (which I don't want to do because it makes debugging |
41 | * and updating more difficult), I've tried to make it a little more | 41 | * and updating more difficult), I've tried to make it a little more |
42 | * developer friendly. You should be able to use the methods here on | 42 | * developer friendly. You should be able to use the methods here on |
43 | * existing DOMElement objects without passing an entire HTML document to | 43 | * existing DOMElement objects without passing an entire HTML document to |
44 | * be parsed. | 44 | * be parsed. |
45 | */ | 45 | */ |
46 | 46 | ||
47 | // This class allows us to do JavaScript like assignements to innerHTML | 47 | // This class allows us to do JavaScript like assignements to innerHTML |
48 | require_once(dirname(__FILE__).'/JSLikeHTMLElement.php'); | 48 | require_once(dirname(__FILE__).'/JSLikeHTMLElement.php'); |
49 | 49 | ||
50 | // Alternative usage (for testing only!) | 50 | // Alternative usage (for testing only!) |
51 | // uncomment the lines below and call Readability.php in your browser | 51 | // uncomment the lines below and call Readability.php in your browser |
52 | // passing it the URL of the page you'd like content from, e.g.: | 52 | // passing it the URL of the page you'd like content from, e.g.: |
53 | // Readability.php?url=http://medialens.org/alerts/09/090615_the_guardian_climate.php | 53 | // Readability.php?url=http://medialens.org/alerts/09/090615_the_guardian_climate.php |
54 | 54 | ||
55 | /* | 55 | /* |
56 | if (!isset($_GET['url']) || $_GET['url'] == '') { | 56 | if (!isset($_GET['url']) || $_GET['url'] == '') { |
57 | die('Please pass a URL to the script. E.g. Readability.php?url=bla.com/story.html'); | 57 | die('Please pass a URL to the script. E.g. Readability.php?url=bla.com/story.html'); |
58 | } | 58 | } |
59 | $url = $_GET['url']; | 59 | $url = $_GET['url']; |
60 | if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url; | 60 | if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url; |
61 | $html = file_get_contents($url); | 61 | $html = file_get_contents($url); |
62 | $r = new Readability($html, $url); | 62 | $r = new Readability($html, $url); |
63 | $r->init(); | 63 | $r->init(); |
64 | echo $r->articleContent->innerHTML; | 64 | echo $r->articleContent->innerHTML; |
65 | */ | 65 | */ |
66 | 66 | ||
67 | class Readability | 67 | class Readability |
68 | { | 68 | { |
69 | public $version = '1.7.1-without-multi-page'; | 69 | public $version = '1.7.1-without-multi-page'; |
70 | public $convertLinksToFootnotes = false; | 70 | public $convertLinksToFootnotes = false; |
71 | public $revertForcedParagraphElements = true; | 71 | public $revertForcedParagraphElements = true; |
72 | public $articleTitle; | 72 | public $articleTitle; |
73 | public $articleContent; | 73 | public $articleContent; |
74 | public $dom; | 74 | public $dom; |
75 | public $url = null; // optional - URL where HTML was retrieved | 75 | public $url = null; // optional - URL where HTML was retrieved |
76 | public $debug = false; | 76 | public $debug = false; |
77 | public $lightClean = true; // preserves more content (experimental) added 2012-09-19 | 77 | public $lightClean = true; // preserves more content (experimental) added 2012-09-19 |
78 | protected $body = null; // | 78 | protected $body = null; // |
79 | protected $bodyCache = null; // Cache the body HTML in case we need to re-use it later | 79 | protected $bodyCache = null; // Cache the body HTML in case we need to re-use it later |
80 | protected $flags = 7; // 1 | 2 | 4; // Start with all flags set. | 80 | protected $flags = 7; // 1 | 2 | 4; // Start with all flags set. |
81 | protected $success = false; // indicates whether we were able to extract or not | 81 | protected $success = false; // indicates whether we were able to extract or not |
82 | 82 | ||
83 | /** | ||
84 | * All of the regular expressions in use within readability. | ||
85 | * Defined up here so we don't instantiate them repeatedly in loops. | ||
86 | **/ | ||
87 | public $regexps = array( | ||
88 | 'unlikelyCandidates' => '/combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup/i', | ||
89 | 'okMaybeItsACandidate' => '/and|article|body|column|main|shadow/i', | ||
90 | 'positive' => '/article|body|content|entry|hentry|main|page|attachment|pagination|post|text|blog|story/i', | ||
91 | 'negative' => '/combx|comment|com-|contact|foot|footer|_nav|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget/i', | ||
92 | 'divToPElements' => '/<(a|blockquote|dl|div|img|ol|p|pre|table|ul)/i', | ||
93 | 'replaceBrs' => '/(<br[^>]*>[ \n\r\t]*){2,}/i', | ||
94 | 'replaceFonts' => '/<(\/?)font[^>]*>/i', | ||
95 | // 'trimRe' => '/^\s+|\s+$/g', // PHP has trim() | ||
96 | 'normalize' => '/\s{2,}/', | ||
97 | 'killBreaks' => '/(<br\s*\/?>(\s| ?)*){1,}/', | ||
98 | 'video' => '!//(player\.|www\.)?(youtube|vimeo|viddler)\.com!i', | ||
99 | 'skipFootnoteLink' => '/^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i' | ||
100 | ); | ||
101 | |||
102 | /* constants */ | ||
103 | const FLAG_STRIP_UNLIKELYS = 1; | ||
104 | const FLAG_WEIGHT_CLASSES = 2; | ||
105 | const FLAG_CLEAN_CONDITIONALLY = 4; | ||
106 | |||
107 | /** | ||
108 | * Create instance of Readability | ||
109 | * @param string UTF-8 encoded string | ||
110 | * @param string (optional) URL associated with HTML (used for footnotes) | ||
111 | * @param string which parser to use for turning raw HTML into a DOMDocument (either 'libxml' or 'html5lib') | ||
112 | */ | ||
113 | function __construct($html, $url=null, $parser='libxml') | ||
114 | { | ||
115 | $this->url = $url; | ||
116 | /* Turn all double br's into p's */ | ||
117 | $html = preg_replace($this->regexps['replaceBrs'], '</p><p>', $html); | ||
118 | $html = preg_replace($this->regexps['replaceFonts'], '<$1span>', $html); | ||
119 | $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"); | ||
120 | if (trim($html) == '') $html = '<html></html>'; | ||
121 | if ($parser=='html5lib' && ($this->dom = HTML5_Parser::parse($html))) { | ||
122 | // all good | ||
123 | } else { | ||
124 | $this->dom = new DOMDocument(); | ||
125 | $this->dom->preserveWhiteSpace = false; | ||
126 | @$this->dom->loadHTML($html); | ||
127 | } | ||
128 | $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement'); | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Get article title element | ||
133 | * @return DOMElement | ||
134 | */ | ||
135 | public function getTitle() { | ||
136 | return $this->articleTitle; | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * Get article content element | ||
141 | * @return DOMElement | ||
142 | */ | ||
143 | public function getContent() { | ||
144 | return $this->articleContent; | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * Runs readability. | ||
149 | * | ||
150 | * Workflow: | ||
151 | * 1. Prep the document by removing script tags, css, etc. | ||
152 | * 2. Build readability's DOM tree. | ||
153 | * 3. Grab the article content from the current dom tree. | ||
154 | * 4. Replace the current DOM tree with the new one. | ||
155 | * 5. Read peacefully. | ||
156 | * | ||
157 | * @return boolean true if we found content, false otherwise | ||
158 | **/ | ||
159 | public function init() | ||
160 | { | ||
161 | if (!isset($this->dom->documentElement)) return false; | ||
162 | $this->removeScripts($this->dom); | ||
163 | //die($this->getInnerHTML($this->dom->documentElement)); | ||
164 | |||
165 | // Assume successful outcome | ||
166 | $this->success = true; | ||
167 | |||
168 | $bodyElems = $this->dom->getElementsByTagName('body'); | ||
169 | if ($bodyElems->length > 0) { | ||
170 | if ($this->bodyCache == null) { | ||
171 | $this->bodyCache = $bodyElems->item(0)->innerHTML; | ||
172 | } | ||
173 | if ($this->body == null) { | ||
174 | $this->body = $bodyElems->item(0); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | $this->prepDocument(); | ||
179 | |||
180 | //die($this->dom->documentElement->parentNode->nodeType); | ||
181 | //$this->setInnerHTML($this->dom->documentElement, $this->getInnerHTML($this->dom->documentElement)); | ||
182 | //die($this->getInnerHTML($this->dom->documentElement)); | ||
183 | |||
184 | /* Build readability's DOM tree */ | ||
185 | $overlay = $this->dom->createElement('div'); | ||
186 | $innerDiv = $this->dom->createElement('div'); | ||
187 | $articleTitle = $this->getArticleTitle(); | ||
188 | $articleContent = $this->grabArticle(); | ||
189 | |||
190 | if (!$articleContent) { | ||
191 | $this->success = false; | ||
192 | $articleContent = $this->dom->createElement('div'); | ||
193 | $articleContent->setAttribute('id', 'readability-content'); | ||
194 | $articleContent->innerHTML = '<p>Sorry, Readability was unable to parse this page for content.</p>'; | ||
195 | } | ||
196 | |||
197 | $overlay->setAttribute('id', 'readOverlay'); | ||
198 | $innerDiv->setAttribute('id', 'readInner'); | ||
199 | |||
200 | /* Glue the structure of our document together. */ | ||
201 | $innerDiv->appendChild($articleTitle); | ||
202 | $innerDiv->appendChild($articleContent); | ||
203 | $overlay->appendChild($innerDiv); | ||
204 | |||
205 | /* Clear the old HTML, insert the new content. */ | ||
206 | $this->body->innerHTML = ''; | ||
207 | $this->body->appendChild($overlay); | ||
208 | //document.body.insertBefore(overlay, document.body.firstChild); | ||
209 | $this->body->removeAttribute('style'); | ||
210 | |||
211 | $this->postProcessContent($articleContent); | ||
212 | |||
213 | // Set title and content instance variables | ||
214 | $this->articleTitle = $articleTitle; | ||
215 | $this->articleContent = $articleContent; | ||
216 | |||
217 | return $this->success; | ||
218 | } | ||
219 | |||
220 | /** | ||
221 | * Debug | ||
222 | */ | ||
223 | protected function dbg($msg) { | ||
224 | if ($this->debug) echo '* ',$msg, "\n"; | ||
225 | } | ||
226 | |||
227 | /** | ||
228 | * Run any post-process modifications to article content as necessary. | ||
229 | * | ||
230 | * @param DOMElement | ||
231 | * @return void | ||
232 | */ | ||
233 | public function postProcessContent($articleContent) { | ||
234 | if ($this->convertLinksToFootnotes && !preg_match('/wikipedia\.org/', @$this->url)) { | ||
235 | $this->addFootnotes($articleContent); | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * Get the article title as an H1. | ||
241 | * | ||
242 | * @return DOMElement | ||
243 | */ | ||
244 | protected function getArticleTitle() { | ||
245 | $curTitle = ''; | ||
246 | $origTitle = ''; | ||
247 | |||
248 | try { | ||
249 | $curTitle = $origTitle = $this->getInnerText($this->dom->getElementsByTagName('title')->item(0)); | ||
250 | } catch(Exception $e) {} | ||
251 | |||
252 | if (preg_match('/ [\|\-] /', $curTitle)) | ||
253 | { | ||
254 | $curTitle = preg_replace('/(.*)[\|\-] .*/i', '$1', $origTitle); | ||
255 | |||
256 | if (count(explode(' ', $curTitle)) < 3) { | ||
257 | $curTitle = preg_replace('/[^\|\-]*[\|\-](.*)/i', '$1', $origTitle); | ||
258 | } | ||
259 | } | ||
260 | else if (strpos($curTitle, ': ') !== false) | ||
261 | { | ||
262 | $curTitle = preg_replace('/.*:(.*)/i', '$1', $origTitle); | ||
263 | |||
264 | if (count(explode(' ', $curTitle)) < 3) { | ||
265 | $curTitle = preg_replace('/[^:]*[:](.*)/i','$1', $origTitle); | ||
266 | } | ||
267 | } | ||
268 | else if(strlen($curTitle) > 150 || strlen($curTitle) < 15) | ||
269 | { | ||
270 | $hOnes = $this->dom->getElementsByTagName('h1'); | ||
271 | if($hOnes->length == 1) | ||
272 | { | ||
273 | $curTitle = $this->getInnerText($hOnes->item(0)); | ||
274 | } | ||
275 | } | ||
276 | |||
277 | $curTitle = trim($curTitle); | ||
278 | |||
279 | if (count(explode(' ', $curTitle)) <= 4) { | ||
280 | $curTitle = $origTitle; | ||
281 | } | ||
282 | |||
283 | $articleTitle = $this->dom->createElement('h1'); | ||
284 | $articleTitle->innerHTML = $curTitle; | ||
285 | |||
286 | return $articleTitle; | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Prepare the HTML document for readability to scrape it. | ||
291 | * This includes things like stripping javascript, CSS, and handling terrible markup. | ||
292 | * | ||
293 | * @return void | ||
294 | **/ | ||
295 | protected function prepDocument() { | ||
296 | /** | ||
297 | * In some cases a body element can't be found (if the HTML is totally hosed for example) | ||
298 | * so we create a new body node and append it to the document. | ||
299 | */ | ||
300 | if ($this->body == null) | ||
301 | { | ||
302 | $this->body = $this->dom->createElement('body'); | ||
303 | $this->dom->documentElement->appendChild($this->body); | ||
304 | } | ||
305 | $this->body->setAttribute('id', 'readabilityBody'); | ||
306 | |||
307 | /* Remove all style tags in head */ | ||
308 | $styleTags = $this->dom->getElementsByTagName('style'); | ||
309 | for ($i = $styleTags->length-1; $i >= 0; $i--) | ||
310 | { | ||
311 | $styleTags->item($i)->parentNode->removeChild($styleTags->item($i)); | ||
312 | } | ||
313 | |||
314 | /* Turn all double br's into p's */ | ||
315 | /* Note, this is pretty costly as far as processing goes. Maybe optimize later. */ | ||
316 | //document.body.innerHTML = document.body.innerHTML.replace(readability.regexps.replaceBrs, '</p><p>').replace(readability.regexps.replaceFonts, '<$1span>'); | ||
317 | // We do this in the constructor for PHP as that's when we have raw HTML - before parsing it into a DOM tree. | ||
318 | // Manipulating innerHTML as it's done in JS is not possible in PHP. | ||
319 | } | ||
320 | |||
321 | /** | ||
322 | * For easier reading, convert this document to have footnotes at the bottom rather than inline links. | ||
323 | * @see http://www.roughtype.com/archives/2010/05/experiments_in.php | ||
324 | * | ||
325 | * @return void | ||
326 | **/ | ||
327 | public function addFootnotes($articleContent) { | ||
328 | $footnotesWrapper = $this->dom->createElement('div'); | ||
329 | $footnotesWrapper->setAttribute('id', 'readability-footnotes'); | ||
330 | $footnotesWrapper->innerHTML = '<h3>References</h3>'; | ||
331 | |||
332 | $articleFootnotes = $this->dom->createElement('ol'); | ||
333 | $articleFootnotes->setAttribute('id', 'readability-footnotes-list'); | ||
334 | $footnotesWrapper->appendChild($articleFootnotes); | ||
335 | |||
336 | $articleLinks = $articleContent->getElementsByTagName('a'); | ||
337 | |||
338 | $linkCount = 0; | ||
339 | for ($i = 0; $i < $articleLinks->length; $i++) | ||
340 | { | ||
341 | $articleLink = $articleLinks->item($i); | ||
342 | $footnoteLink = $articleLink->cloneNode(true); | ||
343 | $refLink = $this->dom->createElement('a'); | ||
344 | $footnote = $this->dom->createElement('li'); | ||
345 | $linkDomain = @parse_url($footnoteLink->getAttribute('href'), PHP_URL_HOST); | ||
346 | if (!$linkDomain && isset($this->url)) $linkDomain = @parse_url($this->url, PHP_URL_HOST); | ||
347 | //linkDomain = footnoteLink.host ? footnoteLink.host : document.location.host, | ||
348 | $linkText = $this->getInnerText($articleLink); | ||
349 | |||
350 | if ((strpos($articleLink->getAttribute('class'), 'readability-DoNotFootnote') !== false) || preg_match($this->regexps['skipFootnoteLink'], $linkText)) { | ||
351 | continue; | ||
352 | } | ||
353 | |||
354 | $linkCount++; | ||
355 | |||
356 | /** Add a superscript reference after the article link */ | ||
357 | $refLink->setAttribute('href', '#readabilityFootnoteLink-' . $linkCount); | ||
358 | $refLink->innerHTML = '<small><sup>[' . $linkCount . ']</sup></small>'; | ||
359 | $refLink->setAttribute('class', 'readability-DoNotFootnote'); | ||
360 | $refLink->setAttribute('style', 'color: inherit;'); | ||
361 | |||
362 | //TODO: does this work or should we use DOMNode.isSameNode()? | ||
363 | if ($articleLink->parentNode->lastChild == $articleLink) { | ||
364 | $articleLink->parentNode->appendChild($refLink); | ||
365 | } else { | ||
366 | $articleLink->parentNode->insertBefore($refLink, $articleLink->nextSibling); | ||
367 | } | ||
368 | |||
369 | $articleLink->setAttribute('style', 'color: inherit; text-decoration: none;'); | ||
370 | $articleLink->setAttribute('name', 'readabilityLink-' . $linkCount); | ||
371 | |||
372 | $footnote->innerHTML = '<small><sup><a href="#readabilityLink-' . $linkCount . '" title="Jump to Link in Article">^</a></sup></small> '; | ||
373 | |||
374 | $footnoteLink->innerHTML = ($footnoteLink->getAttribute('title') != '' ? $footnoteLink->getAttribute('title') : $linkText); | ||
375 | $footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount); | ||
376 | |||
377 | $footnote->appendChild($footnoteLink); | ||
378 | if ($linkDomain) $footnote->innerHTML = $footnote->innerHTML . '<small> (' . $linkDomain . ')</small>'; | ||
379 | |||
380 | $articleFootnotes->appendChild($footnote); | ||
381 | } | ||
382 | |||
383 | if ($linkCount > 0) { | ||
384 | $articleContent->appendChild($footnotesWrapper); | ||
385 | } | ||
386 | } | ||
387 | |||
388 | /** | ||
389 | * Reverts P elements with class 'readability-styled' | ||
390 | * to text nodes - which is what they were before. | ||
391 | * | ||
392 | * @param DOMElement | ||
393 | * @return void | ||
394 | */ | ||
395 | function revertReadabilityStyledElements($articleContent) { | ||
396 | $xpath = new DOMXPath($articleContent->ownerDocument); | ||
397 | $elems = $xpath->query('.//p[@class="readability-styled"]', $articleContent); | ||
398 | //$elems = $articleContent->getElementsByTagName('p'); | ||
399 | for ($i = $elems->length-1; $i >= 0; $i--) { | ||
400 | $e = $elems->item($i); | ||
401 | $e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e); | ||
402 | //if ($e->hasAttribute('class') && $e->getAttribute('class') == 'readability-styled') { | ||
403 | // $e->parentNode->replaceChild($this->dom->createTextNode($e->textContent), $e); | ||
404 | //} | ||
405 | } | ||
406 | } | ||
407 | |||
408 | /** | ||
409 | * Prepare the article node for display. Clean out any inline styles, | ||
410 | * iframes, forms, strip extraneous <p> tags, etc. | ||
411 | * | ||
412 | * @param DOMElement | ||
413 | * @return void | ||
414 | */ | ||
415 | function prepArticle($articleContent) { | ||
416 | $this->cleanStyles($articleContent); | ||
417 | $this->killBreaks($articleContent); | ||
418 | if ($this->revertForcedParagraphElements) { | ||
419 | $this->revertReadabilityStyledElements($articleContent); | ||
420 | } | ||
421 | |||
422 | /* Clean out junk from the article content */ | ||
423 | $this->cleanConditionally($articleContent, 'form'); | ||
424 | $this->clean($articleContent, 'object'); | ||
425 | $this->clean($articleContent, 'h1'); | ||
426 | |||
427 | /** | ||
428 | * If there is only one h2, they are probably using it | ||
429 | * as a header and not a subheader, so remove it since we already have a header. | ||
430 | ***/ | ||
431 | if (!$this->lightClean && ($articleContent->getElementsByTagName('h2')->length == 1)) { | ||
432 | $this->clean($articleContent, 'h2'); | ||
433 | } | ||
434 | $this->clean($articleContent, 'iframe'); | ||
435 | |||
436 | $this->cleanHeaders($articleContent); | ||
437 | |||
438 | /* Do these last as the previous stuff may have removed junk that will affect these */ | ||
439 | $this->cleanConditionally($articleContent, 'table'); | ||
440 | $this->cleanConditionally($articleContent, 'ul'); | ||
441 | $this->cleanConditionally($articleContent, 'div'); | ||
442 | |||
443 | /* Remove extra paragraphs */ | ||
444 | $articleParagraphs = $articleContent->getElementsByTagName('p'); | ||
445 | for ($i = $articleParagraphs->length-1; $i >= 0; $i--) | ||
446 | { | ||
447 | $imgCount = $articleParagraphs->item($i)->getElementsByTagName('img')->length; | ||
448 | $embedCount = $articleParagraphs->item($i)->getElementsByTagName('embed')->length; | ||
449 | $objectCount = $articleParagraphs->item($i)->getElementsByTagName('object')->length; | ||
450 | $iframeCount = $articleParagraphs->item($i)->getElementsByTagName('iframe')->length; | ||
451 | |||
452 | if ($imgCount === 0 && $embedCount === 0 && $objectCount === 0 && $iframeCount === 0 && $this->getInnerText($articleParagraphs->item($i), false) == '') | ||
453 | { | ||
454 | $articleParagraphs->item($i)->parentNode->removeChild($articleParagraphs->item($i)); | ||
455 | } | ||
456 | } | ||
457 | |||
458 | try { | ||
459 | $articleContent->innerHTML = preg_replace('/<br[^>]*>\s*<p/i', '<p', $articleContent->innerHTML); | ||
460 | //articleContent.innerHTML = articleContent.innerHTML.replace(/<br[^>]*>\s*<p/gi, '<p'); | ||
461 | } | ||
462 | catch (Exception $e) { | ||
463 | $this->dbg("Cleaning innerHTML of breaks failed. This is an IE strict-block-elements bug. Ignoring.: " . $e); | ||
464 | } | ||
465 | } | ||
466 | |||
467 | /** | ||
468 | * Initialize a node with the readability object. Also checks the | ||
469 | * className/id for special names to add to its score. | ||
470 | * | ||
471 | * @param Element | ||
472 | * @return void | ||
473 | **/ | ||
474 | protected function initializeNode($node) { | ||
475 | $readability = $this->dom->createAttribute('readability'); | ||
476 | $readability->value = 0; // this is our contentScore | ||
477 | $node->setAttributeNode($readability); | ||
478 | |||
479 | switch (strtoupper($node->tagName)) { // unsure if strtoupper is needed, but using it just in case | ||
480 | case 'DIV': | ||
481 | $readability->value += 5; | ||
482 | break; | ||
483 | |||
484 | case 'PRE': | ||
485 | case 'TD': | ||
486 | case 'BLOCKQUOTE': | ||
487 | $readability->value += 3; | ||
488 | break; | ||
489 | |||
490 | case 'ADDRESS': | ||
491 | case 'OL': | ||
492 | case 'UL': | ||
493 | case 'DL': | ||
494 | case 'DD': | ||
495 | case 'DT': | ||
496 | case 'LI': | ||
497 | case 'FORM': | ||
498 | $readability->value -= 3; | ||
499 | break; | ||
500 | |||
501 | case 'H1': | ||
502 | case 'H2': | ||
503 | case 'H3': | ||
504 | case 'H4': | ||
505 | case 'H5': | ||
506 | case 'H6': | ||
507 | case 'TH': | ||
508 | $readability->value -= 5; | ||
509 | break; | ||
510 | } | ||
511 | $readability->value += $this->getClassWeight($node); | ||
512 | } | ||
513 | |||
514 | /*** | ||
515 | * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is | ||
516 | * most likely to be the stuff a user wants to read. Then return it wrapped up in a div. | ||
517 | * | ||
518 | * @return DOMElement | ||
519 | **/ | ||
520 | protected function grabArticle($page=null) { | ||
521 | $stripUnlikelyCandidates = $this->flagIsActive(self::FLAG_STRIP_UNLIKELYS); | ||
522 | if (!$page) $page = $this->dom; | ||
523 | $allElements = $page->getElementsByTagName('*'); | ||
524 | /** | ||
525 | * First, node prepping. Trash nodes that look cruddy (like ones with the class name "comment", etc), and turn divs | ||
526 | * into P tags where they have been used inappropriately (as in, where they contain no other block level elements.) | ||
527 | * | ||
528 | * Note: Assignment from index for performance. See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5 | ||
529 | * TODO: Shouldn't this be a reverse traversal? | ||
530 | **/ | ||
531 | $node = null; | ||
532 | $nodesToScore = array(); | ||
533 | for ($nodeIndex = 0; ($node = $allElements->item($nodeIndex)); $nodeIndex++) { | ||
534 | //for ($nodeIndex=$targetList->length-1; $nodeIndex >= 0; $nodeIndex--) { | ||
535 | //$node = $targetList->item($nodeIndex); | ||
536 | $tagName = strtoupper($node->tagName); | ||
537 | /* Remove unlikely candidates */ | ||
538 | if ($stripUnlikelyCandidates) { | ||
539 | $unlikelyMatchString = $node->getAttribute('class') . $node->getAttribute('id'); | ||
540 | if ( | ||
541 | preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) && | ||
542 | !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) && | ||
543 | $tagName != 'BODY' | ||
544 | ) | ||
545 | { | ||
546 | $this->dbg('Removing unlikely candidate - ' . $unlikelyMatchString); | ||
547 | //$nodesToRemove[] = $node; | ||
548 | $node->parentNode->removeChild($node); | ||
549 | $nodeIndex--; | ||
550 | continue; | ||
551 | } | ||
552 | } | ||
553 | |||
554 | if ($tagName == 'P' || $tagName == 'TD' || $tagName == 'PRE') { | ||
555 | $nodesToScore[] = $node; | ||
556 | } | ||
557 | |||
558 | /* Turn all divs that don't have children block level elements into p's */ | ||
559 | if ($tagName == 'DIV') { | ||
560 | if (!preg_match($this->regexps['divToPElements'], $node->innerHTML)) { | ||
561 | //$this->dbg('Altering div to p'); | ||
562 | $newNode = $this->dom->createElement('p'); | ||
563 | try { | ||
564 | $newNode->innerHTML = $node->innerHTML; | ||
565 | //$nodesToReplace[] = array('new'=>$newNode, 'old'=>$node); | ||
566 | $node->parentNode->replaceChild($newNode, $node); | ||
567 | $nodeIndex--; | ||
568 | $nodesToScore[] = $node; // or $newNode? | ||
569 | } | ||
570 | catch(Exception $e) { | ||
571 | $this->dbg('Could not alter div to p, reverting back to div.: ' . $e); | ||
572 | } | ||
573 | } | ||
574 | else | ||
575 | { | ||
576 | /* EXPERIMENTAL */ | ||
577 | // TODO: change these p elements back to text nodes after processing | ||
578 | for ($i = 0, $il = $node->childNodes->length; $i < $il; $i++) { | ||
579 | $childNode = $node->childNodes->item($i); | ||
580 | if ($childNode->nodeType == 3) { // XML_TEXT_NODE | ||
581 | //$this->dbg('replacing text node with a p tag with the same content.'); | ||
582 | $p = $this->dom->createElement('p'); | ||
583 | $p->innerHTML = $childNode->nodeValue; | ||
584 | $p->setAttribute('style', 'display: inline;'); | ||
585 | $p->setAttribute('class', 'readability-styled'); | ||
586 | $childNode->parentNode->replaceChild($p, $childNode); | ||
587 | } | ||
588 | } | ||
589 | } | ||
590 | } | ||
591 | } | ||
592 | |||
593 | /** | ||
594 | * Loop through all paragraphs, and assign a score to them based on how content-y they look. | ||
595 | * Then add their score to their parent node. | ||
596 | * | ||
597 | * A score is determined by things like number of commas, class names, etc. Maybe eventually link density. | ||
598 | **/ | ||
599 | $candidates = array(); | ||
600 | for ($pt=0; $pt < count($nodesToScore); $pt++) { | ||
601 | $parentNode = $nodesToScore[$pt]->parentNode; | ||
602 | // $grandParentNode = $parentNode ? $parentNode->parentNode : null; | ||
603 | $grandParentNode = !$parentNode ? null : (($parentNode->parentNode instanceof DOMElement) ? $parentNode->parentNode : null); | ||
604 | $innerText = $this->getInnerText($nodesToScore[$pt]); | ||
605 | |||
606 | if (!$parentNode || !isset($parentNode->tagName)) { | ||
607 | continue; | ||
608 | } | ||
609 | |||
610 | /* If this paragraph is less than 25 characters, don't even count it. */ | ||
611 | if(strlen($innerText) < 25) { | ||
612 | continue; | ||
613 | } | ||
614 | |||
615 | /* Initialize readability data for the parent. */ | ||
616 | if (!$parentNode->hasAttribute('readability')) | ||
617 | { | ||
618 | $this->initializeNode($parentNode); | ||
619 | $candidates[] = $parentNode; | ||
620 | } | ||
621 | |||
622 | /* Initialize readability data for the grandparent. */ | ||
623 | if ($grandParentNode && !$grandParentNode->hasAttribute('readability') && isset($grandParentNode->tagName)) | ||
624 | { | ||
625 | $this->initializeNode($grandParentNode); | ||
626 | $candidates[] = $grandParentNode; | ||
627 | } | ||
628 | |||
629 | $contentScore = 0; | ||
630 | |||
631 | /* Add a point for the paragraph itself as a base. */ | ||
632 | $contentScore++; | ||
633 | |||
634 | /* Add points for any commas within this paragraph */ | ||
635 | $contentScore += count(explode(',', $innerText)); | ||
636 | |||
637 | /* For every 100 characters in this paragraph, add another point. Up to 3 points. */ | ||
638 | $contentScore += min(floor(strlen($innerText) / 100), 3); | ||
639 | |||
640 | /* Add the score to the parent. The grandparent gets half. */ | ||
641 | $parentNode->getAttributeNode('readability')->value += $contentScore; | ||
642 | |||
643 | if ($grandParentNode) { | ||
644 | $grandParentNode->getAttributeNode('readability')->value += $contentScore/2; | ||
645 | } | ||
646 | } | ||
647 | |||
648 | /** | ||
649 | * After we've calculated scores, loop through all of the possible candidate nodes we found | ||
650 | * and find the one with the highest score. | ||
651 | **/ | ||
652 | $topCandidate = null; | ||
653 | for ($c=0, $cl=count($candidates); $c < $cl; $c++) | ||
654 | { | ||
655 | /** | ||
656 | * Scale the final candidates score based on link density. Good content should have a | ||
657 | * relatively small link density (5% or less) and be mostly unaffected by this operation. | ||
658 | **/ | ||
659 | $readability = $candidates[$c]->getAttributeNode('readability'); | ||
660 | $readability->value = $readability->value * (1-$this->getLinkDensity($candidates[$c])); | ||
661 | |||
662 | $this->dbg('Candidate: ' . $candidates[$c]->tagName . ' (' . $candidates[$c]->getAttribute('class') . ':' . $candidates[$c]->getAttribute('id') . ') with score ' . $readability->value); | ||
663 | |||
664 | if (!$topCandidate || $readability->value > (int)$topCandidate->getAttribute('readability')) { | ||
665 | $topCandidate = $candidates[$c]; | ||
666 | } | ||
667 | } | ||
668 | |||
669 | /** | ||
670 | * If we still have no top candidate, just use the body as a last resort. | ||
671 | * We also have to copy the body node so it is something we can modify. | ||
672 | **/ | ||
673 | if ($topCandidate === null || strtoupper($topCandidate->tagName) == 'BODY') | ||
674 | { | ||
675 | $topCandidate = $this->dom->createElement('div'); | ||
676 | if ($page instanceof DOMDocument) { | ||
677 | if (!isset($page->documentElement)) { | ||
678 | // we don't have a body either? what a mess! :) | ||
679 | } else { | ||
680 | $topCandidate->innerHTML = $page->documentElement->innerHTML; | ||
681 | $page->documentElement->innerHTML = ''; | ||
682 | $this->reinitBody(); | ||
683 | $page->documentElement->appendChild($topCandidate); | ||
684 | } | ||
685 | } else { | ||
686 | $topCandidate->innerHTML = $page->innerHTML; | ||
687 | $page->innerHTML = ''; | ||
688 | $page->appendChild($topCandidate); | ||
689 | } | ||
690 | $this->initializeNode($topCandidate); | ||
691 | } | ||
692 | |||
693 | /** | ||
694 | * Now that we have the top candidate, look through its siblings for content that might also be related. | ||
695 | * Things like preambles, content split by ads that we removed, etc. | ||
696 | **/ | ||
697 | $articleContent = $this->dom->createElement('div'); | ||
698 | $articleContent->setAttribute('id', 'readability-content'); | ||
699 | $siblingScoreThreshold = max(10, ((int)$topCandidate->getAttribute('readability')) * 0.2); | ||
700 | $siblingNodes = $topCandidate->parentNode->childNodes; | ||
701 | if (!isset($siblingNodes)) { | ||
702 | $siblingNodes = new stdClass; | ||
703 | $siblingNodes->length = 0; | ||
704 | } | ||
705 | |||
706 | for ($s=0, $sl=$siblingNodes->length; $s < $sl; $s++) | ||
707 | { | ||
708 | $siblingNode = $siblingNodes->item($s); | ||
709 | $append = false; | ||
710 | |||
711 | $this->dbg('Looking at sibling node: ' . $siblingNode->nodeName . (($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability')) ? (' with score ' . $siblingNode->getAttribute('readability')) : '')); | ||
712 | |||
713 | //dbg('Sibling has score ' . ($siblingNode->readability ? siblingNode.readability.contentScore : 'Unknown')); | ||
714 | |||
715 | if ($siblingNode === $topCandidate) | ||
716 | // or if ($siblingNode->isSameNode($topCandidate)) | ||
717 | { | ||
718 | $append = true; | ||
719 | } | ||
720 | |||
721 | $contentBonus = 0; | ||
722 | /* Give a bonus if sibling nodes and top candidates have the example same classname */ | ||
723 | if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->getAttribute('class') == $topCandidate->getAttribute('class') && $topCandidate->getAttribute('class') != '') { | ||
724 | $contentBonus += ((int)$topCandidate->getAttribute('readability')) * 0.2; | ||
725 | } | ||
726 | |||
727 | if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability') && (((int)$siblingNode->getAttribute('readability')) + $contentBonus) >= $siblingScoreThreshold) | ||
728 | { | ||
729 | $append = true; | ||
730 | } | ||
731 | |||
732 | if (strtoupper($siblingNode->nodeName) == 'P') { | ||
733 | $linkDensity = $this->getLinkDensity($siblingNode); | ||
734 | $nodeContent = $this->getInnerText($siblingNode); | ||
735 | $nodeLength = strlen($nodeContent); | ||
736 | |||
737 | if ($nodeLength > 80 && $linkDensity < 0.25) | ||
738 | { | ||
739 | $append = true; | ||
740 | } | ||
741 | else if ($nodeLength < 80 && $linkDensity === 0 && preg_match('/\.( |$)/', $nodeContent)) | ||
742 | { | ||
743 | $append = true; | ||
744 | } | ||
745 | } | ||
746 | |||
747 | if ($append) | ||
748 | { | ||
749 | $this->dbg('Appending node: ' . $siblingNode->nodeName); | ||
750 | |||
751 | $nodeToAppend = null; | ||
752 | $sibNodeName = strtoupper($siblingNode->nodeName); | ||
753 | if ($sibNodeName != 'DIV' && $sibNodeName != 'P') { | ||
754 | /* We have a node that isn't a common block level element, like a form or td tag. Turn it into a div so it doesn't get filtered out later by accident. */ | ||
755 | |||
756 | $this->dbg('Altering siblingNode of ' . $sibNodeName . ' to div.'); | ||
757 | $nodeToAppend = $this->dom->createElement('div'); | ||
758 | try { | ||
759 | $nodeToAppend->setAttribute('id', $siblingNode->getAttribute('id')); | ||
760 | $nodeToAppend->innerHTML = $siblingNode->innerHTML; | ||
761 | } | ||
762 | catch(Exception $e) | ||
763 | { | ||
764 | $this->dbg('Could not alter siblingNode to div, reverting back to original.'); | ||
765 | $nodeToAppend = $siblingNode; | ||
766 | $s--; | ||
767 | $sl--; | ||
768 | } | ||
769 | } else { | ||
770 | $nodeToAppend = $siblingNode; | ||
771 | $s--; | ||
772 | $sl--; | ||
773 | } | ||
774 | |||
775 | /* To ensure a node does not interfere with readability styles, remove its classnames */ | ||
776 | $nodeToAppend->removeAttribute('class'); | ||
777 | |||
778 | /* Append sibling and subtract from our list because it removes the node when you append to another node */ | ||
779 | $articleContent->appendChild($nodeToAppend); | ||
780 | } | ||
781 | } | ||
782 | |||
783 | /** | ||
784 | * So we have all of the content that we need. Now we clean it up for presentation. | ||
785 | **/ | ||
786 | $this->prepArticle($articleContent); | ||
787 | |||
788 | /** | ||
789 | * Now that we've gone through the full algorithm, check to see if we got any meaningful content. | ||
790 | * If we didn't, we may need to re-run grabArticle with different flags set. This gives us a higher | ||
791 | * likelihood of finding the content, and the sieve approach gives us a higher likelihood of | ||
792 | * finding the -right- content. | ||
793 | **/ | ||
794 | if (strlen($this->getInnerText($articleContent, false)) < 250) | ||
795 | { | ||
796 | // TODO: find out why element disappears sometimes, e.g. for this URL http://www.businessinsider.com/6-hedge-fund-etfs-for-average-investors-2011-7 | ||
797 | // in the meantime, we check and create an empty element if it's not there. | ||
798 | $this->reinitBody(); | ||
799 | |||
800 | if ($this->flagIsActive(self::FLAG_STRIP_UNLIKELYS)) { | ||
801 | $this->removeFlag(self::FLAG_STRIP_UNLIKELYS); | ||
802 | return $this->grabArticle($this->body); | ||
803 | } | ||
804 | else if ($this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) { | ||
805 | $this->removeFlag(self::FLAG_WEIGHT_CLASSES); | ||
806 | return $this->grabArticle($this->body); | ||
807 | } | ||
808 | else if ($this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) { | ||
809 | $this->removeFlag(self::FLAG_CLEAN_CONDITIONALLY); | ||
810 | return $this->grabArticle($this->body); | ||
811 | } | ||
812 | else { | ||
813 | return false; | ||
814 | } | ||
815 | } | ||
816 | return $articleContent; | ||
817 | } | ||
818 | |||
819 | /** | ||
820 | * Remove script tags from document | ||
821 | * | ||
822 | * @param DOMElement | ||
823 | * @return void | ||
824 | */ | ||
825 | public function removeScripts($doc) { | ||
826 | $scripts = $doc->getElementsByTagName('script'); | ||
827 | for($i = $scripts->length-1; $i >= 0; $i--) | ||
828 | { | ||
829 | $scripts->item($i)->parentNode->removeChild($scripts->item($i)); | ||
830 | } | ||
831 | } | ||
832 | |||
833 | /** | ||
834 | * Get the inner text of a node. | ||
835 | * This also strips out any excess whitespace to be found. | ||
836 | * | ||
837 | * @param DOMElement $ | ||
838 | * @param boolean $normalizeSpaces (default: true) | ||
839 | * @return string | ||
840 | **/ | ||
841 | public function getInnerText($e, $normalizeSpaces=true) { | ||
842 | $textContent = ''; | ||
843 | |||
844 | if (!isset($e->textContent) || $e->textContent == '') { | ||
845 | return ''; | ||
846 | } | ||
847 | |||
848 | $textContent = trim($e->textContent); | ||
849 | |||
850 | if ($normalizeSpaces) { | ||
851 | return preg_replace($this->regexps['normalize'], ' ', $textContent); | ||
852 | } else { | ||
853 | return $textContent; | ||
854 | } | ||
855 | } | ||
856 | |||
857 | /** | ||
858 | * Get the number of times a string $s appears in the node $e. | ||
859 | * | ||
860 | * @param DOMElement $e | ||
861 | * @param string - what to count. Default is "," | ||
862 | * @return number (integer) | ||
863 | **/ | ||
864 | public function getCharCount($e, $s=',') { | ||
865 | return substr_count($this->getInnerText($e), $s); | ||
866 | } | ||
867 | |||
868 | /** | ||
869 | * Remove the style attribute on every $e and under. | ||
870 | * | ||
871 | * @param DOMElement $e | ||
872 | * @return void | ||
873 | */ | ||
874 | public function cleanStyles($e) { | ||
875 | if (!is_object($e)) return; | ||
876 | $elems = $e->getElementsByTagName('*'); | ||
877 | foreach ($elems as $elem) { | ||
878 | $elem->removeAttribute('style'); | ||
879 | } | ||
880 | } | ||
881 | |||
882 | /** | ||
883 | * Get the density of links as a percentage of the content | ||
884 | * This is the amount of text that is inside a link divided by the total text in the node. | ||
885 | * | ||
886 | * @param DOMElement $e | ||
887 | * @return number (float) | ||
888 | */ | ||
889 | public function getLinkDensity($e) { | ||
890 | $links = $e->getElementsByTagName('a'); | ||
891 | $textLength = strlen($this->getInnerText($e)); | ||
892 | $linkLength = 0; | ||
893 | for ($i=0, $il=$links->length; $i < $il; $i++) | ||
894 | { | ||
895 | $linkLength += strlen($this->getInnerText($links->item($i))); | ||
896 | } | ||
897 | if ($textLength > 0) { | ||
898 | return $linkLength / $textLength; | ||
899 | } else { | ||
900 | return 0; | ||
901 | } | ||
902 | } | ||
903 | |||
904 | /** | ||
905 | * Get an elements class/id weight. Uses regular expressions to tell if this | ||
906 | * element looks good or bad. | ||
907 | * | ||
908 | * @param DOMElement $e | ||
909 | * @return number (Integer) | ||
910 | */ | ||
911 | public function getClassWeight($e) { | ||
912 | if(!$this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) { | ||
913 | return 0; | ||
914 | } | ||
915 | |||
916 | $weight = 0; | ||
917 | |||
918 | /* Look for a special classname */ | ||
919 | if ($e->hasAttribute('class') && $e->getAttribute('class') != '') | ||
920 | { | ||
921 | if (preg_match($this->regexps['negative'], $e->getAttribute('class'))) { | ||
922 | $weight -= 25; | ||
923 | } | ||
924 | if (preg_match($this->regexps['positive'], $e->getAttribute('class'))) { | ||
925 | $weight += 25; | ||
926 | } | ||
927 | } | ||
928 | |||
929 | /* Look for a special ID */ | ||
930 | if ($e->hasAttribute('id') && $e->getAttribute('id') != '') | ||
931 | { | ||
932 | if (preg_match($this->regexps['negative'], $e->getAttribute('id'))) { | ||
933 | $weight -= 25; | ||
934 | } | ||
935 | if (preg_match($this->regexps['positive'], $e->getAttribute('id'))) { | ||
936 | $weight += 25; | ||
937 | } | ||
938 | } | ||
939 | return $weight; | ||
940 | } | ||
941 | |||
942 | /** | ||
943 | * Remove extraneous break tags from a node. | ||
944 | * | ||
945 | * @param DOMElement $node | ||
946 | * @return void | ||
947 | */ | ||
948 | public function killBreaks($node) { | ||
949 | $html = $node->innerHTML; | ||
950 | $html = preg_replace($this->regexps['killBreaks'], '<br />', $html); | ||
951 | $node->innerHTML = $html; | ||
952 | } | ||
953 | |||
954 | /** | ||
955 | * Clean a node of all elements of type "tag". | ||
956 | * (Unless it's a youtube/vimeo video. People love movies.) | ||
957 | * | ||
958 | * Updated 2012-09-18 to preserve youtube/vimeo iframes | ||
959 | * | ||
960 | * @param DOMElement $e | ||
961 | * @param string $tag | ||
962 | * @return void | ||
963 | */ | ||
964 | public function clean($e, $tag) { | ||
965 | $targetList = $e->getElementsByTagName($tag); | ||
966 | $isEmbed = ($tag == 'iframe' || $tag == 'object' || $tag == 'embed'); | ||
967 | |||
968 | for ($y=$targetList->length-1; $y >= 0; $y--) { | ||
969 | /* Allow youtube and vimeo videos through as people usually want to see those. */ | ||
970 | if ($isEmbed) { | ||
971 | $attributeValues = ''; | ||
972 | for ($i=0, $il=$targetList->item($y)->attributes->length; $i < $il; $i++) { | ||
973 | $attributeValues .= $targetList->item($y)->attributes->item($i)->value . '|'; // DOMAttr? (TODO: test) | ||
974 | } | ||
975 | |||
976 | /* First, check the elements attributes to see if any of them contain youtube or vimeo */ | ||
977 | if (preg_match($this->regexps['video'], $attributeValues)) { | ||
978 | continue; | ||
979 | } | ||
980 | |||
981 | /* Then check the elements inside this element for the same. */ | ||
982 | if (preg_match($this->regexps['video'], $targetList->item($y)->innerHTML)) { | ||
983 | continue; | ||
984 | } | ||
985 | } | ||
986 | $targetList->item($y)->parentNode->removeChild($targetList->item($y)); | ||
987 | } | ||
988 | } | ||
989 | |||
990 | /** | ||
991 | * Clean an element of all tags of type "tag" if they look fishy. | ||
992 | * "Fishy" is an algorithm based on content length, classnames, | ||
993 | * link density, number of images & embeds, etc. | ||
994 | * | ||
995 | * @param DOMElement $e | ||
996 | * @param string $tag | ||
997 | * @return void | ||
998 | */ | ||
999 | public function cleanConditionally($e, $tag) { | ||
1000 | if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) { | ||
1001 | return; | ||
1002 | } | ||
1003 | |||
1004 | $tagsList = $e->getElementsByTagName($tag); | ||
1005 | $curTagsLength = $tagsList->length; | ||
1006 | |||
1007 | /** | ||
1008 | * Gather counts for other typical elements embedded within. | ||
1009 | * Traverse backwards so we can remove nodes at the same time without effecting the traversal. | ||
1010 | * | ||
1011 | * TODO: Consider taking into account original contentScore here. | ||
1012 | */ | ||
1013 | for ($i=$curTagsLength-1; $i >= 0; $i--) { | ||
1014 | $weight = $this->getClassWeight($tagsList->item($i)); | ||
1015 | $contentScore = ($tagsList->item($i)->hasAttribute('readability')) ? (int)$tagsList->item($i)->getAttribute('readability') : 0; | ||
1016 | |||
1017 | $this->dbg('Cleaning Conditionally ' . $tagsList->item($i)->tagName . ' (' . $tagsList->item($i)->getAttribute('class') . ':' . $tagsList->item($i)->getAttribute('id') . ')' . (($tagsList->item($i)->hasAttribute('readability')) ? (' with score ' . $tagsList->item($i)->getAttribute('readability')) : '')); | ||
1018 | |||
1019 | if ($weight + $contentScore < 0) { | ||
1020 | $tagsList->item($i)->parentNode->removeChild($tagsList->item($i)); | ||
1021 | } | ||
1022 | else if ( $this->getCharCount($tagsList->item($i), ',') < 10) { | ||
1023 | /** | ||
1024 | * If there are not very many commas, and the number of | ||
1025 | * non-paragraph elements is more than paragraphs or other ominous signs, remove the element. | ||
1026 | **/ | ||
1027 | $p = $tagsList->item($i)->getElementsByTagName('p')->length; | ||
1028 | $img = $tagsList->item($i)->getElementsByTagName('img')->length; | ||
1029 | $li = $tagsList->item($i)->getElementsByTagName('li')->length-100; | ||
1030 | $input = $tagsList->item($i)->getElementsByTagName('input')->length; | ||
1031 | $a = $tagsList->item($i)->getElementsByTagName('a')->length; | ||
1032 | |||
1033 | $embedCount = 0; | ||
1034 | $embeds = $tagsList->item($i)->getElementsByTagName('embed'); | ||
1035 | for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) { | ||
1036 | if (preg_match($this->regexps['video'], $embeds->item($ei)->getAttribute('src'))) { | ||
1037 | $embedCount++; | ||
1038 | } | ||
1039 | } | ||
1040 | $embeds = $tagsList->item($i)->getElementsByTagName('iframe'); | ||
1041 | for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) { | ||
1042 | if (preg_match($this->regexps['video'], $embeds->item($ei)->getAttribute('src'))) { | ||
1043 | $embedCount++; | ||
1044 | } | ||
1045 | } | ||
1046 | |||
1047 | $linkDensity = $this->getLinkDensity($tagsList->item($i)); | ||
1048 | $contentLength = strlen($this->getInnerText($tagsList->item($i))); | ||
1049 | $toRemove = false; | ||
1050 | |||
1051 | if ($this->lightClean) { | ||
1052 | $this->dbg('Light clean...'); | ||
1053 | if ( ($img > $p) && ($img > 4) ) { | ||
1054 | $this->dbg(' more than 4 images and more image elements than paragraph elements'); | ||
1055 | $toRemove = true; | ||
1056 | } else if ($li > $p && $tag != 'ul' && $tag != 'ol') { | ||
1057 | $this->dbg(' too many <li> elements, and parent is not <ul> or <ol>'); | ||
1058 | $toRemove = true; | ||
1059 | } else if ( $input > floor($p/3) ) { | ||
1060 | $this->dbg(' too many <input> elements'); | ||
1061 | $toRemove = true; | ||
1062 | } else if ($contentLength < 10 && ($embedCount === 0 && ($img === 0 || $img > 2))) { | ||
1063 | $this->dbg(' content length less than 10 chars, 0 embeds and either 0 images or more than 2 images'); | ||
1064 | $toRemove = true; | ||
1065 | } else if($weight < 25 && $linkDensity > 0.2) { | ||
1066 | $this->dbg(' weight smaller than 25 and link density above 0.2'); | ||
1067 | $toRemove = true; | ||
1068 | } else if($a > 2 && ($weight >= 25 && $linkDensity > 0.5)) { | ||
1069 | $this->dbg(' more than 2 links and weight above 25 but link density greater than 0.5'); | ||
1070 | $toRemove = true; | ||
1071 | } else if($embedCount > 3) { | ||
1072 | $this->dbg(' more than 3 embeds'); | ||
1073 | $toRemove = true; | ||
1074 | } | ||
1075 | } else { | ||
1076 | $this->dbg('Standard clean...'); | ||
1077 | if ( $img > $p ) { | ||
1078 | $this->dbg(' more image elements than paragraph elements'); | ||
1079 | $toRemove = true; | ||
1080 | } else if ($li > $p && $tag != 'ul' && $tag != 'ol') { | ||
1081 | $this->dbg(' too many <li> elements, and parent is not <ul> or <ol>'); | ||
1082 | $toRemove = true; | ||
1083 | } else if ( $input > floor($p/3) ) { | ||
1084 | $this->dbg(' too many <input> elements'); | ||
1085 | $toRemove = true; | ||
1086 | } else if ($contentLength < 25 && ($img === 0 || $img > 2) ) { | ||
1087 | $this->dbg(' content length less than 25 chars and 0 images, or more than 2 images'); | ||
1088 | $toRemove = true; | ||
1089 | } else if($weight < 25 && $linkDensity > 0.2) { | ||
1090 | $this->dbg(' weight smaller than 25 and link density above 0.2'); | ||
1091 | $toRemove = true; | ||
1092 | } else if($weight >= 25 && $linkDensity > 0.5) { | ||
1093 | $this->dbg(' weight above 25 but link density greater than 0.5'); | ||
1094 | $toRemove = true; | ||
1095 | } else if(($embedCount == 1 && $contentLength < 75) || $embedCount > 1) { | ||
1096 | $this->dbg(' 1 embed and content length smaller than 75 chars, or more than one embed'); | ||
1097 | $toRemove = true; | ||
1098 | } | ||
1099 | } | ||
1100 | |||
1101 | if ($toRemove) { | ||
1102 | //$this->dbg('Removing: '.$tagsList->item($i)->innerHTML); | ||
1103 | $tagsList->item($i)->parentNode->removeChild($tagsList->item($i)); | ||
1104 | } | ||
1105 | } | ||
1106 | } | ||
1107 | } | ||
1108 | |||
1109 | /** | ||
1110 | * Clean out spurious headers from an Element. Checks things like classnames and link density. | ||
1111 | * | ||
1112 | * @param DOMElement $e | ||
1113 | * @return void | ||
1114 | */ | ||
1115 | public function cleanHeaders($e) { | ||
1116 | for ($headerIndex = 1; $headerIndex < 3; $headerIndex++) { | ||
1117 | $headers = $e->getElementsByTagName('h' . $headerIndex); | ||
1118 | for ($i=$headers->length-1; $i >=0; $i--) { | ||
1119 | if ($this->getClassWeight($headers->item($i)) < 0 || $this->getLinkDensity($headers->item($i)) > 0.33) { | ||
1120 | $headers->item($i)->parentNode->removeChild($headers->item($i)); | ||
1121 | } | ||
1122 | } | ||
1123 | } | ||
1124 | } | ||
1125 | |||
1126 | public function flagIsActive($flag) { | ||
1127 | return ($this->flags & $flag) > 0; | ||
1128 | } | ||
1129 | |||
1130 | public function addFlag($flag) { | ||
1131 | $this->flags = $this->flags | $flag; | ||
1132 | } | ||
1133 | |||
1134 | public function removeFlag($flag) { | ||
1135 | $this->flags = $this->flags & ~$flag; | ||
1136 | } | ||
1137 | |||
83 | /** | 1138 | /** |
84 | * All of the regular expressions in use within readability. | 1139 | * Will recreate previously deleted body property |
85 | * Defined up here so we don't instantiate them repeatedly in loops. | 1140 | * |
86 | **/ | 1141 | * @return void |
87 | public $regexps = array( | 1142 | */ |
88 | 'unlikelyCandidates' => '/combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup/i', | 1143 | protected function reinitBody() { |
89 | 'okMaybeItsACandidate' => '/and|article|body|column|main|shadow/i', | 1144 | if (!isset($this->body->childNodes)) { |
90 | 'positive' => '/article|body|content|entry|hentry|main|page|attachment|pagination|post|text|blog|story/i', | 1145 | $this->body = $this->dom->createElement('body'); |
91 | 'negative' => '/combx|comment|com-|contact|foot|footer|_nav|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget/i', | ||
92 | 'divToPElements' => '/<(a|blockquote|dl|div|img|ol|p|pre|table|ul)/i', | ||
93 | 'replaceBrs' => '/(<br[^>]*>[ \n\r\t]*){2,}/i', | ||
94 | 'replaceFonts' => '/<(\/?)font[^>]*>/i', | ||
95 | // 'trimRe' => '/^\s+|\s+$/g', // PHP has trim() | ||
96 | 'normalize' => '/\s{2,}/', | ||
97 | 'killBreaks' => '/(<br\s*\/?>(\s| ?)*){1,}/', | ||
98 | 'video' => '!//(player\.|www\.)?(youtube|vimeo|viddler)\.com!i', | ||
99 | 'skipFootnoteLink' => '/^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i' | ||
100 | ); | ||
101 | |||
102 | /* constants */ | ||
103 | const FLAG_STRIP_UNLIKELYS = 1; | ||
104 | const FLAG_WEIGHT_CLASSES = 2; | ||
105 | const FLAG_CLEAN_CONDITIONALLY = 4; | ||
106 | |||
107 | /** | ||
108 | * Create instance of Readability | ||
109 | * @param string UTF-8 encoded string | ||
110 | * @param string (optional) URL associated with HTML (used for footnotes) | ||
111 | * @param string which parser to use for turning raw HTML into a DOMDocument (either 'libxml' or 'html5lib') | ||
112 | */ | ||
113 | function __construct($html, $url=null, $parser='libxml') | ||
114 | { | ||
115 | $this->url = $url; | ||
116 | /* Turn all double br's into p's */ | ||
117 | $html = preg_replace($this->regexps['replaceBrs'], '</p><p>', $html); | ||
118 | $html = preg_replace($this->regexps['replaceFonts'], '<$1span>', $html); | ||
119 | $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"); | ||
120 | if (trim($html) == '') $html = '<html></html>'; | ||
121 | if ($parser=='html5lib' && ($this->dom = HTML5_Parser::parse($html))) { | ||
122 | // all good | ||
123 | } else { | ||
124 | $this->dom = new DOMDocument(); | ||
125 | $this->dom->preserveWhiteSpace = false; | ||
126 | @$this->dom->loadHTML($html); | ||
127 | } | ||
128 | $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement'); | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Get article title element | ||
133 | * @return DOMElement | ||
134 | */ | ||
135 | public function getTitle() { | ||
136 | return $this->articleTitle; | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * Get article content element | ||
141 | * @return DOMElement | ||
142 | */ | ||
143 | public function getContent() { | ||
144 | return $this->articleContent; | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * Runs readability. | ||
149 | * | ||
150 | * Workflow: | ||
151 | * 1. Prep the document by removing script tags, css, etc. | ||
152 | * 2. Build readability's DOM tree. | ||
153 | * 3. Grab the article content from the current dom tree. | ||
154 | * 4. Replace the current DOM tree with the new one. | ||
155 | * 5. Read peacefully. | ||
156 | * | ||
157 | * @return boolean true if we found content, false otherwise | ||
158 | **/ | ||
159 | public function init() | ||
160 | { | ||
161 | if (!isset($this->dom->documentElement)) return false; | ||
162 | $this->removeScripts($this->dom); | ||
163 | //die($this->getInnerHTML($this->dom->documentElement)); | ||
164 | |||
165 | // Assume successful outcome | ||
166 | $this->success = true; | ||
167 | |||
168 | $bodyElems = $this->dom->getElementsByTagName('body'); | ||
169 | if ($bodyElems->length > 0) { | ||
170 | if ($this->bodyCache == null) { | ||
171 | $this->bodyCache = $bodyElems->item(0)->innerHTML; | ||
172 | } | ||
173 | if ($this->body == null) { | ||
174 | $this->body = $bodyElems->item(0); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | $this->prepDocument(); | ||
179 | |||
180 | //die($this->dom->documentElement->parentNode->nodeType); | ||
181 | //$this->setInnerHTML($this->dom->documentElement, $this->getInnerHTML($this->dom->documentElement)); | ||
182 | //die($this->getInnerHTML($this->dom->documentElement)); | ||
183 | |||
184 | /* Build readability's DOM tree */ | ||
185 | $overlay = $this->dom->createElement('div'); | ||
186 | $innerDiv = $this->dom->createElement('div'); | ||
187 | $articleTitle = $this->getArticleTitle(); | ||
188 | $articleContent = $this->grabArticle(); | ||
189 | |||
190 | if (!$articleContent) { | ||
191 | $this->success = false; | ||
192 | $articleContent = $this->dom->createElement('div'); | ||
193 | $articleContent->setAttribute('id', 'readability-content'); | ||
194 | $articleContent->innerHTML = '<p>Sorry, Readability was unable to parse this page for content.</p>'; | ||
195 | } | ||
196 | |||
197 | $overlay->setAttribute('id', 'readOverlay'); | ||
198 | $innerDiv->setAttribute('id', 'readInner'); | ||
199 | |||
200 | /* Glue the structure of our document together. */ | ||
201 | $innerDiv->appendChild($articleTitle); | ||
202 | $innerDiv->appendChild($articleContent); | ||
203 | $overlay->appendChild($innerDiv); | ||
204 | |||
205 | /* Clear the old HTML, insert the new content. */ | ||
206 | $this->body->innerHTML = ''; | ||
207 | $this->body->appendChild($overlay); | ||
208 | //document.body.insertBefore(overlay, document.body.firstChild); | ||
209 | $this->body->removeAttribute('style'); | ||
210 | |||
211 | $this->postProcessContent($articleContent); | ||
212 | |||
213 | // Set title and content instance variables | ||
214 | $this->articleTitle = $articleTitle; | ||
215 | $this->articleContent = $articleContent; | ||
216 | |||
217 | return $this->success; | ||
218 | } | ||
219 | |||
220 | /** | ||
221 | * Debug | ||
222 | */ | ||
223 | protected function dbg($msg) { | ||
224 | if ($this->debug) echo '* ',$msg, "\n"; | ||
225 | } | ||
226 | |||
227 | /** | ||
228 | * Run any post-process modifications to article content as necessary. | ||
229 | * | ||
230 | * @param DOMElement | ||
231 | * @return void | ||
232 | */ | ||
233 | public function postProcessContent($articleContent) { | ||
234 | if ($this->convertLinksToFootnotes && !preg_match('/wikipedia\.org/', @$this->url)) { | ||
235 | $this->addFootnotes($articleContent); | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * Get the article title as an H1. | ||
241 | * | ||
242 | * @return DOMElement | ||
243 | */ | ||
244 | protected function getArticleTitle() { | ||
245 | $curTitle = ''; | ||
246 | $origTitle = ''; | ||
247 | |||
248 | try { | ||
249 | $curTitle = $origTitle = $this->getInnerText($this->dom->getElementsByTagName('title')->item(0)); | ||
250 | } catch(Exception $e) {} | ||
251 | |||
252 | if (preg_match('/ [\|\-] /', $curTitle)) | ||
253 | { | ||
254 | $curTitle = preg_replace('/(.*)[\|\-] .*/i', '$1', $origTitle); | ||
255 | |||
256 | if (count(explode(' ', $curTitle)) < 3) { | ||
257 | $curTitle = preg_replace('/[^\|\-]*[\|\-](.*)/i', '$1', $origTitle); | ||
258 | } | ||
259 | } | ||
260 | else if (strpos($curTitle, ': ') !== false) | ||
261 | { | ||
262 | $curTitle = preg_replace('/.*:(.*)/i', '$1', $origTitle); | ||
263 | |||
264 | if (count(explode(' ', $curTitle)) < 3) { | ||
265 | $curTitle = preg_replace('/[^:]*[:](.*)/i','$1', $origTitle); | ||
266 | } | ||
267 | } | ||
268 | else if(strlen($curTitle) > 150 || strlen($curTitle) < 15) | ||
269 | { | ||
270 | $hOnes = $this->dom->getElementsByTagName('h1'); | ||
271 | if($hOnes->length == 1) | ||
272 | { | ||
273 | $curTitle = $this->getInnerText($hOnes->item(0)); | ||
274 | } | ||
275 | } | ||
276 | |||
277 | $curTitle = trim($curTitle); | ||
278 | |||
279 | if (count(explode(' ', $curTitle)) <= 4) { | ||
280 | $curTitle = $origTitle; | ||
281 | } | ||
282 | |||
283 | $articleTitle = $this->dom->createElement('h1'); | ||
284 | $articleTitle->innerHTML = $curTitle; | ||
285 | |||
286 | return $articleTitle; | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Prepare the HTML document for readability to scrape it. | ||
291 | * This includes things like stripping javascript, CSS, and handling terrible markup. | ||
292 | * | ||
293 | * @return void | ||
294 | **/ | ||
295 | protected function prepDocument() { | ||
296 | /** | ||
297 | * In some cases a body element can't be found (if the HTML is totally hosed for example) | ||
298 | * so we create a new body node and append it to the document. | ||
299 | */ | ||
300 | if ($this->body == null) | ||
301 | { | ||
302 | $this->body = $this->dom->createElement('body'); | ||
303 | $this->dom->documentElement->appendChild($this->body); | ||
304 | } | ||
305 | $this->body->setAttribute('id', 'readabilityBody'); | ||
306 | |||
307 | /* Remove all style tags in head */ | ||
308 | $styleTags = $this->dom->getElementsByTagName('style'); | ||
309 | for ($i = $styleTags->length-1; $i >= 0; $i--) | ||
310 | { | ||
311 | $styleTags->item($i)->parentNode->removeChild($styleTags->item($i)); | ||
312 | } | ||
313 | |||
314 | /* Turn all double br's into p's */ | ||
315 | /* Note, this is pretty costly as far as processing goes. Maybe optimize later. */ | ||
316 | //document.body.innerHTML = document.body.innerHTML.replace(readability.regexps.replaceBrs, '</p><p>').replace(readability.regexps.replaceFonts, '<$1span>'); | ||
317 | // We do this in the constructor for PHP as that's when we have raw HTML - before parsing it into a DOM tree. | ||
318 | // Manipulating innerHTML as it's done in JS is not possible in PHP. | ||
319 | } | ||
320 | |||
321 | /** | ||
322 | * For easier reading, convert this document to have footnotes at the bottom rather than inline links. | ||
323 | * @see http://www.roughtype.com/archives/2010/05/experiments_in.php | ||
324 | * | ||
325 | * @return void | ||
326 | **/ | ||
327 | public function addFootnotes($articleContent) { | ||
328 | $footnotesWrapper = $this->dom->createElement('div'); | ||
329 | $footnotesWrapper->setAttribute('id', 'readability-footnotes'); | ||
330 | $footnotesWrapper->innerHTML = '<h3>References</h3>'; | ||
331 | |||
332 | $articleFootnotes = $this->dom->createElement('ol'); | ||
333 | $articleFootnotes->setAttribute('id', 'readability-footnotes-list'); | ||
334 | $footnotesWrapper->appendChild($articleFootnotes); | ||
335 | |||
336 | $articleLinks = $articleContent->getElementsByTagName('a'); | ||
337 | |||
338 | $linkCount = 0; | ||
339 | for ($i = 0; $i < $articleLinks->length; $i++) | ||
340 | { | ||
341 | $articleLink = $articleLinks->item($i); | ||
342 | $footnoteLink = $articleLink->cloneNode(true); | ||
343 | $refLink = $this->dom->createElement('a'); | ||
344 | $footnote = $this->dom->createElement('li'); | ||
345 | $linkDomain = @parse_url($footnoteLink->getAttribute('href'), PHP_URL_HOST); | ||
346 | if (!$linkDomain && isset($this->url)) $linkDomain = @parse_url($this->url, PHP_URL_HOST); | ||
347 | //linkDomain = footnoteLink.host ? footnoteLink.host : document.location.host, | ||
348 | $linkText = $this->getInnerText($articleLink); | ||
349 | |||
350 | if ((strpos($articleLink->getAttribute('class'), 'readability-DoNotFootnote') !== false) || preg_match($this->regexps['skipFootnoteLink'], $linkText)) { | ||
351 | continue; | ||
352 | } | ||
353 | |||
354 | $linkCount++; | ||
355 | |||
356 | /** Add a superscript reference after the article link */ | ||
357 | $refLink->setAttribute('href', '#readabilityFootnoteLink-' . $linkCount); | ||
358 | $refLink->innerHTML = '<small><sup>[' . $linkCount . ']</sup></small>'; | ||
359 | $refLink->setAttribute('class', 'readability-DoNotFootnote'); | ||
360 | $refLink->setAttribute('style', 'color: inherit;'); | ||
361 | |||
362 | //TODO: does this work or should we use DOMNode.isSameNode()? | ||
363 | if ($articleLink->parentNode->lastChild == $articleLink) { | ||
364 | $articleLink->parentNode->appendChild($refLink); | ||
365 | } else { | ||
366 | $articleLink->parentNode->insertBefore($refLink, $articleLink->nextSibling); | ||
367 | } | ||
368 | |||
369 | $articleLink->setAttribute('style', 'color: inherit; text-decoration: none;'); | ||
370 | $articleLink->setAttribute('name', 'readabilityLink-' . $linkCount); | ||
371 | |||
372 | $footnote->innerHTML = '<small><sup><a href="#readabilityLink-' . $linkCount . '" title="Jump to Link in Article">^</a></sup></small> '; | ||
373 | |||
374 | $footnoteLink->innerHTML = ($footnoteLink->getAttribute('title') != '' ? $footnoteLink->getAttribute('title') : $linkText); | ||
375 | $footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount); | ||
376 | |||
377 | $footnote->appendChild($footnoteLink); | ||
378 | if ($linkDomain) $footnote->innerHTML = $footnote->innerHTML . '<small> (' . $linkDomain . ')</small>'; | ||
379 | |||
380 | $articleFootnotes->appendChild($footnote); | ||
381 | } | ||
382 | |||
383 | if ($linkCount > 0) { | ||
384 | $articleContent->appendChild($footnotesWrapper); | ||
385 | } | ||
386 | } | ||
387 | |||
388 | /** | ||
389 | * Reverts P elements with class 'readability-styled' | ||
390 | * to text nodes - which is what they were before. | ||
391 | * | ||
392 | * @param DOMElement | ||
393 | * @return void | ||
394 | */ | ||
395 | function revertReadabilityStyledElements($articleContent) { | ||
396 | $xpath = new DOMXPath($articleContent->ownerDocument); | ||
397 | $elems = $xpath->query('.//p[@class="readability-styled"]', $articleContent); | ||
398 | //$elems = $articleContent->getElementsByTagName('p'); | ||
399 | for ($i = $elems->length-1; $i >= 0; $i--) { | ||
400 | $e = $elems->item($i); | ||
401 | $e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e); | ||
402 | //if ($e->hasAttribute('class') && $e->getAttribute('class') == 'readability-styled') { | ||
403 | // $e->parentNode->replaceChild($this->dom->createTextNode($e->textContent), $e); | ||
404 | //} | ||
405 | } | ||
406 | } | ||
407 | |||
408 | /** | ||
409 | * Prepare the article node for display. Clean out any inline styles, | ||
410 | * iframes, forms, strip extraneous <p> tags, etc. | ||
411 | * | ||
412 | * @param DOMElement | ||
413 | * @return void | ||
414 | */ | ||
415 | function prepArticle($articleContent) { | ||
416 | $this->cleanStyles($articleContent); | ||
417 | $this->killBreaks($articleContent); | ||
418 | if ($this->revertForcedParagraphElements) { | ||
419 | $this->revertReadabilityStyledElements($articleContent); | ||
420 | } | ||
421 | |||
422 | /* Clean out junk from the article content */ | ||
423 | $this->cleanConditionally($articleContent, 'form'); | ||
424 | $this->clean($articleContent, 'object'); | ||
425 | $this->clean($articleContent, 'h1'); | ||
426 | |||
427 | /** | ||
428 | * If there is only one h2, they are probably using it | ||
429 | * as a header and not a subheader, so remove it since we already have a header. | ||
430 | ***/ | ||
431 | if (!$this->lightClean && ($articleContent->getElementsByTagName('h2')->length == 1)) { | ||
432 | $this->clean($articleContent, 'h2'); | ||
433 | } | ||
434 | $this->clean($articleContent, 'iframe'); | ||
435 | |||
436 | $this->cleanHeaders($articleContent); | ||
437 | |||
438 | /* Do these last as the previous stuff may have removed junk that will affect these */ | ||
439 | $this->cleanConditionally($articleContent, 'table'); | ||
440 | $this->cleanConditionally($articleContent, 'ul'); | ||
441 | $this->cleanConditionally($articleContent, 'div'); | ||
442 | |||
443 | /* Remove extra paragraphs */ | ||
444 | $articleParagraphs = $articleContent->getElementsByTagName('p'); | ||
445 | for ($i = $articleParagraphs->length-1; $i >= 0; $i--) | ||
446 | { | ||
447 | $imgCount = $articleParagraphs->item($i)->getElementsByTagName('img')->length; | ||
448 | $embedCount = $articleParagraphs->item($i)->getElementsByTagName('embed')->length; | ||
449 | $objectCount = $articleParagraphs->item($i)->getElementsByTagName('object')->length; | ||
450 | $iframeCount = $articleParagraphs->item($i)->getElementsByTagName('iframe')->length; | ||
451 | |||
452 | if ($imgCount === 0 && $embedCount === 0 && $objectCount === 0 && $iframeCount === 0 && $this->getInnerText($articleParagraphs->item($i), false) == '') | ||
453 | { | ||
454 | $articleParagraphs->item($i)->parentNode->removeChild($articleParagraphs->item($i)); | ||
455 | } | ||
456 | } | ||
457 | |||
458 | try { | ||
459 | $articleContent->innerHTML = preg_replace('/<br[^>]*>\s*<p/i', '<p', $articleContent->innerHTML); | ||
460 | //articleContent.innerHTML = articleContent.innerHTML.replace(/<br[^>]*>\s*<p/gi, '<p'); | ||
461 | } | ||
462 | catch (Exception $e) { | ||
463 | $this->dbg("Cleaning innerHTML of breaks failed. This is an IE strict-block-elements bug. Ignoring.: " . $e); | ||
464 | } | ||
465 | } | ||
466 | |||
467 | /** | ||
468 | * Initialize a node with the readability object. Also checks the | ||
469 | * className/id for special names to add to its score. | ||
470 | * | ||
471 | * @param Element | ||
472 | * @return void | ||
473 | **/ | ||
474 | protected function initializeNode($node) { | ||
475 | $readability = $this->dom->createAttribute('readability'); | ||
476 | $readability->value = 0; // this is our contentScore | ||
477 | $node->setAttributeNode($readability); | ||
478 | |||
479 | switch (strtoupper($node->tagName)) { // unsure if strtoupper is needed, but using it just in case | ||
480 | case 'DIV': | ||
481 | $readability->value += 5; | ||
482 | break; | ||
483 | |||
484 | case 'PRE': | ||
485 | case 'TD': | ||
486 | case 'BLOCKQUOTE': | ||
487 | $readability->value += 3; | ||
488 | break; | ||
489 | |||
490 | case 'ADDRESS': | ||
491 | case 'OL': | ||
492 | case 'UL': | ||
493 | case 'DL': | ||
494 | case 'DD': | ||
495 | case 'DT': | ||
496 | case 'LI': | ||
497 | case 'FORM': | ||
498 | $readability->value -= 3; | ||
499 | break; | ||
500 | |||
501 | case 'H1': | ||
502 | case 'H2': | ||
503 | case 'H3': | ||
504 | case 'H4': | ||
505 | case 'H5': | ||
506 | case 'H6': | ||
507 | case 'TH': | ||
508 | $readability->value -= 5; | ||
509 | break; | ||
510 | } | ||
511 | $readability->value += $this->getClassWeight($node); | ||
512 | } | ||
513 | |||
514 | /*** | ||
515 | * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is | ||
516 | * most likely to be the stuff a user wants to read. Then return it wrapped up in a div. | ||
517 | * | ||
518 | * @return DOMElement | ||
519 | **/ | ||
520 | protected function grabArticle($page=null) { | ||
521 | $stripUnlikelyCandidates = $this->flagIsActive(self::FLAG_STRIP_UNLIKELYS); | ||
522 | if (!$page) $page = $this->dom; | ||
523 | $allElements = $page->getElementsByTagName('*'); | ||
524 | /** | ||
525 | * First, node prepping. Trash nodes that look cruddy (like ones with the class name "comment", etc), and turn divs | ||
526 | * into P tags where they have been used inappropriately (as in, where they contain no other block level elements.) | ||
527 | * | ||
528 | * Note: Assignment from index for performance. See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5 | ||
529 | * TODO: Shouldn't this be a reverse traversal? | ||
530 | **/ | ||
531 | $node = null; | ||
532 | $nodesToScore = array(); | ||
533 | for ($nodeIndex = 0; ($node = $allElements->item($nodeIndex)); $nodeIndex++) { | ||
534 | //for ($nodeIndex=$targetList->length-1; $nodeIndex >= 0; $nodeIndex--) { | ||
535 | //$node = $targetList->item($nodeIndex); | ||
536 | $tagName = strtoupper($node->tagName); | ||
537 | /* Remove unlikely candidates */ | ||
538 | if ($stripUnlikelyCandidates) { | ||
539 | $unlikelyMatchString = $node->getAttribute('class') . $node->getAttribute('id'); | ||
540 | if ( | ||
541 | preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) && | ||
542 | !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) && | ||
543 | $tagName != 'BODY' | ||
544 | ) | ||
545 | { | ||
546 | $this->dbg('Removing unlikely candidate - ' . $unlikelyMatchString); | ||
547 | //$nodesToRemove[] = $node; | ||
548 | $node->parentNode->removeChild($node); | ||
549 | $nodeIndex--; | ||
550 | continue; | ||
551 | } | ||
552 | } | ||
553 | |||
554 | if ($tagName == 'P' || $tagName == 'TD' || $tagName == 'PRE') { | ||
555 | $nodesToScore[] = $node; | ||
556 | } | ||
557 | |||
558 | /* Turn all divs that don't have children block level elements into p's */ | ||
559 | if ($tagName == 'DIV') { | ||
560 | if (!preg_match($this->regexps['divToPElements'], $node->innerHTML)) { | ||
561 | //$this->dbg('Altering div to p'); | ||
562 | $newNode = $this->dom->createElement('p'); | ||
563 | try { | ||
564 | $newNode->innerHTML = $node->innerHTML; | ||
565 | //$nodesToReplace[] = array('new'=>$newNode, 'old'=>$node); | ||
566 | $node->parentNode->replaceChild($newNode, $node); | ||
567 | $nodeIndex--; | ||
568 | $nodesToScore[] = $node; // or $newNode? | ||
569 | } | ||
570 | catch(Exception $e) { | ||
571 | $this->dbg('Could not alter div to p, reverting back to div.: ' . $e); | ||
572 | } | ||
573 | } | ||
574 | else | ||
575 | { | ||
576 | /* EXPERIMENTAL */ | ||
577 | // TODO: change these p elements back to text nodes after processing | ||
578 | for ($i = 0, $il = $node->childNodes->length; $i < $il; $i++) { | ||
579 | $childNode = $node->childNodes->item($i); | ||
580 | if ($childNode->nodeType == 3) { // XML_TEXT_NODE | ||
581 | //$this->dbg('replacing text node with a p tag with the same content.'); | ||
582 | $p = $this->dom->createElement('p'); | ||
583 | $p->innerHTML = $childNode->nodeValue; | ||
584 | $p->setAttribute('style', 'display: inline;'); | ||
585 | $p->setAttribute('class', 'readability-styled'); | ||
586 | $childNode->parentNode->replaceChild($p, $childNode); | ||
587 | } | ||
588 | } | ||
589 | } | ||
590 | } | ||
591 | } | ||
592 | |||
593 | /** | ||
594 | * Loop through all paragraphs, and assign a score to them based on how content-y they look. | ||
595 | * Then add their score to their parent node. | ||
596 | * | ||
597 | * A score is determined by things like number of commas, class names, etc. Maybe eventually link density. | ||
598 | **/ | ||
599 | $candidates = array(); | ||
600 | for ($pt=0; $pt < count($nodesToScore); $pt++) { | ||
601 | $parentNode = $nodesToScore[$pt]->parentNode; | ||
602 | // $grandParentNode = $parentNode ? $parentNode->parentNode : null; | ||
603 | $grandParentNode = !$parentNode ? null : (($parentNode->parentNode instanceof DOMElement) ? $parentNode->parentNode : null); | ||
604 | $innerText = $this->getInnerText($nodesToScore[$pt]); | ||
605 | |||
606 | if (!$parentNode || !isset($parentNode->tagName)) { | ||
607 | continue; | ||
608 | } | ||
609 | |||
610 | /* If this paragraph is less than 25 characters, don't even count it. */ | ||
611 | if(strlen($innerText) < 25) { | ||
612 | continue; | ||
613 | } | ||
614 | |||
615 | /* Initialize readability data for the parent. */ | ||
616 | if (!$parentNode->hasAttribute('readability')) | ||
617 | { | ||
618 | $this->initializeNode($parentNode); | ||
619 | $candidates[] = $parentNode; | ||
620 | } | ||
621 | |||
622 | /* Initialize readability data for the grandparent. */ | ||
623 | if ($grandParentNode && !$grandParentNode->hasAttribute('readability') && isset($grandParentNode->tagName)) | ||
624 | { | ||
625 | $this->initializeNode($grandParentNode); | ||
626 | $candidates[] = $grandParentNode; | ||
627 | } | ||
628 | |||
629 | $contentScore = 0; | ||
630 | |||
631 | /* Add a point for the paragraph itself as a base. */ | ||
632 | $contentScore++; | ||
633 | |||
634 | /* Add points for any commas within this paragraph */ | ||
635 | $contentScore += count(explode(',', $innerText)); | ||
636 | |||
637 | /* For every 100 characters in this paragraph, add another point. Up to 3 points. */ | ||
638 | $contentScore += min(floor(strlen($innerText) / 100), 3); | ||
639 | |||
640 | /* Add the score to the parent. The grandparent gets half. */ | ||
641 | $parentNode->getAttributeNode('readability')->value += $contentScore; | ||
642 | |||
643 | if ($grandParentNode) { | ||
644 | $grandParentNode->getAttributeNode('readability')->value += $contentScore/2; | ||
645 | } | ||
646 | } | ||
647 | |||
648 | /** | ||
649 | * After we've calculated scores, loop through all of the possible candidate nodes we found | ||
650 | * and find the one with the highest score. | ||
651 | **/ | ||
652 | $topCandidate = null; | ||
653 | for ($c=0, $cl=count($candidates); $c < $cl; $c++) | ||
654 | { | ||
655 | /** | ||
656 | * Scale the final candidates score based on link density. Good content should have a | ||
657 | * relatively small link density (5% or less) and be mostly unaffected by this operation. | ||
658 | **/ | ||
659 | $readability = $candidates[$c]->getAttributeNode('readability'); | ||
660 | $readability->value = $readability->value * (1-$this->getLinkDensity($candidates[$c])); | ||
661 | |||
662 | $this->dbg('Candidate: ' . $candidates[$c]->tagName . ' (' . $candidates[$c]->getAttribute('class') . ':' . $candidates[$c]->getAttribute('id') . ') with score ' . $readability->value); | ||
663 | |||
664 | if (!$topCandidate || $readability->value > (int)$topCandidate->getAttribute('readability')) { | ||
665 | $topCandidate = $candidates[$c]; | ||
666 | } | ||
667 | } | ||
668 | |||
669 | /** | ||
670 | * If we still have no top candidate, just use the body as a last resort. | ||
671 | * We also have to copy the body node so it is something we can modify. | ||
672 | **/ | ||
673 | if ($topCandidate === null || strtoupper($topCandidate->tagName) == 'BODY') | ||
674 | { | ||
675 | $topCandidate = $this->dom->createElement('div'); | ||
676 | if ($page instanceof DOMDocument) { | ||
677 | if (!isset($page->documentElement)) { | ||
678 | // we don't have a body either? what a mess! :) | ||
679 | } else { | ||
680 | $topCandidate->innerHTML = $page->documentElement->innerHTML; | ||
681 | $page->documentElement->innerHTML = ''; | ||
682 | $page->documentElement->appendChild($topCandidate); | ||
683 | } | ||
684 | } else { | ||
685 | $topCandidate->innerHTML = $page->innerHTML; | ||
686 | $page->innerHTML = ''; | ||
687 | $page->appendChild($topCandidate); | ||
688 | } | ||
689 | $this->initializeNode($topCandidate); | ||
690 | } | ||
691 | |||
692 | /** | ||
693 | * Now that we have the top candidate, look through its siblings for content that might also be related. | ||
694 | * Things like preambles, content split by ads that we removed, etc. | ||
695 | **/ | ||
696 | $articleContent = $this->dom->createElement('div'); | ||
697 | $articleContent->setAttribute('id', 'readability-content'); | ||
698 | $siblingScoreThreshold = max(10, ((int)$topCandidate->getAttribute('readability')) * 0.2); | ||
699 | $siblingNodes = $topCandidate->parentNode->childNodes; | ||
700 | if (!isset($siblingNodes)) { | ||
701 | $siblingNodes = new stdClass; | ||
702 | $siblingNodes->length = 0; | ||
703 | } | ||
704 | |||
705 | for ($s=0, $sl=$siblingNodes->length; $s < $sl; $s++) | ||
706 | { | ||
707 | $siblingNode = $siblingNodes->item($s); | ||
708 | $append = false; | ||
709 | |||
710 | $this->dbg('Looking at sibling node: ' . $siblingNode->nodeName . (($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability')) ? (' with score ' . $siblingNode->getAttribute('readability')) : '')); | ||
711 | |||
712 | //dbg('Sibling has score ' . ($siblingNode->readability ? siblingNode.readability.contentScore : 'Unknown')); | ||
713 | |||
714 | if ($siblingNode === $topCandidate) | ||
715 | // or if ($siblingNode->isSameNode($topCandidate)) | ||
716 | { | ||
717 | $append = true; | ||
718 | } | ||
719 | |||
720 | $contentBonus = 0; | ||
721 | /* Give a bonus if sibling nodes and top candidates have the example same classname */ | ||
722 | if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->getAttribute('class') == $topCandidate->getAttribute('class') && $topCandidate->getAttribute('class') != '') { | ||
723 | $contentBonus += ((int)$topCandidate->getAttribute('readability')) * 0.2; | ||
724 | } | ||
725 | |||
726 | if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability') && (((int)$siblingNode->getAttribute('readability')) + $contentBonus) >= $siblingScoreThreshold) | ||
727 | { | ||
728 | $append = true; | ||
729 | } | ||
730 | |||
731 | if (strtoupper($siblingNode->nodeName) == 'P') { | ||
732 | $linkDensity = $this->getLinkDensity($siblingNode); | ||
733 | $nodeContent = $this->getInnerText($siblingNode); | ||
734 | $nodeLength = strlen($nodeContent); | ||
735 | |||
736 | if ($nodeLength > 80 && $linkDensity < 0.25) | ||
737 | { | ||
738 | $append = true; | ||
739 | } | ||
740 | else if ($nodeLength < 80 && $linkDensity === 0 && preg_match('/\.( |$)/', $nodeContent)) | ||
741 | { | ||
742 | $append = true; | ||
743 | } | ||
744 | } | ||
745 | |||
746 | if ($append) | ||
747 | { | ||
748 | $this->dbg('Appending node: ' . $siblingNode->nodeName); | ||
749 | |||
750 | $nodeToAppend = null; | ||
751 | $sibNodeName = strtoupper($siblingNode->nodeName); | ||
752 | if ($sibNodeName != 'DIV' && $sibNodeName != 'P') { | ||
753 | /* We have a node that isn't a common block level element, like a form or td tag. Turn it into a div so it doesn't get filtered out later by accident. */ | ||
754 | |||
755 | $this->dbg('Altering siblingNode of ' . $sibNodeName . ' to div.'); | ||
756 | $nodeToAppend = $this->dom->createElement('div'); | ||
757 | try { | ||
758 | $nodeToAppend->setAttribute('id', $siblingNode->getAttribute('id')); | ||
759 | $nodeToAppend->innerHTML = $siblingNode->innerHTML; | ||
760 | } | ||
761 | catch(Exception $e) | ||
762 | { | ||
763 | $this->dbg('Could not alter siblingNode to div, reverting back to original.'); | ||
764 | $nodeToAppend = $siblingNode; | ||
765 | $s--; | ||
766 | $sl--; | ||
767 | } | ||
768 | } else { | ||
769 | $nodeToAppend = $siblingNode; | ||
770 | $s--; | ||
771 | $sl--; | ||
772 | } | ||
773 | |||
774 | /* To ensure a node does not interfere with readability styles, remove its classnames */ | ||
775 | $nodeToAppend->removeAttribute('class'); | ||
776 | |||
777 | /* Append sibling and subtract from our list because it removes the node when you append to another node */ | ||
778 | $articleContent->appendChild($nodeToAppend); | ||
779 | } | ||
780 | } | ||
781 | |||
782 | /** | ||
783 | * So we have all of the content that we need. Now we clean it up for presentation. | ||
784 | **/ | ||
785 | $this->prepArticle($articleContent); | ||
786 | |||
787 | /** | ||
788 | * Now that we've gone through the full algorithm, check to see if we got any meaningful content. | ||
789 | * If we didn't, we may need to re-run grabArticle with different flags set. This gives us a higher | ||
790 | * likelihood of finding the content, and the sieve approach gives us a higher likelihood of | ||
791 | * finding the -right- content. | ||
792 | **/ | ||
793 | if (strlen($this->getInnerText($articleContent, false)) < 250) | ||
794 | { | ||
795 | // TODO: find out why element disappears sometimes, e.g. for this URL http://www.businessinsider.com/6-hedge-fund-etfs-for-average-investors-2011-7 | ||
796 | // in the meantime, we check and create an empty element if it's not there. | ||
797 | if (!isset($this->body->childNodes)) $this->body = $this->dom->createElement('body'); | ||
798 | $this->body->innerHTML = $this->bodyCache; | 1146 | $this->body->innerHTML = $this->bodyCache; |
799 | |||
800 | if ($this->flagIsActive(self::FLAG_STRIP_UNLIKELYS)) { | ||
801 | $this->removeFlag(self::FLAG_STRIP_UNLIKELYS); | ||
802 | return $this->grabArticle($this->body); | ||
803 | } | ||
804 | else if ($this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) { | ||
805 | $this->removeFlag(self::FLAG_WEIGHT_CLASSES); | ||
806 | return $this->grabArticle($this->body); | ||
807 | } | ||
808 | else if ($this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) { | ||
809 | $this->removeFlag(self::FLAG_CLEAN_CONDITIONALLY); | ||
810 | return $this->grabArticle($this->body); | ||
811 | } | ||
812 | else { | ||
813 | return false; | ||
814 | } | ||
815 | } | ||
816 | return $articleContent; | ||
817 | } | ||
818 | |||
819 | /** | ||
820 | * Remove script tags from document | ||
821 | * | ||
822 | * @param DOMElement | ||
823 | * @return void | ||
824 | */ | ||
825 | public function removeScripts($doc) { | ||
826 | $scripts = $doc->getElementsByTagName('script'); | ||
827 | for($i = $scripts->length-1; $i >= 0; $i--) | ||
828 | { | ||
829 | $scripts->item($i)->parentNode->removeChild($scripts->item($i)); | ||
830 | } | ||
831 | } | ||
832 | |||
833 | /** | ||
834 | * Get the inner text of a node. | ||
835 | * This also strips out any excess whitespace to be found. | ||
836 | * | ||
837 | * @param DOMElement $ | ||
838 | * @param boolean $normalizeSpaces (default: true) | ||
839 | * @return string | ||
840 | **/ | ||
841 | public function getInnerText($e, $normalizeSpaces=true) { | ||
842 | $textContent = ''; | ||
843 | |||
844 | if (!isset($e->textContent) || $e->textContent == '') { | ||
845 | return ''; | ||
846 | } | ||
847 | |||
848 | $textContent = trim($e->textContent); | ||
849 | |||
850 | if ($normalizeSpaces) { | ||
851 | return preg_replace($this->regexps['normalize'], ' ', $textContent); | ||
852 | } else { | ||
853 | return $textContent; | ||
854 | } | ||
855 | } | ||
856 | |||
857 | /** | ||
858 | * Get the number of times a string $s appears in the node $e. | ||
859 | * | ||
860 | * @param DOMElement $e | ||
861 | * @param string - what to count. Default is "," | ||
862 | * @return number (integer) | ||
863 | **/ | ||
864 | public function getCharCount($e, $s=',') { | ||
865 | return substr_count($this->getInnerText($e), $s); | ||
866 | } | ||
867 | |||
868 | /** | ||
869 | * Remove the style attribute on every $e and under. | ||
870 | * | ||
871 | * @param DOMElement $e | ||
872 | * @return void | ||
873 | */ | ||
874 | public function cleanStyles($e) { | ||
875 | if (!is_object($e)) return; | ||
876 | $elems = $e->getElementsByTagName('*'); | ||
877 | foreach ($elems as $elem) { | ||
878 | $elem->removeAttribute('style'); | ||
879 | } | ||
880 | } | ||
881 | |||
882 | /** | ||
883 | * Get the density of links as a percentage of the content | ||
884 | * This is the amount of text that is inside a link divided by the total text in the node. | ||
885 | * | ||
886 | * @param DOMElement $e | ||
887 | * @return number (float) | ||
888 | */ | ||
889 | public function getLinkDensity($e) { | ||
890 | $links = $e->getElementsByTagName('a'); | ||
891 | $textLength = strlen($this->getInnerText($e)); | ||
892 | $linkLength = 0; | ||
893 | for ($i=0, $il=$links->length; $i < $il; $i++) | ||
894 | { | ||
895 | $linkLength += strlen($this->getInnerText($links->item($i))); | ||
896 | } | ||
897 | if ($textLength > 0) { | ||
898 | return $linkLength / $textLength; | ||
899 | } else { | ||
900 | return 0; | ||
901 | } | ||
902 | } | ||
903 | |||
904 | /** | ||
905 | * Get an elements class/id weight. Uses regular expressions to tell if this | ||
906 | * element looks good or bad. | ||
907 | * | ||
908 | * @param DOMElement $e | ||
909 | * @return number (Integer) | ||
910 | */ | ||
911 | public function getClassWeight($e) { | ||
912 | if(!$this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) { | ||
913 | return 0; | ||
914 | } | ||
915 | |||
916 | $weight = 0; | ||
917 | |||
918 | /* Look for a special classname */ | ||
919 | if ($e->hasAttribute('class') && $e->getAttribute('class') != '') | ||
920 | { | ||
921 | if (preg_match($this->regexps['negative'], $e->getAttribute('class'))) { | ||
922 | $weight -= 25; | ||
923 | } | ||
924 | if (preg_match($this->regexps['positive'], $e->getAttribute('class'))) { | ||
925 | $weight += 25; | ||
926 | } | ||
927 | } | ||
928 | |||
929 | /* Look for a special ID */ | ||
930 | if ($e->hasAttribute('id') && $e->getAttribute('id') != '') | ||
931 | { | ||
932 | if (preg_match($this->regexps['negative'], $e->getAttribute('id'))) { | ||
933 | $weight -= 25; | ||
934 | } | ||
935 | if (preg_match($this->regexps['positive'], $e->getAttribute('id'))) { | ||
936 | $weight += 25; | ||
937 | } | ||
938 | } | ||
939 | return $weight; | ||
940 | } | ||
941 | |||
942 | /** | ||
943 | * Remove extraneous break tags from a node. | ||
944 | * | ||
945 | * @param DOMElement $node | ||
946 | * @return void | ||
947 | */ | ||
948 | public function killBreaks($node) { | ||
949 | $html = $node->innerHTML; | ||
950 | $html = preg_replace($this->regexps['killBreaks'], '<br />', $html); | ||
951 | $node->innerHTML = $html; | ||
952 | } | ||
953 | |||
954 | /** | ||
955 | * Clean a node of all elements of type "tag". | ||
956 | * (Unless it's a youtube/vimeo video. People love movies.) | ||
957 | * | ||
958 | * Updated 2012-09-18 to preserve youtube/vimeo iframes | ||
959 | * | ||
960 | * @param DOMElement $e | ||
961 | * @param string $tag | ||
962 | * @return void | ||
963 | */ | ||
964 | public function clean($e, $tag) { | ||
965 | $targetList = $e->getElementsByTagName($tag); | ||
966 | $isEmbed = ($tag == 'iframe' || $tag == 'object' || $tag == 'embed'); | ||
967 | |||
968 | for ($y=$targetList->length-1; $y >= 0; $y--) { | ||
969 | /* Allow youtube and vimeo videos through as people usually want to see those. */ | ||
970 | if ($isEmbed) { | ||
971 | $attributeValues = ''; | ||
972 | for ($i=0, $il=$targetList->item($y)->attributes->length; $i < $il; $i++) { | ||
973 | $attributeValues .= $targetList->item($y)->attributes->item($i)->value . '|'; // DOMAttr? (TODO: test) | ||
974 | } | ||
975 | |||
976 | /* First, check the elements attributes to see if any of them contain youtube or vimeo */ | ||
977 | if (preg_match($this->regexps['video'], $attributeValues)) { | ||
978 | continue; | ||
979 | } | ||
980 | |||
981 | /* Then check the elements inside this element for the same. */ | ||
982 | if (preg_match($this->regexps['video'], $targetList->item($y)->innerHTML)) { | ||
983 | continue; | ||
984 | } | ||
985 | } | ||
986 | $targetList->item($y)->parentNode->removeChild($targetList->item($y)); | ||
987 | } | ||
988 | } | ||
989 | |||
990 | /** | ||
991 | * Clean an element of all tags of type "tag" if they look fishy. | ||
992 | * "Fishy" is an algorithm based on content length, classnames, | ||
993 | * link density, number of images & embeds, etc. | ||
994 | * | ||
995 | * @param DOMElement $e | ||
996 | * @param string $tag | ||
997 | * @return void | ||
998 | */ | ||
999 | public function cleanConditionally($e, $tag) { | ||
1000 | if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) { | ||
1001 | return; | ||
1002 | } | ||
1003 | |||
1004 | $tagsList = $e->getElementsByTagName($tag); | ||
1005 | $curTagsLength = $tagsList->length; | ||
1006 | |||
1007 | /** | ||
1008 | * Gather counts for other typical elements embedded within. | ||
1009 | * Traverse backwards so we can remove nodes at the same time without effecting the traversal. | ||
1010 | * | ||
1011 | * TODO: Consider taking into account original contentScore here. | ||
1012 | */ | ||
1013 | for ($i=$curTagsLength-1; $i >= 0; $i--) { | ||
1014 | $weight = $this->getClassWeight($tagsList->item($i)); | ||
1015 | $contentScore = ($tagsList->item($i)->hasAttribute('readability')) ? (int)$tagsList->item($i)->getAttribute('readability') : 0; | ||
1016 | |||
1017 | $this->dbg('Cleaning Conditionally ' . $tagsList->item($i)->tagName . ' (' . $tagsList->item($i)->getAttribute('class') . ':' . $tagsList->item($i)->getAttribute('id') . ')' . (($tagsList->item($i)->hasAttribute('readability')) ? (' with score ' . $tagsList->item($i)->getAttribute('readability')) : '')); | ||
1018 | |||
1019 | if ($weight + $contentScore < 0) { | ||
1020 | $tagsList->item($i)->parentNode->removeChild($tagsList->item($i)); | ||
1021 | } | ||
1022 | else if ( $this->getCharCount($tagsList->item($i), ',') < 10) { | ||
1023 | /** | ||
1024 | * If there are not very many commas, and the number of | ||
1025 | * non-paragraph elements is more than paragraphs or other ominous signs, remove the element. | ||
1026 | **/ | ||
1027 | $p = $tagsList->item($i)->getElementsByTagName('p')->length; | ||
1028 | $img = $tagsList->item($i)->getElementsByTagName('img')->length; | ||
1029 | $li = $tagsList->item($i)->getElementsByTagName('li')->length-100; | ||
1030 | $input = $tagsList->item($i)->getElementsByTagName('input')->length; | ||
1031 | $a = $tagsList->item($i)->getElementsByTagName('a')->length; | ||
1032 | |||
1033 | $embedCount = 0; | ||
1034 | $embeds = $tagsList->item($i)->getElementsByTagName('embed'); | ||
1035 | for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) { | ||
1036 | if (preg_match($this->regexps['video'], $embeds->item($ei)->getAttribute('src'))) { | ||
1037 | $embedCount++; | ||
1038 | } | ||
1039 | } | ||
1040 | $embeds = $tagsList->item($i)->getElementsByTagName('iframe'); | ||
1041 | for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) { | ||
1042 | if (preg_match($this->regexps['video'], $embeds->item($ei)->getAttribute('src'))) { | ||
1043 | $embedCount++; | ||
1044 | } | ||
1045 | } | ||
1046 | |||
1047 | $linkDensity = $this->getLinkDensity($tagsList->item($i)); | ||
1048 | $contentLength = strlen($this->getInnerText($tagsList->item($i))); | ||
1049 | $toRemove = false; | ||
1050 | |||
1051 | if ($this->lightClean) { | ||
1052 | $this->dbg('Light clean...'); | ||
1053 | if ( ($img > $p) && ($img > 4) ) { | ||
1054 | $this->dbg(' more than 4 images and more image elements than paragraph elements'); | ||
1055 | $toRemove = true; | ||
1056 | } else if ($li > $p && $tag != 'ul' && $tag != 'ol') { | ||
1057 | $this->dbg(' too many <li> elements, and parent is not <ul> or <ol>'); | ||
1058 | $toRemove = true; | ||
1059 | } else if ( $input > floor($p/3) ) { | ||
1060 | $this->dbg(' too many <input> elements'); | ||
1061 | $toRemove = true; | ||
1062 | } else if ($contentLength < 25 && ($embedCount === 0 && ($img === 0 || $img > 2))) { | ||
1063 | $this->dbg(' content length less than 25 chars, 0 embeds and either 0 images or more than 2 images'); | ||
1064 | $toRemove = true; | ||
1065 | } else if($weight < 25 && $linkDensity > 0.2) { | ||
1066 | $this->dbg(' weight smaller than 25 and link density above 0.2'); | ||
1067 | $toRemove = true; | ||
1068 | } else if($a > 2 && ($weight >= 25 && $linkDensity > 0.5)) { | ||
1069 | $this->dbg(' more than 2 links and weight above 25 but link density greater than 0.5'); | ||
1070 | $toRemove = true; | ||
1071 | } else if($embedCount > 3) { | ||
1072 | $this->dbg(' more than 3 embeds'); | ||
1073 | $toRemove = true; | ||
1074 | } | ||
1075 | } else { | ||
1076 | $this->dbg('Standard clean...'); | ||
1077 | if ( $img > $p ) { | ||
1078 | $this->dbg(' more image elements than paragraph elements'); | ||
1079 | $toRemove = true; | ||
1080 | } else if ($li > $p && $tag != 'ul' && $tag != 'ol') { | ||
1081 | $this->dbg(' too many <li> elements, and parent is not <ul> or <ol>'); | ||
1082 | $toRemove = true; | ||
1083 | } else if ( $input > floor($p/3) ) { | ||
1084 | $this->dbg(' too many <input> elements'); | ||
1085 | $toRemove = true; | ||
1086 | } else if ($contentLength < 25 && ($img === 0 || $img > 2) ) { | ||
1087 | $this->dbg(' content length less than 25 chars and 0 images, or more than 2 images'); | ||
1088 | $toRemove = true; | ||
1089 | } else if($weight < 25 && $linkDensity > 0.2) { | ||
1090 | $this->dbg(' weight smaller than 25 and link density above 0.2'); | ||
1091 | $toRemove = true; | ||
1092 | } else if($weight >= 25 && $linkDensity > 0.5) { | ||
1093 | $this->dbg(' weight above 25 but link density greater than 0.5'); | ||
1094 | $toRemove = true; | ||
1095 | } else if(($embedCount == 1 && $contentLength < 75) || $embedCount > 1) { | ||
1096 | $this->dbg(' 1 embed and content length smaller than 75 chars, or more than one embed'); | ||
1097 | $toRemove = true; | ||
1098 | } | ||
1099 | } | ||
1100 | |||
1101 | if ($toRemove) { | ||
1102 | //$this->dbg('Removing: '.$tagsList->item($i)->innerHTML); | ||
1103 | $tagsList->item($i)->parentNode->removeChild($tagsList->item($i)); | ||
1104 | } | ||
1105 | } | ||
1106 | } | ||
1107 | } | ||
1108 | |||
1109 | /** | ||
1110 | * Clean out spurious headers from an Element. Checks things like classnames and link density. | ||
1111 | * | ||
1112 | * @param DOMElement $e | ||
1113 | * @return void | ||
1114 | */ | ||
1115 | public function cleanHeaders($e) { | ||
1116 | for ($headerIndex = 1; $headerIndex < 3; $headerIndex++) { | ||
1117 | $headers = $e->getElementsByTagName('h' . $headerIndex); | ||
1118 | for ($i=$headers->length-1; $i >=0; $i--) { | ||
1119 | if ($this->getClassWeight($headers->item($i)) < 0 || $this->getLinkDensity($headers->item($i)) > 0.33) { | ||
1120 | $headers->item($i)->parentNode->removeChild($headers->item($i)); | ||
1121 | } | ||
1122 | } | ||
1123 | } | 1147 | } |
1124 | } | 1148 | } |
1125 | 1149 | ||
1126 | public function flagIsActive($flag) { | 1150 | } |
1127 | return ($this->flags & $flag) > 0; | ||
1128 | } | ||
1129 | |||
1130 | public function addFlag($flag) { | ||
1131 | $this->flags = $this->flags | $flag; | ||
1132 | } | ||
1133 | |||
1134 | public function removeFlag($flag) { | ||
1135 | $this->flags = $this->flags & ~$flag; | ||
1136 | } | ||
1137 | } | ||
1138 | ?> \ No newline at end of file | 1151 | ?> \ No newline at end of file |
diff --git a/inc/3rdparty/makefulltextfeed.php b/inc/3rdparty/makefulltextfeed.php index 2852c4c2..a081f88b 100755 --- a/inc/3rdparty/makefulltextfeed.php +++ b/inc/3rdparty/makefulltextfeed.php | |||
@@ -3,8 +3,8 @@ | |||
3 | // Author: Keyvan Minoukadeh | 3 | // Author: Keyvan Minoukadeh |
4 | // Copyright (c) 2013 Keyvan Minoukadeh | 4 | // Copyright (c) 2013 Keyvan Minoukadeh |
5 | // License: AGPLv3 | 5 | // License: AGPLv3 |
6 | // Version: 3.1 | 6 | // Version: 3.2 |
7 | // Date: 2013-03-05 | 7 | // Date: 2013-05-13 |
8 | // More info: http://fivefilters.org/content-only/ | 8 | // More info: http://fivefilters.org/content-only/ |
9 | // Help: http://help.fivefilters.org | 9 | // Help: http://help.fivefilters.org |
10 | 10 | ||
@@ -25,14 +25,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
25 | 25 | ||
26 | // Usage | 26 | // Usage |
27 | // ----- | 27 | // ----- |
28 | // Request this file passing it your feed in the querystring: makefulltextfeed.php?url=mysite.org | 28 | // Request this file passing it a web page or feed URL in the querystring: makefulltextfeed.php?url=example.org/article |
29 | // The following options can be passed in the querystring: | 29 | // For more request parameters, see http://help.fivefilters.org/customer/portal/articles/226660-usage |
30 | // * URL: url=[feed or website url] (required, should be URL-encoded - in php: urlencode($url)) | ||
31 | // * URL points to HTML (not feed): html=true (optional, by default it's automatically detected) | ||
32 | // * API key: key=[api key] (optional, refer to config.php) | ||
33 | // * Max entries to process: max=[max number of items] (optional) | ||
34 | 30 | ||
35 | error_reporting(E_ALL ^ E_NOTICE); | 31 | //error_reporting(E_ALL ^ E_NOTICE); |
36 | ini_set("display_errors", 1); | 32 | ini_set("display_errors", 1); |
37 | @set_time_limit(120); | 33 | @set_time_limit(120); |
38 | 34 | ||
@@ -55,42 +51,8 @@ if (get_magic_quotes_gpc()) { | |||
55 | 51 | ||
56 | // set include path | 52 | // set include path |
57 | set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path()); | 53 | set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path()); |
58 | // Autoloading of classes allows us to include files only when they're | 54 | |
59 | // needed. If we've got a cached copy, for example, only Zend_Cache is loaded. | 55 | require_once dirname(__FILE__).'/makefulltextfeedHelpers.php'; |
60 | function autoload($class_name) { | ||
61 | static $dir = null; | ||
62 | if ($dir === null) $dir = dirname(__FILE__).'/libraries/'; | ||
63 | static $mapping = array( | ||
64 | // Include FeedCreator for RSS/Atom creation | ||
65 | 'FeedWriter' => 'feedwriter/FeedWriter.php', | ||
66 | 'FeedItem' => 'feedwriter/FeedItem.php', | ||
67 | // Include ContentExtractor and Readability for identifying and extracting content from URLs | ||
68 | 'ContentExtractor' => 'content-extractor/ContentExtractor.php', | ||
69 | 'SiteConfig' => 'content-extractor/SiteConfig.php', | ||
70 | 'Readability' => 'readability/Readability.php', | ||
71 | // Include Humble HTTP Agent to allow parallel requests and response caching | ||
72 | 'HumbleHttpAgent' => 'humble-http-agent/HumbleHttpAgent.php', | ||
73 | 'SimplePie_HumbleHttpAgent' => 'humble-http-agent/SimplePie_HumbleHttpAgent.php', | ||
74 | 'CookieJar' => 'humble-http-agent/CookieJar.php', | ||
75 | // Include Zend Cache to improve performance (cache results) | ||
76 | 'Zend_Cache' => 'Zend/Cache.php', | ||
77 | // Language detect | ||
78 | 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php', | ||
79 | // HTML5 Lib | ||
80 | 'HTML5_Parser' => 'html5/Parser.php', | ||
81 | // htmLawed - used if XSS filter is enabled (xss_filter) | ||
82 | 'htmLawed' => 'htmLawed/htmLawed.php' | ||
83 | ); | ||
84 | if (isset($mapping[$class_name])) { | ||
85 | debug("** Loading class $class_name ({$mapping[$class_name]})"); | ||
86 | require $dir.$mapping[$class_name]; | ||
87 | return true; | ||
88 | } else { | ||
89 | return false; | ||
90 | } | ||
91 | } | ||
92 | spl_autoload_register('autoload'); | ||
93 | require dirname(__FILE__).'/libraries/simplepie/autoloader.php'; | ||
94 | 56 | ||
95 | //////////////////////////////// | 57 | //////////////////////////////// |
96 | // Load config file | 58 | // Load config file |
@@ -110,8 +72,8 @@ header('X-Robots-Tag: noindex, nofollow'); | |||
110 | //////////////////////////////// | 72 | //////////////////////////////// |
111 | // Check if service is enabled | 73 | // Check if service is enabled |
112 | //////////////////////////////// | 74 | //////////////////////////////// |
113 | if (!$options->enabled) { | 75 | if (!$options->enabled) { |
114 | die('The full-text RSS service is currently disabled'); | 76 | die('The full-text RSS service is currently disabled'); |
115 | } | 77 | } |
116 | 78 | ||
117 | //////////////////////////////// | 79 | //////////////////////////////// |
@@ -155,8 +117,8 @@ $options->smart_cache = $options->smart_cache && function_exists('apc_inc'); | |||
155 | //////////////////////////////// | 117 | //////////////////////////////// |
156 | // Check for feed URL | 118 | // Check for feed URL |
157 | //////////////////////////////// | 119 | //////////////////////////////// |
158 | if (!isset($_GET['url'])) { | 120 | if (!isset($_GET['url'])) { |
159 | die('No URL supplied'); | 121 | die('No URL supplied'); |
160 | } | 122 | } |
161 | $url = trim($_GET['url']); | 123 | $url = trim($_GET['url']); |
162 | if (strtolower(substr($url, 0, 7)) == 'feed://') { | 124 | if (strtolower(substr($url, 0, 7)) == 'feed://') { |
@@ -195,10 +157,12 @@ if (isset($_GET['key']) && ($key_index = array_search($_GET['key'], $options->ap | |||
195 | if (isset($_GET['links'])) $redirect .= '&links='.urlencode($_GET['links']); | 157 | if (isset($_GET['links'])) $redirect .= '&links='.urlencode($_GET['links']); |
196 | if (isset($_GET['exc'])) $redirect .= '&exc='.urlencode($_GET['exc']); | 158 | if (isset($_GET['exc'])) $redirect .= '&exc='.urlencode($_GET['exc']); |
197 | if (isset($_GET['format'])) $redirect .= '&format='.urlencode($_GET['format']); | 159 | if (isset($_GET['format'])) $redirect .= '&format='.urlencode($_GET['format']); |
198 | if (isset($_GET['callback'])) $redirect .= '&callback='.urlencode($_GET['callback']); | 160 | if (isset($_GET['callback'])) $redirect .= '&callback='.urlencode($_GET['callback']); |
199 | if (isset($_GET['l'])) $redirect .= '&l='.urlencode($_GET['l']); | 161 | if (isset($_GET['l'])) $redirect .= '&l='.urlencode($_GET['l']); |
200 | if (isset($_GET['xss'])) $redirect .= '&xss'; | 162 | if (isset($_GET['xss'])) $redirect .= '&xss'; |
201 | if (isset($_GET['use_extracted_title'])) $redirect .= '&use_extracted_title'; | 163 | if (isset($_GET['use_extracted_title'])) $redirect .= '&use_extracted_title'; |
164 | if (isset($_GET['content'])) $redirect .= '&content='.urlencode($_GET['content']); | ||
165 | if (isset($_GET['summary'])) $redirect .= '&summary='.urlencode($_GET['summary']); | ||
202 | if (isset($_GET['debug'])) $redirect .= '&debug'; | 166 | if (isset($_GET['debug'])) $redirect .= '&debug'; |
203 | if ($debug_mode) { | 167 | if ($debug_mode) { |
204 | debug('Redirecting to hide access key, follow URL below to continue'); | 168 | debug('Redirecting to hide access key, follow URL below to continue'); |
@@ -211,7 +175,7 @@ if (isset($_GET['key']) && ($key_index = array_search($_GET['key'], $options->ap | |||
211 | 175 | ||
212 | /////////////////////////////////////////////// | 176 | /////////////////////////////////////////////// |
213 | // Set timezone. | 177 | // Set timezone. |
214 | // Prevents warnings, but needs more testing - | 178 | // Prevents warnings, but needs more testing - |
215 | // perhaps if timezone is set in php.ini we | 179 | // perhaps if timezone is set in php.ini we |
216 | // don't need to set it at all... | 180 | // don't need to set it at all... |
217 | /////////////////////////////////////////////// | 181 | /////////////////////////////////////////////// |
@@ -233,7 +197,7 @@ if (isset($_GET['key']) && isset($_GET['hash']) && isset($options->api_keys[(int | |||
233 | } | 197 | } |
234 | $key_index = ($valid_key) ? (int)$_GET['key'] : 0; | 198 | $key_index = ($valid_key) ? (int)$_GET['key'] : 0; |
235 | if (!$valid_key && $options->key_required) { | 199 | if (!$valid_key && $options->key_required) { |
236 | die('A valid key must be supplied'); | 200 | die('A valid key must be supplied'); |
237 | } | 201 | } |
238 | if (!$valid_key && isset($_GET['key']) && $_GET['key'] != '') { | 202 | if (!$valid_key && isset($_GET['key']) && $_GET['key'] != '') { |
239 | die('The entered key is invalid'); | 203 | die('The entered key is invalid'); |
@@ -285,6 +249,28 @@ if ($options->favour_feed_titles == 'user') { | |||
285 | } | 249 | } |
286 | 250 | ||
287 | /////////////////////////////////////////////// | 251 | /////////////////////////////////////////////// |
252 | // Include full content in output? | ||
253 | /////////////////////////////////////////////// | ||
254 | if ($options->content === 'user') { | ||
255 | if (isset($_GET['content']) && $_GET['content'] === '0') { | ||
256 | $options->content = false; | ||
257 | } else { | ||
258 | $options->content = true; | ||
259 | } | ||
260 | } | ||
261 | |||
262 | /////////////////////////////////////////////// | ||
263 | // Include summaries in output? | ||
264 | /////////////////////////////////////////////// | ||
265 | if ($options->summary === 'user') { | ||
266 | if (isset($_GET['summary']) && $_GET['summary'] === '1') { | ||
267 | $options->summary = true; | ||
268 | } else { | ||
269 | $options->summary = false; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | /////////////////////////////////////////////// | ||
288 | // Exclude items if extraction fails | 274 | // Exclude items if extraction fails |
289 | /////////////////////////////////////////////// | 275 | /////////////////////////////////////////////// |
290 | if ($options->exclude_items_on_fail === 'user') { | 276 | if ($options->exclude_items_on_fail === 'user') { |
@@ -306,15 +292,6 @@ if ($options->detect_language === 'user') { | |||
306 | $detect_language = $options->detect_language; | 292 | $detect_language = $options->detect_language; |
307 | } | 293 | } |
308 | 294 | ||
309 | if ($detect_language >= 2) { | ||
310 | $language_codes = array('albanian' => 'sq','arabic' => 'ar','azeri' => 'az','bengali' => 'bn','bulgarian' => 'bg', | ||
311 | 'cebuano' => 'ceb', // ISO 639-2 | ||
312 | 'croatian' => 'hr','czech' => 'cs','danish' => 'da','dutch' => 'nl','english' => 'en','estonian' => 'et','farsi' => 'fa','finnish' => 'fi','french' => 'fr','german' => 'de','hausa' => 'ha', | ||
313 | 'hawaiian' => 'haw', // ISO 639-2 | ||
314 | 'hindi' => 'hi','hungarian' => 'hu','icelandic' => 'is','indonesian' => 'id','italian' => 'it','kazakh' => 'kk','kyrgyz' => 'ky','latin' => 'la','latvian' => 'lv','lithuanian' => 'lt','macedonian' => 'mk','mongolian' => 'mn','nepali' => 'ne','norwegian' => 'no','pashto' => 'ps', | ||
315 | 'pidgin' => 'cpe', // ISO 639-2 | ||
316 | 'polish' => 'pl','portuguese' => 'pt','romanian' => 'ro','russian' => 'ru','serbian' => 'sr','slovak' => 'sk','slovene' => 'sl','somali' => 'so','spanish' => 'es','swahili' => 'sw','swedish' => 'sv','tagalog' => 'tl','turkish' => 'tr','ukrainian' => 'uk','urdu' => 'ur','uzbek' => 'uz','vietnamese' => 'vi','welsh' => 'cy'); | ||
317 | } | ||
318 | $use_cld = extension_loaded('cld') && (version_compare(PHP_VERSION, '5.3.0') >= 0); | 295 | $use_cld = extension_loaded('cld') && (version_compare(PHP_VERSION, '5.3.0') >= 0); |
319 | 296 | ||
320 | ///////////////////////////////////// | 297 | ///////////////////////////////////// |
@@ -364,7 +341,7 @@ if ($options->cors) header('Access-Control-Allow-Origin: *'); | |||
364 | ////////////////////////////////// | 341 | ////////////////////////////////// |
365 | if ($options->caching) { | 342 | if ($options->caching) { |
366 | debug('Caching is enabled...'); | 343 | debug('Caching is enabled...'); |
367 | $cache_id = md5($max.$url.$valid_key.$links.$favour_feed_titles.$xss_filter.$exclude_on_fail.$format.$detect_language.(int)isset($_GET['pubsub'])); | 344 | $cache_id = md5($max.$url.(int)$valid_key.$links.(int)$favour_feed_titles.(int)$options->content.(int)$options->summary.(int)$xss_filter.(int)$exclude_on_fail.$format.$detect_language.(int)isset($_GET['pubsub'])); |
368 | $check_cache = true; | 345 | $check_cache = true; |
369 | if ($options->apc && $options->smart_cache) { | 346 | if ($options->apc && $options->smart_cache) { |
370 | apc_add("cache.$cache_id", 0, 10*60); | 347 | apc_add("cache.$cache_id", 0, 10*60); |
@@ -415,6 +392,7 @@ if (!$debug_mode) { | |||
415 | ////////////////////////////////// | 392 | ////////////////////////////////// |
416 | // Set up HTTP agent | 393 | // Set up HTTP agent |
417 | ////////////////////////////////// | 394 | ////////////////////////////////// |
395 | global $http; | ||
418 | $http = new HumbleHttpAgent(); | 396 | $http = new HumbleHttpAgent(); |
419 | $http->debug = $debug_mode; | 397 | $http->debug = $debug_mode; |
420 | $http->userAgentMap = $options->user_agents; | 398 | $http->userAgentMap = $options->user_agents; |
@@ -478,29 +456,6 @@ if ($html_only || !$result) { | |||
478 | $isDummyFeed = true; | 456 | $isDummyFeed = true; |
479 | unset($feed, $result); | 457 | unset($feed, $result); |
480 | // create single item dummy feed object | 458 | // create single item dummy feed object |
481 | class DummySingleItemFeed { | ||
482 | public $item; | ||
483 | function __construct($url) { $this->item = new DummySingleItem($url); } | ||
484 | public function get_title() { return ''; } | ||
485 | public function get_description() { return 'Content extracted from '.$this->item->url; } | ||
486 | public function get_link() { return $this->item->url; } | ||
487 | public function get_language() { return false; } | ||
488 | public function get_image_url() { return false; } | ||
489 | public function get_items($start=0, $max=1) { return array(0=>$this->item); } | ||
490 | } | ||
491 | class DummySingleItem { | ||
492 | public $url; | ||
493 | function __construct($url) { $this->url = $url; } | ||
494 | public function get_permalink() { return $this->url; } | ||
495 | public function get_title() { return null; } | ||
496 | public function get_date($format='') { return false; } | ||
497 | public function get_author($key=0) { return null; } | ||
498 | public function get_authors() { return null; } | ||
499 | public function get_description() { return ''; } | ||
500 | public function get_enclosure($key=0, $prefer=null) { return null; } | ||
501 | public function get_enclosures() { return null; } | ||
502 | public function get_categories() { return null; } | ||
503 | } | ||
504 | $feed = new DummySingleItemFeed($url); | 459 | $feed = new DummySingleItemFeed($url); |
505 | } | 460 | } |
506 | 461 | ||
@@ -524,7 +479,7 @@ if ($img_url = $feed->get_image_url()) { | |||
524 | //////////////////////////////////////////// | 479 | //////////////////////////////////////////// |
525 | // Loop through feed items | 480 | // Loop through feed items |
526 | //////////////////////////////////////////// | 481 | //////////////////////////////////////////// |
527 | $items = $feed->get_items(0, $max); | 482 | $items = $feed->get_items(0, $max); |
528 | // Request all feed items in parallel (if supported) | 483 | // Request all feed items in parallel (if supported) |
529 | $urls_sanitized = array(); | 484 | $urls_sanitized = array(); |
530 | $urls = array(); | 485 | $urls = array(); |
@@ -606,24 +561,43 @@ foreach ($items as $key => $item) { | |||
606 | $is_single_page = false; | 561 | $is_single_page = false; |
607 | if ($single_page_response = getSinglePage($item, $html, $effective_url)) { | 562 | if ($single_page_response = getSinglePage($item, $html, $effective_url)) { |
608 | $is_single_page = true; | 563 | $is_single_page = true; |
609 | $html = $single_page_response['body']; | ||
610 | // remove strange things | ||
611 | $html = str_replace('</[>', '', $html); | ||
612 | $html = convert_to_utf8($html, $single_page_response['headers']); | ||
613 | $effective_url = $single_page_response['effective_url']; | 564 | $effective_url = $single_page_response['effective_url']; |
614 | debug("Retrieved single-page view from $effective_url"); | 565 | // check if action defined for returned Content-Type |
566 | $mime_info = get_mime_action_info($single_page_response['headers']); | ||
567 | if (isset($mime_info['action'])) { | ||
568 | if ($mime_info['action'] == 'exclude') { | ||
569 | continue; // skip this feed item entry | ||
570 | } elseif ($mime_info['action'] == 'link') { | ||
571 | if ($mime_info['type'] == 'image') { | ||
572 | $html = "<a href=\"$effective_url\"><img src=\"$effective_url\" alt=\"{$mime_info['name']}\" /></a>"; | ||
573 | } else { | ||
574 | $html = "<a href=\"$effective_url\">Download {$mime_info['name']}</a>"; | ||
575 | } | ||
576 | $extracted_title = $mime_info['name']; | ||
577 | $do_content_extraction = false; | ||
578 | } | ||
579 | } | ||
580 | if ($do_content_extraction) { | ||
581 | $html = $single_page_response['body']; | ||
582 | // remove strange things | ||
583 | $html = str_replace('</[>', '', $html); | ||
584 | $html = convert_to_utf8($html, $single_page_response['headers']); | ||
585 | debug("Retrieved single-page view from $effective_url"); | ||
586 | } | ||
615 | unset($single_page_response); | 587 | unset($single_page_response); |
616 | } | 588 | } |
589 | } | ||
590 | if ($do_content_extraction) { | ||
617 | debug('--------'); | 591 | debug('--------'); |
618 | debug('Attempting to extract content'); | 592 | debug('Attempting to extract content'); |
619 | $extract_result = $extractor->process($html, $effective_url); | 593 | $extract_result = $extractor->process($html, $effective_url); |
620 | $readability = $extractor->readability; | 594 | $readability = $extractor->readability; |
621 | $content_block = ($extract_result) ? $extractor->getContent() : null; | 595 | $content_block = ($extract_result) ? $extractor->getContent() : null; |
622 | $extracted_title = ($extract_result) ? $extractor->getTitle() : ''; | 596 | $extracted_title = ($extract_result) ? $extractor->getTitle() : ''; |
623 | // Deal with multi-page articles | 597 | // Deal with multi-page articles |
624 | //die('Next: '.$extractor->getNextPageUrl()); | 598 | //die('Next: '.$extractor->getNextPageUrl()); |
625 | $is_multi_page = (!$is_single_page && $extract_result && $extractor->getNextPageUrl()); | 599 | $is_multi_page = (!$is_single_page && $extract_result && $extractor->getNextPageUrl()); |
626 | if ($options->multipage && $is_multi_page) { | 600 | if ($options->multipage && $is_multi_page && $options->content) { |
627 | debug('--------'); | 601 | debug('--------'); |
628 | debug('Attempting to process multi-page article'); | 602 | debug('Attempting to process multi-page article'); |
629 | $multi_page_urls = array(); | 603 | $multi_page_urls = array(); |
@@ -636,7 +610,7 @@ foreach ($items as $key => $item) { | |||
636 | // check it's not what we have already! | 610 | // check it's not what we have already! |
637 | if (!in_array($next_page_url, $multi_page_urls)) { | 611 | if (!in_array($next_page_url, $multi_page_urls)) { |
638 | // it's not, so let's attempt to fetch it | 612 | // it's not, so let's attempt to fetch it |
639 | $multi_page_urls[] = $next_page_url; | 613 | $multi_page_urls[] = $next_page_url; |
640 | $_prev_ref = $http->referer; | 614 | $_prev_ref = $http->referer; |
641 | if (($response = $http->get($next_page_url, true)) && $response['status_code'] < 300) { | 615 | if (($response = $http->get($next_page_url, true)) && $response['status_code'] < 300) { |
642 | // make sure mime type is not something with a different action associated | 616 | // make sure mime type is not something with a different action associated |
@@ -661,13 +635,15 @@ foreach ($items as $key => $item) { | |||
661 | // did we successfully deal with this multi-page article? | 635 | // did we successfully deal with this multi-page article? |
662 | if (empty($multi_page_content)) { | 636 | if (empty($multi_page_content)) { |
663 | debug('Failed to extract all parts of multi-page article, so not going to include them'); | 637 | debug('Failed to extract all parts of multi-page article, so not going to include them'); |
664 | $multi_page_content[] = $readability->dom->createElement('p')->innerHTML = '<em>This article appears to continue on subsequent pages which we could not extract</em>'; | 638 | $_page = $readability->dom->createElement('p'); |
639 | $_page->innerHTML = '<em>This article appears to continue on subsequent pages which we could not extract</em>'; | ||
640 | $multi_page_content[] = $_page; | ||
665 | } | 641 | } |
666 | foreach ($multi_page_content as $_page) { | 642 | foreach ($multi_page_content as $_page) { |
667 | $_page = $content_block->ownerDocument->importNode($_page, true); | 643 | $_page = $content_block->ownerDocument->importNode($_page, true); |
668 | $content_block->appendChild($_page); | 644 | $content_block->appendChild($_page); |
669 | } | 645 | } |
670 | unset($multi_page_urls, $multi_page_content, $page_mime_info, $next_page_url); | 646 | unset($multi_page_urls, $multi_page_content, $page_mime_info, $next_page_url, $_page); |
671 | } | 647 | } |
672 | } | 648 | } |
673 | // use extracted title for both feed and item title if we're using single-item dummy feed | 649 | // use extracted title for both feed and item title if we're using single-item dummy feed |
@@ -695,7 +671,11 @@ foreach ($items as $key => $item) { | |||
695 | $html .= $item->get_description(); | 671 | $html .= $item->get_description(); |
696 | } else { | 672 | } else { |
697 | $readability->clean($content_block, 'select'); | 673 | $readability->clean($content_block, 'select'); |
698 | if ($options->rewrite_relative_urls) makeAbsolute($effective_url, $content_block); | 674 | // get base URL |
675 | $base_url = get_base_url($readability->dom); | ||
676 | if (!$base_url) $base_url = $effective_url; | ||
677 | // rewrite URLs | ||
678 | if ($options->rewrite_relative_urls) makeAbsolute($base_url, $content_block); | ||
699 | // footnotes | 679 | // footnotes |
700 | if (($links == 'footnotes') && (strpos($effective_url, 'wikipedia.org') === false)) { | 680 | if (($links == 'footnotes') && (strpos($effective_url, 'wikipedia.org') === false)) { |
701 | $readability->addFootnotes($content_block); | 681 | $readability->addFootnotes($content_block); |
@@ -714,7 +694,7 @@ foreach ($items as $key => $item) { | |||
714 | } else { | 694 | } else { |
715 | $html = $content_block->ownerDocument->saveXML($content_block); // essentially outerHTML | 695 | $html = $content_block->ownerDocument->saveXML($content_block); // essentially outerHTML |
716 | } | 696 | } |
717 | unset($content_block); | 697 | //unset($content_block); |
718 | // post-processing cleanup | 698 | // post-processing cleanup |
719 | $html = preg_replace('!<p>[\s\h\v]*</p>!u', '', $html); | 699 | $html = preg_replace('!<p>[\s\h\v]*</p>!u', '', $html); |
720 | if ($links == 'remove') { | 700 | if ($links == 'remove') { |
@@ -727,130 +707,155 @@ foreach ($items as $key => $item) { | |||
727 | } | 707 | } |
728 | } | 708 | } |
729 | 709 | ||
730 | if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment | 710 | if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment |
731 | $newitem->addElement('guid', 'http://fivefilters.org/content-only/redirect.php?url='.urlencode($item->get_permalink()), array('isPermaLink'=>'false')); | 711 | $newitem->addElement('guid', 'http://fivefilters.org/content-only/redirect.php?url='.urlencode($item->get_permalink()), array('isPermaLink'=>'false')); |
712 | } else { | ||
713 | $newitem->addElement('guid', $item->get_permalink(), array('isPermaLink'=>'true')); | ||
714 | } | ||
715 | // filter xss? | ||
716 | if ($xss_filter) { | ||
717 | debug('Filtering HTML to remove XSS'); | ||
718 | $html = htmLawed::hl($html, array('safe'=>1, 'deny_attribute'=>'style', 'comment'=>1, 'cdata'=>1)); | ||
719 | } | ||
720 | |||
721 | // add content | ||
722 | if ($options->summary === true) { | ||
723 | // get summary | ||
724 | $summary = ''; | ||
725 | if (!$do_content_extraction) { | ||
726 | $summary = $html; | ||
732 | } else { | 727 | } else { |
733 | $newitem->addElement('guid', $item->get_permalink(), array('isPermaLink'=>'true')); | 728 | // Try to get first few paragraphs |
734 | } | 729 | if (isset($content_block) && ($content_block instanceof DOMElement)) { |
735 | // filter xss? | 730 | $_paras = $content_block->getElementsByTagName('p'); |
736 | if ($xss_filter) { | 731 | foreach ($_paras as $_para) { |
737 | debug('Filtering HTML to remove XSS'); | 732 | $summary .= preg_replace("/[\n\r\t ]+/", ' ', $_para->textContent).' '; |
738 | $html = htmLawed::hl($html, array('safe'=>1, 'deny_attribute'=>'style', 'comment'=>1, 'cdata'=>1)); | 733 | if (strlen($summary) > 200) break; |
739 | } | ||
740 | $newitem->setDescription($html); | ||
741 | |||
742 | // set date | ||
743 | if ((int)$item->get_date('U') > 0) { | ||
744 | $newitem->setDate((int)$item->get_date('U')); | ||
745 | } elseif ($extractor->getDate()) { | ||
746 | $newitem->setDate($extractor->getDate()); | ||
747 | } | ||
748 | |||
749 | // add authors | ||
750 | if ($authors = $item->get_authors()) { | ||
751 | foreach ($authors as $author) { | ||
752 | // for some feeds, SimplePie stores author's name as email, e.g. http://feeds.feedburner.com/nymag/intel | ||
753 | if ($author->get_name() !== null) { | ||
754 | $newitem->addElement('dc:creator', $author->get_name()); | ||
755 | } elseif ($author->get_email() !== null) { | ||
756 | $newitem->addElement('dc:creator', $author->get_email()); | ||
757 | } | 734 | } |
735 | } else { | ||
736 | $summary = $html; | ||
758 | } | 737 | } |
759 | } elseif ($authors = $extractor->getAuthors()) { | 738 | } |
760 | //TODO: make sure the list size is reasonable | 739 | unset($_paras, $_para); |
761 | foreach ($authors as $author) { | 740 | $summary = get_excerpt($summary); |
762 | // TODO: xpath often selects authors from other articles linked from the page. | 741 | $newitem->setDescription($summary); |
763 | // for now choose first item | 742 | if ($options->content) $newitem->setElement('content:encoded', $html); |
764 | $newitem->addElement('dc:creator', $author); | 743 | } else { |
765 | break; | 744 | if ($options->content) $newitem->setDescription($html); |
745 | } | ||
746 | |||
747 | // set date | ||
748 | if ((int)$item->get_date('U') > 0) { | ||
749 | $newitem->setDate((int)$item->get_date('U')); | ||
750 | } elseif ($extractor->getDate()) { | ||
751 | $newitem->setDate($extractor->getDate()); | ||
752 | } | ||
753 | |||
754 | // add authors | ||
755 | if ($authors = $item->get_authors()) { | ||
756 | foreach ($authors as $author) { | ||
757 | // for some feeds, SimplePie stores author's name as email, e.g. http://feeds.feedburner.com/nymag/intel | ||
758 | if ($author->get_name() !== null) { | ||
759 | $newitem->addElement('dc:creator', $author->get_name()); | ||
760 | } elseif ($author->get_email() !== null) { | ||
761 | $newitem->addElement('dc:creator', $author->get_email()); | ||
766 | } | 762 | } |
767 | } | 763 | } |
768 | 764 | } elseif ($authors = $extractor->getAuthors()) { | |
769 | // add language | 765 | //TODO: make sure the list size is reasonable |
770 | if ($detect_language) { | 766 | foreach ($authors as $author) { |
771 | $language = $extractor->getLanguage(); | 767 | // TODO: xpath often selects authors from other articles linked from the page. |
772 | if (!$language) $language = $feed->get_language(); | 768 | // for now choose first item |
773 | if (($detect_language == 3 || (!$language && $detect_language == 2)) && $text_sample) { | 769 | $newitem->addElement('dc:creator', $author); |
774 | try { | 770 | break; |
775 | if ($use_cld) { | 771 | } |
776 | // Use PHP-CLD extension | 772 | } |
777 | $php_cld = 'CLD\detect'; // in quotes to prevent PHP 5.2 parse error | 773 | |
778 | $res = $php_cld($text_sample); | 774 | // add language |
779 | if (is_array($res) && count($res) > 0) { | 775 | if ($detect_language) { |
780 | $language = $res[0]['code']; | 776 | $language = $extractor->getLanguage(); |
781 | } | 777 | if (!$language) $language = $feed->get_language(); |
782 | } else { | 778 | if (($detect_language == 3 || (!$language && $detect_language == 2)) && $text_sample) { |
783 | //die('what'); | 779 | try { |
784 | // Use PEAR's Text_LanguageDetect | 780 | if ($use_cld) { |
785 | if (!isset($l)) { | 781 | // Use PHP-CLD extension |
786 | $l = new Text_LanguageDetect('libraries/language-detect/lang.dat', 'libraries/language-detect/unicode_blocks.dat'); | 782 | $php_cld = 'CLD\detect'; // in quotes to prevent PHP 5.2 parse error |
787 | } | 783 | $res = $php_cld($text_sample); |
788 | $l_result = $l->detect($text_sample, 1); | 784 | if (is_array($res) && count($res) > 0) { |
789 | if (count($l_result) > 0) { | 785 | $language = $res[0]['code']; |
790 | $language = $language_codes[key($l_result)]; | 786 | } |
791 | } | 787 | } else { |
788 | //die('what'); | ||
789 | // Use PEAR's Text_LanguageDetect | ||
790 | if (!isset($l)) { | ||
791 | $l = new Text_LanguageDetect(); | ||
792 | $l->setNameMode(2); // return ISO 639-1 codes (e.g. "en") | ||
793 | } | ||
794 | $l_result = $l->detect($text_sample, 1); | ||
795 | if (count($l_result) > 0) { | ||
796 | $language = key($l_result); | ||
792 | } | 797 | } |
793 | } catch (Exception $e) { | ||
794 | //die('error: '.$e); | ||
795 | // do nothing | ||
796 | } | 798 | } |
797 | } | 799 | } catch (Exception $e) { |
798 | if ($language && (strlen($language) < 7)) { | 800 | //die('error: '.$e); |
799 | $newitem->addElement('dc:language', $language); | 801 | // do nothing |
800 | } | 802 | } |
801 | } | 803 | } |
802 | 804 | if ($language && (strlen($language) < 7)) { | |
803 | // add MIME type (if it appeared in our exclusions lists) | 805 | $newitem->addElement('dc:language', $language); |
804 | if (isset($mime_info['mime'])) $newitem->addElement('dc:format', $mime_info['mime']); | ||
805 | // add effective URL (URL after redirects) | ||
806 | if (isset($effective_url)) { | ||
807 | //TODO: ensure $effective_url is valid witout - sometimes it causes problems, e.g. | ||
808 | //http://www.siasat.pk/forum/showthread.php?108883-Pakistan-Chowk-by-Rana-Mubashir--25th-March-2012-Special-Program-from-Liari-(Karachi) | ||
809 | //temporary measure: use utf8_encode() | ||
810 | $newitem->addElement('dc:identifier', remove_url_cruft(utf8_encode($effective_url))); | ||
811 | } else { | ||
812 | $newitem->addElement('dc:identifier', remove_url_cruft($item->get_permalink())); | ||
813 | } | 806 | } |
814 | 807 | } | |
815 | // add categories | 808 | |
816 | if ($categories = $item->get_categories()) { | 809 | // add MIME type (if it appeared in our exclusions lists) |
817 | foreach ($categories as $category) { | 810 | if (isset($mime_info['mime'])) $newitem->addElement('dc:format', $mime_info['mime']); |
818 | if ($category->get_label() !== null) { | 811 | // add effective URL (URL after redirects) |
819 | $newitem->addElement('category', $category->get_label()); | 812 | if (isset($effective_url)) { |
820 | } | 813 | //TODO: ensure $effective_url is valid witout - sometimes it causes problems, e.g. |
814 | //http://www.siasat.pk/forum/showthread.php?108883-Pakistan-Chowk-by-Rana-Mubashir-�-25th-March-2012-Special-Program-from-Liari-(Karachi) | ||
815 | //temporary measure: use utf8_encode() | ||
816 | $newitem->addElement('dc:identifier', remove_url_cruft(utf8_encode($effective_url))); | ||
817 | } else { | ||
818 | $newitem->addElement('dc:identifier', remove_url_cruft($item->get_permalink())); | ||
819 | } | ||
820 | |||
821 | // add categories | ||
822 | if ($categories = $item->get_categories()) { | ||
823 | foreach ($categories as $category) { | ||
824 | if ($category->get_label() !== null) { | ||
825 | $newitem->addElement('category', $category->get_label()); | ||
821 | } | 826 | } |
822 | } | 827 | } |
823 | 828 | } | |
824 | // check for enclosures | 829 | |
825 | if ($options->keep_enclosures) { | 830 | // check for enclosures |
826 | if ($enclosures = $item->get_enclosures()) { | 831 | if ($options->keep_enclosures) { |
827 | foreach ($enclosures as $enclosure) { | 832 | if ($enclosures = $item->get_enclosures()) { |
828 | // thumbnails | 833 | foreach ($enclosures as $enclosure) { |
829 | foreach ((array)$enclosure->get_thumbnails() as $thumbnail) { | 834 | // thumbnails |
830 | $newitem->addElement('media:thumbnail', '', array('url'=>$thumbnail)); | 835 | foreach ((array)$enclosure->get_thumbnails() as $thumbnail) { |
831 | } | 836 | $newitem->addElement('media:thumbnail', '', array('url'=>$thumbnail)); |
832 | if (!$enclosure->get_link()) continue; | ||
833 | $enc = array(); | ||
834 | // Media RSS spec ($enc): http://search.yahoo.com/mrss | ||
835 | // SimplePie methods ($enclosure): http://simplepie.org/wiki/reference/start#methods4 | ||
836 | $enc['url'] = $enclosure->get_link(); | ||
837 | if ($enclosure->get_length()) $enc['fileSize'] = $enclosure->get_length(); | ||
838 | if ($enclosure->get_type()) $enc['type'] = $enclosure->get_type(); | ||
839 | if ($enclosure->get_medium()) $enc['medium'] = $enclosure->get_medium(); | ||
840 | if ($enclosure->get_expression()) $enc['expression'] = $enclosure->get_expression(); | ||
841 | if ($enclosure->get_bitrate()) $enc['bitrate'] = $enclosure->get_bitrate(); | ||
842 | if ($enclosure->get_framerate()) $enc['framerate'] = $enclosure->get_framerate(); | ||
843 | if ($enclosure->get_sampling_rate()) $enc['samplingrate'] = $enclosure->get_sampling_rate(); | ||
844 | if ($enclosure->get_channels()) $enc['channels'] = $enclosure->get_channels(); | ||
845 | if ($enclosure->get_duration()) $enc['duration'] = $enclosure->get_duration(); | ||
846 | if ($enclosure->get_height()) $enc['height'] = $enclosure->get_height(); | ||
847 | if ($enclosure->get_width()) $enc['width'] = $enclosure->get_width(); | ||
848 | if ($enclosure->get_language()) $enc['lang'] = $enclosure->get_language(); | ||
849 | $newitem->addElement('media:content', '', $enc); | ||
850 | } | 837 | } |
838 | if (!$enclosure->get_link()) continue; | ||
839 | $enc = array(); | ||
840 | // Media RSS spec ($enc): http://search.yahoo.com/mrss | ||
841 | // SimplePie methods ($enclosure): http://simplepie.org/wiki/reference/start#methods4 | ||
842 | $enc['url'] = $enclosure->get_link(); | ||
843 | if ($enclosure->get_length()) $enc['fileSize'] = $enclosure->get_length(); | ||
844 | if ($enclosure->get_type()) $enc['type'] = $enclosure->get_type(); | ||
845 | if ($enclosure->get_medium()) $enc['medium'] = $enclosure->get_medium(); | ||
846 | if ($enclosure->get_expression()) $enc['expression'] = $enclosure->get_expression(); | ||
847 | if ($enclosure->get_bitrate()) $enc['bitrate'] = $enclosure->get_bitrate(); | ||
848 | if ($enclosure->get_framerate()) $enc['framerate'] = $enclosure->get_framerate(); | ||
849 | if ($enclosure->get_sampling_rate()) $enc['samplingrate'] = $enclosure->get_sampling_rate(); | ||
850 | if ($enclosure->get_channels()) $enc['channels'] = $enclosure->get_channels(); | ||
851 | if ($enclosure->get_duration()) $enc['duration'] = $enclosure->get_duration(); | ||
852 | if ($enclosure->get_height()) $enc['height'] = $enclosure->get_height(); | ||
853 | if ($enclosure->get_width()) $enc['width'] = $enclosure->get_width(); | ||
854 | if ($enclosure->get_language()) $enc['lang'] = $enclosure->get_language(); | ||
855 | $newitem->addElement('media:content', '', $enc); | ||
851 | } | 856 | } |
852 | } | 857 | } |
853 | /* } */ | 858 | } |
854 | $output->addItem($newitem); | 859 | $output->addItem($newitem); |
855 | unset($html); | 860 | unset($html); |
856 | $item_count++; | 861 | $item_count++; |
@@ -887,7 +892,7 @@ if (!$debug_mode) { | |||
887 | } | 892 | } |
888 | if ($add_to_cache) { | 893 | if ($add_to_cache) { |
889 | ob_start(); | 894 | ob_start(); |
890 | $output->genarateFeed(); | 895 | $output->genarateFeed(false); |
891 | $output = ob_get_contents(); | 896 | $output = ob_get_contents(); |
892 | ob_end_clean(); | 897 | ob_end_clean(); |
893 | if ($html_only && $item_count == 0) { | 898 | if ($html_only && $item_count == 0) { |
@@ -898,299 +903,8 @@ if (!$debug_mode) { | |||
898 | } | 903 | } |
899 | echo $output; | 904 | echo $output; |
900 | } else { | 905 | } else { |
901 | $output->genarateFeed(); | 906 | $output->genarateFeed(false); |
902 | } | 907 | } |
903 | if ($callback) echo ');'; | 908 | if ($callback) echo ');'; |
904 | } | 909 | } |
905 | 910 | ||
906 | /////////////////////////////// | ||
907 | // HELPER FUNCTIONS | ||
908 | /////////////////////////////// | ||
909 | |||
910 | function url_allowed($url) { | ||
911 | global $options; | ||
912 | if (!empty($options->allowed_urls)) { | ||
913 | $allowed = false; | ||
914 | foreach ($options->allowed_urls as $allowurl) { | ||
915 | if (stristr($url, $allowurl) !== false) { | ||
916 | $allowed = true; | ||
917 | break; | ||
918 | } | ||
919 | } | ||
920 | if (!$allowed) return false; | ||
921 | } else { | ||
922 | foreach ($options->blocked_urls as $blockurl) { | ||
923 | if (stristr($url, $blockurl) !== false) { | ||
924 | return false; | ||
925 | } | ||
926 | } | ||
927 | } | ||
928 | return true; | ||
929 | } | ||
930 | |||
931 | ////////////////////////////////////////////// | ||
932 | // Convert $html to UTF8 | ||
933 | // (uses HTTP headers and HTML to find encoding) | ||
934 | // adapted from http://stackoverflow.com/questions/910793/php-detect-encoding-and-make-everything-utf-8 | ||
935 | ////////////////////////////////////////////// | ||
936 | function convert_to_utf8($html, $header=null) | ||
937 | { | ||
938 | $encoding = null; | ||
939 | if ($html || $header) { | ||
940 | if (is_array($header)) $header = implode("\n", $header); | ||
941 | if (!$header || !preg_match_all('/^Content-Type:\s+([^;]+)(?:;\s*charset=["\']?([^;"\'\n]*))?/im', $header, $match, PREG_SET_ORDER)) { | ||
942 | // error parsing the response | ||
943 | debug('Could not find Content-Type header in HTTP response'); | ||
944 | } else { | ||
945 | $match = end($match); // get last matched element (in case of redirects) | ||
946 | if (isset($match[2])) $encoding = trim($match[2], "\"' \r\n\0\x0B\t"); | ||
947 | } | ||
948 | // TODO: check to see if encoding is supported (can we convert it?) | ||
949 | // If it's not, result will be empty string. | ||
950 | // For now we'll check for invalid encoding types returned by some sites, e.g. 'none' | ||
951 | // Problem URL: http://facta.co.jp/blog/archives/20111026001026.html | ||
952 | if (!$encoding || $encoding == 'none') { | ||
953 | // search for encoding in HTML - only look at the first 50000 characters | ||
954 | // Why 50000? See, for example, http://www.lemonde.fr/festival-de-cannes/article/2012/05/23/deux-cretes-en-goguette-sur-la-croisette_1705732_766360.html | ||
955 | // TODO: improve this so it looks at smaller chunks first | ||
956 | $html_head = substr($html, 0, 50000); | ||
957 | if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $html_head, $match)) { | ||
958 | $encoding = trim($match[1], '"\''); | ||
959 | } elseif (preg_match('/<meta\s+http-equiv=["\']?Content-Type["\']? content=["\'][^;]+;\s*charset=["\']?([^;"\'>]+)/i', $html_head, $match)) { | ||
960 | $encoding = trim($match[1]); | ||
961 | } elseif (preg_match_all('/<meta\s+([^>]+)>/i', $html_head, $match)) { | ||
962 | foreach ($match[1] as $_test) { | ||
963 | if (preg_match('/charset=["\']?([^"\']+)/i', $_test, $_m)) { | ||
964 | $encoding = trim($_m[1]); | ||
965 | break; | ||
966 | } | ||
967 | } | ||
968 | } | ||
969 | } | ||
970 | if (isset($encoding)) $encoding = trim($encoding); | ||
971 | // trim is important here! | ||
972 | if (!$encoding || (strtolower($encoding) == 'iso-8859-1')) { | ||
973 | // replace MS Word smart qutoes | ||
974 | $trans = array(); | ||
975 | $trans[chr(130)] = '‚'; // Single Low-9 Quotation Mark | ||
976 | $trans[chr(131)] = 'ƒ'; // Latin Small Letter F With Hook | ||
977 | $trans[chr(132)] = '„'; // Double Low-9 Quotation Mark | ||
978 | $trans[chr(133)] = '…'; // Horizontal Ellipsis | ||
979 | $trans[chr(134)] = '†'; // Dagger | ||
980 | $trans[chr(135)] = '‡'; // Double Dagger | ||
981 | $trans[chr(136)] = 'ˆ'; // Modifier Letter Circumflex Accent | ||
982 | $trans[chr(137)] = '‰'; // Per Mille Sign | ||
983 | $trans[chr(138)] = 'Š'; // Latin Capital Letter S With Caron | ||
984 | $trans[chr(139)] = '‹'; // Single Left-Pointing Angle Quotation Mark | ||
985 | $trans[chr(140)] = 'Œ'; // Latin Capital Ligature OE | ||
986 | $trans[chr(145)] = '‘'; // Left Single Quotation Mark | ||
987 | $trans[chr(146)] = '’'; // Right Single Quotation Mark | ||
988 | $trans[chr(147)] = '“'; // Left Double Quotation Mark | ||
989 | $trans[chr(148)] = '”'; // Right Double Quotation Mark | ||
990 | $trans[chr(149)] = '•'; // Bullet | ||
991 | $trans[chr(150)] = '–'; // En Dash | ||
992 | $trans[chr(151)] = '—'; // Em Dash | ||
993 | $trans[chr(152)] = '˜'; // Small Tilde | ||
994 | $trans[chr(153)] = '™'; // Trade Mark Sign | ||
995 | $trans[chr(154)] = 'š'; // Latin Small Letter S With Caron | ||
996 | $trans[chr(155)] = '›'; // Single Right-Pointing Angle Quotation Mark | ||
997 | $trans[chr(156)] = 'œ'; // Latin Small Ligature OE | ||
998 | $trans[chr(159)] = 'Ÿ'; // Latin Capital Letter Y With Diaeresis | ||
999 | $html = strtr($html, $trans); | ||
1000 | } | ||
1001 | if (!$encoding) { | ||
1002 | debug('No character encoding found, so treating as UTF-8'); | ||
1003 | $encoding = 'utf-8'; | ||
1004 | } else { | ||
1005 | debug('Character encoding: '.$encoding); | ||
1006 | if (strtolower($encoding) != 'utf-8') { | ||
1007 | debug('Converting to UTF-8'); | ||
1008 | $html = SimplePie_Misc::change_encoding($html, $encoding, 'utf-8'); | ||
1009 | /* | ||
1010 | if (function_exists('iconv')) { | ||
1011 | // iconv appears to handle certain character encodings better than mb_convert_encoding | ||
1012 | $html = iconv($encoding, 'utf-8', $html); | ||
1013 | } else { | ||
1014 | $html = mb_convert_encoding($html, 'utf-8', $encoding); | ||
1015 | } | ||
1016 | */ | ||
1017 | } | ||
1018 | } | ||
1019 | } | ||
1020 | return $html; | ||
1021 | } | ||
1022 | |||
1023 | function makeAbsolute($base, $elem) { | ||
1024 | $base = new SimplePie_IRI($base); | ||
1025 | // remove '//' in URL path (used to prevent URLs from resolving properly) | ||
1026 | // TODO: check if this is still the case | ||
1027 | if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); | ||
1028 | foreach(array('a'=>'href', 'img'=>'src') as $tag => $attr) { | ||
1029 | $elems = $elem->getElementsByTagName($tag); | ||
1030 | for ($i = $elems->length-1; $i >= 0; $i--) { | ||
1031 | $e = $elems->item($i); | ||
1032 | //$e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e); | ||
1033 | makeAbsoluteAttr($base, $e, $attr); | ||
1034 | } | ||
1035 | if (strtolower($elem->tagName) == $tag) makeAbsoluteAttr($base, $elem, $attr); | ||
1036 | } | ||
1037 | } | ||
1038 | function makeAbsoluteAttr($base, $e, $attr) { | ||
1039 | if ($e->hasAttribute($attr)) { | ||
1040 | // Trim leading and trailing white space. I don't really like this but | ||
1041 | // unfortunately it does appear on some sites. e.g. <img src=" /path/to/image.jpg" /> | ||
1042 | $url = trim(str_replace('%20', ' ', $e->getAttribute($attr))); | ||
1043 | $url = str_replace(' ', '%20', $url); | ||
1044 | if (!preg_match('!https?://!i', $url)) { | ||
1045 | if ($absolute = SimplePie_IRI::absolutize($base, $url)) { | ||
1046 | $e->setAttribute($attr, $absolute); | ||
1047 | } | ||
1048 | } | ||
1049 | } | ||
1050 | } | ||
1051 | function makeAbsoluteStr($base, $url) { | ||
1052 | $base = new SimplePie_IRI($base); | ||
1053 | // remove '//' in URL path (causes URLs not to resolve properly) | ||
1054 | if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); | ||
1055 | if (preg_match('!^https?://!i', $url)) { | ||
1056 | // already absolute | ||
1057 | return $url; | ||
1058 | } else { | ||
1059 | if ($absolute = SimplePie_IRI::absolutize($base, $url)) { | ||
1060 | return $absolute; | ||
1061 | } | ||
1062 | return false; | ||
1063 | } | ||
1064 | } | ||
1065 | // returns single page response, or false if not found | ||
1066 | function getSinglePage($item, $html, $url) { | ||
1067 | global $http, $extractor; | ||
1068 | debug('Looking for site config files to see if single page link exists'); | ||
1069 | $site_config = $extractor->buildSiteConfig($url, $html); | ||
1070 | $splink = null; | ||
1071 | if (!empty($site_config->single_page_link)) { | ||
1072 | $splink = $site_config->single_page_link; | ||
1073 | } elseif (!empty($site_config->single_page_link_in_feed)) { | ||
1074 | // single page link xpath is targeted at feed | ||
1075 | $splink = $site_config->single_page_link_in_feed; | ||
1076 | // so let's replace HTML with feed item description | ||
1077 | $html = $item->get_description(); | ||
1078 | } | ||
1079 | if (isset($splink)) { | ||
1080 | // Build DOM tree from HTML | ||
1081 | $readability = new Readability($html, $url); | ||
1082 | $xpath = new DOMXPath($readability->dom); | ||
1083 | // Loop through single_page_link xpath expressions | ||
1084 | $single_page_url = null; | ||
1085 | foreach ($splink as $pattern) { | ||
1086 | $elems = @$xpath->evaluate($pattern, $readability->dom); | ||
1087 | if (is_string($elems)) { | ||
1088 | $single_page_url = trim($elems); | ||
1089 | break; | ||
1090 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | ||
1091 | foreach ($elems as $item) { | ||
1092 | if ($item instanceof DOMElement && $item->hasAttribute('href')) { | ||
1093 | $single_page_url = $item->getAttribute('href'); | ||
1094 | break 2; | ||
1095 | } elseif ($item instanceof DOMAttr && $item->value) { | ||
1096 | $single_page_url = $item->value; | ||
1097 | break 2; | ||
1098 | } | ||
1099 | } | ||
1100 | } | ||
1101 | } | ||
1102 | // If we've got URL, resolve against $url | ||
1103 | if (isset($single_page_url) && ($single_page_url = makeAbsoluteStr($url, $single_page_url))) { | ||
1104 | // check it's not what we have already! | ||
1105 | if ($single_page_url != $url) { | ||
1106 | // it's not, so let's try to fetch it... | ||
1107 | $_prev_ref = $http->referer; | ||
1108 | $http->referer = $single_page_url; | ||
1109 | if (($response = $http->get($single_page_url, true)) && $response['status_code'] < 300) { | ||
1110 | $http->referer = $_prev_ref; | ||
1111 | return $response; | ||
1112 | } | ||
1113 | $http->referer = $_prev_ref; | ||
1114 | } | ||
1115 | } | ||
1116 | } | ||
1117 | return false; | ||
1118 | } | ||
1119 | |||
1120 | // based on content-type http header, decide what to do | ||
1121 | // param: HTTP headers string | ||
1122 | // return: array with keys: 'mime', 'type', 'subtype', 'action', 'name' | ||
1123 | // e.g. array('mime'=>'image/jpeg', 'type'=>'image', 'subtype'=>'jpeg', 'action'=>'link', 'name'=>'Image') | ||
1124 | function get_mime_action_info($headers) { | ||
1125 | global $options; | ||
1126 | // check if action defined for returned Content-Type | ||
1127 | $info = array(); | ||
1128 | if (preg_match('!^Content-Type:\s*(([-\w]+)/([-\w\+]+))!im', $headers, $match)) { | ||
1129 | // look for full mime type (e.g. image/jpeg) or just type (e.g. image) | ||
1130 | // match[1] = full mime type, e.g. image/jpeg | ||
1131 | // match[2] = first part, e.g. image | ||
1132 | // match[3] = last part, e.g. jpeg | ||
1133 | $info['mime'] = strtolower(trim($match[1])); | ||
1134 | $info['type'] = strtolower(trim($match[2])); | ||
1135 | $info['subtype'] = strtolower(trim($match[3])); | ||
1136 | foreach (array($info['mime'], $info['type']) as $_mime) { | ||
1137 | if (isset($options->content_type_exc[$_mime])) { | ||
1138 | $info['action'] = $options->content_type_exc[$_mime]['action']; | ||
1139 | $info['name'] = $options->content_type_exc[$_mime]['name']; | ||
1140 | break; | ||
1141 | } | ||
1142 | } | ||
1143 | } | ||
1144 | return $info; | ||
1145 | } | ||
1146 | |||
1147 | function remove_url_cruft($url) { | ||
1148 | // remove google analytics for the time being | ||
1149 | // regex adapted from http://navitronic.co.uk/2010/12/removing-google-analytics-cruft-from-urls/ | ||
1150 | // https://gist.github.com/758177 | ||
1151 | return preg_replace('/(\?|\&)utm_[a-z]+=[^\&]+/', '', $url); | ||
1152 | } | ||
1153 | |||
1154 | function make_substitutions($string) { | ||
1155 | if ($string == '') return $string; | ||
1156 | global $item, $effective_url; | ||
1157 | $string = str_replace('{url}', htmlspecialchars($item->get_permalink()), $string); | ||
1158 | $string = str_replace('{effective-url}', htmlspecialchars($effective_url), $string); | ||
1159 | return $string; | ||
1160 | } | ||
1161 | |||
1162 | function get_cache() { | ||
1163 | global $options, $valid_key; | ||
1164 | static $cache = null; | ||
1165 | if ($cache === null) { | ||
1166 | $frontendOptions = array( | ||
1167 | 'lifetime' => 10*60, // cache lifetime of 10 minutes | ||
1168 | 'automatic_serialization' => false, | ||
1169 | 'write_control' => false, | ||
1170 | 'automatic_cleaning_factor' => $options->cache_cleanup, | ||
1171 | 'ignore_user_abort' => false | ||
1172 | ); | ||
1173 | $backendOptions = array( | ||
1174 | 'cache_dir' => ($valid_key) ? $options->cache_dir.'/rss-with-key/' : $options->cache_dir.'/rss/', // directory where to put the cache files | ||
1175 | 'file_locking' => false, | ||
1176 | 'read_control' => true, | ||
1177 | 'read_control_type' => 'strlen', | ||
1178 | 'hashed_directory_level' => $options->cache_directory_level, | ||
1179 | 'hashed_directory_perm' => 0777, | ||
1180 | 'cache_file_perm' => 0664, | ||
1181 | 'file_name_prefix' => 'ff' | ||
1182 | ); | ||
1183 | // getting a Zend_Cache_Core object | ||
1184 | $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); | ||
1185 | } | ||
1186 | return $cache; | ||
1187 | } | ||
1188 | |||
1189 | function debug($msg) { | ||
1190 | global $debug_mode; | ||
1191 | if ($debug_mode) { | ||
1192 | echo '* ',$msg,"\n"; | ||
1193 | ob_flush(); | ||
1194 | flush(); | ||
1195 | } | ||
1196 | } \ No newline at end of file | ||
diff --git a/inc/3rdparty/makefulltextfeedHelpers.php b/inc/3rdparty/makefulltextfeedHelpers.php new file mode 100755 index 00000000..ac872ab8 --- /dev/null +++ b/inc/3rdparty/makefulltextfeedHelpers.php | |||
@@ -0,0 +1,389 @@ | |||
1 | <?php | ||
2 | |||
3 | // Autoloading of classes allows us to include files only when they're | ||
4 | // needed. If we've got a cached copy, for example, only Zend_Cache is loaded. | ||
5 | function autoload($class_name) { | ||
6 | static $dir = null; | ||
7 | if ($dir === null) $dir = dirname(__FILE__).'/libraries/'; | ||
8 | static $mapping = array( | ||
9 | // Include FeedCreator for RSS/Atom creation | ||
10 | 'FeedWriter' => 'feedwriter/FeedWriter.php', | ||
11 | 'FeedItem' => 'feedwriter/FeedItem.php', | ||
12 | // Include ContentExtractor and Readability for identifying and extracting content from URLs | ||
13 | 'ContentExtractor' => 'content-extractor/ContentExtractor.php', | ||
14 | 'SiteConfig' => 'content-extractor/SiteConfig.php', | ||
15 | 'Readability' => 'readability/Readability.php', | ||
16 | // Include Humble HTTP Agent to allow parallel requests and response caching | ||
17 | 'HumbleHttpAgent' => 'humble-http-agent/HumbleHttpAgent.php', | ||
18 | 'SimplePie_HumbleHttpAgent' => 'humble-http-agent/SimplePie_HumbleHttpAgent.php', | ||
19 | 'CookieJar' => 'humble-http-agent/CookieJar.php', | ||
20 | // Include Zend Cache to improve performance (cache results) | ||
21 | 'Zend_Cache' => 'Zend/Cache.php', | ||
22 | // Language detect | ||
23 | 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php', | ||
24 | // HTML5 Lib | ||
25 | 'HTML5_Parser' => 'html5/Parser.php', | ||
26 | // htmLawed - used if XSS filter is enabled (xss_filter) | ||
27 | 'htmLawed' => 'htmLawed/htmLawed.php' | ||
28 | ); | ||
29 | if (isset($mapping[$class_name])) { | ||
30 | debug("** Loading class $class_name ({$mapping[$class_name]})"); | ||
31 | require $dir.$mapping[$class_name]; | ||
32 | return true; | ||
33 | } else { | ||
34 | return false; | ||
35 | } | ||
36 | } | ||
37 | spl_autoload_register('autoload'); | ||
38 | require dirname(__FILE__).'/libraries/simplepie/autoloader.php'; | ||
39 | |||
40 | |||
41 | class DummySingleItemFeed { | ||
42 | public $item; | ||
43 | function __construct($url) { $this->item = new DummySingleItem($url); } | ||
44 | public function get_title() { return ''; } | ||
45 | public function get_description() { return 'Content extracted from '.$this->item->url; } | ||
46 | public function get_link() { return $this->item->url; } | ||
47 | public function get_language() { return false; } | ||
48 | public function get_image_url() { return false; } | ||
49 | public function get_items($start=0, $max=1) { return array(0=>$this->item); } | ||
50 | } | ||
51 | class DummySingleItem { | ||
52 | public $url; | ||
53 | function __construct($url) { $this->url = $url; } | ||
54 | public function get_permalink() { return $this->url; } | ||
55 | public function get_title() { return null; } | ||
56 | public function get_date($format='') { return false; } | ||
57 | public function get_author($key=0) { return null; } | ||
58 | public function get_authors() { return null; } | ||
59 | public function get_description() { return ''; } | ||
60 | public function get_enclosure($key=0, $prefer=null) { return null; } | ||
61 | public function get_enclosures() { return null; } | ||
62 | public function get_categories() { return null; } | ||
63 | } | ||
64 | |||
65 | /////////////////////////////// | ||
66 | // HELPER FUNCTIONS | ||
67 | /////////////////////////////// | ||
68 | |||
69 | // Adapted from WordPress | ||
70 | // http://core.trac.wordpress.org/browser/tags/3.5.1/wp-includes/formatting.php#L2173 | ||
71 | function get_excerpt($text, $num_words=55, $more=null) { | ||
72 | if (null === $more) $more = '…'; | ||
73 | $text = strip_tags($text); | ||
74 | //TODO: Check if word count is based on single characters (East Asian characters) | ||
75 | /* | ||
76 | if (1==2) { | ||
77 | $text = trim(preg_replace("/[\n\r\t ]+/", ' ', $text), ' '); | ||
78 | preg_match_all('/./u', $text, $words_array); | ||
79 | $words_array = array_slice($words_array[0], 0, $num_words + 1); | ||
80 | $sep = ''; | ||
81 | } else { | ||
82 | $words_array = preg_split("/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY); | ||
83 | $sep = ' '; | ||
84 | } | ||
85 | */ | ||
86 | $words_array = preg_split("/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY); | ||
87 | $sep = ' '; | ||
88 | if (count($words_array) > $num_words) { | ||
89 | array_pop($words_array); | ||
90 | $text = implode($sep, $words_array); | ||
91 | $text = $text.$more; | ||
92 | } else { | ||
93 | $text = implode($sep, $words_array); | ||
94 | } | ||
95 | // trim whitespace at beginning or end of string | ||
96 | // See: http://stackoverflow.com/questions/4166896/trim-unicode-whitespace-in-php-5-2 | ||
97 | $text = preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text); | ||
98 | return $text; | ||
99 | } | ||
100 | |||
101 | function url_allowed($url) { | ||
102 | global $options; | ||
103 | if (!empty($options->allowed_urls)) { | ||
104 | $allowed = false; | ||
105 | foreach ($options->allowed_urls as $allowurl) { | ||
106 | if (stristr($url, $allowurl) !== false) { | ||
107 | $allowed = true; | ||
108 | break; | ||
109 | } | ||
110 | } | ||
111 | if (!$allowed) return false; | ||
112 | } else { | ||
113 | foreach ($options->blocked_urls as $blockurl) { | ||
114 | if (stristr($url, $blockurl) !== false) { | ||
115 | return false; | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | return true; | ||
120 | } | ||
121 | |||
122 | ////////////////////////////////////////////// | ||
123 | // Convert $html to UTF8 | ||
124 | // (uses HTTP headers and HTML to find encoding) | ||
125 | // adapted from http://stackoverflow.com/questions/910793/php-detect-encoding-and-make-everything-utf-8 | ||
126 | ////////////////////////////////////////////// | ||
127 | function convert_to_utf8($html, $header=null) | ||
128 | { | ||
129 | $encoding = null; | ||
130 | if ($html || $header) { | ||
131 | if (is_array($header)) $header = implode("\n", $header); | ||
132 | if (!$header || !preg_match_all('/^Content-Type:\s+([^;]+)(?:;\s*charset=["\']?([^;"\'\n]*))?/im', $header, $match, PREG_SET_ORDER)) { | ||
133 | // error parsing the response | ||
134 | debug('Could not find Content-Type header in HTTP response'); | ||
135 | } else { | ||
136 | $match = end($match); // get last matched element (in case of redirects) | ||
137 | if (isset($match[2])) $encoding = trim($match[2], "\"' \r\n\0\x0B\t"); | ||
138 | } | ||
139 | // TODO: check to see if encoding is supported (can we convert it?) | ||
140 | // If it's not, result will be empty string. | ||
141 | // For now we'll check for invalid encoding types returned by some sites, e.g. 'none' | ||
142 | // Problem URL: http://facta.co.jp/blog/archives/20111026001026.html | ||
143 | if (!$encoding || $encoding == 'none') { | ||
144 | // search for encoding in HTML - only look at the first 50000 characters | ||
145 | // Why 50000? See, for example, http://www.lemonde.fr/festival-de-cannes/article/2012/05/23/deux-cretes-en-goguette-sur-la-croisette_1705732_766360.html | ||
146 | // TODO: improve this so it looks at smaller chunks first | ||
147 | $html_head = substr($html, 0, 50000); | ||
148 | if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $html_head, $match)) { | ||
149 | $encoding = trim($match[1], '"\''); | ||
150 | } elseif (preg_match('/<meta\s+http-equiv=["\']?Content-Type["\']? content=["\'][^;]+;\s*charset=["\']?([^;"\'>]+)/i', $html_head, $match)) { | ||
151 | $encoding = trim($match[1]); | ||
152 | } elseif (preg_match_all('/<meta\s+([^>]+)>/i', $html_head, $match)) { | ||
153 | foreach ($match[1] as $_test) { | ||
154 | if (preg_match('/charset=["\']?([^"\']+)/i', $_test, $_m)) { | ||
155 | $encoding = trim($_m[1]); | ||
156 | break; | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | } | ||
161 | if (isset($encoding)) $encoding = trim($encoding); | ||
162 | // trim is important here! | ||
163 | if (!$encoding || (strtolower($encoding) == 'iso-8859-1')) { | ||
164 | // replace MS Word smart qutoes | ||
165 | $trans = array(); | ||
166 | $trans[chr(130)] = '‚'; // Single Low-9 Quotation Mark | ||
167 | $trans[chr(131)] = 'ƒ'; // Latin Small Letter F With Hook | ||
168 | $trans[chr(132)] = '„'; // Double Low-9 Quotation Mark | ||
169 | $trans[chr(133)] = '…'; // Horizontal Ellipsis | ||
170 | $trans[chr(134)] = '†'; // Dagger | ||
171 | $trans[chr(135)] = '‡'; // Double Dagger | ||
172 | $trans[chr(136)] = 'ˆ'; // Modifier Letter Circumflex Accent | ||
173 | $trans[chr(137)] = '‰'; // Per Mille Sign | ||
174 | $trans[chr(138)] = 'Š'; // Latin Capital Letter S With Caron | ||
175 | $trans[chr(139)] = '‹'; // Single Left-Pointing Angle Quotation Mark | ||
176 | $trans[chr(140)] = 'Œ'; // Latin Capital Ligature OE | ||
177 | $trans[chr(145)] = '‘'; // Left Single Quotation Mark | ||
178 | $trans[chr(146)] = '’'; // Right Single Quotation Mark | ||
179 | $trans[chr(147)] = '“'; // Left Double Quotation Mark | ||
180 | $trans[chr(148)] = '”'; // Right Double Quotation Mark | ||
181 | $trans[chr(149)] = '•'; // Bullet | ||
182 | $trans[chr(150)] = '–'; // En Dash | ||
183 | $trans[chr(151)] = '—'; // Em Dash | ||
184 | $trans[chr(152)] = '˜'; // Small Tilde | ||
185 | $trans[chr(153)] = '™'; // Trade Mark Sign | ||
186 | $trans[chr(154)] = 'š'; // Latin Small Letter S With Caron | ||
187 | $trans[chr(155)] = '›'; // Single Right-Pointing Angle Quotation Mark | ||
188 | $trans[chr(156)] = 'œ'; // Latin Small Ligature OE | ||
189 | $trans[chr(159)] = 'Ÿ'; // Latin Capital Letter Y With Diaeresis | ||
190 | $html = strtr($html, $trans); | ||
191 | } | ||
192 | if (!$encoding) { | ||
193 | debug('No character encoding found, so treating as UTF-8'); | ||
194 | $encoding = 'utf-8'; | ||
195 | } else { | ||
196 | debug('Character encoding: '.$encoding); | ||
197 | if (strtolower($encoding) != 'utf-8') { | ||
198 | debug('Converting to UTF-8'); | ||
199 | $html = SimplePie_Misc::change_encoding($html, $encoding, 'utf-8'); | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | return $html; | ||
204 | } | ||
205 | |||
206 | function makeAbsolute($base, $elem) { | ||
207 | $base = new SimplePie_IRI($base); | ||
208 | // remove '//' in URL path (used to prevent URLs from resolving properly) | ||
209 | // TODO: check if this is still the case | ||
210 | if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); | ||
211 | foreach(array('a'=>'href', 'img'=>'src') as $tag => $attr) { | ||
212 | $elems = $elem->getElementsByTagName($tag); | ||
213 | for ($i = $elems->length-1; $i >= 0; $i--) { | ||
214 | $e = $elems->item($i); | ||
215 | //$e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e); | ||
216 | makeAbsoluteAttr($base, $e, $attr); | ||
217 | } | ||
218 | if (strtolower($elem->tagName) == $tag) makeAbsoluteAttr($base, $elem, $attr); | ||
219 | } | ||
220 | } | ||
221 | function makeAbsoluteAttr($base, $e, $attr) { | ||
222 | if ($e->hasAttribute($attr)) { | ||
223 | // Trim leading and trailing white space. I don't really like this but | ||
224 | // unfortunately it does appear on some sites. e.g. <img src=" /path/to/image.jpg" /> | ||
225 | $url = trim(str_replace('%20', ' ', $e->getAttribute($attr))); | ||
226 | $url = str_replace(' ', '%20', $url); | ||
227 | if (!preg_match('!https?://!i', $url)) { | ||
228 | if ($absolute = SimplePie_IRI::absolutize($base, $url)) { | ||
229 | $e->setAttribute($attr, $absolute); | ||
230 | } | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | function makeAbsoluteStr($base, $url) { | ||
235 | $base = new SimplePie_IRI($base); | ||
236 | // remove '//' in URL path (causes URLs not to resolve properly) | ||
237 | if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path); | ||
238 | if (preg_match('!^https?://!i', $url)) { | ||
239 | // already absolute | ||
240 | return $url; | ||
241 | } else { | ||
242 | if ($absolute = SimplePie_IRI::absolutize($base, $url)) { | ||
243 | return $absolute; | ||
244 | } | ||
245 | return false; | ||
246 | } | ||
247 | } | ||
248 | // returns single page response, or false if not found | ||
249 | function getSinglePage($item, $html, $url) { | ||
250 | global $http, $extractor; | ||
251 | debug('Looking for site config files to see if single page link exists'); | ||
252 | $site_config = $extractor->buildSiteConfig($url, $html); | ||
253 | $splink = null; | ||
254 | if (!empty($site_config->single_page_link)) { | ||
255 | $splink = $site_config->single_page_link; | ||
256 | } elseif (!empty($site_config->single_page_link_in_feed)) { | ||
257 | // single page link xpath is targeted at feed | ||
258 | $splink = $site_config->single_page_link_in_feed; | ||
259 | // so let's replace HTML with feed item description | ||
260 | $html = $item->get_description(); | ||
261 | } | ||
262 | if (isset($splink)) { | ||
263 | // Build DOM tree from HTML | ||
264 | $readability = new Readability($html, $url); | ||
265 | $xpath = new DOMXPath($readability->dom); | ||
266 | // Loop through single_page_link xpath expressions | ||
267 | $single_page_url = null; | ||
268 | foreach ($splink as $pattern) { | ||
269 | $elems = @$xpath->evaluate($pattern, $readability->dom); | ||
270 | if (is_string($elems)) { | ||
271 | $single_page_url = trim($elems); | ||
272 | break; | ||
273 | } elseif ($elems instanceof DOMNodeList && $elems->length > 0) { | ||
274 | foreach ($elems as $item) { | ||
275 | if ($item instanceof DOMElement && $item->hasAttribute('href')) { | ||
276 | $single_page_url = $item->getAttribute('href'); | ||
277 | break 2; | ||
278 | } elseif ($item instanceof DOMAttr && $item->value) { | ||
279 | $single_page_url = $item->value; | ||
280 | break 2; | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | } | ||
285 | // If we've got URL, resolve against $url | ||
286 | if (isset($single_page_url) && ($single_page_url = makeAbsoluteStr($url, $single_page_url))) { | ||
287 | // check it's not what we have already! | ||
288 | if ($single_page_url != $url) { | ||
289 | // it's not, so let's try to fetch it... | ||
290 | $_prev_ref = $http->referer; | ||
291 | $http->referer = $single_page_url; | ||
292 | if (($response = $http->get($single_page_url, true)) && $response['status_code'] < 300) { | ||
293 | $http->referer = $_prev_ref; | ||
294 | return $response; | ||
295 | } | ||
296 | $http->referer = $_prev_ref; | ||
297 | } | ||
298 | } | ||
299 | } | ||
300 | return false; | ||
301 | } | ||
302 | |||
303 | // based on content-type http header, decide what to do | ||
304 | // param: HTTP headers string | ||
305 | // return: array with keys: 'mime', 'type', 'subtype', 'action', 'name' | ||
306 | // e.g. array('mime'=>'image/jpeg', 'type'=>'image', 'subtype'=>'jpeg', 'action'=>'link', 'name'=>'Image') | ||
307 | function get_mime_action_info($headers) { | ||
308 | global $options; | ||
309 | // check if action defined for returned Content-Type | ||
310 | $info = array(); | ||
311 | if (preg_match('!^Content-Type:\s*(([-\w]+)/([-\w\+]+))!im', $headers, $match)) { | ||
312 | // look for full mime type (e.g. image/jpeg) or just type (e.g. image) | ||
313 | // match[1] = full mime type, e.g. image/jpeg | ||
314 | // match[2] = first part, e.g. image | ||
315 | // match[3] = last part, e.g. jpeg | ||
316 | $info['mime'] = strtolower(trim($match[1])); | ||
317 | $info['type'] = strtolower(trim($match[2])); | ||
318 | $info['subtype'] = strtolower(trim($match[3])); | ||
319 | foreach (array($info['mime'], $info['type']) as $_mime) { | ||
320 | if (isset($options->content_type_exc[$_mime])) { | ||
321 | $info['action'] = $options->content_type_exc[$_mime]['action']; | ||
322 | $info['name'] = $options->content_type_exc[$_mime]['name']; | ||
323 | break; | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | return $info; | ||
328 | } | ||
329 | |||
330 | function remove_url_cruft($url) { | ||
331 | // remove google analytics for the time being | ||
332 | // regex adapted from http://navitronic.co.uk/2010/12/removing-google-analytics-cruft-from-urls/ | ||
333 | // https://gist.github.com/758177 | ||
334 | return preg_replace('/(\?|\&)utm_[a-z]+=[^\&]+/', '', $url); | ||
335 | } | ||
336 | |||
337 | function make_substitutions($string) { | ||
338 | if ($string == '') return $string; | ||
339 | global $item, $effective_url; | ||
340 | $string = str_replace('{url}', htmlspecialchars($item->get_permalink()), $string); | ||
341 | $string = str_replace('{effective-url}', htmlspecialchars($effective_url), $string); | ||
342 | return $string; | ||
343 | } | ||
344 | |||
345 | function get_cache() { | ||
346 | global $options, $valid_key; | ||
347 | static $cache = null; | ||
348 | if ($cache === null) { | ||
349 | $frontendOptions = array( | ||
350 | 'lifetime' => 10*60, // cache lifetime of 10 minutes | ||
351 | 'automatic_serialization' => false, | ||
352 | 'write_control' => false, | ||
353 | 'automatic_cleaning_factor' => $options->cache_cleanup, | ||
354 | 'ignore_user_abort' => false | ||
355 | ); | ||
356 | $backendOptions = array( | ||
357 | 'cache_dir' => ($valid_key) ? $options->cache_dir.'/rss-with-key/' : $options->cache_dir.'/rss/', // directory where to put the cache files | ||
358 | 'file_locking' => false, | ||
359 | 'read_control' => true, | ||
360 | 'read_control_type' => 'strlen', | ||
361 | 'hashed_directory_level' => $options->cache_directory_level, | ||
362 | 'hashed_directory_perm' => 0777, | ||
363 | 'cache_file_perm' => 0664, | ||
364 | 'file_name_prefix' => 'ff' | ||
365 | ); | ||
366 | // getting a Zend_Cache_Core object | ||
367 | $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); | ||
368 | } | ||
369 | return $cache; | ||
370 | } | ||
371 | |||
372 | function debug($msg) { | ||
373 | global $debug_mode; | ||
374 | if ($debug_mode) { | ||
375 | echo '* ',$msg,"\n"; | ||
376 | ob_flush(); | ||
377 | flush(); | ||
378 | } | ||
379 | } | ||
380 | |||
381 | function get_base_url($dom) { | ||
382 | $xpath = new DOMXPath($dom); | ||
383 | $base_url = @$xpath->evaluate('string(//head/base/@href)', $dom); | ||
384 | if ($base_url !== '') { | ||
385 | return $base_url; | ||
386 | } else { | ||
387 | return false; | ||
388 | } | ||
389 | } | ||
diff --git a/inc/3rdparty/simple_html_dom.php b/inc/3rdparty/simple_html_dom.php index 43b94e57..9b73b105 100644..100755 --- a/inc/3rdparty/simple_html_dom.php +++ b/inc/3rdparty/simple_html_dom.php | |||
@@ -34,7 +34,7 @@ | |||
34 | * @author S.C. Chen <me578022@gmail.com> | 34 | * @author S.C. Chen <me578022@gmail.com> |
35 | * @author John Schlick | 35 | * @author John Schlick |
36 | * @author Rus Carroll | 36 | * @author Rus Carroll |
37 | * @version 1.5 ($Rev: 202 $) | 37 | * @version 1.5 ($Rev: 210 $) |
38 | * @package PlaceLocalInclude | 38 | * @package PlaceLocalInclude |
39 | * @subpackage simple_html_dom | 39 | * @subpackage simple_html_dom |
40 | */ | 40 | */ |
@@ -269,7 +269,10 @@ class simple_html_dom_node | |||
269 | { | 269 | { |
270 | return $this->children; | 270 | return $this->children; |
271 | } | 271 | } |
272 | if (isset($this->children[$idx])) return $this->children[$idx]; | 272 | if (isset($this->children[$idx])) |
273 | { | ||
274 | return $this->children[$idx]; | ||
275 | } | ||
273 | return null; | 276 | return null; |
274 | } | 277 | } |
275 | 278 | ||
@@ -330,14 +333,14 @@ class simple_html_dom_node | |||
330 | function find_ancestor_tag($tag) | 333 | function find_ancestor_tag($tag) |
331 | { | 334 | { |
332 | global $debug_object; | 335 | global $debug_object; |
333 | if (is_object($debug_object)) { $debug_object->debugLogEntry(1); } | 336 | if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } |
334 | 337 | ||
335 | // Start by including ourselves in the comparison. | 338 | // Start by including ourselves in the comparison. |
336 | $returnDom = $this; | 339 | $returnDom = $this; |
337 | 340 | ||
338 | while (!is_null($returnDom)) | 341 | while (!is_null($returnDom)) |
339 | { | 342 | { |
340 | if (is_object($debug_object)) { $debug_object->debugLog(2, "Current tag is: " . $returnDom->tag); } | 343 | if (is_object($debug_object)) { $debug_object->debug_log(2, "Current tag is: " . $returnDom->tag); } |
341 | 344 | ||
342 | if ($returnDom->tag == $tag) | 345 | if ($returnDom->tag == $tag) |
343 | { | 346 | { |
@@ -374,7 +377,7 @@ class simple_html_dom_node | |||
374 | $text = " with text: " . $this->text; | 377 | $text = " with text: " . $this->text; |
375 | } | 378 | } |
376 | } | 379 | } |
377 | $debug_object->debugLog(1, 'Innertext of tag: ' . $this->tag . $text); | 380 | $debug_object->debug_log(1, 'Innertext of tag: ' . $this->tag . $text); |
378 | } | 381 | } |
379 | 382 | ||
380 | if ($this->tag==='root') return $this->innertext(); | 383 | if ($this->tag==='root') return $this->innertext(); |
@@ -532,7 +535,9 @@ class simple_html_dom_node | |||
532 | foreach ($head as $k=>$v) | 535 | foreach ($head as $k=>$v) |
533 | { | 536 | { |
534 | if (!isset($found_keys[$k])) | 537 | if (!isset($found_keys[$k])) |
538 | { | ||
535 | $found_keys[$k] = 1; | 539 | $found_keys[$k] = 1; |
540 | } | ||
536 | } | 541 | } |
537 | } | 542 | } |
538 | 543 | ||
@@ -554,7 +559,7 @@ class simple_html_dom_node | |||
554 | protected function seek($selector, &$ret, $lowercase=false) | 559 | protected function seek($selector, &$ret, $lowercase=false) |
555 | { | 560 | { |
556 | global $debug_object; | 561 | global $debug_object; |
557 | if (is_object($debug_object)) { $debug_object->debugLogEntry(1); } | 562 | if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } |
558 | 563 | ||
559 | list($tag, $key, $val, $exp, $no_key) = $selector; | 564 | list($tag, $key, $val, $exp, $no_key) = $selector; |
560 | 565 | ||
@@ -615,7 +620,7 @@ class simple_html_dom_node | |||
615 | // this is a normal search, we want the value of that attribute of the tag. | 620 | // this is a normal search, we want the value of that attribute of the tag. |
616 | $nodeKeyValue = $node->attr[$key]; | 621 | $nodeKeyValue = $node->attr[$key]; |
617 | } | 622 | } |
618 | if (is_object($debug_object)) {$debug_object->debugLog(2, "testing node: " . $node->tag . " for attribute: " . $key . $exp . $val . " where nodes value is: " . $nodeKeyValue);} | 623 | if (is_object($debug_object)) {$debug_object->debug_log(2, "testing node: " . $node->tag . " for attribute: " . $key . $exp . $val . " where nodes value is: " . $nodeKeyValue);} |
619 | 624 | ||
620 | //PaperG - If lowercase is set, do a case insensitive test of the value of the selector. | 625 | //PaperG - If lowercase is set, do a case insensitive test of the value of the selector. |
621 | if ($lowercase) { | 626 | if ($lowercase) { |
@@ -623,7 +628,7 @@ class simple_html_dom_node | |||
623 | } else { | 628 | } else { |
624 | $check = $this->match($exp, $val, $nodeKeyValue); | 629 | $check = $this->match($exp, $val, $nodeKeyValue); |
625 | } | 630 | } |
626 | if (is_object($debug_object)) {$debug_object->debugLog(2, "after match: " . ($check ? "true" : "false"));} | 631 | if (is_object($debug_object)) {$debug_object->debug_log(2, "after match: " . ($check ? "true" : "false"));} |
627 | 632 | ||
628 | // handle multiple class | 633 | // handle multiple class |
629 | if (!$check && strcasecmp($key, 'class')===0) { | 634 | if (!$check && strcasecmp($key, 'class')===0) { |
@@ -645,12 +650,12 @@ class simple_html_dom_node | |||
645 | unset($node); | 650 | unset($node); |
646 | } | 651 | } |
647 | // It's passed by reference so this is actually what this function returns. | 652 | // It's passed by reference so this is actually what this function returns. |
648 | if (is_object($debug_object)) {$debug_object->debugLog(1, "EXIT - ret: ", $ret);} | 653 | if (is_object($debug_object)) {$debug_object->debug_log(1, "EXIT - ret: ", $ret);} |
649 | } | 654 | } |
650 | 655 | ||
651 | protected function match($exp, $pattern, $value) { | 656 | protected function match($exp, $pattern, $value) { |
652 | global $debug_object; | 657 | global $debug_object; |
653 | if (is_object($debug_object)) {$debug_object->debugLogEntry(1);} | 658 | if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} |
654 | 659 | ||
655 | switch ($exp) { | 660 | switch ($exp) { |
656 | case '=': | 661 | case '=': |
@@ -672,7 +677,7 @@ class simple_html_dom_node | |||
672 | 677 | ||
673 | protected function parse_selector($selector_string) { | 678 | protected function parse_selector($selector_string) { |
674 | global $debug_object; | 679 | global $debug_object; |
675 | if (is_object($debug_object)) {$debug_object->debugLogEntry(1);} | 680 | if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} |
676 | 681 | ||
677 | // pattern of CSS selectors, modified from mootools | 682 | // pattern of CSS selectors, modified from mootools |
678 | // Paperg: Add the colon to the attrbute, so that it properly finds <tag attr:ibute="something" > like google does. | 683 | // Paperg: Add the colon to the attrbute, so that it properly finds <tag attr:ibute="something" > like google does. |
@@ -683,7 +688,7 @@ class simple_html_dom_node | |||
683 | // $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; | 688 | // $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; |
684 | $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; | 689 | $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; |
685 | preg_match_all($pattern, trim($selector_string).' ', $matches, PREG_SET_ORDER); | 690 | preg_match_all($pattern, trim($selector_string).' ', $matches, PREG_SET_ORDER); |
686 | if (is_object($debug_object)) {$debug_object->debugLog(2, "Matches Array: ", $matches);} | 691 | if (is_object($debug_object)) {$debug_object->debug_log(2, "Matches Array: ", $matches);} |
687 | 692 | ||
688 | $selectors = array(); | 693 | $selectors = array(); |
689 | $result = array(); | 694 | $result = array(); |
@@ -718,12 +723,14 @@ class simple_html_dom_node | |||
718 | return $selectors; | 723 | return $selectors; |
719 | } | 724 | } |
720 | 725 | ||
721 | function __get($name) { | 726 | function __get($name) |
727 | { | ||
722 | if (isset($this->attr[$name])) | 728 | if (isset($this->attr[$name])) |
723 | { | 729 | { |
724 | return $this->convert_text($this->attr[$name]); | 730 | return $this->convert_text($this->attr[$name]); |
725 | } | 731 | } |
726 | switch ($name) { | 732 | switch ($name) |
733 | { | ||
727 | case 'outertext': return $this->outertext(); | 734 | case 'outertext': return $this->outertext(); |
728 | case 'innertext': return $this->innertext(); | 735 | case 'innertext': return $this->innertext(); |
729 | case 'plaintext': return $this->text(); | 736 | case 'plaintext': return $this->text(); |
@@ -732,22 +739,30 @@ class simple_html_dom_node | |||
732 | } | 739 | } |
733 | } | 740 | } |
734 | 741 | ||
735 | function __set($name, $value) { | 742 | function __set($name, $value) |
736 | switch ($name) { | 743 | { |
744 | global $debug_object; | ||
745 | if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} | ||
746 | |||
747 | switch ($name) | ||
748 | { | ||
737 | case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value; | 749 | case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value; |
738 | case 'innertext': | 750 | case 'innertext': |
739 | if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value; | 751 | if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value; |
740 | return $this->_[HDOM_INFO_INNER] = $value; | 752 | return $this->_[HDOM_INFO_INNER] = $value; |
741 | } | 753 | } |
742 | if (!isset($this->attr[$name])) { | 754 | if (!isset($this->attr[$name])) |
755 | { | ||
743 | $this->_[HDOM_INFO_SPACE][] = array(' ', '', ''); | 756 | $this->_[HDOM_INFO_SPACE][] = array(' ', '', ''); |
744 | $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; | 757 | $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; |
745 | } | 758 | } |
746 | $this->attr[$name] = $value; | 759 | $this->attr[$name] = $value; |
747 | } | 760 | } |
748 | 761 | ||
749 | function __isset($name) { | 762 | function __isset($name) |
750 | switch ($name) { | 763 | { |
764 | switch ($name) | ||
765 | { | ||
751 | case 'outertext': return true; | 766 | case 'outertext': return true; |
752 | case 'innertext': return true; | 767 | case 'innertext': return true; |
753 | case 'plaintext': return true; | 768 | case 'plaintext': return true; |
@@ -765,7 +780,7 @@ class simple_html_dom_node | |||
765 | function convert_text($text) | 780 | function convert_text($text) |
766 | { | 781 | { |
767 | global $debug_object; | 782 | global $debug_object; |
768 | if (is_object($debug_object)) {$debug_object->debugLogEntry(1);} | 783 | if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} |
769 | 784 | ||
770 | $converted_text = $text; | 785 | $converted_text = $text; |
771 | 786 | ||
@@ -777,7 +792,7 @@ class simple_html_dom_node | |||
777 | $sourceCharset = strtoupper($this->dom->_charset); | 792 | $sourceCharset = strtoupper($this->dom->_charset); |
778 | $targetCharset = strtoupper($this->dom->_target_charset); | 793 | $targetCharset = strtoupper($this->dom->_target_charset); |
779 | } | 794 | } |
780 | if (is_object($debug_object)) {$debug_object->debugLog(3, "source charset: " . $sourceCharset . " target charaset: " . $targetCharset);} | 795 | if (is_object($debug_object)) {$debug_object->debug_log(3, "source charset: " . $sourceCharset . " target charaset: " . $targetCharset);} |
781 | 796 | ||
782 | if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset, $targetCharset) != 0)) | 797 | if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset, $targetCharset) != 0)) |
783 | { | 798 | { |
@@ -1045,10 +1060,10 @@ class simple_html_dom | |||
1045 | 1060 | ||
1046 | // prepare | 1061 | // prepare |
1047 | $this->prepare($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText); | 1062 | $this->prepare($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText); |
1048 | // strip out comments | ||
1049 | $this->remove_noise("'<!--(.*?)-->'is"); | ||
1050 | // strip out cdata | 1063 | // strip out cdata |
1051 | $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true); | 1064 | $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true); |
1065 | // strip out comments | ||
1066 | $this->remove_noise("'<!--(.*?)-->'is"); | ||
1052 | // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037 | 1067 | // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037 |
1053 | // Script tags removal now preceeds style tag removal. | 1068 | // Script tags removal now preceeds style tag removal. |
1054 | // strip out <script> tags | 1069 | // strip out <script> tags |
@@ -1078,10 +1093,15 @@ class simple_html_dom | |||
1078 | // load html from file | 1093 | // load html from file |
1079 | function load_file() | 1094 | function load_file() |
1080 | { | 1095 | { |
1096 | //external error: NOT related to dom loading | ||
1097 | $extError=error_get_last(); | ||
1098 | |||
1081 | $args = func_get_args(); | 1099 | $args = func_get_args(); |
1082 | $this->load(call_user_func_array('file_get_contents', $args), true); | 1100 | $this->load(call_user_func_array('file_get_contents', $args), true); |
1101 | |||
1083 | // Throw an error if we can't properly load the dom. | 1102 | // Throw an error if we can't properly load the dom. |
1084 | if (($error=error_get_last())!==null) { | 1103 | $error=error_get_last(); |
1104 | if ($error!==$extError) { | ||
1085 | $this->clear(); | 1105 | $this->clear(); |
1086 | return false; | 1106 | return false; |
1087 | } | 1107 | } |
@@ -1198,22 +1218,22 @@ class simple_html_dom | |||
1198 | if ($success) | 1218 | if ($success) |
1199 | { | 1219 | { |
1200 | $charset = $matches[1]; | 1220 | $charset = $matches[1]; |
1201 | if (is_object($debug_object)) {$debug_object->debugLog(2, 'header content-type found charset of: ' . $charset);} | 1221 | if (is_object($debug_object)) {$debug_object->debug_log(2, 'header content-type found charset of: ' . $charset);} |
1202 | } | 1222 | } |
1203 | 1223 | ||
1204 | } | 1224 | } |
1205 | 1225 | ||
1206 | if (empty($charset)) | 1226 | if (empty($charset)) |
1207 | { | 1227 | { |
1208 | $el = $this->root->find('meta[http-equiv=Content-Type]',0); | 1228 | $el = $this->root->find('meta[http-equiv=Content-Type]',0, true); |
1209 | if (!empty($el)) | 1229 | if (!empty($el)) |
1210 | { | 1230 | { |
1211 | $fullvalue = $el->content; | 1231 | $fullvalue = $el->content; |
1212 | if (is_object($debug_object)) {$debug_object->debugLog(2, 'meta content-type tag found' . $fullvalue);} | 1232 | if (is_object($debug_object)) {$debug_object->debug_log(2, 'meta content-type tag found' . $fullvalue);} |
1213 | 1233 | ||
1214 | if (!empty($fullvalue)) | 1234 | if (!empty($fullvalue)) |
1215 | { | 1235 | { |
1216 | $success = preg_match('/charset=(.+)/', $fullvalue, $matches); | 1236 | $success = preg_match('/charset=(.+)/i', $fullvalue, $matches); |
1217 | if ($success) | 1237 | if ($success) |
1218 | { | 1238 | { |
1219 | $charset = $matches[1]; | 1239 | $charset = $matches[1]; |
@@ -1221,7 +1241,7 @@ class simple_html_dom | |||
1221 | else | 1241 | else |
1222 | { | 1242 | { |
1223 | // If there is a meta tag, and they don't specify the character set, research says that it's typically ISO-8859-1 | 1243 | // If there is a meta tag, and they don't specify the character set, research says that it's typically ISO-8859-1 |
1224 | if (is_object($debug_object)) {$debug_object->debugLog(2, 'meta content-type tag couldn\'t be parsed. using iso-8859 default.');} | 1244 | if (is_object($debug_object)) {$debug_object->debug_log(2, 'meta content-type tag couldn\'t be parsed. using iso-8859 default.');} |
1225 | $charset = 'ISO-8859-1'; | 1245 | $charset = 'ISO-8859-1'; |
1226 | } | 1246 | } |
1227 | } | 1247 | } |
@@ -1231,14 +1251,19 @@ class simple_html_dom | |||
1231 | // If we couldn't find a charset above, then lets try to detect one based on the text we got... | 1251 | // If we couldn't find a charset above, then lets try to detect one based on the text we got... |
1232 | if (empty($charset)) | 1252 | if (empty($charset)) |
1233 | { | 1253 | { |
1234 | // Have php try to detect the encoding from the text given to us. | 1254 | // Use this in case mb_detect_charset isn't installed/loaded on this machine. |
1235 | $charset = mb_detect_encoding($this->root->plaintext . "ascii", $encoding_list = array( "UTF-8", "CP1252" ) ); | 1255 | $charset = false; |
1236 | if (is_object($debug_object)) {$debug_object->debugLog(2, 'mb_detect found: ' . $charset);} | 1256 | if (function_exists('mb_detect_encoding')) |
1257 | { | ||
1258 | // Have php try to detect the encoding from the text given to us. | ||
1259 | $charset = mb_detect_encoding($this->root->plaintext . "ascii", $encoding_list = array( "UTF-8", "CP1252" ) ); | ||
1260 | if (is_object($debug_object)) {$debug_object->debug_log(2, 'mb_detect found: ' . $charset);} | ||
1261 | } | ||
1237 | 1262 | ||
1238 | // and if this doesn't work... then we need to just wrongheadedly assume it's UTF-8 so that we can move on - cause this will usually give us most of what we need... | 1263 | // and if this doesn't work... then we need to just wrongheadedly assume it's UTF-8 so that we can move on - cause this will usually give us most of what we need... |
1239 | if ($charset === false) | 1264 | if ($charset === false) |
1240 | { | 1265 | { |
1241 | if (is_object($debug_object)) {$debug_object->debugLog(2, 'since mb_detect failed - using default of utf-8');} | 1266 | if (is_object($debug_object)) {$debug_object->debug_log(2, 'since mb_detect failed - using default of utf-8');} |
1242 | $charset = 'UTF-8'; | 1267 | $charset = 'UTF-8'; |
1243 | } | 1268 | } |
1244 | } | 1269 | } |
@@ -1246,11 +1271,11 @@ class simple_html_dom | |||
1246 | // Since CP1252 is a superset, if we get one of it's subsets, we want it instead. | 1271 | // Since CP1252 is a superset, if we get one of it's subsets, we want it instead. |
1247 | if ((strtolower($charset) == strtolower('ISO-8859-1')) || (strtolower($charset) == strtolower('Latin1')) || (strtolower($charset) == strtolower('Latin-1'))) | 1272 | if ((strtolower($charset) == strtolower('ISO-8859-1')) || (strtolower($charset) == strtolower('Latin1')) || (strtolower($charset) == strtolower('Latin-1'))) |
1248 | { | 1273 | { |
1249 | if (is_object($debug_object)) {$debug_object->debugLog(2, 'replacing ' . $charset . ' with CP1252 as its a superset');} | 1274 | if (is_object($debug_object)) {$debug_object->debug_log(2, 'replacing ' . $charset . ' with CP1252 as its a superset');} |
1250 | $charset = 'CP1252'; | 1275 | $charset = 'CP1252'; |
1251 | } | 1276 | } |
1252 | 1277 | ||
1253 | if (is_object($debug_object)) {$debug_object->debugLog(1, 'EXIT - ' . $charset);} | 1278 | if (is_object($debug_object)) {$debug_object->debug_log(1, 'EXIT - ' . $charset);} |
1254 | 1279 | ||
1255 | return $this->_charset = $charset; | 1280 | return $this->_charset = $charset; |
1256 | } | 1281 | } |
@@ -1616,14 +1641,14 @@ class simple_html_dom | |||
1616 | protected function remove_noise($pattern, $remove_tag=false) | 1641 | protected function remove_noise($pattern, $remove_tag=false) |
1617 | { | 1642 | { |
1618 | global $debug_object; | 1643 | global $debug_object; |
1619 | if (is_object($debug_object)) { $debug_object->debugLogEntry(1); } | 1644 | if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } |
1620 | 1645 | ||
1621 | $count = preg_match_all($pattern, $this->doc, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE); | 1646 | $count = preg_match_all($pattern, $this->doc, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE); |
1622 | 1647 | ||
1623 | for ($i=$count-1; $i>-1; --$i) | 1648 | for ($i=$count-1; $i>-1; --$i) |
1624 | { | 1649 | { |
1625 | $key = '___noise___'.sprintf('% 5d', count($this->noise)+1000); | 1650 | $key = '___noise___'.sprintf('% 5d', count($this->noise)+1000); |
1626 | if (is_object($debug_object)) { $debug_object->debugLog(2, 'key is: ' . $key); } | 1651 | if (is_object($debug_object)) { $debug_object->debug_log(2, 'key is: ' . $key); } |
1627 | $idx = ($remove_tag) ? 0 : 1; | 1652 | $idx = ($remove_tag) ? 0 : 1; |
1628 | $this->noise[$key] = $matches[$i][$idx][0]; | 1653 | $this->noise[$key] = $matches[$i][$idx][0]; |
1629 | $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0])); | 1654 | $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0])); |
@@ -1641,7 +1666,7 @@ class simple_html_dom | |||
1641 | function restore_noise($text) | 1666 | function restore_noise($text) |
1642 | { | 1667 | { |
1643 | global $debug_object; | 1668 | global $debug_object; |
1644 | if (is_object($debug_object)) { $debug_object->debugLogEntry(1); } | 1669 | if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } |
1645 | 1670 | ||
1646 | while (($pos=strpos($text, '___noise___'))!==false) | 1671 | while (($pos=strpos($text, '___noise___'))!==false) |
1647 | { | 1672 | { |
@@ -1649,7 +1674,7 @@ class simple_html_dom | |||
1649 | if (strlen($text) > $pos+15) | 1674 | if (strlen($text) > $pos+15) |
1650 | { | 1675 | { |
1651 | $key = '___noise___'.$text[$pos+11].$text[$pos+12].$text[$pos+13].$text[$pos+14].$text[$pos+15]; | 1676 | $key = '___noise___'.$text[$pos+11].$text[$pos+12].$text[$pos+13].$text[$pos+14].$text[$pos+15]; |
1652 | if (is_object($debug_object)) { $debug_object->debugLog(2, 'located key of: ' . $key); } | 1677 | if (is_object($debug_object)) { $debug_object->debug_log(2, 'located key of: ' . $key); } |
1653 | 1678 | ||
1654 | if (isset($this->noise[$key])) | 1679 | if (isset($this->noise[$key])) |
1655 | { | 1680 | { |
@@ -1674,7 +1699,7 @@ class simple_html_dom | |||
1674 | function search_noise($text) | 1699 | function search_noise($text) |
1675 | { | 1700 | { |
1676 | global $debug_object; | 1701 | global $debug_object; |
1677 | if (is_object($debug_object)) { $debug_object->debugLogEntry(1); } | 1702 | if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } |
1678 | 1703 | ||
1679 | foreach($this->noise as $noiseElement) | 1704 | foreach($this->noise as $noiseElement) |
1680 | { | 1705 | { |
diff --git a/inc/3rdparty/site_config/custom/dailymotion.com.txt b/inc/3rdparty/site_config/custom/dailymotion.com.txt new file mode 100755 index 00000000..0cad808f --- /dev/null +++ b/inc/3rdparty/site_config/custom/dailymotion.com.txt | |||
@@ -0,0 +1,12 @@ | |||
1 | title: //title | ||
2 | body: //iframe | ||
3 | |||
4 | replace_string(<![CDATA[): _ | ||
5 | replace_string(]]>): _ | ||
6 | |||
7 | single_page_link: //link[@type='application/xml+oembed'] | ||
8 | |||
9 | prune: no | ||
10 | tidy: no | ||
11 | |||
12 | http://www.dailymotion.com/video/x1vk5oh_before-they-were-on-game-of-thrones_people | ||
diff --git a/inc/3rdparty/site_config/custom/index.php b/inc/3rdparty/site_config/custom/index.php new file mode 100644 index 00000000..a3d5f739 --- /dev/null +++ b/inc/3rdparty/site_config/custom/index.php | |||
@@ -0,0 +1,3 @@ | |||
1 | <?php | ||
2 | // this is here to prevent directory listing over the web | ||
3 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/site_config/custom/mobile.lemondeinformatique.fr.txt b/inc/3rdparty/site_config/custom/mobile.lemondeinformatique.fr.txt new file mode 100644 index 00000000..24aec5c3 --- /dev/null +++ b/inc/3rdparty/site_config/custom/mobile.lemondeinformatique.fr.txt | |||
@@ -0,0 +1,6 @@ | |||
1 | title: //h2 | ||
2 | body: div[@id='illustration'] | //p | ||
3 | prune: no | ||
4 | tidy: no | ||
5 | |||
6 | test_url: http://mobile.lemondeinformatique.fr/actualites/lire-les-datacenters-d-apple-google-et-facebook-eco-responsables-selon-greenpeace-le-monde-informatique-57122.html | ||
diff --git a/inc/3rdparty/site_config/custom/ted.com.txt b/inc/3rdparty/site_config/custom/ted.com.txt new file mode 100755 index 00000000..4940d2bc --- /dev/null +++ b/inc/3rdparty/site_config/custom/ted.com.txt | |||
@@ -0,0 +1,11 @@ | |||
1 | title: //title | ||
2 | body: //div[@class='talk-article__body talk-transcript__body'] | //div[@class='media__image media__image--thumb talk-link__image'] | ||
3 | |||
4 | strip_id_or_class: talk-transcript__para__time | ||
5 | |||
6 | single_page_link: //a[@id='hero-transcript-link'] | ||
7 | |||
8 | #prune: no | ||
9 | tidy: no | ||
10 | |||
11 | test_url: http://www.ted.com/talks/andrew_solomon_how_the_worst_moments_in_our_lives_make_us_who_we_are | ||
diff --git a/inc/3rdparty/site_config/index.php b/inc/3rdparty/site_config/index.php index a1b767fd..76ca8b3c 100644 --- a/inc/3rdparty/site_config/index.php +++ b/inc/3rdparty/site_config/index.php | |||
@@ -1,3 +1,2 @@ | |||
1 | <?php | 1 | <?php |
2 | // this is here to prevent directory listing over the web | 2 | // this is here to prevent directory listing over the web \ No newline at end of file |
3 | ?> \ No newline at end of file | ||
diff --git a/inc/3rdparty/site_config/standard/.about.com.txt b/inc/3rdparty/site_config/standard/.about.com.txt new file mode 100644 index 00000000..e1ebaee3 --- /dev/null +++ b/inc/3rdparty/site_config/standard/.about.com.txt | |||
@@ -0,0 +1,14 @@ | |||
1 | body: //div[@id='articlebody'] | ||
2 | title: //h1 | ||
3 | author: //p[@id='by']//a | ||
4 | |||
5 | next_page_link: //span[@class='next']/a | ||
6 | # Not the same as below! | ||
7 | |||
8 | prune: yes | ||
9 | tidy: no | ||
10 | |||
11 | # Annoying 'next' links plainly inside the article body | ||
12 | strip: //*[text()[contains(.,'Next: ')]] | ||
13 | |||
14 | test_url: http://psychology.about.com/od/theoriesofpersonality/ss/defensemech.htm | ||
diff --git a/inc/3rdparty/site_config/standard/moo.nac.uci.edu.txt b/inc/3rdparty/site_config/standard/moo.nac.uci.edu.txt new file mode 100644 index 00000000..24c949e9 --- /dev/null +++ b/inc/3rdparty/site_config/standard/moo.nac.uci.edu.txt | |||
@@ -0,0 +1,9 @@ | |||
1 | title: //div[@id='header']//h1[1] | ||
2 | |||
3 | body: //div[@id='content'] | ||
4 | |||
5 | strip_id_or_class: toc | ||
6 | |||
7 | prune: no | ||
8 | |||
9 | test_url: http://moo.nac.uci.edu/~hjm/HOWTO_move_data.html | ||
diff --git a/inc/3rdparty/site_config/standard/politico.com.txt b/inc/3rdparty/site_config/standard/politico.com.txt index 121fd5b9..c5302d1b 100644..100755 --- a/inc/3rdparty/site_config/standard/politico.com.txt +++ b/inc/3rdparty/site_config/standard/politico.com.txt | |||
@@ -4,10 +4,14 @@ body://div[contains(@class,"story-text")] | |||
4 | # Why doesn't this work? next_page_link://ul[contains(@class,"pagination")]/li/a[@rel="next"] | 4 | # Why doesn't this work? next_page_link://ul[contains(@class,"pagination")]/li/a[@rel="next"] |
5 | 5 | ||
6 | next_page_link://ul[contains(@class,"pagination")]/li[contains(@class, "current")]/following-sibling::node()/a | 6 | next_page_link://ul[contains(@class,"pagination")]/li[contains(@class, "current")]/following-sibling::node()/a |
7 | next_page_link://div[contains(@class,"pagination")]/ol/li[contains(@class, "current")]/following-sibling::node()/a | ||
7 | date://meta[@name="publish_date"]/@content | 8 | date://meta[@name="publish_date"]/@content |
8 | 9 | ||
9 | strip://div[contains(@class, "breadcrumbs")] | 10 | strip://div[contains(@class, "breadcrumbs")] |
10 | strip://a[contains(@class, "hidden")] | 11 | strip://a[contains(@class, "hidden")] |
11 | strip://div[contains(@class, "story-embed")] | 12 | strip://div[contains(@class, "story-embed")] |
12 | strip://div[contains(@class, "story-text")]//p/a[contains(text(), "Also on POLITICO:")]/.. | 13 | strip://div[contains(@class, "story-text")]//p/a[contains(text(), "Also on POLITICO:")]/.. |
14 | strip://div[contains(@class, "story-interrupt")] | ||
15 | strip://footer[contains(@class, "author-bio")] | ||
16 | |||
13 | test_url: http://www.politico.com/news/stories/0712/78105.html \ No newline at end of file | 17 | test_url: http://www.politico.com/news/stories/0712/78105.html \ No newline at end of file |
diff --git a/inc/3rdparty/site_config/standard/version.txt b/inc/3rdparty/site_config/standard/version.txt index bf0d87ab..eaf01ebd 100644 --- a/inc/3rdparty/site_config/standard/version.txt +++ b/inc/3rdparty/site_config/standard/version.txt | |||
@@ -1 +1 @@ | |||
4 \ No newline at end of file | 2013-05-12T22:53:07Z \ No newline at end of file | ||
diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php index c998fe14..11cccb72 100755 --- a/inc/poche/Database.class.php +++ b/inc/poche/Database.class.php | |||
@@ -18,7 +18,7 @@ class Database { | |||
18 | 'default' => 'ORDER BY entries.id' | 18 | 'default' => 'ORDER BY entries.id' |
19 | ); | 19 | ); |
20 | 20 | ||
21 | function __construct() | 21 | function __construct() |
22 | { | 22 | { |
23 | switch (STORAGE) { | 23 | switch (STORAGE) { |
24 | case 'sqlite': | 24 | case 'sqlite': |
@@ -27,12 +27,14 @@ class Database { | |||
27 | break; | 27 | break; |
28 | case 'mysql': | 28 | case 'mysql': |
29 | $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; | 29 | $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; |
30 | $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); | 30 | $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); |
31 | break; | 31 | break; |
32 | case 'postgres': | 32 | case 'postgres': |
33 | $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; | 33 | $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; |
34 | $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); | 34 | $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); |
35 | break; | 35 | break; |
36 | default: | ||
37 | die(STORAGE . ' is not a recognised database system !'); | ||
36 | } | 38 | } |
37 | 39 | ||
38 | $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | 40 | $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
@@ -51,7 +53,7 @@ class Database { | |||
51 | } | 53 | } |
52 | $hasAdmin = count($query->fetchAll()); | 54 | $hasAdmin = count($query->fetchAll()); |
53 | 55 | ||
54 | if ($hasAdmin == 0) | 56 | if ($hasAdmin == 0) |
55 | return false; | 57 | return false; |
56 | 58 | ||
57 | return true; | 59 | return true; |
@@ -77,7 +79,7 @@ class Database { | |||
77 | } | 79 | } |
78 | else { | 80 | else { |
79 | $sql = ' | 81 | $sql = ' |
80 | CREATE TABLE tags ( | 82 | CREATE TABLE IF NOT EXISTS tags ( |
81 | id bigserial primary key, | 83 | id bigserial primary key, |
82 | value varchar(255) NOT NULL | 84 | value varchar(255) NOT NULL |
83 | ); | 85 | ); |
@@ -110,7 +112,7 @@ class Database { | |||
110 | } | 112 | } |
111 | else { | 113 | else { |
112 | $sql = ' | 114 | $sql = ' |
113 | CREATE TABLE tags_entries ( | 115 | CREATE TABLE IF NOT EXISTS tags_entries ( |
114 | id bigserial primary key, | 116 | id bigserial primary key, |
115 | entry_id integer NOT NULL, | 117 | entry_id integer NOT NULL, |
116 | tag_id integer NOT NULL | 118 | tag_id integer NOT NULL |
@@ -140,7 +142,7 @@ class Database { | |||
140 | $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; | 142 | $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; |
141 | $params = array($id_user, 'language', LANG); | 143 | $params = array($id_user, 'language', LANG); |
142 | $query = $this->executeQuery($sql, $params); | 144 | $query = $this->executeQuery($sql, $params); |
143 | 145 | ||
144 | $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; | 146 | $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; |
145 | $params = array($id_user, 'theme', DEFAULT_THEME); | 147 | $params = array($id_user, 'theme', DEFAULT_THEME); |
146 | $query = $this->executeQuery($sql, $params); | 148 | $query = $this->executeQuery($sql, $params); |
@@ -153,7 +155,7 @@ class Database { | |||
153 | $query = $this->executeQuery($sql, array($id)); | 155 | $query = $this->executeQuery($sql, array($id)); |
154 | $result = $query->fetchAll(); | 156 | $result = $query->fetchAll(); |
155 | $user_config = array(); | 157 | $user_config = array(); |
156 | 158 | ||
157 | foreach ($result as $key => $value) { | 159 | foreach ($result as $key => $value) { |
158 | $user_config[$value['name']] = $value['value']; | 160 | $user_config[$value['name']] = $value['value']; |
159 | } | 161 | } |
@@ -201,10 +203,10 @@ class Database { | |||
201 | $params_update = array($password, $userId); | 203 | $params_update = array($password, $userId); |
202 | $query = $this->executeQuery($sql_update, $params_update); | 204 | $query = $this->executeQuery($sql_update, $params_update); |
203 | } | 205 | } |
204 | 206 | ||
205 | public function updateUserConfig($userId, $key, $value) { | 207 | public function updateUserConfig($userId, $key, $value) { |
206 | $config = $this->getConfigUser($userId); | 208 | $config = $this->getConfigUser($userId); |
207 | 209 | ||
208 | if (! isset($config[$key])) { | 210 | if (! isset($config[$key])) { |
209 | $sql = "INSERT INTO users_config (value, user_id, name) VALUES (?, ?, ?)"; | 211 | $sql = "INSERT INTO users_config (value, user_id, name) VALUES (?, ?, ?)"; |
210 | } | 212 | } |
@@ -229,6 +231,73 @@ class Database { | |||
229 | return FALSE; | 231 | return FALSE; |
230 | } | 232 | } |
231 | } | 233 | } |
234 | |||
235 | public function listUsers($username=null) { | ||
236 | $sql = 'SELECT count(*) FROM users'.( $username ? ' WHERE username=?' : ''); | ||
237 | $query = $this->executeQuery($sql, ( $username ? array($username) : array())); | ||
238 | list($count) = $query->fetch(); | ||
239 | return $count; | ||
240 | } | ||
241 | |||
242 | public function getUserPassword($userID) { | ||
243 | $sql = "SELECT * FROM users WHERE id=?"; | ||
244 | $query = $this->executeQuery($sql, array($userID)); | ||
245 | $password = $query->fetchAll(); | ||
246 | return isset($password[0]['password']) ? $password[0]['password'] : null; | ||
247 | } | ||
248 | |||
249 | public function deleteUserConfig($userID) { | ||
250 | $sql_action = 'DELETE from users_config WHERE user_id=?'; | ||
251 | $params_action = array($userID); | ||
252 | $query = $this->executeQuery($sql_action, $params_action); | ||
253 | return $query; | ||
254 | } | ||
255 | |||
256 | public function deleteTagsEntriesAndEntries($userID) { | ||
257 | $entries = $this->retrieveAll($userID); | ||
258 | foreach($entries as $entryid) { | ||
259 | $tags = $this->retrieveTagsByEntry($entryid); | ||
260 | foreach($tags as $tag) { | ||
261 | $this->removeTagForEntry($entryid,$tags); | ||
262 | } | ||
263 | $this->deleteById($entryid,$userID); | ||
264 | } | ||
265 | } | ||
266 | |||
267 | public function deleteUser($userID) { | ||
268 | $sql_action = 'DELETE from users WHERE id=?'; | ||
269 | $params_action = array($userID); | ||
270 | $query = $this->executeQuery($sql_action, $params_action); | ||
271 | } | ||
272 | |||
273 | public function updateContentAndTitle($id, $title, $body, $user_id) { | ||
274 | $sql_action = 'UPDATE entries SET content = ?, title = ? WHERE id=? AND user_id=?'; | ||
275 | $params_action = array($body, $title, $id, $user_id); | ||
276 | $query = $this->executeQuery($sql_action, $params_action); | ||
277 | return $query; | ||
278 | } | ||
279 | |||
280 | public function retrieveUnfetchedEntries($user_id, $limit) { | ||
281 | |||
282 | $sql_limit = "LIMIT 0,".$limit; | ||
283 | if (STORAGE == 'postgres') { | ||
284 | $sql_limit = "LIMIT ".$limit." OFFSET 0"; | ||
285 | } | ||
286 | |||
287 | $sql = "SELECT * FROM entries WHERE (content = '' OR content IS NULL) AND title LIKE 'Untitled - Import%' AND user_id=? ORDER BY id " . $sql_limit; | ||
288 | $query = $this->executeQuery($sql, array($user_id)); | ||
289 | $entries = $query->fetchAll(); | ||
290 | |||
291 | return $entries; | ||
292 | } | ||
293 | |||
294 | public function retrieveUnfetchedEntriesCount($user_id) { | ||
295 | $sql = "SELECT count(*) FROM entries WHERE (content = '' OR content IS NULL) AND title LIKE 'Untitled - Import%' AND user_id=?"; | ||
296 | $query = $this->executeQuery($sql, array($user_id)); | ||
297 | list($count) = $query->fetch(); | ||
298 | |||
299 | return $count; | ||
300 | } | ||
232 | 301 | ||
233 | public function retrieveAll($user_id) { | 302 | public function retrieveAll($user_id) { |
234 | $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id"; | 303 | $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id"; |
@@ -294,24 +363,24 @@ class Database { | |||
294 | return $entries; | 363 | return $entries; |
295 | } | 364 | } |
296 | 365 | ||
297 | public function getEntriesByViewCount($view, $user_id, $tag_id = 0) { | 366 | public function getEntriesByViewCount($view, $user_id, $tag_id = 0) { |
298 | switch ($view) { | 367 | switch ($view) { |
299 | case 'archive': | 368 | case 'archive': |
300 | $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; | 369 | $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; |
301 | $params = array($user_id, 1); | 370 | $params = array($user_id, 1); |
302 | break; | 371 | break; |
303 | case 'fav' : | 372 | case 'fav' : |
304 | $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_fav=? "; | 373 | $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_fav=? "; |
305 | $params = array($user_id, 1); | 374 | $params = array($user_id, 1); |
306 | break; | 375 | break; |
307 | case 'tag' : | 376 | case 'tag' : |
308 | $sql = "SELECT count(*) FROM entries | 377 | $sql = "SELECT count(*) FROM entries |
309 | LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id | 378 | LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id |
310 | WHERE entries.user_id=? AND tags_entries.tag_id = ? "; | 379 | WHERE entries.user_id=? AND tags_entries.tag_id = ? "; |
311 | $params = array($user_id, $tag_id); | 380 | $params = array($user_id, $tag_id); |
312 | break; | 381 | break; |
313 | default: | 382 | default: |
314 | $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; | 383 | $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; |
315 | $params = array($user_id, 0); | 384 | $params = array($user_id, 0); |
316 | break; | 385 | break; |
317 | } | 386 | } |
@@ -319,7 +388,7 @@ class Database { | |||
319 | $query = $this->executeQuery($sql, $params); | 388 | $query = $this->executeQuery($sql, $params); |
320 | list($count) = $query->fetch(); | 389 | list($count) = $query->fetch(); |
321 | 390 | ||
322 | return $count; | 391 | return $count; |
323 | } | 392 | } |
324 | 393 | ||
325 | public function updateContent($id, $content, $user_id) { | 394 | public function updateContent($id, $content, $user_id) { |
@@ -329,11 +398,25 @@ class Database { | |||
329 | return $query; | 398 | return $query; |
330 | } | 399 | } |
331 | 400 | ||
332 | public function add($url, $title, $content, $user_id) { | 401 | /** |
333 | $sql_action = 'INSERT INTO entries ( url, title, content, user_id ) VALUES (?, ?, ?, ?)'; | 402 | * |
334 | $params_action = array($url, $title, $content, $user_id); | 403 | * @param string $url |
335 | $query = $this->executeQuery($sql_action, $params_action); | 404 | * @param string $title |
336 | return $query; | 405 | * @param string $content |
406 | * @param integer $user_id | ||
407 | * @return integer $id of inserted record | ||
408 | */ | ||
409 | public function add($url, $title, $content, $user_id, $isFavorite=0, $isRead=0) { | ||
410 | $sql_action = 'INSERT INTO entries ( url, title, content, user_id, is_fav, is_read ) VALUES (?, ?, ?, ?, ?, ?)'; | ||
411 | $params_action = array($url, $title, $content, $user_id, $isFavorite, $isRead); | ||
412 | |||
413 | if ( !$this->executeQuery($sql_action, $params_action) ) { | ||
414 | $id = null; | ||
415 | } | ||
416 | else { | ||
417 | $id = intval($this->getLastId( (STORAGE == 'postgres') ? 'entries_id_seq' : '') ); | ||
418 | } | ||
419 | return $id; | ||
337 | } | 420 | } |
338 | 421 | ||
339 | public function deleteById($id, $user_id) { | 422 | public function deleteById($id, $user_id) { |
@@ -365,12 +448,24 @@ class Database { | |||
365 | return $this->getHandle()->lastInsertId($column); | 448 | return $this->getHandle()->lastInsertId($column); |
366 | } | 449 | } |
367 | 450 | ||
368 | public function retrieveAllTags($user_id) { | 451 | public function search($term, $user_id, $limit = '') { |
369 | $sql = "SELECT DISTINCT tags.* FROM tags | 452 | $search = '%'.$term.'%'; |
453 | $sql_action = "SELECT * FROM entries WHERE user_id=? AND (content LIKE ? OR title LIKE ? OR url LIKE ?) "; //searches in content, title and URL | ||
454 | $sql_action .= $this->getEntriesOrder().' ' . $limit; | ||
455 | $params_action = array($user_id, $search, $search, $search); | ||
456 | $query = $this->executeQuery($sql_action, $params_action); | ||
457 | return $query->fetchAll(); | ||
458 | } | ||
459 | |||
460 | public function retrieveAllTags($user_id, $term = null) { | ||
461 | $sql = "SELECT DISTINCT tags.*, count(entries.id) AS entriescount FROM tags | ||
370 | LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id | 462 | LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id |
371 | LEFT JOIN entries ON tags_entries.entry_id=entries.id | 463 | LEFT JOIN entries ON tags_entries.entry_id=entries.id |
372 | WHERE entries.user_id=?"; | 464 | WHERE entries.user_id=? |
373 | $query = $this->executeQuery($sql, array($user_id)); | 465 | ". (($term) ? "AND lower(tags.value) LIKE ?" : '') ." |
466 | GROUP BY tags.id, tags.value | ||
467 | ORDER BY tags.value"; | ||
468 | $query = $this->executeQuery($sql, (($term)? array($user_id, strtolower('%'.$term.'%')) : array($user_id) )); | ||
374 | $tags = $query->fetchAll(); | 469 | $tags = $query->fetchAll(); |
375 | 470 | ||
376 | return $tags; | 471 | return $tags; |
@@ -390,10 +485,10 @@ class Database { | |||
390 | } | 485 | } |
391 | 486 | ||
392 | public function retrieveEntriesByTag($tag_id, $user_id) { | 487 | public function retrieveEntriesByTag($tag_id, $user_id) { |
393 | $sql = | 488 | $sql = |
394 | "SELECT entries.* FROM entries | 489 | "SELECT entries.* FROM entries |
395 | LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id | 490 | LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id |
396 | WHERE tags_entries.tag_id = ? AND entries.user_id=?"; | 491 | WHERE tags_entries.tag_id = ? AND entries.user_id=? ORDER by entries.id DESC"; |
397 | $query = $this->executeQuery($sql, array($tag_id, $user_id)); | 492 | $query = $this->executeQuery($sql, array($tag_id, $user_id)); |
398 | $entries = $query->fetchAll(); | 493 | $entries = $query->fetchAll(); |
399 | 494 | ||
@@ -401,7 +496,7 @@ class Database { | |||
401 | } | 496 | } |
402 | 497 | ||
403 | public function retrieveTagsByEntry($entry_id) { | 498 | public function retrieveTagsByEntry($entry_id) { |
404 | $sql = | 499 | $sql = |
405 | "SELECT tags.* FROM tags | 500 | "SELECT tags.* FROM tags |
406 | LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id | 501 | LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id |
407 | WHERE tags_entries.entry_id = ?"; | 502 | WHERE tags_entries.entry_id = ?"; |
@@ -417,6 +512,25 @@ class Database { | |||
417 | $query = $this->executeQuery($sql_action, $params_action); | 512 | $query = $this->executeQuery($sql_action, $params_action); |
418 | return $query; | 513 | return $query; |
419 | } | 514 | } |
515 | |||
516 | public function cleanUnusedTag($tag_id) { | ||
517 | $sql_action = "SELECT tags.* FROM tags JOIN tags_entries ON tags_entries.tag_id=tags.id WHERE tags.id=?"; | ||
518 | $query = $this->executeQuery($sql_action,array($tag_id)); | ||
519 | $tagstokeep = $query->fetchAll(); | ||
520 | $sql_action = "SELECT tags.* FROM tags LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id WHERE tags.id=?"; | ||
521 | $query = $this->executeQuery($sql_action,array($tag_id)); | ||
522 | $alltags = $query->fetchAll(); | ||
523 | |||
524 | foreach ($alltags as $tag) { | ||
525 | if ($tag && !in_array($tag,$tagstokeep)) { | ||
526 | $sql_action = "DELETE FROM tags WHERE id=?"; | ||
527 | $params_action = array($tag[0]); | ||
528 | $this->executeQuery($sql_action, $params_action); | ||
529 | return true; | ||
530 | } | ||
531 | } | ||
532 | |||
533 | } | ||
420 | 534 | ||
421 | public function retrieveTagByValue($value) { | 535 | public function retrieveTagByValue($value) { |
422 | $tag = NULL; | 536 | $tag = NULL; |
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index 8a9de488..09a9f5ff 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php | |||
@@ -18,7 +18,7 @@ class Poche | |||
18 | public $tpl; | 18 | public $tpl; |
19 | public $messages; | 19 | public $messages; |
20 | public $pagination; | 20 | public $pagination; |
21 | 21 | ||
22 | private $currentTheme = ''; | 22 | private $currentTheme = ''; |
23 | private $currentLanguage = ''; | 23 | private $currentLanguage = ''; |
24 | private $notInstalledMessage = array(); | 24 | private $notInstalledMessage = array(); |
@@ -32,20 +32,21 @@ class Poche | |||
32 | 'fr_FR.utf8' => 'Français', | 32 | 'fr_FR.utf8' => 'Français', |
33 | 'it_IT.utf8' => 'Italiano', | 33 | 'it_IT.utf8' => 'Italiano', |
34 | 'pl_PL.utf8' => 'Polski', | 34 | 'pl_PL.utf8' => 'Polski', |
35 | 'pt_BR.utf8' => 'Português (Brasil)', | ||
35 | 'ru_RU.utf8' => 'Pусский', | 36 | 'ru_RU.utf8' => 'Pусский', |
36 | 'sl_SI.utf8' => 'Slovenščina', | 37 | 'sl_SI.utf8' => 'Slovenščina', |
37 | 'uk_UA.utf8' => 'Українськй', | 38 | 'uk_UA.utf8' => 'Українськ', |
38 | ); | 39 | ); |
39 | public function __construct() | 40 | public function __construct() |
40 | { | 41 | { |
41 | if ($this->configFileIsAvailable()) { | 42 | if ($this->configFileIsAvailable()) { |
42 | $this->init(); | 43 | $this->init(); |
43 | } | 44 | } |
44 | 45 | ||
45 | if ($this->themeIsInstalled()) { | 46 | if ($this->themeIsInstalled()) { |
46 | $this->initTpl(); | 47 | $this->initTpl(); |
47 | } | 48 | } |
48 | 49 | ||
49 | if ($this->systemIsInstalled()) { | 50 | if ($this->systemIsInstalled()) { |
50 | $this->store = new Database(); | 51 | $this->store = new Database(); |
51 | $this->messages = new Messages(); | 52 | $this->messages = new Messages(); |
@@ -56,12 +57,10 @@ class Poche | |||
56 | $this->store->checkTags(); | 57 | $this->store->checkTags(); |
57 | } | 58 | } |
58 | } | 59 | } |
59 | 60 | ||
60 | private function init() | 61 | private function init() |
61 | { | 62 | { |
62 | Tools::initPhp(); | 63 | Tools::initPhp(); |
63 | Session::$sessionName = 'poche'; | ||
64 | Session::init(); | ||
65 | 64 | ||
66 | if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { | 65 | if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { |
67 | $this->user = $_SESSION['poche_user']; | 66 | $this->user = $_SESSION['poche_user']; |
@@ -73,43 +72,43 @@ class Poche | |||
73 | 72 | ||
74 | # l10n | 73 | # l10n |
75 | $language = $this->user->getConfigValue('language'); | 74 | $language = $this->user->getConfigValue('language'); |
76 | putenv('LC_ALL=' . $language); | 75 | @putenv('LC_ALL=' . $language); |
77 | setlocale(LC_ALL, $language); | 76 | setlocale(LC_ALL, $language); |
78 | bindtextdomain($language, LOCALE); | 77 | bindtextdomain($language, LOCALE); |
79 | textdomain($language); | 78 | textdomain($language); |
80 | 79 | ||
81 | # Pagination | 80 | # Pagination |
82 | $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p'); | 81 | $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p'); |
83 | 82 | ||
84 | # Set up theme | 83 | # Set up theme |
85 | $themeDirectory = $this->user->getConfigValue('theme'); | 84 | $themeDirectory = $this->user->getConfigValue('theme'); |
86 | 85 | ||
87 | if ($themeDirectory === false) { | 86 | if ($themeDirectory === false) { |
88 | $themeDirectory = DEFAULT_THEME; | 87 | $themeDirectory = DEFAULT_THEME; |
89 | } | 88 | } |
90 | 89 | ||
91 | $this->currentTheme = $themeDirectory; | 90 | $this->currentTheme = $themeDirectory; |
92 | 91 | ||
93 | # Set up language | 92 | # Set up language |
94 | $languageDirectory = $this->user->getConfigValue('language'); | 93 | $languageDirectory = $this->user->getConfigValue('language'); |
95 | 94 | ||
96 | if ($languageDirectory === false) { | 95 | if ($languageDirectory === false) { |
97 | $languageDirectory = DEFAULT_THEME; | 96 | $languageDirectory = DEFAULT_THEME; |
98 | } | 97 | } |
99 | 98 | ||
100 | $this->currentLanguage = $languageDirectory; | 99 | $this->currentLanguage = $languageDirectory; |
101 | } | 100 | } |
102 | 101 | ||
103 | public function configFileIsAvailable() { | 102 | public function configFileIsAvailable() { |
104 | if (! self::$configFileAvailable) { | 103 | if (! self::$configFileAvailable) { |
105 | $this->notInstalledMessage[] = 'You have to rename inc/poche/config.inc.php.new to inc/poche/config.inc.php.'; | 104 | $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.'; |
106 | 105 | ||
107 | return false; | 106 | return false; |
108 | } | 107 | } |
109 | 108 | ||
110 | return true; | 109 | return true; |
111 | } | 110 | } |
112 | 111 | ||
113 | public function themeIsInstalled() { | 112 | public function themeIsInstalled() { |
114 | $passTheme = TRUE; | 113 | $passTheme = TRUE; |
115 | # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet | 114 | # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet |
@@ -124,27 +123,27 @@ class Poche | |||
124 | self::$canRenderTemplates = false; | 123 | self::$canRenderTemplates = false; |
125 | 124 | ||
126 | $passTheme = FALSE; | 125 | $passTheme = FALSE; |
127 | } | 126 | } |
128 | 127 | ||
129 | # Check if the selected theme and its requirements are present | 128 | # Check if the selected theme and its requirements are present |
130 | $theme = $this->getTheme(); | 129 | $theme = $this->getTheme(); |
131 | 130 | ||
132 | if ($theme != '' && ! is_dir(THEME . '/' . $theme)) { | 131 | if ($theme != '' && ! is_dir(THEME . '/' . $theme)) { |
133 | $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')'; | 132 | $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')'; |
134 | 133 | ||
135 | self::$canRenderTemplates = false; | 134 | self::$canRenderTemplates = false; |
136 | 135 | ||
137 | $passTheme = FALSE; | 136 | $passTheme = FALSE; |
138 | } | 137 | } |
139 | 138 | ||
140 | $themeInfo = $this->getThemeInfo($theme); | 139 | $themeInfo = $this->getThemeInfo($theme); |
141 | if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { | 140 | if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { |
142 | foreach ($themeInfo['requirements'] as $requiredTheme) { | 141 | foreach ($themeInfo['requirements'] as $requiredTheme) { |
143 | if (! is_dir(THEME . '/' . $requiredTheme)) { | 142 | if (! is_dir(THEME . '/' . $requiredTheme)) { |
144 | $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'; | 143 | $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'; |
145 | 144 | ||
146 | self::$canRenderTemplates = false; | 145 | self::$canRenderTemplates = false; |
147 | 146 | ||
148 | $passTheme = FALSE; | 147 | $passTheme = FALSE; |
149 | } | 148 | } |
150 | } | 149 | } |
@@ -154,21 +153,21 @@ class Poche | |||
154 | return FALSE; | 153 | return FALSE; |
155 | } | 154 | } |
156 | 155 | ||
157 | 156 | ||
158 | return true; | 157 | return true; |
159 | } | 158 | } |
160 | 159 | ||
161 | /** | 160 | /** |
162 | * all checks before installation. | 161 | * all checks before installation. |
163 | * @todo move HTML to template | 162 | * @todo move HTML to template |
164 | * @return boolean | 163 | * @return boolean |
165 | */ | 164 | */ |
166 | public function systemIsInstalled() | 165 | public function systemIsInstalled() |
167 | { | 166 | { |
168 | $msg = TRUE; | 167 | $msg = TRUE; |
169 | 168 | ||
170 | $configSalt = defined('SALT') ? constant('SALT') : ''; | 169 | $configSalt = defined('SALT') ? constant('SALT') : ''; |
171 | 170 | ||
172 | if (empty($configSalt)) { | 171 | if (empty($configSalt)) { |
173 | $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.'; | 172 | $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.'; |
174 | $msg = FALSE; | 173 | $msg = FALSE; |
@@ -194,7 +193,7 @@ class Poche | |||
194 | 193 | ||
195 | return true; | 194 | return true; |
196 | } | 195 | } |
197 | 196 | ||
198 | public function getNotInstalledMessage() { | 197 | public function getNotInstalledMessage() { |
199 | return $this->notInstalledMessage; | 198 | return $this->notInstalledMessage; |
200 | } | 199 | } |
@@ -203,7 +202,7 @@ class Poche | |||
203 | { | 202 | { |
204 | $loaderChain = new Twig_Loader_Chain(); | 203 | $loaderChain = new Twig_Loader_Chain(); |
205 | $theme = $this->getTheme(); | 204 | $theme = $this->getTheme(); |
206 | 205 | ||
207 | # add the current theme as first to the loader chain so Twig will look there first for overridden template files | 206 | # add the current theme as first to the loader chain so Twig will look there first for overridden template files |
208 | try { | 207 | try { |
209 | $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme)); | 208 | $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme)); |
@@ -211,7 +210,7 @@ class Poche | |||
211 | # @todo isInstalled() should catch this, inject Twig later | 210 | # @todo isInstalled() should catch this, inject Twig later |
212 | die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)'); | 211 | die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)'); |
213 | } | 212 | } |
214 | 213 | ||
215 | # add all required themes to the loader chain | 214 | # add all required themes to the loader chain |
216 | $themeInfo = $this->getThemeInfo($theme); | 215 | $themeInfo = $this->getThemeInfo($theme); |
217 | if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { | 216 | if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { |
@@ -224,16 +223,16 @@ class Poche | |||
224 | } | 223 | } |
225 | } | 224 | } |
226 | } | 225 | } |
227 | 226 | ||
228 | if (DEBUG_POCHE) { | 227 | if (DEBUG_POCHE) { |
229 | $twigParams = array(); | 228 | $twigParams = array(); |
230 | } else { | 229 | } else { |
231 | $twigParams = array('cache' => CACHE); | 230 | $twigParams = array('cache' => CACHE); |
232 | } | 231 | } |
233 | 232 | ||
234 | $this->tpl = new Twig_Environment($loaderChain, $twigParams); | 233 | $this->tpl = new Twig_Environment($loaderChain, $twigParams); |
235 | $this->tpl->addExtension(new Twig_Extensions_Extension_I18n()); | 234 | $this->tpl->addExtension(new Twig_Extensions_Extension_I18n()); |
236 | 235 | ||
237 | # filter to display domain name of an url | 236 | # filter to display domain name of an url |
238 | $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain'); | 237 | $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain'); |
239 | $this->tpl->addFilter($filter); | 238 | $this->tpl->addFilter($filter); |
@@ -243,6 +242,58 @@ class Poche | |||
243 | $this->tpl->addFilter($filter); | 242 | $this->tpl->addFilter($filter); |
244 | } | 243 | } |
245 | 244 | ||
245 | public function createNewUser() { | ||
246 | if (isset($_GET['newuser'])){ | ||
247 | if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){ | ||
248 | $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING); | ||
249 | if (!$this->store->userExists($newusername)){ | ||
250 | if ($this->store->install($newusername, Tools::encodeString($_POST['password4newuser'] . $newusername))) { | ||
251 | Tools::logm('The new user '.$newusername.' has been installed'); | ||
252 | $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'),$newusername)); | ||
253 | Tools::redirect(); | ||
254 | } | ||
255 | else { | ||
256 | Tools::logm('error during adding new user'); | ||
257 | Tools::redirect(); | ||
258 | } | ||
259 | } | ||
260 | else { | ||
261 | $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'),$newusername)); | ||
262 | Tools::logm('An user with the name '.$newusername.' already exists !'); | ||
263 | Tools::redirect(); | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | |||
269 | public function deleteUser(){ | ||
270 | if (isset($_GET['deluser'])){ | ||
271 | if ($this->store->listUsers() > 1) { | ||
272 | if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) { | ||
273 | $username = $this->user->getUsername(); | ||
274 | $this->store->deleteUserConfig($this->user->getId()); | ||
275 | Tools::logm('The configuration for user '. $username .' has been deleted !'); | ||
276 | $this->store->deleteTagsEntriesAndEntries($this->user->getId()); | ||
277 | Tools::logm('The entries for user '. $username .' has been deleted !'); | ||
278 | $this->store->deleteUser($this->user->getId()); | ||
279 | Tools::logm('User '. $username .' has been completely deleted !'); | ||
280 | Session::logout(); | ||
281 | Tools::logm('logout'); | ||
282 | Tools::redirect(); | ||
283 | $this->messages->add('s', sprintf(_('User %s has been successfully deleted !'),$newusername)); | ||
284 | } | ||
285 | else { | ||
286 | Tools::logm('Bad password !'); | ||
287 | $this->messages->add('e', _('Error : The password is wrong !')); | ||
288 | } | ||
289 | } | ||
290 | else { | ||
291 | Tools::logm('Only user !'); | ||
292 | $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !')); | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | |||
246 | private function install() | 297 | private function install() |
247 | { | 298 | { |
248 | Tools::logm('poche still not installed'); | 299 | Tools::logm('poche still not installed'); |
@@ -252,7 +303,7 @@ class Poche | |||
252 | 'poche_url' => Tools::getPocheUrl() | 303 | 'poche_url' => Tools::getPocheUrl() |
253 | )); | 304 | )); |
254 | if (isset($_GET['install'])) { | 305 | if (isset($_GET['install'])) { |
255 | if (($_POST['password'] == $_POST['password_repeat']) | 306 | if (($_POST['password'] == $_POST['password_repeat']) |
256 | && $_POST['password'] != "" && $_POST['login'] != "") { | 307 | && $_POST['password'] != "" && $_POST['login'] != "") { |
257 | # let's rock, install poche baby ! | 308 | # let's rock, install poche baby ! |
258 | if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']))) | 309 | if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']))) |
@@ -269,7 +320,7 @@ class Poche | |||
269 | } | 320 | } |
270 | exit(); | 321 | exit(); |
271 | } | 322 | } |
272 | 323 | ||
273 | public function getTheme() { | 324 | public function getTheme() { |
274 | return $this->currentTheme; | 325 | return $this->currentTheme; |
275 | } | 326 | } |
@@ -294,7 +345,7 @@ class Poche | |||
294 | if (is_file($themeIniFile) && is_readable($themeIniFile)) { | 345 | if (is_file($themeIniFile) && is_readable($themeIniFile)) { |
295 | $themeInfo = parse_ini_file($themeIniFile); | 346 | $themeInfo = parse_ini_file($themeIniFile); |
296 | } | 347 | } |
297 | 348 | ||
298 | if ($themeInfo === false) { | 349 | if ($themeInfo === false) { |
299 | $themeInfo = array(); | 350 | $themeInfo = array(); |
300 | } | 351 | } |
@@ -305,7 +356,7 @@ class Poche | |||
305 | 356 | ||
306 | return $themeInfo; | 357 | return $themeInfo; |
307 | } | 358 | } |
308 | 359 | ||
309 | public function getInstalledThemes() { | 360 | public function getInstalledThemes() { |
310 | $handle = opendir(THEME); | 361 | $handle = opendir(THEME); |
311 | $themes = array(); | 362 | $themes = array(); |
@@ -332,28 +383,28 @@ class Poche | |||
332 | public function getInstalledLanguages() { | 383 | public function getInstalledLanguages() { |
333 | $handle = opendir(LOCALE); | 384 | $handle = opendir(LOCALE); |
334 | $languages = array(); | 385 | $languages = array(); |
335 | 386 | ||
336 | while (($language = readdir($handle)) !== false) { | 387 | while (($language = readdir($handle)) !== false) { |
337 | # Languages are stored in a directory, so all directory names are languages | 388 | # Languages are stored in a directory, so all directory names are languages |
338 | # @todo move language installation data to database | 389 | # @todo move language installation data to database |
339 | if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.'))) { | 390 | if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) { |
340 | continue; | 391 | continue; |
341 | } | 392 | } |
342 | 393 | ||
343 | $current = false; | 394 | $current = false; |
344 | 395 | ||
345 | if ($language === $this->getLanguage()) { | 396 | if ($language === $this->getLanguage()) { |
346 | $current = true; | 397 | $current = true; |
347 | } | 398 | } |
348 | 399 | ||
349 | $languages[] = array('name' => $this->language_names[$language], 'value' => $language, 'current' => $current); | 400 | $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current); |
350 | } | 401 | } |
351 | 402 | ||
352 | return $languages; | 403 | return $languages; |
353 | } | 404 | } |
354 | 405 | ||
355 | public function getDefaultConfig() | 406 | public function getDefaultConfig() |
356 | { | 407 | { |
357 | return array( | 408 | return array( |
358 | 'pager' => PAGINATION, | 409 | 'pager' => PAGINATION, |
359 | 'language' => LANG, | 410 | 'language' => LANG, |
@@ -361,60 +412,6 @@ class Poche | |||
361 | ); | 412 | ); |
362 | } | 413 | } |
363 | 414 | ||
364 | protected function getPageContent(Url $url) | ||
365 | { | ||
366 | // Saving and clearing context | ||
367 | $REAL = array(); | ||
368 | foreach( $GLOBALS as $key => $value ) { | ||
369 | if( $key != "GLOBALS" && $key != "_SESSION" ) { | ||
370 | $GLOBALS[$key] = array(); | ||
371 | $REAL[$key] = $value; | ||
372 | } | ||
373 | } | ||
374 | // Saving and clearing session | ||
375 | $REAL_SESSION = array(); | ||
376 | foreach( $_SESSION as $key => $value ) { | ||
377 | $REAL_SESSION[$key] = $value; | ||
378 | unset($_SESSION[$key]); | ||
379 | } | ||
380 | |||
381 | // Running code in different context | ||
382 | $scope = function() { | ||
383 | extract( func_get_arg(1) ); | ||
384 | $_GET = $_REQUEST = array( | ||
385 | "url" => $url->getUrl(), | ||
386 | "max" => 5, | ||
387 | "links" => "preserve", | ||
388 | "exc" => "", | ||
389 | "format" => "json", | ||
390 | "submit" => "Create Feed" | ||
391 | ); | ||
392 | ob_start(); | ||
393 | require func_get_arg(0); | ||
394 | $json = ob_get_flush(); | ||
395 | return $json; | ||
396 | }; | ||
397 | $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) ); | ||
398 | |||
399 | // Clearing and restoring context | ||
400 | foreach( $GLOBALS as $key => $value ) { | ||
401 | if( $key != "GLOBALS" && $key != "_SESSION" ) { | ||
402 | unset($GLOBALS[$key]); | ||
403 | } | ||
404 | } | ||
405 | foreach( $REAL as $key => $value ) { | ||
406 | $GLOBALS[$key] = $value; | ||
407 | } | ||
408 | // Clearing and restoring session | ||
409 | foreach( $_SESSION as $key => $value ) { | ||
410 | unset($_SESSION[$key]); | ||
411 | } | ||
412 | foreach( $REAL_SESSION as $key => $value ) { | ||
413 | $_SESSION[$key] = $value; | ||
414 | } | ||
415 | return json_decode($json, true); | ||
416 | } | ||
417 | |||
418 | /** | 415 | /** |
419 | * Call action (mark as fav, archive, delete, etc.) | 416 | * Call action (mark as fav, archive, delete, etc.) |
420 | */ | 417 | */ |
@@ -423,28 +420,22 @@ class Poche | |||
423 | switch ($action) | 420 | switch ($action) |
424 | { | 421 | { |
425 | case 'add': | 422 | case 'add': |
426 | $content = $this->getPageContent($url); | 423 | $content = Tools::getPageContent($url); |
427 | $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'); | 424 | $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'); |
428 | $body = $content['rss']['channel']['item']['description']; | 425 | $body = $content['rss']['channel']['item']['description']; |
429 | 426 | ||
430 | // clean content from prevent xss attack | 427 | // clean content from prevent xss attack |
431 | $config = HTMLPurifier_Config::createDefault(); | 428 | $purifier = $this->getPurifier(); |
432 | $purifier = new HTMLPurifier($config); | ||
433 | $title = $purifier->purify($title); | 429 | $title = $purifier->purify($title); |
434 | $body = $purifier->purify($body); | 430 | $body = $purifier->purify($body); |
435 | 431 | ||
436 | //search for possible duplicate if not in import mode | 432 | //search for possible duplicate |
437 | if (!$import) { | 433 | $duplicate = NULL; |
438 | $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId()); | 434 | $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId()); |
439 | } | ||
440 | 435 | ||
441 | if ($this->store->add($url->getUrl(), $title, $body, $this->user->getId())) { | 436 | $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId()); |
437 | if ( $last_id ) { | ||
442 | Tools::logm('add link ' . $url->getUrl()); | 438 | Tools::logm('add link ' . $url->getUrl()); |
443 | $sequence = ''; | ||
444 | if (STORAGE == 'postgres') { | ||
445 | $sequence = 'entries_id_seq'; | ||
446 | } | ||
447 | $last_id = $this->store->getLastId($sequence); | ||
448 | if (DOWNLOAD_PICTURES) { | 439 | if (DOWNLOAD_PICTURES) { |
449 | $content = filtre_picture($body, $url->getUrl(), $last_id); | 440 | $content = filtre_picture($body, $url->getUrl(), $last_id); |
450 | Tools::logm('updating content article'); | 441 | Tools::logm('updating content article'); |
@@ -464,23 +455,17 @@ class Poche | |||
464 | } | 455 | } |
465 | } | 456 | } |
466 | 457 | ||
467 | if (!$import) { | 458 | $this->messages->add('s', _('the link has been added successfully')); |
468 | $this->messages->add('s', _('the link has been added successfully')); | ||
469 | } | ||
470 | } | 459 | } |
471 | else { | 460 | else { |
472 | if (!$import) { | 461 | $this->messages->add('e', _('error during insertion : the link wasn\'t added')); |
473 | $this->messages->add('e', _('error during insertion : the link wasn\'t added')); | 462 | Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl()); |
474 | Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl()); | ||
475 | } | ||
476 | } | 463 | } |
477 | 464 | ||
478 | if (!$import) { | 465 | if ($autoclose == TRUE) { |
479 | if ($autoclose == TRUE) { | 466 | Tools::redirect('?view=home'); |
480 | Tools::redirect('?view=home'); | 467 | } else { |
481 | } else { | 468 | Tools::redirect('?view=home&closewin=true'); |
482 | Tools::redirect('?view=home&closewin=true'); | ||
483 | } | ||
484 | } | 469 | } |
485 | break; | 470 | break; |
486 | case 'delete': | 471 | case 'delete': |
@@ -501,62 +486,81 @@ class Poche | |||
501 | case 'toggle_fav' : | 486 | case 'toggle_fav' : |
502 | $this->store->favoriteById($id, $this->user->getId()); | 487 | $this->store->favoriteById($id, $this->user->getId()); |
503 | Tools::logm('mark as favorite link #' . $id); | 488 | Tools::logm('mark as favorite link #' . $id); |
504 | if (!$import) { | 489 | if ( Tools::isAjaxRequest() ) { |
505 | Tools::redirect(); | 490 | echo 1; |
491 | exit; | ||
492 | } | ||
493 | else { | ||
494 | Tools::redirect(); | ||
506 | } | 495 | } |
507 | break; | 496 | break; |
508 | case 'toggle_archive' : | 497 | case 'toggle_archive' : |
509 | $this->store->archiveById($id, $this->user->getId()); | 498 | $this->store->archiveById($id, $this->user->getId()); |
510 | Tools::logm('archive link #' . $id); | 499 | Tools::logm('archive link #' . $id); |
511 | if (!$import) { | 500 | if ( Tools::isAjaxRequest() ) { |
512 | Tools::redirect(); | 501 | echo 1; |
502 | exit; | ||
503 | } | ||
504 | else { | ||
505 | Tools::redirect(); | ||
513 | } | 506 | } |
514 | break; | 507 | break; |
515 | case 'archive_all' : | 508 | case 'archive_all' : |
516 | $this->store->archiveAll($this->user->getId()); | 509 | $this->store->archiveAll($this->user->getId()); |
517 | Tools::logm('archive all links'); | 510 | Tools::logm('archive all links'); |
518 | if (!$import) { | 511 | Tools::redirect(); |
519 | Tools::redirect(); | ||
520 | } | ||
521 | break; | 512 | break; |
522 | case 'add_tag' : | 513 | case 'add_tag' : |
523 | if($import){ | 514 | if (isset($_GET['search'])) { |
524 | $entry_id = $id; | 515 | //when we want to apply a tag to a search |
525 | $tags = explode(',', $tags); | 516 | $tags = array($_GET['search']); |
526 | } | 517 | $allentry_ids = $this->store->search($tags[0], $this->user->getId()); |
527 | else{ | 518 | $entry_ids = array(); |
519 | foreach ($allentry_ids as $eachentry) { | ||
520 | $entry_ids[] = $eachentry[0]; | ||
521 | } | ||
522 | } else { //add a tag to a single article | ||
528 | $tags = explode(',', $_POST['value']); | 523 | $tags = explode(',', $_POST['value']); |
529 | $entry_id = $_POST['entry_id']; | 524 | $entry_ids = array($_POST['entry_id']); |
530 | } | 525 | } |
531 | $entry = $this->store->retrieveOneById($entry_id, $this->user->getId()); | 526 | foreach($entry_ids as $entry_id) { |
532 | if (!$entry) { | 527 | $entry = $this->store->retrieveOneById($entry_id, $this->user->getId()); |
533 | $this->messages->add('e', _('Article not found!')); | 528 | if (!$entry) { |
534 | Tools::logm('error : article not found'); | 529 | $this->messages->add('e', _('Article not found!')); |
535 | Tools::redirect(); | 530 | Tools::logm('error : article not found'); |
536 | } | 531 | Tools::redirect(); |
537 | foreach($tags as $key => $tag_value) { | ||
538 | $value = trim($tag_value); | ||
539 | $tag = $this->store->retrieveTagByValue($value); | ||
540 | |||
541 | if (is_null($tag)) { | ||
542 | # we create the tag | ||
543 | $tag = $this->store->createTag($value); | ||
544 | $sequence = ''; | ||
545 | if (STORAGE == 'postgres') { | ||
546 | $sequence = 'tags_id_seq'; | ||
547 | } | ||
548 | $tag_id = $this->store->getLastId($sequence); | ||
549 | } | 532 | } |
550 | else { | 533 | //get all already set tags to preven duplicates |
551 | $tag_id = $tag['id']; | 534 | $already_set_tags = array(); |
535 | $entry_tags = $this->store->retrieveTagsByEntry($entry_id); | ||
536 | foreach ($entry_tags as $tag) { | ||
537 | $already_set_tags[] = $tag['value']; | ||
538 | } | ||
539 | foreach($tags as $key => $tag_value) { | ||
540 | $value = trim($tag_value); | ||
541 | if ($value && !in_array($value, $already_set_tags)) { | ||
542 | $tag = $this->store->retrieveTagByValue($value); | ||
543 | if (is_null($tag)) { | ||
544 | # we create the tag | ||
545 | $tag = $this->store->createTag($value); | ||
546 | $sequence = ''; | ||
547 | if (STORAGE == 'postgres') { | ||
548 | $sequence = 'tags_id_seq'; | ||
549 | } | ||
550 | $tag_id = $this->store->getLastId($sequence); | ||
551 | } | ||
552 | else { | ||
553 | $tag_id = $tag['id']; | ||
554 | } | ||
555 | |||
556 | # we assign the tag to the article | ||
557 | $this->store->setTagToEntry($tag_id, $entry_id); | ||
558 | } | ||
552 | } | 559 | } |
553 | |||
554 | # we assign the tag to the article | ||
555 | $this->store->setTagToEntry($tag_id, $entry_id); | ||
556 | } | ||
557 | if(!$import) { | ||
558 | Tools::redirect(); | ||
559 | } | 560 | } |
561 | $this->messages->add('s', _('The tag has been applied successfully')); | ||
562 | Tools::logm('The tag has been applied successfully'); | ||
563 | Tools::redirect(); | ||
560 | break; | 564 | break; |
561 | case 'remove_tag' : | 565 | case 'remove_tag' : |
562 | $tag_id = $_GET['tag_id']; | 566 | $tag_id = $_GET['tag_id']; |
@@ -567,6 +571,11 @@ class Poche | |||
567 | Tools::redirect(); | 571 | Tools::redirect(); |
568 | } | 572 | } |
569 | $this->store->removeTagForEntry($id, $tag_id); | 573 | $this->store->removeTagForEntry($id, $tag_id); |
574 | Tools::logm('tag entry deleted'); | ||
575 | if ($this->store->cleanUnusedTag($tag_id)) { | ||
576 | Tools::logm('tag deleted'); | ||
577 | } | ||
578 | $this->messages->add('s', _('The tag has been successfully deleted')); | ||
570 | Tools::redirect(); | 579 | Tools::redirect(); |
571 | break; | 580 | break; |
572 | default: | 581 | default: |
@@ -581,24 +590,32 @@ class Poche | |||
581 | switch ($view) | 590 | switch ($view) |
582 | { | 591 | { |
583 | case 'config': | 592 | case 'config': |
584 | $dev = trim($this->getPocheVersion('dev')); | 593 | $dev_infos = $this->getPocheVersion('dev'); |
585 | $prod = trim($this->getPocheVersion('prod')); | 594 | $dev = trim($dev_infos[0]); |
595 | $check_time_dev = date('d-M-Y H:i', $dev_infos[1]); | ||
596 | $prod_infos = $this->getPocheVersion('prod'); | ||
597 | $prod = trim($prod_infos[0]); | ||
598 | $check_time_prod = date('d-M-Y H:i', $prod_infos[1]); | ||
586 | $compare_dev = version_compare(POCHE, $dev); | 599 | $compare_dev = version_compare(POCHE, $dev); |
587 | $compare_prod = version_compare(POCHE, $prod); | 600 | $compare_prod = version_compare(POCHE, $prod); |
588 | $themes = $this->getInstalledThemes(); | 601 | $themes = $this->getInstalledThemes(); |
589 | $languages = $this->getInstalledLanguages(); | 602 | $languages = $this->getInstalledLanguages(); |
590 | $token = $this->user->getConfigValue('token'); | 603 | $token = $this->user->getConfigValue('token'); |
591 | $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; | 604 | $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; |
605 | $only_user = ($this->store->listUsers() > 1) ? false : true; | ||
592 | $tpl_vars = array( | 606 | $tpl_vars = array( |
593 | 'themes' => $themes, | 607 | 'themes' => $themes, |
594 | 'languages' => $languages, | 608 | 'languages' => $languages, |
595 | 'dev' => $dev, | 609 | 'dev' => $dev, |
596 | 'prod' => $prod, | 610 | 'prod' => $prod, |
611 | 'check_time_dev' => $check_time_dev, | ||
612 | 'check_time_prod' => $check_time_prod, | ||
597 | 'compare_dev' => $compare_dev, | 613 | 'compare_dev' => $compare_dev, |
598 | 'compare_prod' => $compare_prod, | 614 | 'compare_prod' => $compare_prod, |
599 | 'token' => $token, | 615 | 'token' => $token, |
600 | 'user_id' => $this->user->getId(), | 616 | 'user_id' => $this->user->getId(), |
601 | 'http_auth' => $http_auth, | 617 | 'http_auth' => $http_auth, |
618 | 'only_user' => $only_user | ||
602 | ); | 619 | ); |
603 | Tools::logm('config view'); | 620 | Tools::logm('config view'); |
604 | break; | 621 | break; |
@@ -619,13 +636,36 @@ class Poche | |||
619 | break; | 636 | break; |
620 | case 'tags': | 637 | case 'tags': |
621 | $token = $this->user->getConfigValue('token'); | 638 | $token = $this->user->getConfigValue('token'); |
622 | $tags = $this->store->retrieveAllTags($this->user->getId()); | 639 | //if term is set - search tags for this term |
640 | $term = Tools::checkVar('term'); | ||
641 | $tags = $this->store->retrieveAllTags($this->user->getId(), $term); | ||
642 | if (Tools::isAjaxRequest()) { | ||
643 | $result = array(); | ||
644 | foreach ($tags as $tag) { | ||
645 | $result[] = $tag['value']; | ||
646 | } | ||
647 | echo json_encode($result); | ||
648 | exit; | ||
649 | } | ||
623 | $tpl_vars = array( | 650 | $tpl_vars = array( |
624 | 'token' => $token, | 651 | 'token' => $token, |
625 | 'user_id' => $this->user->getId(), | 652 | 'user_id' => $this->user->getId(), |
626 | 'tags' => $tags, | 653 | 'tags' => $tags, |
627 | ); | 654 | ); |
628 | break; | 655 | break; |
656 | case 'search': | ||
657 | if (isset($_GET['search'])) { | ||
658 | $search = filter_var($_GET['search'], FILTER_SANITIZE_STRING); | ||
659 | $tpl_vars['entries'] = $this->store->search($search, $this->user->getId()); | ||
660 | $count = count($tpl_vars['entries']); | ||
661 | $this->pagination->set_total($count); | ||
662 | $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')), | ||
663 | $this->pagination->page_links('?view=' . $view . '?search=' . $search . '&sort=' . $_SESSION['sort'] . '&' )); | ||
664 | $tpl_vars['page_links'] = $page_links; | ||
665 | $tpl_vars['nb_results'] = $count; | ||
666 | $tpl_vars['search_term'] = $search; | ||
667 | } | ||
668 | break; | ||
629 | case 'view': | 669 | case 'view': |
630 | $entry = $this->store->retrieveOneById($id, $this->user->getId()); | 670 | $entry = $this->store->retrieveOneById($id, $this->user->getId()); |
631 | if ($entry != NULL) { | 671 | if ($entry != NULL) { |
@@ -660,8 +700,9 @@ class Poche | |||
660 | 'entries' => '', | 700 | 'entries' => '', |
661 | 'page_links' => '', | 701 | 'page_links' => '', |
662 | 'nb_results' => '', | 702 | 'nb_results' => '', |
703 | 'listmode' => (isset($_COOKIE['listmode']) ? true : false), | ||
663 | ); | 704 | ); |
664 | 705 | ||
665 | //if id is given - we retrive entries by tag: id is tag id | 706 | //if id is given - we retrive entries by tag: id is tag id |
666 | if ($id) { | 707 | if ($id) { |
667 | $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); | 708 | $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); |
@@ -686,8 +727,8 @@ class Poche | |||
686 | } | 727 | } |
687 | 728 | ||
688 | /** | 729 | /** |
689 | * update the password of the current user. | 730 | * update the password of the current user. |
690 | * if MODE_DEMO is TRUE, the password can't be updated. | 731 | * if MODE_DEMO is TRUE, the password can't be updated. |
691 | * @todo add the return value | 732 | * @todo add the return value |
692 | * @todo set the new password in function header like this updatePassword($newPassword) | 733 | * @todo set the new password in function header like this updatePassword($newPassword) |
693 | * @return boolean | 734 | * @return boolean |
@@ -715,42 +756,44 @@ class Poche | |||
715 | } | 756 | } |
716 | } | 757 | } |
717 | } | 758 | } |
718 | 759 | ||
719 | public function updateTheme() | 760 | public function updateTheme() |
720 | { | 761 | { |
721 | # no data | 762 | # no data |
722 | if (empty($_POST['theme'])) { | 763 | if (empty($_POST['theme'])) { |
723 | } | 764 | } |
724 | 765 | ||
725 | # we are not going to change it to the current theme... | 766 | # we are not going to change it to the current theme... |
726 | if ($_POST['theme'] == $this->getTheme()) { | 767 | if ($_POST['theme'] == $this->getTheme()) { |
727 | $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!')); | 768 | $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!')); |
728 | Tools::redirect('?view=config'); | 769 | Tools::redirect('?view=config'); |
729 | } | 770 | } |
730 | 771 | ||
731 | $themes = $this->getInstalledThemes(); | 772 | $themes = $this->getInstalledThemes(); |
732 | $actualTheme = false; | 773 | $actualTheme = false; |
733 | 774 | ||
734 | foreach (array_keys($themes) as $theme) { | 775 | foreach (array_keys($themes) as $theme) { |
735 | if ($theme == $_POST['theme']) { | 776 | if ($theme == $_POST['theme']) { |
736 | $actualTheme = true; | 777 | $actualTheme = true; |
737 | break; | 778 | break; |
738 | } | 779 | } |
739 | } | 780 | } |
740 | 781 | ||
741 | if (! $actualTheme) { | 782 | if (! $actualTheme) { |
742 | $this->messages->add('e', _('that theme does not seem to be installed')); | 783 | $this->messages->add('e', _('that theme does not seem to be installed')); |
743 | Tools::redirect('?view=config'); | 784 | Tools::redirect('?view=config'); |
744 | } | 785 | } |
745 | 786 | ||
746 | $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']); | 787 | $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']); |
747 | $this->messages->add('s', _('you have changed your theme preferences')); | 788 | $this->messages->add('s', _('you have changed your theme preferences')); |
748 | 789 | ||
749 | $currentConfig = $_SESSION['poche_user']->config; | 790 | $currentConfig = $_SESSION['poche_user']->config; |
750 | $currentConfig['theme'] = $_POST['theme']; | 791 | $currentConfig['theme'] = $_POST['theme']; |
751 | 792 | ||
752 | $_SESSION['poche_user']->setConfig($currentConfig); | 793 | $_SESSION['poche_user']->setConfig($currentConfig); |
753 | 794 | ||
795 | $this->emptyCache(); | ||
796 | |||
754 | Tools::redirect('?view=config'); | 797 | Tools::redirect('?view=config'); |
755 | } | 798 | } |
756 | 799 | ||
@@ -759,39 +802,40 @@ class Poche | |||
759 | # no data | 802 | # no data |
760 | if (empty($_POST['language'])) { | 803 | if (empty($_POST['language'])) { |
761 | } | 804 | } |
762 | 805 | ||
763 | # we are not going to change it to the current language... | 806 | # we are not going to change it to the current language... |
764 | if ($_POST['language'] == $this->getLanguage()) { | 807 | if ($_POST['language'] == $this->getLanguage()) { |
765 | $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!')); | 808 | $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!')); |
766 | Tools::redirect('?view=config'); | 809 | Tools::redirect('?view=config'); |
767 | } | 810 | } |
768 | 811 | ||
769 | $languages = $this->getInstalledLanguages(); | 812 | $languages = $this->getInstalledLanguages(); |
770 | $actualLanguage = false; | 813 | $actualLanguage = false; |
771 | 814 | ||
772 | foreach ($languages as $language) { | 815 | foreach ($languages as $language) { |
773 | if ($language['value'] == $_POST['language']) { | 816 | if ($language['value'] == $_POST['language']) { |
774 | $actualLanguage = true; | 817 | $actualLanguage = true; |
775 | break; | 818 | break; |
776 | } | 819 | } |
777 | } | 820 | } |
778 | 821 | ||
779 | if (! $actualLanguage) { | 822 | if (! $actualLanguage) { |
780 | $this->messages->add('e', _('that language does not seem to be installed')); | 823 | $this->messages->add('e', _('that language does not seem to be installed')); |
781 | Tools::redirect('?view=config'); | 824 | Tools::redirect('?view=config'); |
782 | } | 825 | } |
783 | 826 | ||
784 | $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']); | 827 | $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']); |
785 | $this->messages->add('s', _('you have changed your language preferences')); | 828 | $this->messages->add('s', _('you have changed your language preferences')); |
786 | 829 | ||
787 | $currentConfig = $_SESSION['poche_user']->config; | 830 | $currentConfig = $_SESSION['poche_user']->config; |
788 | $currentConfig['language'] = $_POST['language']; | 831 | $currentConfig['language'] = $_POST['language']; |
789 | 832 | ||
790 | $_SESSION['poche_user']->setConfig($currentConfig); | 833 | $_SESSION['poche_user']->setConfig($currentConfig); |
791 | 834 | ||
835 | $this->emptyCache(); | ||
836 | |||
792 | Tools::redirect('?view=config'); | 837 | Tools::redirect('?view=config'); |
793 | } | 838 | } |
794 | |||
795 | /** | 839 | /** |
796 | * get credentials from differents sources | 840 | * get credentials from differents sources |
797 | * it redirects the user to the $referer link | 841 | * it redirects the user to the $referer link |
@@ -846,7 +890,7 @@ class Poche | |||
846 | /** | 890 | /** |
847 | * log out the poche user. It cleans the session. | 891 | * log out the poche user. It cleans the session. |
848 | * @todo add the return value | 892 | * @todo add the return value |
849 | * @return boolean | 893 | * @return boolean |
850 | */ | 894 | */ |
851 | public function logout() | 895 | public function logout() |
852 | { | 896 | { |
@@ -857,238 +901,137 @@ class Poche | |||
857 | } | 901 | } |
858 | 902 | ||
859 | /** | 903 | /** |
860 | * import from Instapaper. poche needs a ./instapaper-export.html file | 904 | * import datas into your poche |
861 | * @todo add the return value | ||
862 | * @param string $targetFile the file used for importing | ||
863 | * @return boolean | 905 | * @return boolean |
864 | */ | 906 | */ |
865 | private function importFromInstapaper($targetFile) | 907 | public function import() { |
866 | { | 908 | |
867 | # TODO gestion des articles favs | 909 | if ( isset($_FILES['file']) ) { |
868 | $html = new simple_html_dom(); | 910 | Tools::logm('Import stated: parsing file'); |
869 | $html->load_file($targetFile); | 911 | |
870 | Tools::logm('starting import from instapaper'); | 912 | // assume, that file is in json format |
871 | 913 | $str_data = file_get_contents($_FILES['file']['tmp_name']); | |
872 | $read = 0; | 914 | $data = json_decode($str_data, true); |
873 | $errors = array(); | 915 | |
874 | foreach($html->find('ol') as $ul) | 916 | if ( $data === null ) { |
875 | { | 917 | //not json - assume html |
876 | foreach($ul->find('li') as $li) | 918 | $html = new simple_html_dom(); |
877 | { | 919 | $html->load_file($_FILES['file']['tmp_name']); |
878 | $a = $li->find('a'); | 920 | $data = array(); |
879 | $url = new Url(base64_encode($a[0]->href)); | 921 | $read = 0; |
880 | $this->action('add', $url, 0, TRUE); | 922 | foreach (array('ol','ul') as $list) { |
881 | if ($read == '1') { | 923 | foreach ($html->find($list) as $ul) { |
882 | $sequence = ''; | 924 | foreach ($ul->find('li') as $li) { |
883 | if (STORAGE == 'postgres') { | 925 | $tmpEntry = array(); |
884 | $sequence = 'entries_id_seq'; | 926 | $a = $li->find('a'); |
885 | } | 927 | $tmpEntry['url'] = $a[0]->href; |
886 | $last_id = $this->store->getLastId($sequence); | 928 | $tmpEntry['tags'] = $a[0]->tags; |
887 | $this->action('toggle_archive', $url, $last_id, TRUE); | 929 | $tmpEntry['is_read'] = $read; |
888 | } | 930 | if ($tmpEntry['url']) { |
931 | $data[] = $tmpEntry; | ||
932 | } | ||
933 | } | ||
934 | # the second <ol/ul> is for read links | ||
935 | $read = ((sizeof($data) && $read)?0:1); | ||
889 | } | 936 | } |
890 | 937 | } | |
891 | # the second <ol> is for read links | ||
892 | $read = 1; | ||
893 | } | 938 | } |
894 | $this->messages->add('s', _('import from instapaper completed')); | ||
895 | Tools::logm('import from instapaper completed'); | ||
896 | Tools::redirect(); | ||
897 | } | ||
898 | 939 | ||
899 | /** | 940 | //for readability structure |
900 | * import from Pocket. poche needs a ./ril_export.html file | 941 | foreach ($data as $record) { |
901 | * @todo add the return value | 942 | if (is_array($record)) { |
902 | * @param string $targetFile the file used for importing | 943 | $data[] = $record; |
903 | * @return boolean | 944 | foreach ($record as $record2) { |
904 | */ | 945 | if (is_array($record2)) { |
905 | private function importFromPocket($targetFile) | 946 | $data[] = $record2; |
906 | { | 947 | } |
907 | # TODO gestion des articles favs | 948 | } |
908 | $html = new simple_html_dom(); | 949 | } |
909 | $html->load_file($targetFile); | 950 | } |
910 | Tools::logm('starting import from pocket'); | 951 | |
911 | 952 | $urlsInserted = array(); //urls of articles inserted | |
912 | $read = 0; | 953 | foreach ($data as $record) { |
913 | $errors = array(); | 954 | $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') ); |
914 | foreach($html->find('ul') as $ul) | 955 | if ( $url and !in_array($url, $urlsInserted) ) { |
915 | { | 956 | $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>'); |
916 | foreach($ul->find('li') as $li) | 957 | $body = (isset($record['content']) ? $record['content'] : ''); |
917 | { | 958 | $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0)); |
918 | $a = $li->find('a'); | 959 | $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) ); |
919 | $url = new Url(base64_encode($a[0]->href)); | 960 | //insert new record |
920 | $this->action('add', $url, 0, TRUE); | 961 | $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead); |
921 | $sequence = ''; | 962 | if ( $id ) { |
922 | if (STORAGE == 'postgres') { | 963 | $urlsInserted[] = $url; //add |
923 | $sequence = 'entries_id_seq'; | 964 | |
924 | } | 965 | if ( isset($record['tags']) && trim($record['tags']) ) { |
925 | $last_id = $this->store->getLastId($sequence); | 966 | //@TODO: set tags |
926 | if ($read == '1') { | 967 | |
927 | $this->action('toggle_archive', $url, $last_id, TRUE); | 968 | } |
928 | } | ||
929 | $tags = $a[0]->tags; | ||
930 | if(!empty($tags)) { | ||
931 | $this->action('add_tag',$url,$last_id,true,false,$tags); | ||
932 | } | ||
933 | } | 969 | } |
934 | 970 | } | |
935 | # the second <ul> is for read links | ||
936 | $read = 1; | ||
937 | } | 971 | } |
938 | $this->messages->add('s', _('import from pocket completed')); | ||
939 | Tools::logm('import from pocket completed'); | ||
940 | Tools::redirect(); | ||
941 | } | ||
942 | |||
943 | /** | ||
944 | * import from Readability. poche needs a ./readability file | ||
945 | * @todo add the return value | ||
946 | * @param string $targetFile the file used for importing | ||
947 | * @return boolean | ||
948 | */ | ||
949 | private function importFromReadability($targetFile) | ||
950 | { | ||
951 | # TODO gestion des articles lus / favs | ||
952 | $str_data = file_get_contents($targetFile); | ||
953 | $data = json_decode($str_data,true); | ||
954 | Tools::logm('starting import from Readability'); | ||
955 | $count = 0; | ||
956 | foreach ($data as $key => $value) { | ||
957 | $url = NULL; | ||
958 | $favorite = FALSE; | ||
959 | $archive = FALSE; | ||
960 | foreach ($value as $item) { | ||
961 | foreach ($item as $attr => $value) { | ||
962 | if ($attr == 'article__url') { | ||
963 | $url = new Url(base64_encode($value)); | ||
964 | } | ||
965 | $sequence = ''; | ||
966 | if (STORAGE == 'postgres') { | ||
967 | $sequence = 'entries_id_seq'; | ||
968 | } | ||
969 | if ($value == 'true') { | ||
970 | if ($attr == 'favorite') { | ||
971 | $favorite = TRUE; | ||
972 | } | ||
973 | if ($attr == 'archive') { | ||
974 | $archive = TRUE; | ||
975 | } | ||
976 | } | ||
977 | } | ||
978 | 972 | ||
979 | # we can add the url | 973 | $i = sizeof($urlsInserted); |
980 | if (!is_null($url) && $url->isCorrect()) { | 974 | if ( $i > 0 ) { |
981 | $this->action('add', $url, 0, TRUE); | 975 | $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".')); |
982 | $count++; | ||
983 | if ($favorite) { | ||
984 | $last_id = $this->store->getLastId($sequence); | ||
985 | $this->action('toggle_fav', $url, $last_id, TRUE); | ||
986 | } | ||
987 | if ($archive) { | ||
988 | $last_id = $this->store->getLastId($sequence); | ||
989 | $this->action('toggle_archive', $url, $last_id, TRUE); | ||
990 | } | ||
991 | } | ||
992 | } | ||
993 | } | 976 | } |
994 | $this->messages->add('s', _('import from Readability completed. ' . $count . ' new links.')); | 977 | Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).'); |
995 | Tools::logm('import from Readability completed'); | 978 | } |
979 | //file parsing finished here | ||
980 | |||
981 | //now download article contents if any | ||
982 | |||
983 | //check if we need to download any content | ||
984 | $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId()); | ||
985 | if ( $recordsDownloadRequired == 0 ) { | ||
986 | //nothing to download | ||
987 | $this->messages->add('s', _('Import finished.')); | ||
988 | Tools::logm('Import finished completely'); | ||
996 | Tools::redirect(); | 989 | Tools::redirect(); |
997 | } | 990 | } |
991 | else { | ||
992 | //if just inserted - don't download anything, download will start in next reload | ||
993 | if ( !isset($_FILES['file']) ) { | ||
994 | //download next batch | ||
995 | Tools::logm('Fetching next batch of articles...'); | ||
996 | $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT); | ||
998 | 997 | ||
999 | /** | 998 | $purifier = $this->getPurifier(); |
1000 | * import from Poche exported file | ||
1001 | * @param string $targetFile the file used for importing | ||
1002 | * @return boolean | ||
1003 | */ | ||
1004 | private function importFromPoche($targetFile) | ||
1005 | { | ||
1006 | $str_data = file_get_contents($targetFile); | ||
1007 | $data = json_decode($str_data,true); | ||
1008 | Tools::logm('starting import from Poche'); | ||
1009 | 999 | ||
1000 | foreach ($items as $item) { | ||
1001 | $url = new Url(base64_encode($item['url'])); | ||
1002 | Tools::logm('Fetching article '.$item['id']); | ||
1003 | $content = Tools::getPageContent($url); | ||
1010 | 1004 | ||
1011 | $sequence = ''; | 1005 | $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled')); |
1012 | if (STORAGE == 'postgres') { | 1006 | $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined')); |
1013 | $sequence = 'entries_id_seq'; | ||
1014 | } | ||
1015 | 1007 | ||
1016 | $count = 0; | 1008 | //clean content to prevent xss attack |
1017 | foreach ($data as $value) { | 1009 | $title = $purifier->purify($title); |
1018 | 1010 | $body = $purifier->purify($body); | |
1019 | $url = new Url(base64_encode($value['url'])); | 1011 | |
1020 | $favorite = ($value['is_fav'] == -1); | 1012 | $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId()); |
1021 | $archive = ($value['is_read'] == -1); | 1013 | Tools::logm('Article '.$item['id'].' updated.'); |
1022 | 1014 | } | |
1023 | # we can add the url | ||
1024 | if (!is_null($url) && $url->isCorrect()) { | ||
1025 | |||
1026 | $this->action('add', $url, 0, TRUE); | ||
1027 | |||
1028 | $count++; | ||
1029 | if ($favorite) { | ||
1030 | $last_id = $this->store->getLastId($sequence); | ||
1031 | $this->action('toggle_fav', $url, $last_id, TRUE); | ||
1032 | } | ||
1033 | if ($archive) { | ||
1034 | $last_id = $this->store->getLastId($sequence); | ||
1035 | $this->action('toggle_archive', $url, $last_id, TRUE); | ||
1036 | } | ||
1037 | } | ||
1038 | |||
1039 | } | ||
1040 | $this->messages->add('s', _('import from Poche completed. ' . $count . ' new links.')); | ||
1041 | Tools::logm('import from Poche completed'); | ||
1042 | Tools::redirect(); | ||
1043 | } | ||
1044 | 1015 | ||
1045 | /** | ||
1046 | * import datas into your poche | ||
1047 | * @param string $from name of the service to import : pocket, instapaper or readability | ||
1048 | * @todo add the return value | ||
1049 | * @return boolean | ||
1050 | */ | ||
1051 | public function import($from) | ||
1052 | { | ||
1053 | $providers = array( | ||
1054 | 'pocket' => 'importFromPocket', | ||
1055 | 'readability' => 'importFromReadability', | ||
1056 | 'instapaper' => 'importFromInstapaper', | ||
1057 | 'poche' => 'importFromPoche', | ||
1058 | ); | ||
1059 | |||
1060 | if (! isset($providers[$from])) { | ||
1061 | $this->messages->add('e', _('Unknown import provider.')); | ||
1062 | Tools::redirect(); | ||
1063 | } | ||
1064 | |||
1065 | $targetDefinition = 'IMPORT_' . strtoupper($from) . '_FILE'; | ||
1066 | $targetFile = constant($targetDefinition); | ||
1067 | |||
1068 | if (! defined($targetDefinition)) { | ||
1069 | $this->messages->add('e', _('Incomplete inc/poche/define.inc.php file, please define "' . $targetDefinition . '".')); | ||
1070 | Tools::redirect(); | ||
1071 | } | ||
1072 | |||
1073 | if (! file_exists($targetFile)) { | ||
1074 | $this->messages->add('e', _('Could not find required "' . $targetFile . '" import file.')); | ||
1075 | Tools::redirect(); | ||
1076 | } | 1016 | } |
1077 | 1017 | } | |
1078 | $this->$providers[$from]($targetFile); | 1018 | |
1019 | return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) ); | ||
1079 | } | 1020 | } |
1080 | 1021 | ||
1081 | /** | 1022 | /** |
1082 | * export poche entries in json | 1023 | * export poche entries in json |
1083 | * @return json all poche entries | 1024 | * @return json all poche entries |
1084 | */ | 1025 | */ |
1085 | public function export() | 1026 | public function export() { |
1086 | { | 1027 | $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json"; |
1087 | $entries = $this->store->retrieveAll($this->user->getId()); | 1028 | header('Content-Disposition: attachment; filename='.$filename); |
1088 | echo $this->tpl->render('export.twig', array( | 1029 | |
1089 | 'export' => Tools::renderJson($entries), | 1030 | $entries = $this->store->retrieveAll($this->user->getId()); |
1090 | )); | 1031 | echo $this->tpl->render('export.twig', array( |
1091 | Tools::logm('export view'); | 1032 | 'export' => Tools::renderJson($entries), |
1033 | )); | ||
1034 | Tools::logm('export view'); | ||
1092 | } | 1035 | } |
1093 | 1036 | ||
1094 | /** | 1037 | /** |
@@ -1096,35 +1039,42 @@ class Poche | |||
1096 | * @param string $which 'prod' or 'dev' | 1039 | * @param string $which 'prod' or 'dev' |
1097 | * @return string latest $which version | 1040 | * @return string latest $which version |
1098 | */ | 1041 | */ |
1099 | private function getPocheVersion($which = 'prod') | 1042 | private function getPocheVersion($which = 'prod') { |
1100 | { | 1043 | $cache_file = CACHE . '/' . $which; |
1101 | $cache_file = CACHE . '/' . $which; | 1044 | $check_time = time(); |
1102 | 1045 | ||
1103 | # checks if the cached version file exists | 1046 | # checks if the cached version file exists |
1104 | if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) { | 1047 | if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) { |
1105 | $version = file_get_contents($cache_file); | 1048 | $version = file_get_contents($cache_file); |
1106 | } else { | 1049 | $check_time = filemtime($cache_file); |
1107 | $version = file_get_contents('http://static.wallabag.org/versions/' . $which); | 1050 | } else { |
1108 | file_put_contents($cache_file, $version, LOCK_EX); | 1051 | $version = file_get_contents('http://static.wallabag.org/versions/' . $which); |
1109 | } | 1052 | file_put_contents($cache_file, $version, LOCK_EX); |
1110 | return $version; | 1053 | } |
1054 | return array($version, $check_time); | ||
1111 | } | 1055 | } |
1112 | 1056 | ||
1113 | public function generateToken() | 1057 | public function generateToken() |
1114 | { | 1058 | { |
1115 | if (ini_get('open_basedir') === '') { | 1059 | if (ini_get('open_basedir') === '') { |
1116 | $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15); | 1060 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
1117 | } | 1061 | echo 'This is a server using Windows!'; |
1118 | else { | 1062 | // alternative to /dev/urandom for Windows |
1119 | $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); | 1063 | $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); |
1120 | } | 1064 | } else { |
1121 | 1065 | $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15); | |
1122 | $token = str_replace('+', '', $token); | 1066 | } |
1123 | $this->store->updateUserConfig($this->user->getId(), 'token', $token); | 1067 | } |
1124 | $currentConfig = $_SESSION['poche_user']->config; | 1068 | else { |
1125 | $currentConfig['token'] = $token; | 1069 | $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); |
1126 | $_SESSION['poche_user']->setConfig($currentConfig); | 1070 | } |
1127 | Tools::redirect(); | 1071 | |
1072 | $token = str_replace('+', '', $token); | ||
1073 | $this->store->updateUserConfig($this->user->getId(), 'token', $token); | ||
1074 | $currentConfig = $_SESSION['poche_user']->config; | ||
1075 | $currentConfig['token'] = $token; | ||
1076 | $_SESSION['poche_user']->setConfig($currentConfig); | ||
1077 | Tools::redirect(); | ||
1128 | } | 1078 | } |
1129 | 1079 | ||
1130 | public function generateFeeds($token, $user_id, $tag_id, $type = 'home') | 1080 | public function generateFeeds($token, $user_id, $tag_id, $type = 'home') |
@@ -1132,8 +1082,11 @@ class Poche | |||
1132 | $allowed_types = array('home', 'fav', 'archive', 'tag'); | 1082 | $allowed_types = array('home', 'fav', 'archive', 'tag'); |
1133 | $config = $this->store->getConfigUser($user_id); | 1083 | $config = $this->store->getConfigUser($user_id); |
1134 | 1084 | ||
1135 | if (!in_array($type, $allowed_types) || | 1085 | if ($config == null) { |
1136 | $token != $config['token']) { | 1086 | die(sprintf(_('User with this id (%d) does not exist.'), $user_id)); |
1087 | } | ||
1088 | |||
1089 | if (!in_array($type, $allowed_types) || $token != $config['token']) { | ||
1137 | die(_('Uh, there is a problem while generating feeds.')); | 1090 | die(_('Uh, there is a problem while generating feeds.')); |
1138 | } | 1091 | } |
1139 | // Check the token | 1092 | // Check the token |
@@ -1141,8 +1094,9 @@ class Poche | |||
1141 | $feed = new FeedWriter(RSS2); | 1094 | $feed = new FeedWriter(RSS2); |
1142 | $feed->setTitle('wallabag — ' . $type . ' feed'); | 1095 | $feed->setTitle('wallabag — ' . $type . ' feed'); |
1143 | $feed->setLink(Tools::getPocheUrl()); | 1096 | $feed->setLink(Tools::getPocheUrl()); |
1144 | $feed->setChannelElement('updated', date(DATE_RSS , time())); | 1097 | $feed->setChannelElement('pubDate', date(DATE_RSS , time())); |
1145 | $feed->setChannelElement('author', 'wallabag'); | 1098 | $feed->setChannelElement('generator', 'wallabag'); |
1099 | $feed->setDescription('wallabag ' . $type . ' elements'); | ||
1146 | 1100 | ||
1147 | if ($type == 'tag') { | 1101 | if ($type == 'tag') { |
1148 | $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id); | 1102 | $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id); |
@@ -1155,7 +1109,8 @@ class Poche | |||
1155 | foreach ($entries as $entry) { | 1109 | foreach ($entries as $entry) { |
1156 | $newItem = $feed->createNewItem(); | 1110 | $newItem = $feed->createNewItem(); |
1157 | $newItem->setTitle($entry['title']); | 1111 | $newItem->setTitle($entry['title']); |
1158 | $newItem->setLink(Tools::getPocheUrl() . '?view=view&id=' . $entry['id']); | 1112 | $newItem->setSource(Tools::getPocheUrl() . '?view=view&id=' . $entry['id']); |
1113 | $newItem->setLink($entry['url']); | ||
1159 | $newItem->setDate(time()); | 1114 | $newItem->setDate(time()); |
1160 | $newItem->setDescription($entry['content']); | 1115 | $newItem->setDescription($entry['content']); |
1161 | $feed->addItem($newItem); | 1116 | $feed->addItem($newItem); |
@@ -1181,4 +1136,132 @@ class Poche | |||
1181 | $this->messages->add('s', _('Cache deleted.')); | 1136 | $this->messages->add('s', _('Cache deleted.')); |
1182 | Tools::redirect(); | 1137 | Tools::redirect(); |
1183 | } | 1138 | } |
1139 | |||
1140 | /** | ||
1141 | * return new purifier object with actual config | ||
1142 | */ | ||
1143 | protected function getPurifier() { | ||
1144 | $config = HTMLPurifier_Config::createDefault(); | ||
1145 | $config->set('Cache.SerializerPath', CACHE); | ||
1146 | $config->set('HTML.SafeIframe', true); | ||
1147 | |||
1148 | //allow YouTube, Vimeo and dailymotion videos | ||
1149 | $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%'); | ||
1150 | |||
1151 | return new HTMLPurifier($config); | ||
1152 | } | ||
1153 | |||
1154 | /** | ||
1155 | * handle epub | ||
1156 | */ | ||
1157 | public function createEpub() { | ||
1158 | |||
1159 | switch ($_GET['method']) { | ||
1160 | case 'id': | ||
1161 | $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT); | ||
1162 | $entry = $this->store->retrieveOneById($entryID, $this->user->getId()); | ||
1163 | $entries = array($entry); | ||
1164 | $bookTitle = $entry['title']; | ||
1165 | $bookFileName = substr($bookTitle, 0, 200); | ||
1166 | break; | ||
1167 | case 'all': | ||
1168 | $entries = $this->store->retrieveAll($this->user->getId()); | ||
1169 | $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system | ||
1170 | $bookFileName = _('Allarticles') . date(_('dmY')); | ||
1171 | break; | ||
1172 | case 'tag': | ||
1173 | $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING); | ||
1174 | $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag); | ||
1175 | $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround. | ||
1176 | $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId()); | ||
1177 | $bookTitle = sprintf(_('Articles tagged %s'),$tag); | ||
1178 | $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200); | ||
1179 | break; | ||
1180 | case 'category': | ||
1181 | $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING); | ||
1182 | $entries = $this->store->getEntriesByView($category,$this->user->getId()); | ||
1183 | $bookTitle = sprintf(_('All articles in category %s'), $category); | ||
1184 | $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200); | ||
1185 | break; | ||
1186 | case 'search': | ||
1187 | $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING); | ||
1188 | $entries = $this->store->search($search,$this->user->getId()); | ||
1189 | $bookTitle = sprintf(_('All articles for search %s'), $search); | ||
1190 | $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200); | ||
1191 | break; | ||
1192 | case 'default': | ||
1193 | die(_('Uh, there is a problem while generating epub.')); | ||
1194 | |||
1195 | } | ||
1196 | |||
1197 | $content_start = | ||
1198 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | ||
1199 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
1200 | . "<head>" | ||
1201 | . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | ||
1202 | . "<title>wallabag articles book</title>\n" | ||
1203 | . "</head>\n" | ||
1204 | . "<body>\n"; | ||
1205 | |||
1206 | $bookEnd = "</body>\n</html>\n"; | ||
1207 | |||
1208 | $log = new Logger("wallabag", TRUE); | ||
1209 | $fileDir = CACHE; | ||
1210 | |||
1211 | $book = new EPub(EPub::BOOK_VERSION_EPUB3, DEBUG_POCHE); | ||
1212 | $log->logLine("new EPub()"); | ||
1213 | $log->logLine("EPub class version: " . EPub::VERSION); | ||
1214 | $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION); | ||
1215 | $log->logLine("Zip version: " . Zip::VERSION); | ||
1216 | $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL()); | ||
1217 | $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL()); | ||
1218 | |||
1219 | $book->setTitle(_('wallabag\'s articles')); | ||
1220 | $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. | ||
1221 | //$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. | ||
1222 | $book->setDescription(_("Some articles saved on my wallabag")); | ||
1223 | $book->setAuthor("wallabag","wallabag"); | ||
1224 | $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :) | ||
1225 | $book->setDate(time()); // Strictly not needed as the book date defaults to time(). | ||
1226 | //$book->setRights("Copyright and licence information specific for the book."); // As this is generated, this _could_ contain the name or licence information of the user who purchased the book, if needed. If this is used that way, the identifier must also be made unique for the book. | ||
1227 | $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); | ||
1228 | |||
1229 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP"); | ||
1230 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag"); | ||
1231 | |||
1232 | $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n"; | ||
1233 | |||
1234 | $log->logLine("Add Cover"); | ||
1235 | |||
1236 | $fullTitle = "<h1> " . $bookTitle . "</h1>\n"; | ||
1237 | |||
1238 | $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle); | ||
1239 | |||
1240 | $cover = $content_start . '<div style="text-align:center;"><p>' . _('Produced by wallabag with PHPePub') . '</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>' . $bookEnd; | ||
1241 | |||
1242 | //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE); | ||
1243 | $book->addChapter("Notices", "Cover2.html", $cover); | ||
1244 | |||
1245 | $book->buildTOC(); | ||
1246 | |||
1247 | foreach ($entries as $entry) { //set tags as subjects | ||
1248 | $tags = $this->store->retrieveTagsByEntry($entry['id']); | ||
1249 | foreach ($tags as $tag) { | ||
1250 | $book->setSubject($tag['value']); | ||
1251 | } | ||
1252 | |||
1253 | $log->logLine("Set up parameters"); | ||
1254 | |||
1255 | $chapter = $content_start . $entry['content'] . $bookEnd; | ||
1256 | $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); | ||
1257 | $log->logLine("Added chapter " . $entry['title']); | ||
1258 | } | ||
1259 | |||
1260 | if (DEBUG_POCHE) { | ||
1261 | $epuplog = $book->getLog(); | ||
1262 | $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation | ||
1263 | } | ||
1264 | $book->finalize(); | ||
1265 | $zipData = $book->sendBook($bookFileName); | ||
1266 | } | ||
1184 | } | 1267 | } |
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php index 4ed28ed1..cc01f403 100644..100755 --- a/inc/poche/Tools.class.php +++ b/inc/poche/Tools.class.php | |||
@@ -7,7 +7,7 @@ | |||
7 | * @copyright 2013 | 7 | * @copyright 2013 |
8 | * @license http://www.wtfpl.net/ see COPYING file | 8 | * @license http://www.wtfpl.net/ see COPYING file |
9 | */ | 9 | */ |
10 | 10 | ||
11 | class Tools | 11 | class Tools |
12 | { | 12 | { |
13 | public static function initPhp() | 13 | public static function initPhp() |
@@ -18,8 +18,6 @@ class Tools | |||
18 | die(_('Oops, it seems you don\'t have PHP 5.')); | 18 | die(_('Oops, it seems you don\'t have PHP 5.')); |
19 | } | 19 | } |
20 | 20 | ||
21 | error_reporting(E_ALL); | ||
22 | |||
23 | function stripslashesDeep($value) { | 21 | function stripslashesDeep($value) { |
24 | return is_array($value) | 22 | return is_array($value) |
25 | ? array_map('stripslashesDeep', $value) | 23 | ? array_map('stripslashesDeep', $value) |
@@ -42,7 +40,7 @@ class Tools | |||
42 | && (strtolower($_SERVER['HTTPS']) == 'on')) | 40 | && (strtolower($_SERVER['HTTPS']) == 'on')) |
43 | || (isset($_SERVER["SERVER_PORT"]) | 41 | || (isset($_SERVER["SERVER_PORT"]) |
44 | && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection. | 42 | && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection. |
45 | || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection | 43 | || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection |
46 | && $_SERVER["SERVER_PORT"] == SSL_PORT) | 44 | && $_SERVER["SERVER_PORT"] == SSL_PORT) |
47 | || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) | 45 | || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) |
48 | && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); | 46 | && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); |
@@ -59,8 +57,14 @@ class Tools | |||
59 | return $scriptname; | 57 | return $scriptname; |
60 | } | 58 | } |
61 | 59 | ||
60 | $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])); | ||
61 | |||
62 | if (strpos($host, ':') !== false) { | ||
63 | $serverport = ''; | ||
64 | } | ||
65 | |||
62 | return 'http' . ($https ? 's' : '') . '://' | 66 | return 'http' . ($https ? 's' : '') . '://' |
63 | . $_SERVER["HTTP_HOST"] . $serverport . $scriptname; | 67 | . $host . $serverport . $scriptname; |
64 | } | 68 | } |
65 | 69 | ||
66 | public static function redirect($url = '') | 70 | public static function redirect($url = '') |
@@ -148,7 +152,7 @@ class Tools | |||
148 | ); | 152 | ); |
149 | 153 | ||
150 | # only download page lesser than 4MB | 154 | # only download page lesser than 4MB |
151 | $data = @file_get_contents($url, false, $context, -1, 4000000); | 155 | $data = @file_get_contents($url, false, $context, -1, 4000000); |
152 | 156 | ||
153 | if (isset($http_response_header) and isset($http_response_header[0])) { | 157 | if (isset($http_response_header) and isset($http_response_header[0])) { |
154 | $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE)); | 158 | $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE)); |
@@ -193,14 +197,14 @@ class Tools | |||
193 | 197 | ||
194 | public static function logm($message) | 198 | public static function logm($message) |
195 | { | 199 | { |
196 | if (DEBUG_POCHE) { | 200 | if (DEBUG_POCHE && php_sapi_name() != 'cli') { |
197 | $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n"; | 201 | $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n"; |
198 | file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND); | 202 | file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND); |
199 | error_log('DEBUG POCHE : ' . $message); | 203 | error_log('DEBUG POCHE : ' . $message); |
200 | } | 204 | } |
201 | } | 205 | } |
202 | 206 | ||
203 | public static function encodeString($string) | 207 | public static function encodeString($string) |
204 | { | 208 | { |
205 | return sha1($string . SALT); | 209 | return sha1($string . SALT); |
206 | } | 210 | } |
@@ -212,7 +216,7 @@ class Tools | |||
212 | 216 | ||
213 | public static function getDomain($url) | 217 | public static function getDomain($url) |
214 | { | 218 | { |
215 | return parse_url($url, PHP_URL_HOST); | 219 | return parse_url($url, PHP_URL_HOST); |
216 | } | 220 | } |
217 | 221 | ||
218 | public static function getReadingTime($text) { | 222 | public static function getReadingTime($text) { |
@@ -241,7 +245,6 @@ class Tools | |||
241 | } | 245 | } |
242 | } | 246 | } |
243 | 247 | ||
244 | |||
245 | public static function download_db() { | 248 | public static function download_db() { |
246 | header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); | 249 | header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); |
247 | self::status(200); | 250 | self::status(200); |
@@ -252,4 +255,74 @@ class Tools | |||
252 | 255 | ||
253 | exit; | 256 | exit; |
254 | } | 257 | } |
258 | |||
259 | public static function getPageContent(Url $url) | ||
260 | { | ||
261 | // Saving and clearing context | ||
262 | $REAL = array(); | ||
263 | foreach( $GLOBALS as $key => $value ) { | ||
264 | if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) { | ||
265 | $GLOBALS[$key] = array(); | ||
266 | $REAL[$key] = $value; | ||
267 | } | ||
268 | } | ||
269 | // Saving and clearing session | ||
270 | if ( isset($_SESSION) ) { | ||
271 | $REAL_SESSION = array(); | ||
272 | foreach( $_SESSION as $key => $value ) { | ||
273 | $REAL_SESSION[$key] = $value; | ||
274 | unset($_SESSION[$key]); | ||
275 | } | ||
276 | } | ||
277 | |||
278 | // Running code in different context | ||
279 | $scope = function() { | ||
280 | extract( func_get_arg(1) ); | ||
281 | $_GET = $_REQUEST = array( | ||
282 | "url" => $url->getUrl(), | ||
283 | "max" => 5, | ||
284 | "links" => "preserve", | ||
285 | "exc" => "", | ||
286 | "format" => "json", | ||
287 | "submit" => "Create Feed" | ||
288 | ); | ||
289 | ob_start(); | ||
290 | require func_get_arg(0); | ||
291 | $json = ob_get_contents(); | ||
292 | ob_end_clean(); | ||
293 | return $json; | ||
294 | }; | ||
295 | $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) ); | ||
296 | |||
297 | // Clearing and restoring context | ||
298 | foreach( $GLOBALS as $key => $value ) { | ||
299 | if( $key != "GLOBALS" && $key != "_SESSION" ) { | ||
300 | unset($GLOBALS[$key]); | ||
301 | } | ||
302 | } | ||
303 | foreach( $REAL as $key => $value ) { | ||
304 | $GLOBALS[$key] = $value; | ||
305 | } | ||
306 | // Clearing and restoring session | ||
307 | if ( isset($REAL_SESSION) ) { | ||
308 | foreach( $_SESSION as $key => $value ) { | ||
309 | unset($_SESSION[$key]); | ||
310 | } | ||
311 | foreach( $REAL_SESSION as $key => $value ) { | ||
312 | $_SESSION[$key] = $value; | ||
313 | } | ||
314 | } | ||
315 | |||
316 | return json_decode($json, true); | ||
317 | } | ||
318 | |||
319 | /** | ||
320 | * Returns whether we handle an AJAX (XMLHttpRequest) request. | ||
321 | * @return boolean whether we handle an AJAX (XMLHttpRequest) request. | ||
322 | */ | ||
323 | public static function isAjaxRequest() | ||
324 | { | ||
325 | return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; | ||
326 | } | ||
327 | |||
255 | } | 328 | } |
diff --git a/inc/poche/config.inc.default.php b/inc/poche/config.inc.default.php new file mode 100755 index 00000000..95f727c6 --- /dev/null +++ b/inc/poche/config.inc.default.php | |||
@@ -0,0 +1,68 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * wallabag, self hostable application allowing you to not miss any content anymore | ||
4 | * | ||
5 | * @category wallabag | ||
6 | * @author Nicolas Lœuillet <nicolas@loeuillet.org> | ||
7 | * @copyright 2013 | ||
8 | * @license http://www.wtfpl.net/ see COPYING file | ||
9 | */ | ||
10 | |||
11 | @define ('SALT', ''); # put a strong string here | ||
12 | @define ('LANG', 'en_EN.utf8'); | ||
13 | |||
14 | @define ('STORAGE', 'sqlite'); # postgres, mysql or sqlite | ||
15 | |||
16 | @define ('STORAGE_SQLITE', ROOT . '/db/poche.sqlite'); # if you are using sqlite, where the database file is located | ||
17 | |||
18 | # only for postgres & mysql | ||
19 | @define ('STORAGE_SERVER', 'localhost'); | ||
20 | @define ('STORAGE_DB', 'poche'); | ||
21 | @define ('STORAGE_USER', 'poche'); | ||
22 | @define ('STORAGE_PASSWORD', 'poche'); | ||
23 | |||
24 | ################################################################################# | ||
25 | # Do not trespass unless you know what you are doing | ||
26 | ################################################################################# | ||
27 | |||
28 | // Change this if not using the standart port for SSL - i.e you server is behind sslh | ||
29 | @define ('SSL_PORT', 443); | ||
30 | |||
31 | @define ('MODE_DEMO', FALSE); | ||
32 | @define ('DEBUG_POCHE', FALSE); | ||
33 | |||
34 | //default level of error reporting in application. Developers should override it in their config.inc.php: set to E_ALL. | ||
35 | @define ('ERROR_REPORTING', E_ALL & ~E_NOTICE); | ||
36 | |||
37 | @define ('DOWNLOAD_PICTURES', FALSE); # This can slow down the process of adding articles | ||
38 | @define ('REGENERATE_PICTURES_QUALITY', 75); | ||
39 | @define ('CONVERT_LINKS_FOOTNOTES', FALSE); | ||
40 | @define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE); | ||
41 | @define ('SHARE_TWITTER', TRUE); | ||
42 | @define ('SHARE_MAIL', TRUE); | ||
43 | @define ('SHARE_SHAARLI', FALSE); | ||
44 | @define ('SHAARLI_URL', 'http://myshaarliurl.com'); | ||
45 | @define ('FLATTR', TRUE); | ||
46 | @define ('FLATTR_API', 'https://api.flattr.com/rest/v2/things/lookup/?url='); | ||
47 | @define ('NOT_FLATTRABLE', '0'); | ||
48 | @define ('FLATTRABLE', '1'); | ||
49 | @define ('FLATTRED', '2'); | ||
50 | // display or not print link in article view | ||
51 | @define ('SHOW_PRINTLINK', '1'); | ||
52 | // display or not percent of read in article view. Affects only default theme. | ||
53 | @define ('SHOW_READPERCENT', '1'); | ||
54 | @define ('ABS_PATH', 'assets/'); | ||
55 | |||
56 | @define ('DEFAULT_THEME', 'baggy'); | ||
57 | |||
58 | @define ('THEME', ROOT . '/themes'); | ||
59 | @define ('LOCALE', ROOT . '/locale'); | ||
60 | @define ('CACHE', ROOT . '/cache'); | ||
61 | |||
62 | @define ('PAGINATION', '10'); | ||
63 | |||
64 | //limit for download of articles during import | ||
65 | @define ('IMPORT_LIMIT', 5); | ||
66 | //delay between downloads (in sec) | ||
67 | @define ('IMPORT_DELAY', 5); | ||
68 | |||
diff --git a/inc/poche/config.inc.php.new b/inc/poche/config.inc.php.new deleted file mode 100755 index 8d52497b..00000000 --- a/inc/poche/config.inc.php.new +++ /dev/null | |||
@@ -1,63 +0,0 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * wallabag, self hostable application allowing you to not miss any content anymore | ||
4 | * | ||
5 | * @category wallabag | ||
6 | * @author Nicolas Lœuillet <nicolas@loeuillet.org> | ||
7 | * @copyright 2013 | ||
8 | * @license http://www.wtfpl.net/ see COPYING file | ||
9 | */ | ||
10 | |||
11 | define ('SALT', ''); # put a strong string here | ||
12 | define ('LANG', 'en_EN.utf8'); | ||
13 | |||
14 | define ('STORAGE', 'sqlite'); # postgres, mysql or sqlite | ||
15 | |||
16 | define ('STORAGE_SQLITE', ROOT . '/db/poche.sqlite'); # if you are using sqlite, where the database file is located | ||
17 | |||
18 | # only for postgres & mysql | ||
19 | define ('STORAGE_SERVER', 'localhost'); | ||
20 | define ('STORAGE_DB', 'poche'); | ||
21 | define ('STORAGE_USER', 'poche'); | ||
22 | define ('STORAGE_PASSWORD', 'poche'); | ||
23 | |||
24 | ################################################################################# | ||
25 | # Do not trespass unless you know what you are doing | ||
26 | ################################################################################# | ||
27 | |||
28 | // Change this if not using the standart port for SSL - i.e you server is behind sslh | ||
29 | define ('SSL_PORT', 443); | ||
30 | |||
31 | define ('MODE_DEMO', FALSE); | ||
32 | define ('DEBUG_POCHE', FALSE); | ||
33 | define ('DOWNLOAD_PICTURES', FALSE); | ||
34 | define ('CONVERT_LINKS_FOOTNOTES', FALSE); | ||
35 | define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE); | ||
36 | define ('SHARE_TWITTER', TRUE); | ||
37 | define ('SHARE_MAIL', TRUE); | ||
38 | define ('SHARE_SHAARLI', FALSE); | ||
39 | define ('SHAARLI_URL', 'http://myshaarliurl.com'); | ||
40 | define ('FLATTR', TRUE); | ||
41 | define ('FLATTR_API', 'https://api.flattr.com/rest/v2/things/lookup/?url='); | ||
42 | define ('NOT_FLATTRABLE', '0'); | ||
43 | define ('FLATTRABLE', '1'); | ||
44 | define ('FLATTRED', '2'); | ||
45 | define ('ABS_PATH', 'assets/'); | ||
46 | |||
47 | define ('DEFAULT_THEME', 'baggy'); | ||
48 | |||
49 | define ('THEME', ROOT . '/themes'); | ||
50 | define ('LOCALE', ROOT . '/locale'); | ||
51 | define ('CACHE', ROOT . '/cache'); | ||
52 | |||
53 | define ('PAGINATION', '10'); | ||
54 | |||
55 | define ('POCKET_FILE', '/ril_export.html'); | ||
56 | define ('READABILITY_FILE', '/readability'); | ||
57 | define ('INSTAPAPER_FILE', '/instapaper-export.html'); | ||
58 | define ('POCHE_FILE', '/poche-export'); | ||
59 | |||
60 | define ('IMPORT_POCKET_FILE', ROOT . POCKET_FILE); | ||
61 | define ('IMPORT_READABILITY_FILE', ROOT . READABILITY_FILE); | ||
62 | define ('IMPORT_INSTAPAPER_FILE', ROOT . INSTAPAPER_FILE); | ||
63 | define ('IMPORT_POCHE_FILE', ROOT . POCHE_FILE); \ No newline at end of file | ||
diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php index d22b0588..8cf86d03 100644..100755 --- a/inc/poche/global.inc.php +++ b/inc/poche/global.inc.php | |||
@@ -31,6 +31,11 @@ require_once INCLUDES . '/3rdparty/FlattrItem.class.php'; | |||
31 | 31 | ||
32 | require_once INCLUDES . '/3rdparty/htmlpurifier/HTMLPurifier.auto.php'; | 32 | require_once INCLUDES . '/3rdparty/htmlpurifier/HTMLPurifier.auto.php'; |
33 | 33 | ||
34 | # epub library | ||
35 | require_once INCLUDES . '/3rdparty/libraries/PHPePub/Logger.php'; | ||
36 | require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php'; | ||
37 | require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php'; | ||
38 | |||
34 | # Composer its autoloader for automatically loading Twig | 39 | # Composer its autoloader for automatically loading Twig |
35 | if (! file_exists(ROOT . '/vendor/autoload.php')) { | 40 | if (! file_exists(ROOT . '/vendor/autoload.php')) { |
36 | Poche::$canRenderTemplates = false; | 41 | Poche::$canRenderTemplates = false; |
@@ -38,11 +43,12 @@ if (! file_exists(ROOT . '/vendor/autoload.php')) { | |||
38 | require_once ROOT . '/vendor/autoload.php'; | 43 | require_once ROOT . '/vendor/autoload.php'; |
39 | } | 44 | } |
40 | 45 | ||
41 | # system configuration; database credentials et cetera | 46 | # system configuration; database credentials et caetera |
42 | if (! file_exists(INCLUDES . '/poche/config.inc.php')) { | 47 | if (! file_exists(INCLUDES . '/poche/config.inc.php')) { |
43 | Poche::$configFileAvailable = false; | 48 | Poche::$configFileAvailable = false; |
44 | } else { | 49 | } else { |
45 | require_once INCLUDES . '/poche/config.inc.php'; | 50 | require_once INCLUDES . '/poche/config.inc.php'; |
51 | require_once INCLUDES . '/poche/config.inc.default.php'; | ||
46 | } | 52 | } |
47 | 53 | ||
48 | if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) { | 54 | if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) { |
diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php index e4b0b160..7c319a85 100644 --- a/inc/poche/pochePictures.php +++ b/inc/poche/pochePictures.php | |||
@@ -14,6 +14,7 @@ | |||
14 | function filtre_picture($content, $url, $id) | 14 | function filtre_picture($content, $url, $id) |
15 | { | 15 | { |
16 | $matches = array(); | 16 | $matches = array(); |
17 | $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice | ||
17 | preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); | 18 | preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); |
18 | foreach($matches as $i => $link) { | 19 | foreach($matches as $i => $link) { |
19 | $link[1] = trim($link[1]); | 20 | $link[1] = trim($link[1]); |
@@ -22,8 +23,17 @@ function filtre_picture($content, $url, $id) | |||
22 | $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); | 23 | $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); |
23 | $directory = create_assets_directory($id); | 24 | $directory = create_assets_directory($id); |
24 | $fullpath = $directory . '/' . $filename; | 25 | $fullpath = $directory . '/' . $filename; |
25 | download_pictures($absolute_path, $fullpath); | 26 | |
26 | $content = str_replace($matches[$i][2], $fullpath, $content); | 27 | if (in_array($absolute_path, $processing_pictures) === true) { |
28 | // replace picture's URL only if processing is OK : already processing -> go to next picture | ||
29 | continue; | ||
30 | } | ||
31 | |||
32 | if (download_pictures($absolute_path, $fullpath) === true) { | ||
33 | $content = str_replace($matches[$i][2], $fullpath, $content); | ||
34 | } | ||
35 | |||
36 | $processing_pictures[] = $absolute_path; | ||
27 | } | 37 | } |
28 | 38 | ||
29 | } | 39 | } |
@@ -64,17 +74,55 @@ function get_absolute_link($relative_link, $url) { | |||
64 | 74 | ||
65 | /** | 75 | /** |
66 | * Téléchargement des images | 76 | * Téléchargement des images |
77 | * | ||
78 | * @return bool true if the download and processing is OK, false else | ||
67 | */ | 79 | */ |
68 | function download_pictures($absolute_path, $fullpath) | 80 | function download_pictures($absolute_path, $fullpath) |
69 | { | 81 | { |
70 | $rawdata = Tools::getFile($absolute_path); | 82 | $rawdata = Tools::getFile($absolute_path); |
83 | $fullpath = urldecode($fullpath); | ||
71 | 84 | ||
72 | if(file_exists($fullpath)) { | 85 | if(file_exists($fullpath)) { |
73 | unlink($fullpath); | 86 | unlink($fullpath); |
74 | } | 87 | } |
75 | $fp = fopen($fullpath, 'x'); | 88 | |
76 | fwrite($fp, $rawdata); | 89 | // check extension |
77 | fclose($fp); | 90 | $file_ext = strrchr($fullpath, '.'); |
91 | $whitelist = array(".jpg",".jpeg",".gif",".png"); | ||
92 | if (!(in_array($file_ext, $whitelist))) { | ||
93 | Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath); | ||
94 | return false; | ||
95 | } | ||
96 | |||
97 | // check headers | ||
98 | $imageinfo = getimagesize($absolute_path); | ||
99 | if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') { | ||
100 | Tools::logm('processed image with bad header. Skipping ' . $fullpath); | ||
101 | return false; | ||
102 | } | ||
103 | |||
104 | // regenerate image | ||
105 | $im = imagecreatefromstring($rawdata); | ||
106 | if ($im === false) { | ||
107 | Tools::logm('error while regenerating image ' . $fullpath); | ||
108 | return false; | ||
109 | } | ||
110 | |||
111 | switch ($imageinfo['mime']) { | ||
112 | case 'image/gif': | ||
113 | $result = imagegif($im, $fullpath); | ||
114 | break; | ||
115 | case 'image/jpeg': | ||
116 | case 'image/jpg': | ||
117 | $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); | ||
118 | break; | ||
119 | case 'image/png': | ||
120 | $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); | ||
121 | break; | ||
122 | } | ||
123 | imagedestroy($im); | ||
124 | |||
125 | return $result; | ||
78 | } | 126 | } |
79 | 127 | ||
80 | /** | 128 | /** |
@@ -8,10 +8,18 @@ | |||
8 | * @license http://www.wtfpl.net/ see COPYING file | 8 | * @license http://www.wtfpl.net/ see COPYING file |
9 | */ | 9 | */ |
10 | 10 | ||
11 | define ('POCHE', '1.5.2'); | 11 | define ('POCHE', '1.7.1'); |
12 | require 'check_setup.php'; | 12 | require 'check_setup.php'; |
13 | require_once 'inc/poche/global.inc.php'; | 13 | require_once 'inc/poche/global.inc.php'; |
14 | session_start(); | 14 | |
15 | # Set error reporting level | ||
16 | if (defined('ERROR_REPORTING')) { | ||
17 | error_reporting(ERROR_REPORTING); | ||
18 | } | ||
19 | |||
20 | # Start session | ||
21 | Session::$sessionName = 'poche'; | ||
22 | Session::init(); | ||
15 | 23 | ||
16 | # Start Poche | 24 | # Start Poche |
17 | $poche = new Poche(); | 25 | $poche = new Poche(); |
@@ -30,14 +38,14 @@ $tpl_vars = array( | |||
30 | 'referer' => $referer, | 38 | 'referer' => $referer, |
31 | 'view' => $view, | 39 | 'view' => $view, |
32 | 'poche_url' => Tools::getPocheUrl(), | 40 | 'poche_url' => Tools::getPocheUrl(), |
33 | 'title' => _('poche, a read it later open source system'), | 41 | 'title' => _('wallabag, a read it later open source system'), |
34 | 'token' => Session::getToken(), | 42 | 'token' => Session::getToken(), |
35 | 'theme' => $poche->getTheme() | 43 | 'theme' => $poche->getTheme() |
36 | ); | 44 | ); |
37 | 45 | ||
38 | if (! empty($notInstalledMessage)) { | 46 | if (! empty($notInstalledMessage)) { |
39 | if (! Poche::$canRenderTemplates || ! Poche::$configFileAvailable) { | 47 | if (! Poche::$canRenderTemplates || ! Poche::$configFileAvailable) { |
40 | # We cannot use Twig to display the error message | 48 | # We cannot use Twig to display the error message |
41 | echo '<h1>Errors</h1><ol>'; | 49 | echo '<h1>Errors</h1><ol>'; |
42 | foreach ($notInstalledMessage as $message) { | 50 | foreach ($notInstalledMessage as $message) { |
43 | echo '<li>' . $message . '</li>'; | 51 | echo '<li>' . $message . '</li>'; |
@@ -63,8 +71,15 @@ if (isset($_GET['login'])) { | |||
63 | } elseif (isset($_GET['config'])) { | 71 | } elseif (isset($_GET['config'])) { |
64 | # Update password | 72 | # Update password |
65 | $poche->updatePassword(); | 73 | $poche->updatePassword(); |
74 | } elseif (isset($_GET['newuser'])) { | ||
75 | $poche->createNewUser(); | ||
76 | } elseif (isset($_GET['deluser'])) { | ||
77 | $poche->deleteUser(); | ||
78 | } elseif (isset($_GET['epub'])) { | ||
79 | $poche->createEpub(); | ||
66 | } elseif (isset($_GET['import'])) { | 80 | } elseif (isset($_GET['import'])) { |
67 | $import = $poche->import($_GET['from']); | 81 | $import = $poche->import(); |
82 | $tpl_vars = array_merge($tpl_vars, $import); | ||
68 | } elseif (isset($_GET['download'])) { | 83 | } elseif (isset($_GET['download'])) { |
69 | Tools::download_db(); | 84 | Tools::download_db(); |
70 | } elseif (isset($_GET['empty-cache'])) { | 85 | } elseif (isset($_GET['empty-cache'])) { |
@@ -75,13 +90,15 @@ if (isset($_GET['login'])) { | |||
75 | $poche->updateTheme(); | 90 | $poche->updateTheme(); |
76 | } elseif (isset($_GET['updatelanguage'])) { | 91 | } elseif (isset($_GET['updatelanguage'])) { |
77 | $poche->updateLanguage(); | 92 | $poche->updateLanguage(); |
93 | } elseif (isset($_GET['uploadfile'])) { | ||
94 | $poche->uploadFile(); | ||
78 | } elseif (isset($_GET['feed'])) { | 95 | } elseif (isset($_GET['feed'])) { |
79 | if (isset($_GET['action']) && $_GET['action'] == 'generate') { | 96 | if (isset($_GET['action']) && $_GET['action'] == 'generate') { |
80 | $poche->generateToken(); | 97 | $poche->generateToken(); |
81 | } | 98 | } |
82 | else { | 99 | else { |
83 | $tag_id = (isset($_GET['tag_id']) ? intval($_GET['tag_id']) : 0); | 100 | $tag_id = (isset($_GET['tag_id']) ? intval($_GET['tag_id']) : 0); |
84 | $poche->generateFeeds($_GET['token'], $_GET['user_id'], $tag_id, $_GET['type']); | 101 | $poche->generateFeeds($_GET['token'], filter_var($_GET['user_id'],FILTER_SANITIZE_NUMBER_INT), $tag_id, $_GET['type']); |
85 | } | 102 | } |
86 | } | 103 | } |
87 | 104 | ||
@@ -115,6 +132,7 @@ if (Session::isLogged()) { | |||
115 | } else { | 132 | } else { |
116 | $tpl_file = Tools::getTplFile('login'); | 133 | $tpl_file = Tools::getTplFile('login'); |
117 | $tpl_vars['http_auth'] = 0; | 134 | $tpl_vars['http_auth'] = 0; |
135 | Session::logout(); | ||
118 | } | 136 | } |
119 | 137 | ||
120 | # because messages can be added in $poche->action(), we have to add this entry now (we can add it before) | 138 | # because messages can be added in $poche->action(), we have to add this entry now (we can add it before) |
diff --git a/install/index.php b/install/index.php index 975b997f..e702891b 100644..100755 --- a/install/index.php +++ b/install/index.php | |||
@@ -1,9 +1,30 @@ | |||
1 | <?php | 1 | <?php |
2 | $errors = array(); | 2 | $errors = array(); |
3 | $successes = array(); | 3 | $successes = array(); |
4 | if ($_POST['download']) { | 4 | |
5 | /* Function taken from at http://php.net/manual/en/function.rmdir.php#110489 | ||
6 | * Idea : nbari at dalmp dot com | ||
7 | * Rights unknown | ||
8 | * Here in case of .gitignore files | ||
9 | */ | ||
10 | function delTree($dir) { | ||
11 | $files = array_diff(scandir($dir), array('.','..')); | ||
12 | foreach ($files as $file) { | ||
13 | (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file"); | ||
14 | } | ||
15 | return rmdir($dir); | ||
16 | } | ||
17 | |||
18 | if (isset($_GET['clean'])) { | ||
19 | if (is_dir('install')){ | ||
20 | delTree('install'); | ||
21 | header('Location: index.php'); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | if (isset($_POST['download'])) { | ||
5 | if (!file_put_contents("cache/vendor.zip", fopen("http://static.wallabag.org/files/vendor.zip", 'r'))) { | 26 | if (!file_put_contents("cache/vendor.zip", fopen("http://static.wallabag.org/files/vendor.zip", 'r'))) { |
6 | $errors[] = 'Impossible to download vendor.zip. Please <a href="http://wllbg.org/vendor">download it manually<∕a> and unzip it in your wallabag folder.'; | 27 | $errors[] = 'Impossible to download vendor.zip. Please <a href="http://wllbg.org/vendor">download it manually</a> and unzip it in your wallabag folder.'; |
7 | } | 28 | } |
8 | else { | 29 | else { |
9 | if (extension_loaded('zip')) { | 30 | if (extension_loaded('zip')) { |
@@ -25,14 +46,14 @@ if ($_POST['download']) { | |||
25 | } | 46 | } |
26 | } | 47 | } |
27 | } | 48 | } |
28 | else if ($_POST['install']) { | 49 | else if (isset($_POST['install'])) { |
29 | if (!is_dir('vendor')) { | 50 | if (!is_dir('vendor')) { |
30 | $errors[] = 'You must install twig before.'; | 51 | $errors[] = 'You must install twig before.'; |
31 | } | 52 | } |
32 | else { | 53 | else { |
33 | $continue = true; | 54 | $continue = true; |
34 | // Create config.inc.php | 55 | // Create config.inc.php |
35 | if (!copy('inc/poche/config.inc.php.new', 'inc/poche/config.inc.php')) { | 56 | if (!copy('inc/poche/config.inc.default.php', 'inc/poche/config.inc.php')) { |
36 | $errors[] = 'Installation aborted, impossible to create inc/poche/config.inc.php file. Maybe you don\'t have write access to create it.'; | 57 | $errors[] = 'Installation aborted, impossible to create inc/poche/config.inc.php file. Maybe you don\'t have write access to create it.'; |
37 | $continue = false; | 58 | $continue = false; |
38 | } | 59 | } |
@@ -64,6 +85,7 @@ else if ($_POST['install']) { | |||
64 | else { | 85 | else { |
65 | $db_path = 'sqlite:' . realpath('') . '/db/poche.sqlite'; | 86 | $db_path = 'sqlite:' . realpath('') . '/db/poche.sqlite'; |
66 | $handle = new PDO($db_path); | 87 | $handle = new PDO($db_path); |
88 | $sql_structure = ""; | ||
67 | } | 89 | } |
68 | } | 90 | } |
69 | else { | 91 | else { |
@@ -79,7 +101,7 @@ else if ($_POST['install']) { | |||
79 | 101 | ||
80 | $sql_structure = file_get_contents('install/mysql.sql'); | 102 | $sql_structure = file_get_contents('install/mysql.sql'); |
81 | } | 103 | } |
82 | else if ($_POST['db_engine'] == 'postgresql') { | 104 | else if ($_POST['db_engine'] == 'postgres') { |
83 | $db_path = 'pgsql:host=' . $_POST['pg_server'] . ';dbname=' . $_POST['pg_database']; | 105 | $db_path = 'pgsql:host=' . $_POST['pg_server'] . ';dbname=' . $_POST['pg_database']; |
84 | $content = str_replace("define ('STORAGE_SERVER', 'localhost');", "define ('STORAGE_SERVER', '".$_POST['pg_server']."');", $content); | 106 | $content = str_replace("define ('STORAGE_SERVER', 'localhost');", "define ('STORAGE_SERVER', '".$_POST['pg_server']."');", $content); |
85 | $content = str_replace("define ('STORAGE_DB', 'poche');", "define ('STORAGE_DB', '".$_POST['pg_database']."');", $content); | 107 | $content = str_replace("define ('STORAGE_DB', 'poche');", "define ('STORAGE_DB', '".$_POST['pg_database']."');", $content); |
@@ -129,7 +151,7 @@ else if ($_POST['install']) { | |||
129 | $params = array($id_user, 'language', 'en_EN.UTF8'); | 151 | $params = array($id_user, 'language', 'en_EN.UTF8'); |
130 | $query = executeQuery($handle, $sql, $params); | 152 | $query = executeQuery($handle, $sql, $params); |
131 | 153 | ||
132 | $successes[] = 'wallabag is now installed. Don\'t forget to delete install folder. Then, <a href="index.php">reload this page</a>.'; | 154 | $successes[] = 'wallabag is now installed. You can now <a href="index.php?clean=0">access it !</a>'; |
133 | } | 155 | } |
134 | } | 156 | } |
135 | } | 157 | } |
@@ -143,7 +165,7 @@ else if ($_POST['install']) { | |||
143 | <!--[if IE]> | 165 | <!--[if IE]> |
144 | <meta http-equiv="X-UA-Compatible" content="IE=10"> | 166 | <meta http-equiv="X-UA-Compatible" content="IE=10"> |
145 | <![endif]--> | 167 | <![endif]--> |
146 | <title>wallabag — installation</title> | 168 | <title>wallabag - installation</title> |
147 | <link rel="shortcut icon" type="image/x-icon" href="themes/baggy/img/favicon.ico" /> | 169 | <link rel="shortcut icon" type="image/x-icon" href="themes/baggy/img/favicon.ico" /> |
148 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="themes/baggy/img/apple-touch-icon-144x144-precomposed.png"> | 170 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="themes/baggy/img/apple-touch-icon-144x144-precomposed.png"> |
149 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="themes/baggy/img/apple-touch-icon-72x72-precomposed.png"> | 171 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="themes/baggy/img/apple-touch-icon-72x72-precomposed.png"> |
@@ -154,7 +176,7 @@ else if ($_POST['install']) { | |||
154 | <link rel="stylesheet" href="themes/baggy/css/main.css" media="all"> | 176 | <link rel="stylesheet" href="themes/baggy/css/main.css" media="all"> |
155 | <link rel="stylesheet" href="themes/baggy/css/messages.css" media="all"> | 177 | <link rel="stylesheet" href="themes/baggy/css/messages.css" media="all"> |
156 | <link rel="stylesheet" href="themes/baggy/css/print.css" media="print"> | 178 | <link rel="stylesheet" href="themes/baggy/css/print.css" media="print"> |
157 | <script src="themes/baggy/js/jquery-2.0.3.min.js"></script> | 179 | <script src="themes/default/js/jquery-2.0.3.min.js"></script> |
158 | <script src="themes/baggy/js/init.js"></script> | 180 | <script src="themes/baggy/js/init.js"></script> |
159 | </head> | 181 | </head> |
160 | <body> | 182 | <body> |
@@ -198,18 +220,18 @@ else if ($_POST['install']) { | |||
198 | <?php if (file_exists('inc/poche/config.inc.php') && is_dir('vendor')) : ?> | 220 | <?php if (file_exists('inc/poche/config.inc.php') && is_dir('vendor')) : ?> |
199 | <div class='messages success install'> | 221 | <div class='messages success install'> |
200 | <p> | 222 | <p> |
201 | wallabag seems already installed. If you want to update it, you only have to delete install folder. | 223 | wallabag seems already installed. If you want to update it, you only have to delete install folder, then <a href="index.php">reload this page</a>. |
202 | </p> | 224 | </p> |
203 | </div> | 225 | </div> |
204 | <?php endif; ?> | 226 | <?php endif; ?> |
205 | <?php endif; ?> | 227 | <?php endif; ?> |
206 | <p>To install wallabag, you just have to fill the following fields. That's all.</p> | 228 | <p>To install wallabag, you just have to fill the following fields. That's all.</p> |
207 | <p>Don't forget to check your server compatibility <a href="wallabag_compatibility_test.php">here</a>.</p> | 229 | <p>Don't forget to check your server compatibility <a href="wallabag_compatibility_test.php?from=install">here</a>.</p> |
208 | <form method="post"> | 230 | <form method="post"> |
209 | <fieldset> | 231 | <fieldset> |
210 | <legend><strong>Technical settings</strong></legend> | 232 | <legend><strong>Technical settings</strong></legend> |
211 | <?php if (!is_dir('vendor')) : ?> | 233 | <?php if (!is_dir('vendor')) : ?> |
212 | <div class='messages notice install'>wallabag needs twig, a template engine (<a href="http://twig.sensiolabs.org/">?</a>). Two ways to install it: | 234 | <div class='messages notice install'>wallabag needs twig, a template engine (<a href="http://twig.sensiolabs.org/">?</a>). Two ways to install it:<br /> |
213 | <ul> | 235 | <ul> |
214 | <li>automatically download and extract vendor.zip into your wallabag folder. | 236 | <li>automatically download and extract vendor.zip into your wallabag folder. |
215 | <p><input type="submit" name="download" value="Download vendor.zip" /></p> | 237 | <p><input type="submit" name="download" value="Download vendor.zip" /></p> |
@@ -225,7 +247,11 @@ php composer.phar install</code></pre></li> | |||
225 | <p> | 247 | <p> |
226 | Database engine: | 248 | Database engine: |
227 | <ul> | 249 | <ul> |
228 | <li><label for="sqlite">SQLite</label> <input name="db_engine" type="radio" checked="" id="sqlite" value="sqlite" /></li> | 250 | <li><label for="sqlite">SQLite</label> <input name="db_engine" type="radio" checked="" id="sqlite" value="sqlite" /> |
251 | <div id="pdo_sqlite" class='messages error install'> | ||
252 | <p>You have to enable <a href="http://php.net/manual/ref.pdo-sqlite.php">pdo_sqlite extension</a>.</p> | ||
253 | </div> | ||
254 | </li> | ||
229 | <li> | 255 | <li> |
230 | <label for="mysql">MySQL</label> <input name="db_engine" type="radio" id="mysql" value="mysql" /> | 256 | <label for="mysql">MySQL</label> <input name="db_engine" type="radio" id="mysql" value="mysql" /> |
231 | <ul id="mysql_infos"> | 257 | <ul id="mysql_infos"> |
@@ -236,12 +262,12 @@ php composer.phar install</code></pre></li> | |||
236 | </ul> | 262 | </ul> |
237 | </li> | 263 | </li> |
238 | <li> | 264 | <li> |
239 | <label for="postgresql">PostgreSQL</label> <input name="db_engine" type="radio" id="postgresql" value="postgresql" /> | 265 | <label for="postgres">PostgreSQL</label> <input name="db_engine" type="radio" id="postgres" value="postgres" /> |
240 | <ul id="pg_infos"> | 266 | <ul id="pg_infos"> |
241 | <li><label for="pg_server">Server</label> <input type="text" placeholder="localhost" id="pg_server" name="pg_server" /></li> | 267 | <li><label for="pg_server">Server</label> <input type="text" placeholder="localhost" id="pg_server" name="pg_server" /></li> |
242 | <li><label for="pg_database">Database</label> <input type="text" placeholder="wallabag" id="pg_database" name="pg_database" /></li> | 268 | <li><label for="pg_database">Database</label> <input type="text" placeholder="wallabag" id="pg_database" name="pg_database" /></li> |
243 | <li><label for="pg_user">User</label> <input type="text" placeholder="user" id="pg_user" name="pg_user" /></li> | 269 | <li><label for="pg_user">User</label> <input type="text" placeholder="user" id="pg_user" name="pg_user" /></li> |
244 | id <li><label for="pg_password">Password</label> <input type="text" placeholder="p4ssw0rd" id="pg_password" name="pg_password" /></li> | 270 | <li><label for="pg_password">Password</label> <input type="text" placeholder="p4ssw0rd" id="pg_password" name="pg_password" /></li> |
245 | </ul> | 271 | </ul> |
246 | </li> | 272 | </li> |
247 | </ul> | 273 | </ul> |
@@ -263,26 +289,49 @@ php composer.phar install</code></pre></li> | |||
263 | </p> | 289 | </p> |
264 | </fieldset> | 290 | </fieldset> |
265 | 291 | ||
266 | <input type="submit" value="Install wallabag" name="install" /> | 292 | <input type="submit" id="install_button" value="Install wallabag" name="install" /> |
267 | </form> | 293 | </form> |
268 | </div> | 294 | </div> |
269 | <script> | 295 | <script> |
270 | $("#mysql_infos").hide(); | 296 | $("#mysql_infos").hide(); |
271 | $("#pg_infos").hide(); | 297 | $("#pg_infos").hide(); |
298 | |||
299 | <?php | ||
300 | if (!extension_loaded('pdo_sqlite')) : ?> | ||
301 | $("#install_button").hide(); | ||
302 | <?php | ||
303 | else : | ||
304 | ?> | ||
305 | $("#pdo_sqlite").hide(); | ||
306 | <?php | ||
307 | endif; | ||
308 | ?> | ||
309 | |||
272 | $("input[name=db_engine]").click(function() | 310 | $("input[name=db_engine]").click(function() |
273 | { | 311 | { |
274 | if ( $("#mysql").prop('checked')) { | 312 | if ( $("#mysql").prop('checked')) { |
275 | $("#mysql_infos").show(); | 313 | $("#mysql_infos").show(); |
276 | $("#pg_infos").hide(); | 314 | $("#pg_infos").hide(); |
315 | $("#pdo_sqlite").hide(); | ||
316 | $("#install_button").show(); | ||
277 | } | 317 | } |
278 | else { | 318 | else { |
279 | if ( $("#postgresql").prop('checked')) { | 319 | if ( $("#postgres").prop('checked')) { |
280 | $("#mysql_infos").hide(); | 320 | $("#mysql_infos").hide(); |
281 | $("#pg_infos").show(); | 321 | $("#pg_infos").show(); |
322 | $("#pdo_sqlite").hide(); | ||
323 | $("#install_button").show(); | ||
282 | } | 324 | } |
283 | else { | 325 | else { |
284 | $("#mysql_infos").hide(); | 326 | $("#mysql_infos").hide(); |
285 | $("#pg_infos").hide(); | 327 | $("#pg_infos").hide(); |
328 | <?php | ||
329 | if (!extension_loaded('pdo_sqlite')) : ?> | ||
330 | $("#pdo_sqlite").show(); | ||
331 | $("#install_button").hide(); | ||
332 | <?php | ||
333 | endif; | ||
334 | ?> | ||
286 | } | 335 | } |
287 | } | 336 | } |
288 | }); | 337 | }); |
diff --git a/install/poche.sqlite b/install/poche.sqlite index f2b79b68..f2b79b68 100755..100644 --- a/install/poche.sqlite +++ b/install/poche.sqlite | |||
Binary files differ | |||
diff --git a/install/postgres.sql b/install/postgres.sql index fe8f559c..1d73dfcb 100644 --- a/install/postgres.sql +++ b/install/postgres.sql | |||
@@ -1,10 +1,10 @@ | |||
1 | CREATE TABLE config ( | 1 | CREATE TABLE IF NOT EXISTS config ( |
2 | id bigserial primary key, | 2 | id bigserial primary key, |
3 | name varchar(255) NOT NULL, | 3 | name varchar(255) NOT NULL, |
4 | value varchar(255) NOT NULL | 4 | value varchar(255) NOT NULL |
5 | ); | 5 | ); |
6 | 6 | ||
7 | CREATE TABLE entries ( | 7 | CREATE TABLE IF NOT EXISTS entries ( |
8 | id bigserial primary key, | 8 | id bigserial primary key, |
9 | title varchar(255) NOT NULL, | 9 | title varchar(255) NOT NULL, |
10 | url varchar(255) NOT NULL, | 10 | url varchar(255) NOT NULL, |
@@ -14,7 +14,7 @@ CREATE TABLE entries ( | |||
14 | user_id integer NOT NULL | 14 | user_id integer NOT NULL |
15 | ); | 15 | ); |
16 | 16 | ||
17 | CREATE TABLE users ( | 17 | CREATE TABLE IF NOT EXISTS users ( |
18 | id bigserial primary key, | 18 | id bigserial primary key, |
19 | username varchar(255) NOT NULL, | 19 | username varchar(255) NOT NULL, |
20 | password varchar(255) NOT NULL, | 20 | password varchar(255) NOT NULL, |
@@ -22,20 +22,20 @@ CREATE TABLE users ( | |||
22 | email varchar(255) NOT NULL | 22 | email varchar(255) NOT NULL |
23 | ); | 23 | ); |
24 | 24 | ||
25 | CREATE TABLE users_config ( | 25 | CREATE TABLE IF NOT EXISTS users_config ( |
26 | id bigserial primary key, | 26 | id bigserial primary key, |
27 | user_id integer NOT NULL, | 27 | user_id integer NOT NULL, |
28 | name varchar(255) NOT NULL, | 28 | name varchar(255) NOT NULL, |
29 | value varchar(255) NOT NULL | 29 | value varchar(255) NOT NULL |
30 | ); | 30 | ); |
31 | 31 | ||
32 | CREATE TABLE tags ( | 32 | CREATE TABLE IF NOT EXISTS tags ( |
33 | id bigserial primary key, | 33 | id bigserial primary key, |
34 | value varchar(255) NOT NULL | 34 | value varchar(255) NOT NULL |
35 | ); | 35 | ); |
36 | 36 | ||
37 | CREATE TABLE tags_entries ( | 37 | CREATE TABLE IF NOT EXISTS tags_entries ( |
38 | id bigserial primary key, | 38 | id bigserial primary key, |
39 | entry_id integer NOT NULL, | 39 | entry_id integer NOT NULL, |
40 | tag_id integer NOT NULL | 40 | tag_id integer NOT NULL |
41 | ) \ No newline at end of file | 41 | ) |
diff --git a/locale/cs_CZ.utf8/LC_MESSAGES/cs_CZ.utf8.po b/locale/cs_CZ.utf8/LC_MESSAGES/cs_CZ.utf8.po index 8209a9cd..cf727432 100644 --- a/locale/cs_CZ.utf8/LC_MESSAGES/cs_CZ.utf8.po +++ b/locale/cs_CZ.utf8/LC_MESSAGES/cs_CZ.utf8.po | |||
@@ -4,54 +4,137 @@ | |||
4 | msgid "" | 4 | msgid "" |
5 | msgstr "" | 5 | msgstr "" |
6 | "Project-Id-Version: poche\n" | 6 | "Project-Id-Version: poche\n" |
7 | "POT-Creation-Date: \n" | 7 | "Report-Msgid-Bugs-To: \n" |
8 | "PO-Revision-Date: 2013-10-08 13:25+0100\n" | 8 | "POT-Creation-Date: 2014-02-25 15:28+0300\n" |
9 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 9 | "PO-Revision-Date: 2014-02-25 15:29+0300\n" |
10 | "Language-Team: Czech (http://www.transifex.com/projects/p/poche/language/" | 10 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
11 | "cs/)\n" | 11 | "Language-Team: Czech (http://www.transifex.com/projects/p/poche/language/cs/)\n" |
12 | "Language: cs\n" | ||
12 | "MIME-Version: 1.0\n" | 13 | "MIME-Version: 1.0\n" |
13 | "Content-Type: text/plain; charset=UTF-8\n" | 14 | "Content-Type: text/plain; charset=UTF-8\n" |
14 | "Content-Transfer-Encoding: 8bit\n" | 15 | "Content-Transfer-Encoding: 8bit\n" |
15 | "Language: cs\n" | ||
16 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" | 16 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" |
17 | "X-Generator: Poedit 1.5.4\n" | 17 | "X-Generator: Poedit 1.5.4\n" |
18 | "X-Poedit-Language: Czech\n" | ||
19 | "X-Poedit-Basepath: .\n" | ||
20 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" | ||
21 | |||
22 | msgid "wallabag, a read it later open source system" | ||
23 | msgstr "" | ||
24 | |||
25 | msgid "login failed: user doesn't exist" | ||
26 | msgstr "" | ||
27 | |||
28 | msgid "return home" | ||
29 | msgstr "" | ||
18 | 30 | ||
19 | msgid "config" | 31 | msgid "config" |
20 | msgstr "nastavení" | 32 | msgstr "nastavení" |
21 | 33 | ||
22 | msgid "Poching a link" | 34 | msgid "Saving articles" |
23 | msgstr "Odkaz se ukládá" | 35 | msgstr "" |
36 | |||
37 | msgid "There are several ways to save an article:" | ||
38 | msgstr "" | ||
24 | 39 | ||
25 | msgid "read the documentation" | 40 | msgid "read the documentation" |
26 | msgstr "číst dokumentaci" | 41 | msgstr "číst dokumentaci" |
27 | 42 | ||
28 | msgid "by filling this field" | 43 | msgid "download the extension" |
44 | msgstr "" | ||
45 | |||
46 | msgid "via F-Droid" | ||
47 | msgstr "" | ||
48 | |||
49 | msgid " or " | ||
50 | msgstr "" | ||
51 | |||
52 | msgid "via Google Play" | ||
53 | msgstr "" | ||
54 | |||
55 | msgid "download the application" | ||
56 | msgstr "" | ||
57 | |||
58 | #, fuzzy | ||
59 | msgid "By filling this field" | ||
29 | msgstr "vyplněním tohoto pole" | 60 | msgstr "vyplněním tohoto pole" |
30 | 61 | ||
31 | msgid "poche it!" | 62 | msgid "bag it!" |
32 | msgstr "uložit!" | 63 | msgstr "" |
33 | 64 | ||
34 | msgid "Updating poche" | 65 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
35 | msgstr "Poche se aktualizuje" | 66 | msgstr "" |
36 | 67 | ||
37 | msgid "your version" | 68 | msgid "Upgrading wallabag" |
38 | msgstr "vaše verze" | 69 | msgstr "" |
39 | 70 | ||
40 | msgid "latest stable version" | 71 | #, fuzzy |
72 | msgid "Installed version" | ||
41 | msgstr "poslední stabilní verze" | 73 | msgstr "poslední stabilní verze" |
42 | 74 | ||
43 | msgid "a more recent stable version is available." | 75 | #, fuzzy |
76 | msgid "Latest stable version" | ||
77 | msgstr "poslední stabilní verze" | ||
78 | |||
79 | #, fuzzy | ||
80 | msgid "A more recent stable version is available." | ||
44 | msgstr "je k dispozici novější stabilní verze." | 81 | msgstr "je k dispozici novější stabilní verze." |
45 | 82 | ||
46 | msgid "you are up to date." | 83 | #, fuzzy |
84 | msgid "You are up to date." | ||
47 | msgstr "je aktuální" | 85 | msgstr "je aktuální" |
48 | 86 | ||
49 | msgid "latest dev version" | 87 | #, fuzzy |
88 | msgid "Latest dev version" | ||
50 | msgstr "poslední vývojová verze" | 89 | msgstr "poslední vývojová verze" |
51 | 90 | ||
52 | msgid "a more recent development version is available." | 91 | #, fuzzy |
92 | msgid "A more recent development version is available." | ||
53 | msgstr "je k dispozici novější vývojová verze." | 93 | msgstr "je k dispozici novější vývojová verze." |
54 | 94 | ||
95 | msgid "Feeds" | ||
96 | msgstr "" | ||
97 | |||
98 | msgid "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
99 | msgstr "" | ||
100 | |||
101 | msgid "Unread feed" | ||
102 | msgstr "" | ||
103 | |||
104 | #, fuzzy | ||
105 | msgid "Favorites feed" | ||
106 | msgstr "oblíbené" | ||
107 | |||
108 | #, fuzzy | ||
109 | msgid "Archive feed" | ||
110 | msgstr "archív" | ||
111 | |||
112 | msgid "Your token:" | ||
113 | msgstr "" | ||
114 | |||
115 | msgid "Your user id:" | ||
116 | msgstr "" | ||
117 | |||
118 | msgid "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
119 | msgstr "" | ||
120 | |||
121 | #, fuzzy | ||
122 | msgid "Change your theme" | ||
123 | msgstr "Změnit heslo" | ||
124 | |||
125 | msgid "Theme:" | ||
126 | msgstr "" | ||
127 | |||
128 | msgid "Update" | ||
129 | msgstr "Aktualizovat" | ||
130 | |||
131 | #, fuzzy | ||
132 | msgid "Change your language" | ||
133 | msgstr "Změnit heslo" | ||
134 | |||
135 | msgid "Language:" | ||
136 | msgstr "" | ||
137 | |||
55 | msgid "Change your password" | 138 | msgid "Change your password" |
56 | msgstr "Změnit heslo" | 139 | msgstr "Změnit heslo" |
57 | 140 | ||
@@ -64,65 +147,68 @@ msgstr "Heslo" | |||
64 | msgid "Repeat your new password:" | 147 | msgid "Repeat your new password:" |
65 | msgstr "Znovu nové heslo:" | 148 | msgstr "Znovu nové heslo:" |
66 | 149 | ||
67 | msgid "Update" | ||
68 | msgstr "Aktualizovat" | ||
69 | |||
70 | msgid "Import" | 150 | msgid "Import" |
71 | msgstr "Importovat" | 151 | msgstr "Importovat" |
72 | 152 | ||
73 | msgid "Please execute the import script locally, it can take a very long time." | 153 | #, fuzzy |
154 | msgid "Please execute the import script locally as it can take a very long time." | ||
74 | msgstr "Spusťte importní skript lokálně, může to dlouho trvat." | 155 | msgstr "Spusťte importní skript lokálně, může to dlouho trvat." |
75 | 156 | ||
76 | msgid "More info in the official doc:" | 157 | #, fuzzy |
158 | msgid "More info in the official documentation:" | ||
77 | msgstr "Více informací v oficiální dokumentaci:" | 159 | msgstr "Více informací v oficiální dokumentaci:" |
78 | 160 | ||
79 | msgid "import from Pocket" | 161 | #, fuzzy |
162 | msgid "Import from Pocket" | ||
80 | msgstr "importovat z Pocket" | 163 | msgstr "importovat z Pocket" |
81 | 164 | ||
82 | msgid "import from Readability" | 165 | #, php-format |
166 | msgid "(you must have a %s file on your server)" | ||
167 | msgstr "" | ||
168 | |||
169 | #, fuzzy | ||
170 | msgid "Import from Readability" | ||
83 | msgstr "importovat z Readability" | 171 | msgstr "importovat z Readability" |
84 | 172 | ||
85 | msgid "import from Instapaper" | 173 | #, fuzzy |
174 | msgid "Import from Instapaper" | ||
86 | msgstr "importovat z Instapaper" | 175 | msgstr "importovat z Instapaper" |
87 | 176 | ||
88 | msgid "Export your poche data" | 177 | #, fuzzy |
178 | msgid "Import from wallabag" | ||
179 | msgstr "importovat z Readability" | ||
180 | |||
181 | #, fuzzy | ||
182 | msgid "Export your wallabag data" | ||
89 | msgstr "Export dat" | 183 | msgstr "Export dat" |
90 | 184 | ||
91 | msgid "Click here" | 185 | msgid "Click here" |
92 | msgstr "Klikněte zde" | 186 | msgstr "Klikněte zde" |
93 | 187 | ||
94 | msgid "to export your poche data." | 188 | msgid "to download your database." |
95 | msgstr "pro export vašich dat." | 189 | msgstr "" |
96 | |||
97 | msgid "back to home" | ||
98 | msgstr "zpět na úvod" | ||
99 | 190 | ||
100 | msgid "installation" | 191 | #, fuzzy |
101 | msgstr "instalace" | 192 | msgid "to export your wallabag data." |
193 | msgstr "pro export vašich dat." | ||
102 | 194 | ||
103 | msgid "install your poche" | 195 | msgid "Cache" |
104 | msgstr "instalovat" | 196 | msgstr "" |
105 | 197 | ||
106 | msgid "" | 198 | msgid "to delete cache." |
107 | "poche is still not installed. Please fill the below form to install it. " | ||
108 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
109 | "on poche website</a>." | ||
110 | msgstr "" | 199 | msgstr "" |
111 | "poche ještě není nainstalováno. Pro instalaci vyplňte níže uvedený formulář. " | ||
112 | "Nezapomeňte <a href='http://doc.inthepoche.com'>si přečíst dokumentaci</a> " | ||
113 | "na stránkách programu." | ||
114 | 200 | ||
115 | msgid "Login" | 201 | msgid "You can enter multiple tags, separated by commas." |
116 | msgstr "Jméno" | 202 | msgstr "" |
117 | 203 | ||
118 | msgid "Repeat your password" | 204 | msgid "return to article" |
119 | msgstr "Zopakujte heslo" | 205 | msgstr "" |
120 | 206 | ||
121 | msgid "Install" | 207 | msgid "plop" |
122 | msgstr "Instalovat" | 208 | msgstr "" |
123 | 209 | ||
124 | msgid "back to top" | 210 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
125 | msgstr "zpět na začátek" | 211 | msgstr "" |
126 | 212 | ||
127 | msgid "favoris" | 213 | msgid "favoris" |
128 | msgstr "oblíbené" | 214 | msgstr "oblíbené" |
@@ -151,10 +237,14 @@ msgstr "podle nadpisu" | |||
151 | msgid "by title desc" | 237 | msgid "by title desc" |
152 | msgstr "podle nadpisu sestupně" | 238 | msgstr "podle nadpisu sestupně" |
153 | 239 | ||
154 | msgid "No link available here!" | 240 | msgid "Tag" |
155 | msgstr "Není k dispozici žádný odkaz!" | 241 | msgstr "" |
156 | 242 | ||
157 | msgid "toggle mark as read" | 243 | msgid "No articles found." |
244 | msgstr "" | ||
245 | |||
246 | #, fuzzy | ||
247 | msgid "Toggle mark as read" | ||
158 | msgstr "označit jako přečtené" | 248 | msgstr "označit jako přečtené" |
159 | 249 | ||
160 | msgid "toggle favorite" | 250 | msgid "toggle favorite" |
@@ -166,13 +256,95 @@ msgstr "smazat" | |||
166 | msgid "original" | 256 | msgid "original" |
167 | msgstr "originál" | 257 | msgstr "originál" |
168 | 258 | ||
259 | msgid "estimated reading time:" | ||
260 | msgstr "" | ||
261 | |||
262 | msgid "mark all the entries as read" | ||
263 | msgstr "" | ||
264 | |||
169 | msgid "results" | 265 | msgid "results" |
170 | msgstr "výsledky" | 266 | msgstr "výsledky" |
171 | 267 | ||
172 | msgid "tweet" | 268 | msgid "installation" |
269 | msgstr "instalace" | ||
270 | |||
271 | #, fuzzy | ||
272 | msgid "install your wallabag" | ||
273 | msgstr "instalovat" | ||
274 | |||
275 | #, fuzzy | ||
276 | msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
277 | msgstr "poche ještě není nainstalováno. Pro instalaci vyplňte níže uvedený formulář. Nezapomeňte <a href='http://doc.inthepoche.com'>si přečíst dokumentaci</a> na stránkách programu." | ||
278 | |||
279 | msgid "Login" | ||
280 | msgstr "Jméno" | ||
281 | |||
282 | msgid "Repeat your password" | ||
283 | msgstr "Zopakujte heslo" | ||
284 | |||
285 | msgid "Install" | ||
286 | msgstr "Instalovat" | ||
287 | |||
288 | #, fuzzy | ||
289 | msgid "login to your wallabag" | ||
290 | msgstr "přihlásit se k poche" | ||
291 | |||
292 | msgid "Login to wallabag" | ||
293 | msgstr "" | ||
294 | |||
295 | msgid "you are in demo mode, some features may be disabled." | ||
296 | msgstr "používáte ukázkový mód, některé funkce jsou zakázány." | ||
297 | |||
298 | msgid "Username" | ||
299 | msgstr "" | ||
300 | |||
301 | msgid "Stay signed in" | ||
302 | msgstr "Zůstat přihlášen(a)" | ||
303 | |||
304 | msgid "(Do not check on public computers)" | ||
305 | msgstr "(Nezaškrtávejte na veřejně dostupných počítačích)" | ||
306 | |||
307 | msgid "Sign in" | ||
308 | msgstr "Přihlásit se" | ||
309 | |||
310 | msgid "favorites" | ||
311 | msgstr "oblíbené" | ||
312 | |||
313 | msgid "estimated reading time :" | ||
314 | msgstr "" | ||
315 | |||
316 | msgid "Mark all the entries as read" | ||
317 | msgstr "" | ||
318 | |||
319 | msgid "Return home" | ||
320 | msgstr "" | ||
321 | |||
322 | #, fuzzy | ||
323 | msgid "Back to top" | ||
324 | msgstr "zpět na začátek" | ||
325 | |||
326 | #, fuzzy | ||
327 | msgid "Mark as read" | ||
328 | msgstr "označit jako přečtené" | ||
329 | |||
330 | #, fuzzy | ||
331 | msgid "Favorite" | ||
332 | msgstr "oblíbené" | ||
333 | |||
334 | #, fuzzy | ||
335 | msgid "Toggle favorite" | ||
336 | msgstr "označit jako oblíbené" | ||
337 | |||
338 | #, fuzzy | ||
339 | msgid "Delete" | ||
340 | msgstr "smazat" | ||
341 | |||
342 | #, fuzzy | ||
343 | msgid "Tweet" | ||
173 | msgstr "tweetnout" | 344 | msgstr "tweetnout" |
174 | 345 | ||
175 | msgid "email" | 346 | #, fuzzy |
347 | msgid "Email" | ||
176 | msgstr "email" | 348 | msgstr "email" |
177 | 349 | ||
178 | msgid "shaarli" | 350 | msgid "shaarli" |
@@ -181,26 +353,24 @@ msgstr "shaarli" | |||
181 | msgid "flattr" | 353 | msgid "flattr" |
182 | msgstr "flattr" | 354 | msgstr "flattr" |
183 | 355 | ||
184 | msgid "this article appears wrong?" | 356 | #, fuzzy |
357 | msgid "Does this article appear wrong?" | ||
185 | msgstr "vypadá tento článek špatně?" | 358 | msgstr "vypadá tento článek špatně?" |
186 | 359 | ||
187 | msgid "create an issue" | 360 | msgid "tags:" |
188 | msgstr "odeslat požadavek" | 361 | msgstr "" |
189 | |||
190 | msgid "or" | ||
191 | msgstr "nebo" | ||
192 | 362 | ||
193 | msgid "contact us by mail" | 363 | msgid "Edit tags" |
194 | msgstr "kontaktovat e-mailem" | 364 | msgstr "" |
195 | 365 | ||
196 | msgid "plop" | 366 | msgid "save link!" |
197 | msgstr "" | 367 | msgstr "" |
198 | 368 | ||
199 | msgid "home" | 369 | msgid "home" |
200 | msgstr "domů" | 370 | msgstr "domů" |
201 | 371 | ||
202 | msgid "favorites" | 372 | msgid "tags" |
203 | msgstr "oblíbené" | 373 | msgstr "" |
204 | 374 | ||
205 | msgid "logout" | 375 | msgid "logout" |
206 | msgstr "odhlásit se" | 376 | msgstr "odhlásit se" |
@@ -211,23 +381,187 @@ msgstr "běží na" | |||
211 | msgid "debug mode is on so cache is off." | 381 | msgid "debug mode is on so cache is off." |
212 | msgstr "je zapnut ladicí mód, proto je keš vypnuta." | 382 | msgstr "je zapnut ladicí mód, proto je keš vypnuta." |
213 | 383 | ||
214 | msgid "your poche version:" | 384 | #, fuzzy |
215 | msgstr "verze:" | 385 | msgid "your wallabag version:" |
386 | msgstr "vaše verze" | ||
216 | 387 | ||
217 | msgid "storage:" | 388 | msgid "storage:" |
218 | msgstr "úložiště:" | 389 | msgstr "úložiště:" |
219 | 390 | ||
220 | msgid "login to your poche" | 391 | msgid "save a link" |
221 | msgstr "přihlásit se k poche" | 392 | msgstr "" |
222 | 393 | ||
223 | msgid "you are in demo mode, some features may be disabled." | 394 | msgid "back to home" |
224 | msgstr "používáte ukázkový mód, některé funkce jsou zakzány." | 395 | msgstr "zpět na vod" |
225 | 396 | ||
226 | msgid "Stay signed in" | 397 | msgid "toggle mark as read" |
227 | msgstr "Zůstat přihlášen(a)" | 398 | msgstr "označit jako přečtené" |
228 | 399 | ||
229 | msgid "(Do not check on public computers)" | 400 | msgid "tweet" |
230 | msgstr "(Nezaškrtávejte na veřejně dostupných počítačích)" | 401 | msgstr "tweetnout" |
231 | 402 | ||
232 | msgid "Sign in" | 403 | msgid "email" |
233 | msgstr "Přihlásit se" | 404 | msgstr "email" |
405 | |||
406 | msgid "this article appears wrong?" | ||
407 | msgstr "vypadá tento článek špatně?" | ||
408 | |||
409 | msgid "No link available here!" | ||
410 | msgstr "Není k dispozici žádný odkaz!" | ||
411 | |||
412 | msgid "Poching a link" | ||
413 | msgstr "Odkaz se ukládá" | ||
414 | |||
415 | msgid "by filling this field" | ||
416 | msgstr "vyplněním tohoto pole" | ||
417 | |||
418 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
419 | msgstr "" | ||
420 | |||
421 | msgid "your version" | ||
422 | msgstr "vaše verze" | ||
423 | |||
424 | msgid "latest stable version" | ||
425 | msgstr "poslední stabilní verze" | ||
426 | |||
427 | msgid "a more recent stable version is available." | ||
428 | msgstr "je k dispozici novější stabilní verze." | ||
429 | |||
430 | msgid "you are up to date." | ||
431 | msgstr "je aktuální" | ||
432 | |||
433 | msgid "latest dev version" | ||
434 | msgstr "poslední vývojová verze" | ||
435 | |||
436 | msgid "a more recent development version is available." | ||
437 | msgstr "je k dispozici novější vývojová verze." | ||
438 | |||
439 | msgid "Please execute the import script locally, it can take a very long time." | ||
440 | msgstr "Spusťte importní skript lokálně, může to dlouho trvat." | ||
441 | |||
442 | #, fuzzy | ||
443 | msgid "More infos in the official doc:" | ||
444 | msgstr "Více informací v oficiální dokumentaci:" | ||
445 | |||
446 | msgid "import from Pocket" | ||
447 | msgstr "importovat z Pocket" | ||
448 | |||
449 | msgid "import from Readability" | ||
450 | msgstr "importovat z Readability" | ||
451 | |||
452 | msgid "import from Instapaper" | ||
453 | msgstr "importovat z Instapaper" | ||
454 | |||
455 | msgid "Tags" | ||
456 | msgstr "" | ||
457 | |||
458 | #, fuzzy | ||
459 | msgid "Untitled" | ||
460 | msgstr "podle nadpisu" | ||
461 | |||
462 | msgid "the link has been added successfully" | ||
463 | msgstr "" | ||
464 | |||
465 | msgid "error during insertion : the link wasn't added" | ||
466 | msgstr "" | ||
467 | |||
468 | msgid "the link has been deleted successfully" | ||
469 | msgstr "" | ||
470 | |||
471 | msgid "the link wasn't deleted" | ||
472 | msgstr "" | ||
473 | |||
474 | msgid "Article not found!" | ||
475 | msgstr "" | ||
476 | |||
477 | msgid "previous" | ||
478 | msgstr "" | ||
479 | |||
480 | msgid "next" | ||
481 | msgstr "" | ||
482 | |||
483 | msgid "in demo mode, you can't update your password" | ||
484 | msgstr "" | ||
485 | |||
486 | msgid "your password has been updated" | ||
487 | msgstr "" | ||
488 | |||
489 | msgid "the two fields have to be filled & the password must be the same in the two fields" | ||
490 | msgstr "" | ||
491 | |||
492 | msgid "still using the \"" | ||
493 | msgstr "" | ||
494 | |||
495 | msgid "that theme does not seem to be installed" | ||
496 | msgstr "" | ||
497 | |||
498 | msgid "you have changed your theme preferences" | ||
499 | msgstr "" | ||
500 | |||
501 | msgid "that language does not seem to be installed" | ||
502 | msgstr "" | ||
503 | |||
504 | msgid "you have changed your language preferences" | ||
505 | msgstr "" | ||
506 | |||
507 | msgid "login failed: you have to fill all fields" | ||
508 | msgstr "" | ||
509 | |||
510 | msgid "welcome to your wallabag" | ||
511 | msgstr "" | ||
512 | |||
513 | msgid "login failed: bad login or password" | ||
514 | msgstr "" | ||
515 | |||
516 | #, fuzzy | ||
517 | msgid "import from instapaper completed" | ||
518 | msgstr "importovat z Instapaper" | ||
519 | |||
520 | #, fuzzy | ||
521 | msgid "import from pocket completed" | ||
522 | msgstr "importovat z Pocket" | ||
523 | |||
524 | #, fuzzy | ||
525 | msgid "import from Readability completed. " | ||
526 | msgstr "importovat z Readability" | ||
527 | |||
528 | #, fuzzy | ||
529 | msgid "import from Poche completed. " | ||
530 | msgstr "importovat z Pocket" | ||
531 | |||
532 | msgid "Unknown import provider." | ||
533 | msgstr "" | ||
534 | |||
535 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
536 | msgstr "" | ||
537 | |||
538 | msgid "Could not find required \"" | ||
539 | msgstr "" | ||
540 | |||
541 | msgid "Uh, there is a problem while generating feeds." | ||
542 | msgstr "" | ||
543 | |||
544 | #, fuzzy | ||
545 | msgid "Cache deleted." | ||
546 | msgstr "smazat" | ||
547 | |||
548 | msgid "Oops, it seems you don't have PHP 5." | ||
549 | msgstr "" | ||
550 | |||
551 | #~ msgid "poche it!" | ||
552 | #~ msgstr "uložit!" | ||
553 | |||
554 | #~ msgid "Updating poche" | ||
555 | #~ msgstr "Poche se aktualizuje" | ||
556 | |||
557 | #~ msgid "create an issue" | ||
558 | #~ msgstr "odeslat požadavek" | ||
559 | |||
560 | #~ msgid "or" | ||
561 | #~ msgstr "nebo" | ||
562 | |||
563 | #~ msgid "contact us by mail" | ||
564 | #~ msgstr "kontaktovat e-mailem" | ||
565 | |||
566 | #~ msgid "your poche version:" | ||
567 | #~ msgstr "verze:" | ||
diff --git a/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.mo b/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.mo index 375e923f..bd18817f 100644 --- a/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.mo +++ b/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.po b/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.po index 5b30d3d7..8b82721d 100644 --- a/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.po +++ b/locale/de_DE.utf8/LC_MESSAGES/de_DE.utf8.po | |||
@@ -1,51 +1,130 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: \n" | 3 | "Project-Id-Version: Wallabag\n" |
4 | "POT-Creation-Date: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-03-27 13:41+0100\n" | ||
5 | "PO-Revision-Date: \n" | 6 | "PO-Revision-Date: \n" |
6 | "Last-Translator: Square252\n" | 7 | "Last-Translator: Kevin Meyer <wallabag@kevin-meyer.de>\n" |
7 | "Language-Team: \n" | 8 | "Language-Team: \n" |
9 | "Language: de\n" | ||
8 | "MIME-Version: 1.0\n" | 10 | "MIME-Version: 1.0\n" |
9 | "Content-Type: text/plain; charset=UTF-8\n" | 11 | "Content-Type: text/plain; charset=UTF-8\n" |
10 | "Content-Transfer-Encoding: 8bit\n" | 12 | "Content-Transfer-Encoding: 8bit\n" |
11 | "X-Generator: Poedit 1.5.7\n" | 13 | "X-Generator: Poedit 1.6.4\n" |
14 | "X-Poedit-Basepath: .\n" | ||
15 | "X-Poedit-SearchPath-0: /Users/kevinmeyer/Dropbox/dev_web/wallabag-dev\n" | ||
12 | 16 | ||
13 | msgid "config" | 17 | msgid "config" |
14 | msgstr "Konfiguration" | 18 | msgstr "Konfiguration" |
15 | 19 | ||
16 | msgid "Poching a link" | 20 | msgid "Saving articles" |
17 | msgstr "Poche einen Link" | 21 | msgstr "Artikel speichern" |
22 | |||
23 | msgid "There are several ways to save an article:" | ||
24 | msgstr "Es gibt viele Methoden um Artikel zu speichern:" | ||
18 | 25 | ||
19 | msgid "read the documentation" | 26 | msgid "read the documentation" |
20 | msgstr "Die Dokumentation lesen" | 27 | msgstr "Die Dokumentation lesen" |
21 | 28 | ||
22 | msgid "by filling this field" | 29 | msgid "download the extension" |
23 | msgstr "durch das ausfüllen dieses Feldes:" | 30 | msgstr "installiere die Erweiterung" |
31 | |||
32 | msgid "via F-Droid" | ||
33 | msgstr "via F-Droid" | ||
34 | |||
35 | msgid " or " | ||
36 | msgstr " oder " | ||
37 | |||
38 | msgid "via Google Play" | ||
39 | msgstr "via Google Play" | ||
40 | |||
41 | msgid "download the application" | ||
42 | msgstr "lade die App" | ||
43 | |||
44 | msgid "By filling this field" | ||
45 | msgstr "Durch Ausfüllen dieses Feldes" | ||
24 | 46 | ||
25 | msgid "poche it!" | 47 | msgid "bag it!" |
26 | msgstr "Poche es!" | 48 | msgstr "bag it!" |
27 | 49 | ||
28 | msgid "Updating poche" | 50 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
29 | msgstr "Poche aktualisieren" | 51 | msgstr "Bookmarklet: Ziehe diesen Link in deine Lesezeichen-Leiste" |
30 | 52 | ||
31 | msgid "your version" | 53 | msgid "Upgrading wallabag" |
32 | msgstr "Deine Version" | 54 | msgstr "wallabag aktualisieren" |
33 | 55 | ||
34 | msgid "latest stable version" | 56 | msgid "Installed version" |
57 | msgstr "Installierte Version" | ||
58 | |||
59 | msgid "Latest stable version" | ||
35 | msgstr "Neuste stabile Version" | 60 | msgstr "Neuste stabile Version" |
36 | 61 | ||
37 | msgid "a more recent stable version is available." | 62 | msgid "A more recent stable version is available." |
38 | msgstr "Eine neuere stabile Version ist verfügbar." | 63 | msgstr "Eine neuere stabile Version ist verfügbar." |
39 | 64 | ||
40 | msgid "you are up to date." | 65 | msgid "You are up to date." |
41 | msgstr "Du bist auf den neuesten Stand." | 66 | msgstr "Du bist auf den neuesten Stand." |
42 | 67 | ||
43 | msgid "latest dev version" | 68 | msgid "Last check:" |
69 | msgstr "Zuletzt geprüft:" | ||
70 | |||
71 | msgid "Latest dev version" | ||
44 | msgstr "Neuste Entwicklungsversion" | 72 | msgstr "Neuste Entwicklungsversion" |
45 | 73 | ||
46 | msgid "a more recent development version is available." | 74 | msgid "A more recent development version is available." |
47 | msgstr "Eine neuere Entwicklungsversion ist verfügbar." | 75 | msgstr "Eine neuere Entwicklungsversion ist verfügbar." |
48 | 76 | ||
77 | msgid "You can clear cache to check the latest release." | ||
78 | msgstr "Leere den Cache um die neueste Version zu prüfen." | ||
79 | |||
80 | msgid "Feeds" | ||
81 | msgstr "Feeds" | ||
82 | |||
83 | msgid "" | ||
84 | "Your feed token is currently empty and must first be generated to enable " | ||
85 | "feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
86 | msgstr "" | ||
87 | "Dein Feed Token ist noch nicht vorhanden und muss zunächst generiert werden, " | ||
88 | "um deine Feeds zu aktivieren. Klicke <a href='?feed&" | ||
89 | "action=generate'>hier um ihn zu generieren</a>." | ||
90 | |||
91 | msgid "Unread feed" | ||
92 | msgstr "Ungelesen Feed" | ||
93 | |||
94 | msgid "Favorites feed" | ||
95 | msgstr "Favoriten Feed" | ||
96 | |||
97 | msgid "Archive feed" | ||
98 | msgstr "Archiv Feed" | ||
99 | |||
100 | msgid "Your token:" | ||
101 | msgstr "Dein Token:" | ||
102 | |||
103 | msgid "Your user id:" | ||
104 | msgstr "Deine User ID:" | ||
105 | |||
106 | msgid "" | ||
107 | "You can regenerate your token: <a href='?feed&action=generate'>generate!" | ||
108 | "</a>." | ||
109 | msgstr "" | ||
110 | "Hier kannst du dein Token erzeugen: <a href='?feed&" | ||
111 | "action=generate'>Generieren!</a>." | ||
112 | |||
113 | msgid "Change your theme" | ||
114 | msgstr "Theme ändern" | ||
115 | |||
116 | msgid "Theme:" | ||
117 | msgstr "Theme:" | ||
118 | |||
119 | msgid "Update" | ||
120 | msgstr "Aktualisieren" | ||
121 | |||
122 | msgid "Change your language" | ||
123 | msgstr "Sprache ändern" | ||
124 | |||
125 | msgid "Language:" | ||
126 | msgstr "Sprache:" | ||
127 | |||
49 | msgid "Change your password" | 128 | msgid "Change your password" |
50 | msgstr "Passwort ändern" | 129 | msgstr "Passwort ändern" |
51 | 130 | ||
@@ -58,75 +137,86 @@ msgstr "Passwort" | |||
58 | msgid "Repeat your new password:" | 137 | msgid "Repeat your new password:" |
59 | msgstr "Neues Passwort wiederholen:" | 138 | msgstr "Neues Passwort wiederholen:" |
60 | 139 | ||
61 | msgid "Update" | ||
62 | msgstr "Aktualisieren" | ||
63 | |||
64 | msgid "Import" | 140 | msgid "Import" |
65 | msgstr "Import" | 141 | msgstr "Import" |
66 | 142 | ||
67 | msgid "Please execute the import script locally, it can take a very long time." | 143 | msgid "" |
68 | msgstr "Bitte führe das Import Script lokal aus, dies kann eine Weile dauern." | 144 | "Importing from other services can be quite long, and webservers default " |
145 | "configuration often prevents long scripts execution time, so it must be done " | ||
146 | "in multiple parts." | ||
147 | msgstr "" | ||
148 | "Der Import von anderen Diensten kann sehr lange dauern. Deswegen bricht der " | ||
149 | "Webserver diesen in vielen Konfigurationen ab. Daher muss der Import in " | ||
150 | "mehrere Teile aufgeteilt werden." | ||
151 | |||
152 | msgid "First, select the export file on your computer and upload it." | ||
153 | msgstr "Wähle eine Datei von deinem Computer aus und lade sie hoch." | ||
154 | |||
155 | msgid "File:" | ||
156 | msgstr "Datei:" | ||
157 | |||
158 | msgid "Upload" | ||
159 | msgstr "Hochladen" | ||
69 | 160 | ||
70 | msgid "More info in the official doc:" | 161 | msgid "Then, click on the right link below." |
71 | msgstr "Mehr Informationen in der offiziellen Dokumentation:" | 162 | msgstr "Klicke dann unten auf den entsprechenden Link." |
72 | 163 | ||
73 | msgid "import from Pocket" | 164 | msgid "Import from Pocket" |
74 | msgstr "Import aus Pocket" | 165 | msgstr "Import aus Pocket" |
75 | 166 | ||
76 | msgid "import from Readability" | 167 | #, php-format |
168 | msgid "(after uploaded %s file)" | ||
169 | msgstr "(nach Upload der Datei %s)" | ||
170 | |||
171 | msgid "Import from Readability" | ||
77 | msgstr "Import aus Readability" | 172 | msgstr "Import aus Readability" |
78 | 173 | ||
79 | msgid "import from Instapaper" | 174 | msgid "Import from Instapaper" |
80 | msgstr "Import aus Instapaper" | 175 | msgstr "Import aus Instapaper" |
81 | 176 | ||
82 | msgid "Export your poche data" | 177 | msgid "Import from wallabag" |
83 | msgstr "Exportieren Sie Ihre Poche Daten." | 178 | msgstr "Import aus Readability" |
84 | |||
85 | msgid "Click here" | ||
86 | msgstr "Klicke hier" | ||
87 | 179 | ||
88 | msgid "to export your poche data." | 180 | msgid "" |
89 | msgstr "um deine Daten aus Poche zu exportieren." | 181 | "3. Your feed token is currently empty and must first be generated to fetch " |
182 | "content. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
183 | msgstr "" | ||
184 | "3. Dein Feed Token ist noch nicht vorhanden und muss zunächst generiert " | ||
185 | "werden, um Inhalt abrufen zu können. Klicke <a href='?feed&" | ||
186 | "action=generate'>hier um ihn zu generieren</a>." | ||
90 | 187 | ||
91 | msgid "back to home" | 188 | msgid "Finally, you have to fetch content for imported items." |
92 | msgstr "züruck zur Hauptseite" | 189 | msgstr "Jetzt musst du den Inhalt der importierten Artikel abrufen." |
93 | 190 | ||
94 | msgid "installation" | 191 | msgid "Click here" |
95 | msgstr "Installieren" | 192 | msgstr "Klicke hier" |
96 | 193 | ||
97 | msgid "install your poche" | 194 | msgid "to fetch content for 10 articles" |
98 | msgstr "Installiere dein Poche" | 195 | msgstr "um den Inhalt von 10 Artikeln abzurufen" |
99 | 196 | ||
100 | msgid "" | 197 | msgid "" |
101 | "poche is still not installed. Please fill the below form to install it. " | 198 | "If you have console access to your server, you can also create a cron task:" |
102 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
103 | "on poche website</a>." | ||
104 | msgstr "" | 199 | msgstr "" |
105 | "Poche ist noch nicht installiert. Bitte fülle die Felder unten aus, um die " | 200 | "Wenn du Konsolenzugang zu deinem Server hast kannst du auch einen cron " |
106 | "Installation durchzuführen. Zögere nicht, <a href='http://inthepoche.com/" | 201 | "erstellen:" |
107 | "doc'>die Dokumentation auf der Website von Poche zu lesen falls du Probleme " | ||
108 | "haben solltest." | ||
109 | 202 | ||
110 | msgid "Login" | 203 | msgid "Export your wallabag data" |
111 | msgstr "Benutzername" | 204 | msgstr "Exportieren deine wallabag Daten" |
112 | 205 | ||
113 | msgid "Repeat your password" | 206 | msgid "to download your database." |
114 | msgstr "Wiederhole dein Passwort" | 207 | msgstr "um deine Datenbank herunterzuladen" |
115 | 208 | ||
116 | msgid "Install" | 209 | msgid "to export your wallabag data." |
117 | msgstr "Installieren" | 210 | msgstr "um deine Daten aus wallabag zu exportieren." |
118 | 211 | ||
119 | msgid "back to top" | 212 | msgid "Cache" |
120 | msgstr "Nach Oben" | 213 | msgstr "Cache" |
121 | 214 | ||
122 | msgid "favoris" | 215 | msgid "to delete cache." |
123 | msgstr "" | 216 | msgstr "um den Cache zu löschen." |
124 | |||
125 | msgid "archive" | ||
126 | msgstr "Archiv" | ||
127 | 217 | ||
128 | msgid "unread" | 218 | msgid "Tags" |
129 | msgstr "ungelesen" | 219 | msgstr "Tags" |
130 | 220 | ||
131 | msgid "by date asc" | 221 | msgid "by date asc" |
132 | msgstr "nach Datum aufsteigend" | 222 | msgstr "nach Datum aufsteigend" |
@@ -146,10 +236,72 @@ msgstr "nach Titel" | |||
146 | msgid "by title desc" | 236 | msgid "by title desc" |
147 | msgstr "nach Titel absteigend" | 237 | msgstr "nach Titel absteigend" |
148 | 238 | ||
149 | msgid "No link available here!" | 239 | #, fuzzy |
150 | msgstr "Kein Link verfügbar!" | 240 | msgid "toggle view mode" |
241 | msgstr "Favorit" | ||
242 | |||
243 | msgid "home" | ||
244 | msgstr "Start" | ||
245 | |||
246 | msgid "favorites" | ||
247 | msgstr "Favoriten" | ||
248 | |||
249 | msgid "archive" | ||
250 | msgstr "Archiv" | ||
251 | |||
252 | msgid "tags" | ||
253 | msgstr "Tags" | ||
254 | |||
255 | msgid "save a link" | ||
256 | msgstr "Speichere einen Link" | ||
257 | |||
258 | msgid "search" | ||
259 | msgstr "Suche" | ||
260 | |||
261 | msgid "logout" | ||
262 | msgstr "Logout" | ||
263 | |||
264 | msgid "return home" | ||
265 | msgstr "Zurück zum Start" | ||
266 | |||
267 | #, fuzzy | ||
268 | msgid "Search" | ||
269 | msgstr "Archiv" | ||
270 | |||
271 | msgid "powered by" | ||
272 | msgstr "bereitgestellt von" | ||
273 | |||
274 | msgid "debug mode is on so cache is off." | ||
275 | msgstr "Debug Modus ist aktiviert, das Caching ist somit deaktiviert" | ||
276 | |||
277 | msgid "your wallabag version:" | ||
278 | msgstr "Deine wallabag Version" | ||
279 | |||
280 | msgid "storage:" | ||
281 | msgstr "Speicher:" | ||
282 | |||
283 | msgid "Save a link" | ||
284 | msgstr "Speichere einen Link" | ||
285 | |||
286 | msgid "save link!" | ||
287 | msgstr "Link speichern!" | ||
288 | |||
289 | msgid "unread" | ||
290 | msgstr "ungelesen" | ||
291 | |||
292 | msgid "Tag" | ||
293 | msgstr "Tag" | ||
294 | |||
295 | msgid "No articles found." | ||
296 | msgstr "Keine Artikel gefunden." | ||
151 | 297 | ||
152 | msgid "toggle mark as read" | 298 | msgid "estimated reading time:" |
299 | msgstr "geschätzte Lesezeit:" | ||
300 | |||
301 | msgid "estimated reading time :" | ||
302 | msgstr "geschätzte Lesezeit:" | ||
303 | |||
304 | msgid "Toggle mark as read" | ||
153 | msgstr "Als gelesen markieren" | 305 | msgstr "Als gelesen markieren" |
154 | 306 | ||
155 | msgid "toggle favorite" | 307 | msgid "toggle favorite" |
@@ -161,69 +313,346 @@ msgstr "Löschen" | |||
161 | msgid "original" | 313 | msgid "original" |
162 | msgstr "Original" | 314 | msgstr "Original" |
163 | 315 | ||
316 | msgid "Mark all the entries as read" | ||
317 | msgstr "Markiere alle als gelesen" | ||
318 | |||
164 | msgid "results" | 319 | msgid "results" |
165 | msgstr "Ergebnisse" | 320 | msgstr "Ergebnisse" |
166 | 321 | ||
167 | msgid "tweet" | 322 | msgid "Uh, there is a problem with the cron." |
168 | msgstr "Twittern" | 323 | msgstr "Oh, es gab ein Problem mit dem cron." |
169 | 324 | ||
170 | msgid "email" | 325 | msgid "Untitled" |
171 | msgstr "senden per E-Mail" | 326 | msgstr "Ohne Titel" |
172 | 327 | ||
173 | msgid "shaarli" | 328 | msgid "the link has been added successfully" |
174 | msgstr "Shaarli" | 329 | msgstr "Speichern des Links erfolgreich" |
175 | 330 | ||
176 | msgid "flattr" | 331 | msgid "error during insertion : the link wasn't added" |
177 | msgstr "flattr" | 332 | msgstr "Fehler beim Einfügen: Der Link wurde nicht hinzugefügt" |
178 | 333 | ||
179 | msgid "this article appears wrong?" | 334 | msgid "the link has been deleted successfully" |
180 | msgstr "dieser Artikel erscheint falsch?" | 335 | msgstr "Löschen des Links erfolgreich" |
181 | 336 | ||
182 | msgid "create an issue" | 337 | msgid "the link wasn't deleted" |
183 | msgstr "ein Ticket erstellen" | 338 | msgstr "Der Link wurde nicht entfernt" |
184 | 339 | ||
185 | msgid "or" | 340 | msgid "Article not found!" |
186 | msgstr "oder" | 341 | msgstr "Artikel nicht gefunden!" |
187 | 342 | ||
188 | msgid "contact us by mail" | 343 | msgid "previous" |
189 | msgstr "kontaktieren Sie uns per E-Mail" | 344 | msgstr "vorherige" |
190 | 345 | ||
191 | msgid "plop" | 346 | msgid "next" |
192 | msgstr "plop" | 347 | msgstr "nächste" |
193 | 348 | ||
194 | msgid "home" | 349 | msgid "in demo mode, you can't update your password" |
195 | msgstr "Start" | 350 | msgstr "im Demo-Modus kann das Passwort nicht geändert werden" |
196 | 351 | ||
197 | msgid "favorites" | 352 | msgid "your password has been updated" |
198 | msgstr "Favoriten" | 353 | msgstr "Dein Passwort wurde geändert" |
199 | 354 | ||
200 | msgid "logout" | 355 | msgid "" |
201 | msgstr "Logout" | 356 | "the two fields have to be filled & the password must be the same in the two " |
357 | "fields" | ||
358 | msgstr "Beide Felder müssen mit selbem Inhalt ausgefüllt sein" | ||
202 | 359 | ||
203 | msgid "powered by" | 360 | msgid "still using the \"" |
204 | msgstr "bereitgestellt von" | 361 | msgstr "nutze immernoch die \"" |
205 | 362 | ||
206 | msgid "debug mode is on so cache is off." | 363 | msgid "that theme does not seem to be installed" |
207 | msgstr "Debug Modus ist aktiviert, das Caching ist somit deaktiviert" | 364 | msgstr "dieses Theme scheint nicht installiert zu sein" |
208 | 365 | ||
209 | msgid "your poche version:" | 366 | msgid "you have changed your theme preferences" |
210 | msgstr "Deine Poche Version" | 367 | msgstr "Du hast deine Theme Einstellungen geändert" |
211 | 368 | ||
212 | msgid "storage:" | 369 | msgid "that language does not seem to be installed" |
213 | msgstr "Speicher:" | 370 | msgstr "Diese Sprache scheint nicht installiert zu sein" |
371 | |||
372 | msgid "you have changed your language preferences" | ||
373 | msgstr "Du hast deine Spracheinstellungen geändert" | ||
374 | |||
375 | msgid "login failed: you have to fill all fields" | ||
376 | msgstr "Anmeldung fehlgeschlagen: Alle Felder müssen ausgefüllt werden" | ||
377 | |||
378 | msgid "welcome to your wallabag" | ||
379 | msgstr "Willkommen bei deiner wallabag" | ||
380 | |||
381 | msgid "login failed: bad login or password" | ||
382 | msgstr "Anmeldung fehlgeschlagen: Falscher Benutzername oder Passwort" | ||
383 | |||
384 | msgid "" | ||
385 | "import from instapaper completed. You have to execute the cron to fetch " | ||
386 | "content." | ||
387 | msgstr "" | ||
388 | "Import aus Instapaper vollständig. Führe den cronjob aus um den Inhalt " | ||
389 | "abzurufen." | ||
390 | |||
391 | msgid "" | ||
392 | "import from pocket completed. You have to execute the cron to fetch content." | ||
393 | msgstr "" | ||
394 | "Import aus Pocket vollständig. Führe den cronjob aus um den Inhalt abzurufen." | ||
214 | 395 | ||
215 | msgid "login to your poche" | 396 | msgid "" |
216 | msgstr "Bei Poche anmelden" | 397 | "import from Readability completed. You have to execute the cron to fetch " |
398 | "content." | ||
399 | msgstr "" | ||
400 | "Import aus Readability vollständig. Führe den cronjob aus um den Inhalt " | ||
401 | "abzurufen." | ||
217 | 402 | ||
218 | msgid "you are in demo mode, some features may be disabled." | 403 | msgid "" |
404 | "import from Poche completed. You have to execute the cron to fetch content." | ||
219 | msgstr "" | 405 | msgstr "" |
220 | "Du befindest dich im Demomodus, einige Funktionen könnten deaktiviert sein." | 406 | "Import aus Poche vollständig. Führe den cronjob aus um den Inhalt abzurufen." |
407 | |||
408 | msgid "Unknown import provider." | ||
409 | msgstr "Unbekannter Import Anbieter." | ||
410 | |||
411 | msgid "Could not find required \"" | ||
412 | msgstr "Nicht gefunden: \"" | ||
413 | |||
414 | msgid "File uploaded. You can now execute import." | ||
415 | msgstr "Datei hochgeladen. Du kannst nun importieren." | ||
416 | |||
417 | msgid "Error while importing file. Do you have access to upload it?" | ||
418 | msgstr "Fehler beim Importieren. Hast du das Recht zum Hochladen?" | ||
419 | |||
420 | msgid "User with this id (" | ||
421 | msgstr "Nutzer mit dieser id (" | ||
422 | |||
423 | msgid "Uh, there is a problem while generating feeds." | ||
424 | msgstr "Oh, es gab ein Problem beim Erstellen des Feeds." | ||
425 | |||
426 | msgid "Cache deleted." | ||
427 | msgstr "Cache gelöscht" | ||
428 | |||
429 | msgid "Oops, it seems you don't have PHP 5." | ||
430 | msgstr "Oops, es scheint als würde PHP 5 fehlen." | ||
431 | |||
432 | msgid "wallabag, a read it later open source system" | ||
433 | msgstr "wallabag, ein Später-Lesen Open Source System" | ||
434 | |||
435 | msgid "login failed: user doesn't exist" | ||
436 | msgstr "Anmeldung fehlgeschlagen: Benutzer existiert nicht" | ||
437 | |||
438 | #~ msgid "You can enter multiple tags, separated by commas." | ||
439 | #~ msgstr "Du kannst mehrere Tags, durch Kommata getrennt, eingeben." | ||
440 | |||
441 | #~ msgid "return to article" | ||
442 | #~ msgstr "zurück zum Artikel" | ||
443 | |||
444 | #, fuzzy | ||
445 | #~ msgid "favoris" | ||
446 | #~ msgstr "Favoriten" | ||
447 | |||
448 | #~ msgid "mark all the entries as read" | ||
449 | #~ msgstr "Markiere alle als gelesen" | ||
450 | |||
451 | #~ msgid "Back to top" | ||
452 | #~ msgstr "Nach Oben" | ||
453 | |||
454 | #~ msgid "Mark as read" | ||
455 | #~ msgstr "Als gelesen markieren" | ||
456 | |||
457 | #~ msgid "Favorite" | ||
458 | #~ msgstr "Favoriten" | ||
459 | |||
460 | #~ msgid "Toggle favorite" | ||
461 | #~ msgstr "Favorit" | ||
462 | |||
463 | #~ msgid "Delete" | ||
464 | #~ msgstr "Löschen" | ||
465 | |||
466 | #~ msgid "Tweet" | ||
467 | #~ msgstr "Twittern" | ||
468 | |||
469 | #~ msgid "Email" | ||
470 | #~ msgstr "per E-Mail senden" | ||
471 | |||
472 | #~ msgid "shaarli" | ||
473 | #~ msgstr "Shaarli" | ||
474 | |||
475 | #~ msgid "flattr" | ||
476 | #~ msgstr "flattr" | ||
477 | |||
478 | #~ msgid "Does this article appear wrong?" | ||
479 | #~ msgstr "Erscheint dieser Artikel falsch?" | ||
480 | |||
481 | #~ msgid "Edit tags" | ||
482 | #~ msgstr "Tags bearbeiten" | ||
483 | |||
484 | #~ msgid "Start typing for auto complete." | ||
485 | #~ msgstr "Beginne zu tippen für die Autovervollständigung." | ||
486 | |||
487 | #~ msgid "Return home" | ||
488 | #~ msgstr "Zurück zum Start" | ||
489 | |||
490 | #~ msgid "tags:" | ||
491 | #~ msgstr "Tags:" | ||
492 | |||
493 | #~ msgid "login to your wallabag" | ||
494 | #~ msgstr "Bei wallabag anmelden" | ||
495 | |||
496 | #~ msgid "you are in demo mode, some features may be disabled." | ||
497 | #~ msgstr "" | ||
498 | #~ "Du befindest dich im Demomodus, einige Funktionen könnten deaktiviert " | ||
499 | #~ "sein." | ||
500 | |||
501 | #~ msgid "Login" | ||
502 | #~ msgstr "Benutzername" | ||
503 | |||
504 | #~ msgid "Stay signed in" | ||
505 | #~ msgstr "Angemeldet bleiben" | ||
506 | |||
507 | #~ msgid "(Do not check on public computers)" | ||
508 | #~ msgstr "(nicht auf einem öffentlichen Computer anhaken)" | ||
509 | |||
510 | #~ msgid "plop" | ||
511 | #~ msgstr "plop" | ||
512 | |||
513 | #~ msgid "Login to wallabag" | ||
514 | #~ msgstr "Bei wallabag anmelden" | ||
515 | |||
516 | #~ msgid "Username" | ||
517 | #~ msgstr "Benutzername" | ||
518 | |||
519 | #~ msgid "Sign in" | ||
520 | #~ msgstr "Einloggen" | ||
521 | |||
522 | #~ msgid "Enter your search here" | ||
523 | #~ msgstr "Gib hier deine Suchanfrage ein" | ||
524 | |||
525 | #~ msgid "installation" | ||
526 | #~ msgstr "Installieren" | ||
527 | |||
528 | #~ msgid "install your wallabag" | ||
529 | #~ msgstr "Installiere deine wallabag" | ||
530 | |||
531 | #~ msgid "" | ||
532 | #~ "wallabag is still not installed. Please fill the below form to install " | ||
533 | #~ "it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the " | ||
534 | #~ "documentation on wallabag website</a>." | ||
535 | #~ msgstr "" | ||
536 | #~ "wallabag ist noch nicht installiert. Bitte fülle die Felder unten aus, um " | ||
537 | #~ "die Installation durchzuführen. Zögere nicht, <a href='http://doc." | ||
538 | #~ "wallabag.org/'>die Dokumentation auf der Website von wallabag zu lesen, " | ||
539 | #~ "falls du Probleme haben solltest." | ||
540 | |||
541 | #~ msgid "Repeat your password" | ||
542 | #~ msgstr "Wiederhole dein Passwort" | ||
543 | |||
544 | #~ msgid "Install" | ||
545 | #~ msgstr "Installieren" | ||
546 | |||
547 | #~ msgid "No link available here!" | ||
548 | #~ msgstr "Kein Link verfügbar!" | ||
549 | |||
550 | #~ msgid "toggle mark as read" | ||
551 | #~ msgstr "Als gelesen markieren" | ||
552 | |||
553 | #~ msgid "" | ||
554 | #~ "You can <a href='wallabag_compatibility_test.php'>check your " | ||
555 | #~ "configuration here</a>." | ||
556 | #~ msgstr "" | ||
557 | #~ "Du kannst deine Konfiguration <a href='wallabag_compatibility_test." | ||
558 | #~ "php'>hier testen</a>." | ||
559 | |||
560 | #~ msgid "back to home" | ||
561 | #~ msgstr "züruck zur Hauptseite" | ||
562 | |||
563 | #~ msgid "tweet" | ||
564 | #~ msgstr "Twittern" | ||
565 | |||
566 | #~ msgid "email" | ||
567 | #~ msgstr "senden per E-Mail" | ||
568 | |||
569 | #~ msgid "this article appears wrong?" | ||
570 | #~ msgstr "dieser Artikel erscheint falsch?" | ||
571 | |||
572 | #~ msgid "Poching a link" | ||
573 | #~ msgstr "Poche einen Link" | ||
574 | |||
575 | #~ msgid "by filling this field" | ||
576 | #~ msgstr "durch das ausfüllen dieses Feldes:" | ||
577 | |||
578 | #~ msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
579 | #~ msgstr "Bookmarklet: Ziehe diesen Link in deine Lesezeichen-Leiste" | ||
580 | |||
581 | #~ msgid "your version" | ||
582 | #~ msgstr "Deine Version" | ||
583 | |||
584 | #~ msgid "latest stable version" | ||
585 | #~ msgstr "Neuste stabile Version" | ||
586 | |||
587 | #~ msgid "a more recent stable version is available." | ||
588 | #~ msgstr "Eine neuere stabile Version ist verfügbar." | ||
589 | |||
590 | #~ msgid "you are up to date." | ||
591 | #~ msgstr "Du bist auf den neuesten Stand." | ||
592 | |||
593 | #~ msgid "latest dev version" | ||
594 | #~ msgstr "Neuste Entwicklungsversion" | ||
595 | |||
596 | #~ msgid "a more recent development version is available." | ||
597 | #~ msgstr "Eine neuere Entwicklungsversion ist verfügbar." | ||
598 | |||
599 | #~ msgid "" | ||
600 | #~ "Please execute the import script locally, it can take a very long time." | ||
601 | #~ msgstr "" | ||
602 | #~ "Bitte führe das Import Script lokal aus, dies kann eine Weile dauern." | ||
603 | |||
604 | #~ msgid "More infos in the official doc:" | ||
605 | #~ msgstr "Mehr Informationen in der offiziellen Dokumentation:" | ||
606 | |||
607 | #~ msgid "import from Pocket" | ||
608 | #~ msgstr "Import aus Pocket" | ||
609 | |||
610 | #~ msgid "(you must have a %s file on your server)" | ||
611 | #~ msgstr "(du brauchst eine %s Datei auf deinem Server)" | ||
612 | |||
613 | #~ msgid "import from Readability" | ||
614 | #~ msgstr "Import aus Readability" | ||
615 | |||
616 | #~ msgid "import from Instapaper" | ||
617 | #~ msgstr "Import aus Instapaper" | ||
618 | |||
619 | #~ msgid "You can also create a cron task:" | ||
620 | #~ msgstr "Du kannst auch einen cronjob anlegen:" | ||
621 | |||
622 | #~ msgid "" | ||
623 | #~ "Please execute the import script locally as it can take a very long time." | ||
624 | #~ msgstr "" | ||
625 | #~ "Bitte führe das Import Script lokal aus, da dies eine Weile dauern kann." | ||
626 | |||
627 | #~ msgid "More info in the official documentation:" | ||
628 | #~ msgstr "Mehr Informationen in der offiziellen Dokumentation:" | ||
629 | |||
630 | #~ msgid "import from instapaper completed" | ||
631 | #~ msgstr "Import aus Instapaper erfolgreich" | ||
632 | |||
633 | #~ msgid "import from pocket completed" | ||
634 | #~ msgstr "Import aus Pocket erfolgreich" | ||
635 | |||
636 | #~ msgid "import from Poche completed. " | ||
637 | #~ msgstr "Import aus Poche erfolgreich" | ||
638 | |||
639 | #~ msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
640 | #~ msgstr "Unvollständige inc/poche/define.inc.php Datei, bitte setze \"" | ||
641 | |||
642 | #~ msgid "poche it!" | ||
643 | #~ msgstr "Poche es!" | ||
644 | |||
645 | #~ msgid "Updating poche" | ||
646 | #~ msgstr "Poche aktualisieren" | ||
647 | |||
648 | #~ msgid "create an issue" | ||
649 | #~ msgstr "ein Ticket erstellen" | ||
221 | 650 | ||
222 | msgid "Stay signed in" | 651 | #~ msgid "or" |
223 | msgstr "Angemeldet bleiben" | 652 | #~ msgstr "oder" |
224 | 653 | ||
225 | msgid "(Do not check on public computers)" | 654 | #~ msgid "contact us by mail" |
226 | msgstr "(nicht auf einem öffentlichen Computer anhaken)" | 655 | #~ msgstr "kontaktieren Sie uns per E-Mail" |
227 | 656 | ||
228 | msgid "Sign in" | 657 | #~ msgid "your poche version:" |
229 | msgstr "Einloggen" | 658 | #~ msgstr "Deine Poche Version" |
diff --git a/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.mo b/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.mo index 83d0a85f..bf5f69e7 100644 --- a/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.mo +++ b/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.po b/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.po index b78759f5..119fb060 100644 --- a/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.po +++ b/locale/en_EN.utf8/LC_MESSAGES/en_EN.utf8.po | |||
@@ -1,50 +1,124 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: \n" | 3 | "Project-Id-Version: \n" |
4 | "POT-Creation-Date: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-02-25 15:17+0300\n" | ||
5 | "PO-Revision-Date: \n" | 6 | "PO-Revision-Date: \n" |
6 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
7 | "Language-Team: \n" | 8 | "Language-Team: \n" |
9 | "Language: \n" | ||
8 | "MIME-Version: 1.0\n" | 10 | "MIME-Version: 1.0\n" |
9 | "Content-Type: text/plain; charset=UTF-8\n" | 11 | "Content-Type: text/plain; charset=UTF-8\n" |
10 | "Content-Transfer-Encoding: 8bit\n" | 12 | "Content-Transfer-Encoding: 8bit\n" |
11 | "X-Generator: Poedit 1.5.4\n" | 13 | "X-Generator: Poedit 1.5.4\n" |
14 | "X-Poedit-Language: English\n" | ||
15 | "X-Poedit-Basepath: .\n" | ||
16 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" | ||
17 | |||
18 | msgid "wallabag, a read it later open source system" | ||
19 | msgstr "wallabag, a read it later open source system" | ||
20 | |||
21 | msgid "login failed: user doesn't exist" | ||
22 | msgstr "login failed: user doesn't exist" | ||
23 | |||
24 | msgid "return home" | ||
25 | msgstr "return home" | ||
12 | 26 | ||
13 | msgid "config" | 27 | msgid "config" |
14 | msgstr "config" | 28 | msgstr "config" |
15 | 29 | ||
16 | msgid "Poching a link" | 30 | msgid "Saving articles" |
17 | msgstr "Poching a link" | 31 | msgstr "Saving articles" |
32 | |||
33 | msgid "There are several ways to save an article:" | ||
34 | msgstr "There are several ways to save an article:" | ||
18 | 35 | ||
19 | msgid "read the documentation" | 36 | msgid "read the documentation" |
20 | msgstr "read the documentation" | 37 | msgstr "read the documentation" |
21 | 38 | ||
22 | msgid "by filling this field" | 39 | msgid "download the extension" |
23 | msgstr "by filling this field" | 40 | msgstr "download the extension" |
24 | 41 | ||
25 | msgid "poche it!" | 42 | msgid "via F-Droid" |
26 | msgstr "poche it!" | 43 | msgstr "via F-Droid" |
27 | 44 | ||
28 | msgid "Updating poche" | 45 | msgid " or " |
29 | msgstr "Updating poche" | 46 | msgstr " or " |
30 | 47 | ||
31 | msgid "your version" | 48 | msgid "via Google Play" |
32 | msgstr "your version" | 49 | msgstr "via Google Play" |
33 | 50 | ||
34 | msgid "latest stable version" | 51 | msgid "download the application" |
35 | msgstr "latest stable version" | 52 | msgstr "download the application" |
36 | 53 | ||
37 | msgid "a more recent stable version is available." | 54 | msgid "By filling this field" |
38 | msgstr "a more recent stable version is available." | 55 | msgstr "By filling this field" |
39 | 56 | ||
40 | msgid "you are up to date." | 57 | msgid "bag it!" |
41 | msgstr "you are up to date." | 58 | msgstr "bag it!" |
42 | 59 | ||
43 | msgid "latest dev version" | 60 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
44 | msgstr "latest dev version" | 61 | msgstr "Bookmarklet: drag & drop this link to your bookmarks bar" |
45 | 62 | ||
46 | msgid "a more recent development version is available." | 63 | msgid "Upgrading wallabag" |
47 | msgstr "a more recent development version is available." | 64 | msgstr "Upgrading wallabag" |
65 | |||
66 | msgid "Installed version" | ||
67 | msgstr "Installed version" | ||
68 | |||
69 | msgid "Latest stable version" | ||
70 | msgstr "Latest stable version" | ||
71 | |||
72 | msgid "A more recent stable version is available." | ||
73 | msgstr "A more recent stable version is available." | ||
74 | |||
75 | msgid "You are up to date." | ||
76 | msgstr "You are up to date." | ||
77 | |||
78 | msgid "Latest dev version" | ||
79 | msgstr "Latest dev version" | ||
80 | |||
81 | msgid "A more recent development version is available." | ||
82 | msgstr "A more recent development version is available." | ||
83 | |||
84 | msgid "Feeds" | ||
85 | msgstr "Feeds" | ||
86 | |||
87 | msgid "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
88 | msgstr "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
89 | |||
90 | msgid "Unread feed" | ||
91 | msgstr "Unread feed" | ||
92 | |||
93 | msgid "Favorites feed" | ||
94 | msgstr "Favorites feed" | ||
95 | |||
96 | msgid "Archive feed" | ||
97 | msgstr "Archive feed" | ||
98 | |||
99 | msgid "Your token:" | ||
100 | msgstr "Your token:" | ||
101 | |||
102 | msgid "Your user id:" | ||
103 | msgstr "Your user id:" | ||
104 | |||
105 | msgid "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
106 | msgstr "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
107 | |||
108 | msgid "Change your theme" | ||
109 | msgstr "Change your theme" | ||
110 | |||
111 | msgid "Theme:" | ||
112 | msgstr "Theme:" | ||
113 | |||
114 | msgid "Update" | ||
115 | msgstr "Update" | ||
116 | |||
117 | msgid "Change your language" | ||
118 | msgstr "Change your language" | ||
119 | |||
120 | msgid "Language:" | ||
121 | msgstr "Language:" | ||
48 | 122 | ||
49 | msgid "Change your password" | 123 | msgid "Change your password" |
50 | msgstr "Change your password" | 124 | msgstr "Change your password" |
@@ -58,66 +132,60 @@ msgstr "Password" | |||
58 | msgid "Repeat your new password:" | 132 | msgid "Repeat your new password:" |
59 | msgstr "Repeat your new password:" | 133 | msgstr "Repeat your new password:" |
60 | 134 | ||
61 | msgid "Update" | ||
62 | msgstr "Update" | ||
63 | |||
64 | msgid "Import" | 135 | msgid "Import" |
65 | msgstr "Import" | 136 | msgstr "Import" |
66 | 137 | ||
67 | msgid "Please execute the import script locally, it can take a very long time." | 138 | msgid "Please execute the import script locally as it can take a very long time." |
68 | msgstr "" | 139 | msgstr "Please execute the import script locally as it can take a very long time." |
69 | "Please execute the import script locally, it can take a very long time." | ||
70 | 140 | ||
71 | msgid "More info in the official doc:" | 141 | msgid "More info in the official documentation:" |
72 | msgstr "More info in the official doc:" | 142 | msgstr "More info in the official documentation:" |
73 | 143 | ||
74 | msgid "import from Pocket" | 144 | msgid "Import from Pocket" |
75 | msgstr "import from Pocket" | 145 | msgstr "Import from Pocket" |
76 | 146 | ||
77 | msgid "import from Readability" | 147 | #, php-format |
78 | msgstr "import from Readability" | 148 | msgid "(you must have a %s file on your server)" |
149 | msgstr "(you must have a %s file on your server)" | ||
79 | 150 | ||
80 | msgid "import from Instapaper" | 151 | msgid "Import from Readability" |
81 | msgstr "import from Instapaper" | 152 | msgstr "Import from Readability" |
153 | |||
154 | msgid "Import from Instapaper" | ||
155 | msgstr "Import from Instapaper" | ||
82 | 156 | ||
83 | msgid "Export your poche data" | 157 | msgid "Import from wallabag" |
84 | msgstr "Export your poche data" | 158 | msgstr "Import from wallabag" |
159 | |||
160 | msgid "Export your wallabag data" | ||
161 | msgstr "Export your wallabag data" | ||
85 | 162 | ||
86 | msgid "Click here" | 163 | msgid "Click here" |
87 | msgstr "Click here" | 164 | msgstr "Click here" |
88 | 165 | ||
89 | msgid "to export your poche data." | 166 | msgid "to download your database." |
90 | msgstr "to export your poche data." | 167 | msgstr "to download your database." |
91 | 168 | ||
92 | msgid "back to home" | 169 | msgid "to export your wallabag data." |
93 | msgstr "back to home" | 170 | msgstr "to export your wallabag data." |
94 | 171 | ||
95 | msgid "installation" | 172 | msgid "Cache" |
96 | msgstr "installation" | 173 | msgstr "Cache" |
97 | 174 | ||
98 | msgid "install your poche" | 175 | msgid "to delete cache." |
99 | msgstr "install your poche" | 176 | msgstr "to delete cache." |
100 | 177 | ||
101 | msgid "" | 178 | msgid "You can enter multiple tags, separated by commas." |
102 | "poche is still not installed. Please fill the below form to install it. " | 179 | msgstr "You can enter multiple tags, separated by commas." |
103 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
104 | "on poche website</a>." | ||
105 | msgstr "" | ||
106 | "poche is still not installed. Please fill the below form to install it. " | ||
107 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
108 | "on poche website</a>." | ||
109 | |||
110 | msgid "Login" | ||
111 | msgstr "Login" | ||
112 | 180 | ||
113 | msgid "Repeat your password" | 181 | msgid "return to article" |
114 | msgstr "Repeat your password" | 182 | msgstr "return to article" |
115 | 183 | ||
116 | msgid "Install" | 184 | msgid "plop" |
117 | msgstr "Install" | 185 | msgstr "plop" |
118 | 186 | ||
119 | msgid "back to top" | 187 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
120 | msgstr "back to top" | 188 | msgstr "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
121 | 189 | ||
122 | msgid "favoris" | 190 | msgid "favoris" |
123 | msgstr "favoris" | 191 | msgstr "favoris" |
@@ -146,11 +214,14 @@ msgstr "by title" | |||
146 | msgid "by title desc" | 214 | msgid "by title desc" |
147 | msgstr "by title desc" | 215 | msgstr "by title desc" |
148 | 216 | ||
149 | msgid "No link available here!" | 217 | msgid "Tag" |
150 | msgstr "No link available here!" | 218 | msgstr "Tag" |
151 | 219 | ||
152 | msgid "toggle mark as read" | 220 | msgid "No articles found." |
153 | msgstr "toggle mark as read" | 221 | msgstr "No articles found." |
222 | |||
223 | msgid "Toggle mark as read" | ||
224 | msgstr "Toggle mark as read" | ||
154 | 225 | ||
155 | msgid "toggle favorite" | 226 | msgid "toggle favorite" |
156 | msgstr "toggle favorite" | 227 | msgstr "toggle favorite" |
@@ -161,14 +232,86 @@ msgstr "delete" | |||
161 | msgid "original" | 232 | msgid "original" |
162 | msgstr "original" | 233 | msgstr "original" |
163 | 234 | ||
235 | msgid "estimated reading time:" | ||
236 | msgstr "estimated reading time:" | ||
237 | |||
238 | msgid "mark all the entries as read" | ||
239 | msgstr "mark all the entries as read" | ||
240 | |||
164 | msgid "results" | 241 | msgid "results" |
165 | msgstr "results" | 242 | msgstr "results" |
166 | 243 | ||
167 | msgid "tweet" | 244 | msgid "installation" |
168 | msgstr "tweet" | 245 | msgstr "installation" |
169 | 246 | ||
170 | msgid "email" | 247 | msgid "install your wallabag" |
171 | msgstr "email" | 248 | msgstr "install your wallabag" |
249 | |||
250 | msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
251 | msgstr "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
252 | |||
253 | msgid "Login" | ||
254 | msgstr "Login" | ||
255 | |||
256 | msgid "Repeat your password" | ||
257 | msgstr "Repeat your password" | ||
258 | |||
259 | msgid "Install" | ||
260 | msgstr "Install" | ||
261 | |||
262 | msgid "login to your wallabag" | ||
263 | msgstr "login to your wallabag" | ||
264 | |||
265 | msgid "Login to wallabag" | ||
266 | msgstr "Login to wallabag" | ||
267 | |||
268 | msgid "you are in demo mode, some features may be disabled." | ||
269 | msgstr "you are in demo mode, some features may be disabled." | ||
270 | |||
271 | msgid "Username" | ||
272 | msgstr "Username" | ||
273 | |||
274 | msgid "Stay signed in" | ||
275 | msgstr "Stay signed in" | ||
276 | |||
277 | msgid "(Do not check on public computers)" | ||
278 | msgstr "(Do not check on public computers)" | ||
279 | |||
280 | msgid "Sign in" | ||
281 | msgstr "Sign in" | ||
282 | |||
283 | msgid "favorites" | ||
284 | msgstr "favorites" | ||
285 | |||
286 | msgid "estimated reading time :" | ||
287 | msgstr "estimated reading time :" | ||
288 | |||
289 | msgid "Mark all the entries as read" | ||
290 | msgstr "Mark all the entries as read" | ||
291 | |||
292 | msgid "Return home" | ||
293 | msgstr "Return home" | ||
294 | |||
295 | msgid "Back to top" | ||
296 | msgstr "Back to top" | ||
297 | |||
298 | msgid "Mark as read" | ||
299 | msgstr "Mark as read" | ||
300 | |||
301 | msgid "Favorite" | ||
302 | msgstr "Favorite" | ||
303 | |||
304 | msgid "Toggle favorite" | ||
305 | msgstr "Toggle favorite" | ||
306 | |||
307 | msgid "Delete" | ||
308 | msgstr "Delete" | ||
309 | |||
310 | msgid "Tweet" | ||
311 | msgstr "Tweet" | ||
312 | |||
313 | msgid "Email" | ||
314 | msgstr "Email" | ||
172 | 315 | ||
173 | msgid "shaarli" | 316 | msgid "shaarli" |
174 | msgstr "shaarli" | 317 | msgstr "shaarli" |
@@ -176,26 +319,23 @@ msgstr "shaarli" | |||
176 | msgid "flattr" | 319 | msgid "flattr" |
177 | msgstr "flattr" | 320 | msgstr "flattr" |
178 | 321 | ||
179 | msgid "this article appears wrong?" | 322 | msgid "Does this article appear wrong?" |
180 | msgstr "this article appears wrong?" | 323 | msgstr "Does this article appear wrong?" |
181 | 324 | ||
182 | msgid "create an issue" | 325 | msgid "tags:" |
183 | msgstr "create an issue" | 326 | msgstr "tags:" |
184 | 327 | ||
185 | msgid "or" | 328 | msgid "Edit tags" |
186 | msgstr "or" | 329 | msgstr "Edit tags" |
187 | 330 | ||
188 | msgid "contact us by mail" | 331 | msgid "save link!" |
189 | msgstr "contact us by mail" | 332 | msgstr "save link!" |
190 | |||
191 | msgid "plop" | ||
192 | msgstr "plop" | ||
193 | 333 | ||
194 | msgid "home" | 334 | msgid "home" |
195 | msgstr "home" | 335 | msgstr "home" |
196 | 336 | ||
197 | msgid "favorites" | 337 | msgid "tags" |
198 | msgstr "favorites" | 338 | msgstr "tags" |
199 | 339 | ||
200 | msgid "logout" | 340 | msgid "logout" |
201 | msgstr "logout" | 341 | msgstr "logout" |
@@ -206,23 +346,179 @@ msgstr "powered by" | |||
206 | msgid "debug mode is on so cache is off." | 346 | msgid "debug mode is on so cache is off." |
207 | msgstr "debug mode is on so cache is off." | 347 | msgstr "debug mode is on so cache is off." |
208 | 348 | ||
209 | msgid "your poche version:" | 349 | msgid "your wallabag version:" |
210 | msgstr "your poche version:" | 350 | msgstr "your wallabag version:" |
211 | 351 | ||
212 | msgid "storage:" | 352 | msgid "storage:" |
213 | msgstr "storage:" | 353 | msgstr "storage:" |
214 | 354 | ||
215 | msgid "login to your poche" | 355 | msgid "save a link" |
216 | msgstr "login to your poche" | 356 | msgstr "save a link" |
217 | 357 | ||
218 | msgid "you are in demo mode, some features may be disabled." | 358 | msgid "back to home" |
219 | msgstr "you are in demo mode, some features may be disabled." | 359 | msgstr "back to home" |
220 | 360 | ||
221 | msgid "Stay signed in" | 361 | msgid "toggle mark as read" |
222 | msgstr "Stay signed in" | 362 | msgstr "toggle mark as read" |
223 | 363 | ||
224 | msgid "(Do not check on public computers)" | 364 | msgid "tweet" |
225 | msgstr "(Do not check on public computers)" | 365 | msgstr "tweet" |
226 | 366 | ||
227 | msgid "Sign in" | 367 | msgid "email" |
228 | msgstr "Sign in" | 368 | msgstr "email" |
369 | |||
370 | msgid "this article appears wrong?" | ||
371 | msgstr "this article appears wrong?" | ||
372 | |||
373 | msgid "No link available here!" | ||
374 | msgstr "No link available here!" | ||
375 | |||
376 | msgid "Poching a link" | ||
377 | msgstr "Poching a link" | ||
378 | |||
379 | msgid "by filling this field" | ||
380 | msgstr "by filling this field" | ||
381 | |||
382 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
383 | msgstr "bookmarklet: drag & drop this link to your bookmarks bar" | ||
384 | |||
385 | msgid "your version" | ||
386 | msgstr "your version" | ||
387 | |||
388 | msgid "latest stable version" | ||
389 | msgstr "latest stable version" | ||
390 | |||
391 | msgid "a more recent stable version is available." | ||
392 | msgstr "a more recent stable version is available." | ||
393 | |||
394 | msgid "you are up to date." | ||
395 | msgstr "you are up to date." | ||
396 | |||
397 | msgid "latest dev version" | ||
398 | msgstr "latest dev version" | ||
399 | |||
400 | msgid "a more recent development version is available." | ||
401 | msgstr "a more recent development version is available." | ||
402 | |||
403 | msgid "Please execute the import script locally, it can take a very long time." | ||
404 | msgstr "Please execute the import script locally, it can take a very long time." | ||
405 | |||
406 | msgid "More infos in the official doc:" | ||
407 | msgstr "More infos in the official doc:" | ||
408 | |||
409 | msgid "import from Pocket" | ||
410 | msgstr "import from Pocket" | ||
411 | |||
412 | msgid "import from Readability" | ||
413 | msgstr "import from Readability" | ||
414 | |||
415 | msgid "import from Instapaper" | ||
416 | msgstr "import from Instapaper" | ||
417 | |||
418 | msgid "Tags" | ||
419 | msgstr "Tags" | ||
420 | |||
421 | msgid "Untitled" | ||
422 | msgstr "Untitled" | ||
423 | |||
424 | msgid "the link has been added successfully" | ||
425 | msgstr "the link has been added successfully" | ||
426 | |||
427 | msgid "error during insertion : the link wasn't added" | ||
428 | msgstr "error during insertion : the link wasn't added" | ||
429 | |||
430 | msgid "the link has been deleted successfully" | ||
431 | msgstr "the link has been deleted successfully" | ||
432 | |||
433 | msgid "the link wasn't deleted" | ||
434 | msgstr "the link wasn't deleted" | ||
435 | |||
436 | msgid "Article not found!" | ||
437 | msgstr "Article not found!" | ||
438 | |||
439 | msgid "previous" | ||
440 | msgstr "previous" | ||
441 | |||
442 | msgid "next" | ||
443 | msgstr "next" | ||
444 | |||
445 | msgid "in demo mode, you can't update your password" | ||
446 | msgstr "in demo mode, you can't update your password" | ||
447 | |||
448 | msgid "your password has been updated" | ||
449 | msgstr "your password has been updated" | ||
450 | |||
451 | msgid "the two fields have to be filled & the password must be the same in the two fields" | ||
452 | msgstr "the two fields have to be filled & the password must be the same in the two fields" | ||
453 | |||
454 | msgid "still using the \"" | ||
455 | msgstr "still using the \"" | ||
456 | |||
457 | msgid "that theme does not seem to be installed" | ||
458 | msgstr "that theme does not seem to be installed" | ||
459 | |||
460 | msgid "you have changed your theme preferences" | ||
461 | msgstr "you have changed your theme preferences" | ||
462 | |||
463 | msgid "that language does not seem to be installed" | ||
464 | msgstr "that language does not seem to be installed" | ||
465 | |||
466 | msgid "you have changed your language preferences" | ||
467 | msgstr "you have changed your language preferences" | ||
468 | |||
469 | msgid "login failed: you have to fill all fields" | ||
470 | msgstr "login failed: you have to fill all fields" | ||
471 | |||
472 | msgid "welcome to your wallabag" | ||
473 | msgstr "welcome to your wallabag" | ||
474 | |||
475 | msgid "login failed: bad login or password" | ||
476 | msgstr "login failed: bad login or password" | ||
477 | |||
478 | msgid "import from instapaper completed" | ||
479 | msgstr "import from instapaper completed" | ||
480 | |||
481 | msgid "import from pocket completed" | ||
482 | msgstr "import from pocket completed" | ||
483 | |||
484 | msgid "import from Readability completed. " | ||
485 | msgstr "import from Readability completed. " | ||
486 | |||
487 | msgid "import from Poche completed. " | ||
488 | msgstr "import from Poche completed. " | ||
489 | |||
490 | msgid "Unknown import provider." | ||
491 | msgstr "Unknown import provider." | ||
492 | |||
493 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
494 | msgstr "Incomplete inc/poche/define.inc.php file, please define \"" | ||
495 | |||
496 | msgid "Could not find required \"" | ||
497 | msgstr "Could not find required \"" | ||
498 | |||
499 | msgid "Uh, there is a problem while generating feeds." | ||
500 | msgstr "Uh, there is a problem while generating feeds." | ||
501 | |||
502 | msgid "Cache deleted." | ||
503 | msgstr "Cache deleted." | ||
504 | |||
505 | msgid "Oops, it seems you don't have PHP 5." | ||
506 | msgstr "Oops, it seems you don't have PHP 5." | ||
507 | |||
508 | #~ msgid "poche it!" | ||
509 | #~ msgstr "poche it!" | ||
510 | |||
511 | #~ msgid "Updating poche" | ||
512 | #~ msgstr "Updating poche" | ||
513 | |||
514 | #~ msgid "create an issue" | ||
515 | #~ msgstr "create an issue" | ||
516 | |||
517 | #~ msgid "or" | ||
518 | #~ msgstr "or" | ||
519 | |||
520 | #~ msgid "contact us by mail" | ||
521 | #~ msgstr "contact us by mail" | ||
522 | |||
523 | #~ msgid "your poche version:" | ||
524 | #~ msgstr "your poche version:" | ||
diff --git a/locale/es_ES.utf8/LC_MESSAGES/es_ES.utf8.po b/locale/es_ES.utf8/LC_MESSAGES/es_ES.utf8.po index afe0595d..c08decfe 100644 --- a/locale/es_ES.utf8/LC_MESSAGES/es_ES.utf8.po +++ b/locale/es_ES.utf8/LC_MESSAGES/es_ES.utf8.po | |||
@@ -1,51 +1,136 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: \n" | 3 | "Project-Id-Version: \n" |
4 | "POT-Creation-Date: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-02-25 15:16+0300\n" | ||
5 | "PO-Revision-Date: \n" | 6 | "PO-Revision-Date: \n" |
6 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
7 | "Language-Team: \n" | 8 | "Language-Team: \n" |
9 | "Language: \n" | ||
8 | "MIME-Version: 1.0\n" | 10 | "MIME-Version: 1.0\n" |
9 | "Content-Type: text/plain; charset=UTF-8\n" | 11 | "Content-Type: text/plain; charset=UTF-8\n" |
10 | "Content-Transfer-Encoding: 8bit\n" | 12 | "Content-Transfer-Encoding: 8bit\n" |
11 | "X-Generator: Poedit 1.5.4\n" | 13 | "X-Generator: Poedit 1.5.4\n" |
14 | "X-Poedit-Language: Spanish\n" | ||
15 | "X-Poedit-Basepath: .\n" | ||
16 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" | ||
17 | |||
18 | msgid "wallabag, a read it later open source system" | ||
19 | msgstr "" | ||
20 | |||
21 | msgid "login failed: user doesn't exist" | ||
22 | msgstr "" | ||
23 | |||
24 | msgid "return home" | ||
25 | msgstr "" | ||
12 | 26 | ||
13 | msgid "config" | 27 | msgid "config" |
14 | msgstr "configuración" | 28 | msgstr "configuración" |
15 | 29 | ||
16 | msgid "Poching a link" | 30 | msgid "Saving articles" |
17 | msgstr "Pochear un enlace" | 31 | msgstr "" |
32 | |||
33 | msgid "There are several ways to save an article:" | ||
34 | msgstr "" | ||
18 | 35 | ||
19 | msgid "read the documentation" | 36 | msgid "read the documentation" |
20 | msgstr "leer la documentación" | 37 | msgstr "leer la documentación" |
21 | 38 | ||
22 | msgid "by filling this field" | 39 | msgid "download the extension" |
40 | msgstr "" | ||
41 | |||
42 | msgid "via F-Droid" | ||
43 | msgstr "" | ||
44 | |||
45 | msgid " or " | ||
46 | msgstr "" | ||
47 | |||
48 | msgid "via Google Play" | ||
49 | msgstr "" | ||
50 | |||
51 | msgid "download the application" | ||
52 | msgstr "" | ||
53 | |||
54 | #, fuzzy | ||
55 | msgid "By filling this field" | ||
23 | msgstr "rellenando este campo" | 56 | msgstr "rellenando este campo" |
24 | 57 | ||
25 | msgid "poche it!" | 58 | msgid "bag it!" |
26 | msgstr "pochéalo!" | 59 | msgstr "" |
27 | 60 | ||
28 | msgid "Updating poche" | 61 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
29 | msgstr "Actualizar" | 62 | msgstr "" |
30 | 63 | ||
31 | msgid "your version" | 64 | msgid "Upgrading wallabag" |
32 | msgstr "su versión" | 65 | msgstr "" |
33 | 66 | ||
34 | msgid "latest stable version" | 67 | #, fuzzy |
68 | msgid "Installed version" | ||
35 | msgstr "ultima versión estable" | 69 | msgstr "ultima versión estable" |
36 | 70 | ||
37 | msgid "a more recent stable version is available." | 71 | #, fuzzy |
72 | msgid "Latest stable version" | ||
73 | msgstr "ultima versión estable" | ||
74 | |||
75 | #, fuzzy | ||
76 | msgid "A more recent stable version is available." | ||
38 | msgstr "una versión estable más reciente está disponible." | 77 | msgstr "una versión estable más reciente está disponible." |
39 | 78 | ||
40 | msgid "you are up to date." | 79 | #, fuzzy |
80 | msgid "You are up to date." | ||
41 | msgstr "estás actualizado." | 81 | msgstr "estás actualizado." |
42 | 82 | ||
43 | msgid "latest dev version" | 83 | #, fuzzy |
84 | msgid "Latest dev version" | ||
44 | msgstr "ultima versión de desarollo" | 85 | msgstr "ultima versión de desarollo" |
45 | 86 | ||
46 | msgid "a more recent development version is available." | 87 | #, fuzzy |
88 | msgid "A more recent development version is available." | ||
47 | msgstr "una versión de desarollo más reciente está disponible." | 89 | msgstr "una versión de desarollo más reciente está disponible." |
48 | 90 | ||
91 | msgid "Feeds" | ||
92 | msgstr "" | ||
93 | |||
94 | msgid "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
95 | msgstr "" | ||
96 | |||
97 | msgid "Unread feed" | ||
98 | msgstr "" | ||
99 | |||
100 | #, fuzzy | ||
101 | msgid "Favorites feed" | ||
102 | msgstr "preferidos" | ||
103 | |||
104 | #, fuzzy | ||
105 | msgid "Archive feed" | ||
106 | msgstr "archivos" | ||
107 | |||
108 | msgid "Your token:" | ||
109 | msgstr "" | ||
110 | |||
111 | msgid "Your user id:" | ||
112 | msgstr "" | ||
113 | |||
114 | msgid "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
115 | msgstr "" | ||
116 | |||
117 | #, fuzzy | ||
118 | msgid "Change your theme" | ||
119 | msgstr "Modificar tu contraseña" | ||
120 | |||
121 | msgid "Theme:" | ||
122 | msgstr "" | ||
123 | |||
124 | msgid "Update" | ||
125 | msgstr "Actualizar" | ||
126 | |||
127 | #, fuzzy | ||
128 | msgid "Change your language" | ||
129 | msgstr "Modificar tu contraseña" | ||
130 | |||
131 | msgid "Language:" | ||
132 | msgstr "" | ||
133 | |||
49 | msgid "Change your password" | 134 | msgid "Change your password" |
50 | msgstr "Modificar tu contraseña" | 135 | msgstr "Modificar tu contraseña" |
51 | 136 | ||
@@ -58,66 +143,68 @@ msgstr "Contraseña" | |||
58 | msgid "Repeat your new password:" | 143 | msgid "Repeat your new password:" |
59 | msgstr "Repetir la nueva contraseña :" | 144 | msgstr "Repetir la nueva contraseña :" |
60 | 145 | ||
61 | msgid "Update" | ||
62 | msgstr "Actualizar" | ||
63 | |||
64 | msgid "Import" | 146 | msgid "Import" |
65 | msgstr "Importar" | 147 | msgstr "Importar" |
66 | 148 | ||
67 | msgid "Please execute the import script locally, it can take a very long time." | 149 | #, fuzzy |
68 | msgstr "" | 150 | msgid "Please execute the import script locally as it can take a very long time." |
69 | "Por favor, ejecute la importación en local, esto puede demorar un tiempo." | 151 | msgstr "Por favor, ejecute la importación en local, esto puede demorar un tiempo." |
70 | 152 | ||
71 | msgid "More info in the official doc:" | 153 | #, fuzzy |
154 | msgid "More info in the official documentation:" | ||
72 | msgstr "Más información en la documentación oficial :" | 155 | msgstr "Más información en la documentación oficial :" |
73 | 156 | ||
74 | msgid "import from Pocket" | 157 | #, fuzzy |
158 | msgid "Import from Pocket" | ||
75 | msgstr "importación desde Pocket" | 159 | msgstr "importación desde Pocket" |
76 | 160 | ||
77 | msgid "import from Readability" | 161 | #, php-format |
162 | msgid "(you must have a %s file on your server)" | ||
163 | msgstr "" | ||
164 | |||
165 | #, fuzzy | ||
166 | msgid "Import from Readability" | ||
78 | msgstr "importación desde Readability" | 167 | msgstr "importación desde Readability" |
79 | 168 | ||
80 | msgid "import from Instapaper" | 169 | #, fuzzy |
170 | msgid "Import from Instapaper" | ||
81 | msgstr "importación desde Instapaper" | 171 | msgstr "importación desde Instapaper" |
82 | 172 | ||
83 | msgid "Export your poche data" | 173 | #, fuzzy |
174 | msgid "Import from wallabag" | ||
175 | msgstr "importación desde Readability" | ||
176 | |||
177 | #, fuzzy | ||
178 | msgid "Export your wallabag data" | ||
84 | msgstr "Exportar sus datos de poche" | 179 | msgstr "Exportar sus datos de poche" |
85 | 180 | ||
86 | msgid "Click here" | 181 | msgid "Click here" |
87 | msgstr "Haga clic aquí" | 182 | msgstr "Haga clic aquí" |
88 | 183 | ||
89 | msgid "to export your poche data." | 184 | msgid "to download your database." |
90 | msgstr "para exportar sus datos de poche." | 185 | msgstr "" |
91 | |||
92 | msgid "back to home" | ||
93 | msgstr "volver a la página de inicio" | ||
94 | 186 | ||
95 | msgid "installation" | 187 | #, fuzzy |
96 | msgstr "instalación" | 188 | msgid "to export your wallabag data." |
189 | msgstr "para exportar sus datos de poche." | ||
97 | 190 | ||
98 | msgid "install your poche" | 191 | msgid "Cache" |
99 | msgstr "instala tu Poche" | 192 | msgstr "" |
100 | 193 | ||
101 | msgid "" | 194 | msgid "to delete cache." |
102 | "Poche is still not installed. Please fill the below form to install it. " | ||
103 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
104 | "on poche website</a>." | ||
105 | msgstr "" | 195 | msgstr "" |
106 | "Poche todavia no està instalado. Por favor, completa los campos siguientes " | ||
107 | "para instalarlo. No dudes de <a href='http://doc.inthepoche.com'>leer la " | ||
108 | "documentación en el sitio de Poche</a>." | ||
109 | 196 | ||
110 | msgid "Login" | 197 | msgid "You can enter multiple tags, separated by commas." |
111 | msgstr "Nombre de usuario" | 198 | msgstr "" |
112 | 199 | ||
113 | msgid "Repeat your password" | 200 | msgid "return to article" |
114 | msgstr "Repita su contraseña" | 201 | msgstr "" |
115 | 202 | ||
116 | msgid "Install" | 203 | msgid "plop" |
117 | msgstr "Instalar" | 204 | msgstr "plop" |
118 | 205 | ||
119 | msgid "back to top" | 206 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
120 | msgstr "volver arriba" | 207 | msgstr "" |
121 | 208 | ||
122 | msgid "favoris" | 209 | msgid "favoris" |
123 | msgstr "preferidos" | 210 | msgstr "preferidos" |
@@ -146,10 +233,14 @@ msgstr "por título" | |||
146 | msgid "by title desc" | 233 | msgid "by title desc" |
147 | msgstr "por título descendiente" | 234 | msgstr "por título descendiente" |
148 | 235 | ||
149 | msgid "No link available here!" | 236 | msgid "Tag" |
150 | msgstr "¡No hay ningún enlace disponible por aquí!" | 237 | msgstr "" |
151 | 238 | ||
152 | msgid "toggle mark as read" | 239 | msgid "No articles found." |
240 | msgstr "" | ||
241 | |||
242 | #, fuzzy | ||
243 | msgid "Toggle mark as read" | ||
153 | msgstr "marcar como leído" | 244 | msgstr "marcar como leído" |
154 | 245 | ||
155 | msgid "toggle favorite" | 246 | msgid "toggle favorite" |
@@ -161,13 +252,95 @@ msgstr "eliminar" | |||
161 | msgid "original" | 252 | msgid "original" |
162 | msgstr "original" | 253 | msgstr "original" |
163 | 254 | ||
255 | msgid "estimated reading time:" | ||
256 | msgstr "" | ||
257 | |||
258 | msgid "mark all the entries as read" | ||
259 | msgstr "" | ||
260 | |||
164 | msgid "results" | 261 | msgid "results" |
165 | msgstr "resultados" | 262 | msgstr "resultados" |
166 | 263 | ||
167 | msgid "tweet" | 264 | msgid "installation" |
265 | msgstr "instalación" | ||
266 | |||
267 | #, fuzzy | ||
268 | msgid "install your wallabag" | ||
269 | msgstr "instala tu Poche" | ||
270 | |||
271 | #, fuzzy | ||
272 | msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
273 | msgstr "Poche todavia no està instalado. Por favor, completa los campos siguientes para instalarlo. No dudes de <a href='http://doc.inthepoche.com'>leer la documentación en el sitio de Poche</a>." | ||
274 | |||
275 | msgid "Login" | ||
276 | msgstr "Nombre de usuario" | ||
277 | |||
278 | msgid "Repeat your password" | ||
279 | msgstr "Repita su contraseña" | ||
280 | |||
281 | msgid "Install" | ||
282 | msgstr "Instalar" | ||
283 | |||
284 | #, fuzzy | ||
285 | msgid "login to your wallabag" | ||
286 | msgstr "conectarse a tu Poche" | ||
287 | |||
288 | msgid "Login to wallabag" | ||
289 | msgstr "" | ||
290 | |||
291 | msgid "you are in demo mode, some features may be disabled." | ||
292 | msgstr "este es el modo de demostración, algunas funcionalidades pueden estar desactivadas." | ||
293 | |||
294 | msgid "Username" | ||
295 | msgstr "" | ||
296 | |||
297 | msgid "Stay signed in" | ||
298 | msgstr "Seguir conectado" | ||
299 | |||
300 | msgid "(Do not check on public computers)" | ||
301 | msgstr "(no marcar en un ordenador público)" | ||
302 | |||
303 | msgid "Sign in" | ||
304 | msgstr "Iniciar sesión" | ||
305 | |||
306 | msgid "favorites" | ||
307 | msgstr "preferidos" | ||
308 | |||
309 | msgid "estimated reading time :" | ||
310 | msgstr "" | ||
311 | |||
312 | msgid "Mark all the entries as read" | ||
313 | msgstr "" | ||
314 | |||
315 | msgid "Return home" | ||
316 | msgstr "" | ||
317 | |||
318 | #, fuzzy | ||
319 | msgid "Back to top" | ||
320 | msgstr "volver arriba" | ||
321 | |||
322 | #, fuzzy | ||
323 | msgid "Mark as read" | ||
324 | msgstr "marcar como leído" | ||
325 | |||
326 | #, fuzzy | ||
327 | msgid "Favorite" | ||
328 | msgstr "preferidos" | ||
329 | |||
330 | #, fuzzy | ||
331 | msgid "Toggle favorite" | ||
332 | msgstr "preferido" | ||
333 | |||
334 | #, fuzzy | ||
335 | msgid "Delete" | ||
336 | msgstr "eliminar" | ||
337 | |||
338 | #, fuzzy | ||
339 | msgid "Tweet" | ||
168 | msgstr "tweetear" | 340 | msgstr "tweetear" |
169 | 341 | ||
170 | msgid "email" | 342 | #, fuzzy |
343 | msgid "Email" | ||
171 | msgstr "enviar por mail" | 344 | msgstr "enviar por mail" |
172 | 345 | ||
173 | msgid "shaarli" | 346 | msgid "shaarli" |
@@ -176,26 +349,24 @@ msgstr "shaarli" | |||
176 | msgid "flattr" | 349 | msgid "flattr" |
177 | msgstr "flattr" | 350 | msgstr "flattr" |
178 | 351 | ||
179 | msgid "this article appears wrong?" | 352 | #, fuzzy |
353 | msgid "Does this article appear wrong?" | ||
180 | msgstr "este articulo no se ve bien?" | 354 | msgstr "este articulo no se ve bien?" |
181 | 355 | ||
182 | msgid "create an issue" | 356 | msgid "tags:" |
183 | msgstr "crear un ticket" | 357 | msgstr "" |
184 | |||
185 | msgid "or" | ||
186 | msgstr "o" | ||
187 | 358 | ||
188 | msgid "contact us by mail" | 359 | msgid "Edit tags" |
189 | msgstr "contactarnos por mail" | 360 | msgstr "" |
190 | 361 | ||
191 | msgid "plop" | 362 | msgid "save link!" |
192 | msgstr "plop" | 363 | msgstr "" |
193 | 364 | ||
194 | msgid "home" | 365 | msgid "home" |
195 | msgstr "inicio" | 366 | msgstr "inicio" |
196 | 367 | ||
197 | msgid "favorites" | 368 | msgid "tags" |
198 | msgstr "preferidos" | 369 | msgstr "" |
199 | 370 | ||
200 | msgid "logout" | 371 | msgid "logout" |
201 | msgstr "cerrar sesión" | 372 | msgstr "cerrar sesión" |
@@ -206,25 +377,187 @@ msgstr "hecho con" | |||
206 | msgid "debug mode is on so cache is off." | 377 | msgid "debug mode is on so cache is off." |
207 | msgstr "el modo de depuración está activado, así que la cache está desactivada." | 378 | msgstr "el modo de depuración está activado, así que la cache está desactivada." |
208 | 379 | ||
209 | msgid "your poche version:" | 380 | #, fuzzy |
210 | msgstr "tu versión de Poche:" | 381 | msgid "your wallabag version:" |
382 | msgstr "su versión" | ||
211 | 383 | ||
212 | msgid "storage:" | 384 | msgid "storage:" |
213 | msgstr "almacenamiento:" | 385 | msgstr "almacenamiento:" |
214 | 386 | ||
215 | msgid "login to your poche" | 387 | msgid "save a link" |
216 | msgstr "conectarse a tu Poche" | 388 | msgstr "" |
217 | 389 | ||
218 | msgid "you are in demo mode, some features may be disabled." | 390 | msgid "back to home" |
391 | msgstr "volver a la página de inicio" | ||
392 | |||
393 | msgid "toggle mark as read" | ||
394 | msgstr "marcar como leído" | ||
395 | |||
396 | msgid "tweet" | ||
397 | msgstr "tweetear" | ||
398 | |||
399 | msgid "email" | ||
400 | msgstr "enviar por mail" | ||
401 | |||
402 | msgid "this article appears wrong?" | ||
403 | msgstr "este articulo no se ve bien?" | ||
404 | |||
405 | msgid "No link available here!" | ||
406 | msgstr "¡No hay ningún enlace disponible por aquí!" | ||
407 | |||
408 | msgid "Poching a link" | ||
409 | msgstr "Pochear un enlace" | ||
410 | |||
411 | msgid "by filling this field" | ||
412 | msgstr "rellenando este campo" | ||
413 | |||
414 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
219 | msgstr "" | 415 | msgstr "" |
220 | "este es el modo de demostración, algunas funcionalidades pueden estar " | ||
221 | "desactivadas." | ||
222 | 416 | ||
223 | msgid "Stay signed in" | 417 | msgid "your version" |
224 | msgstr "Seguir conectado" | 418 | msgstr "su versión" |
225 | 419 | ||
226 | msgid "(Do not check on public computers)" | 420 | msgid "latest stable version" |
227 | msgstr "(no marcar en un ordenador público)" | 421 | msgstr "ultima versión estable" |
228 | 422 | ||
229 | msgid "Sign in" | 423 | msgid "a more recent stable version is available." |
230 | msgstr "Iniciar sesión" | 424 | msgstr "una versión estable más reciente está disponible." |
425 | |||
426 | msgid "you are up to date." | ||
427 | msgstr "estás actualizado." | ||
428 | |||
429 | msgid "latest dev version" | ||
430 | msgstr "ultima versión de desarollo" | ||
431 | |||
432 | msgid "a more recent development version is available." | ||
433 | msgstr "una versión de desarollo más reciente está disponible." | ||
434 | |||
435 | msgid "Please execute the import script locally, it can take a very long time." | ||
436 | msgstr "Por favor, ejecute la importación en local, esto puede demorar un tiempo." | ||
437 | |||
438 | #, fuzzy | ||
439 | msgid "More infos in the official doc:" | ||
440 | msgstr "Más información en la documentación oficial :" | ||
441 | |||
442 | msgid "import from Pocket" | ||
443 | msgstr "importación desde Pocket" | ||
444 | |||
445 | msgid "import from Readability" | ||
446 | msgstr "importación desde Readability" | ||
447 | |||
448 | msgid "import from Instapaper" | ||
449 | msgstr "importación desde Instapaper" | ||
450 | |||
451 | msgid "Tags" | ||
452 | msgstr "" | ||
453 | |||
454 | #, fuzzy | ||
455 | msgid "Untitled" | ||
456 | msgstr "por título" | ||
457 | |||
458 | msgid "the link has been added successfully" | ||
459 | msgstr "" | ||
460 | |||
461 | msgid "error during insertion : the link wasn't added" | ||
462 | msgstr "" | ||
463 | |||
464 | msgid "the link has been deleted successfully" | ||
465 | msgstr "" | ||
466 | |||
467 | msgid "the link wasn't deleted" | ||
468 | msgstr "" | ||
469 | |||
470 | msgid "Article not found!" | ||
471 | msgstr "" | ||
472 | |||
473 | msgid "previous" | ||
474 | msgstr "" | ||
475 | |||
476 | msgid "next" | ||
477 | msgstr "" | ||
478 | |||
479 | msgid "in demo mode, you can't update your password" | ||
480 | msgstr "" | ||
481 | |||
482 | msgid "your password has been updated" | ||
483 | msgstr "" | ||
484 | |||
485 | msgid "the two fields have to be filled & the password must be the same in the two fields" | ||
486 | msgstr "" | ||
487 | |||
488 | msgid "still using the \"" | ||
489 | msgstr "" | ||
490 | |||
491 | msgid "that theme does not seem to be installed" | ||
492 | msgstr "" | ||
493 | |||
494 | msgid "you have changed your theme preferences" | ||
495 | msgstr "" | ||
496 | |||
497 | msgid "that language does not seem to be installed" | ||
498 | msgstr "" | ||
499 | |||
500 | msgid "you have changed your language preferences" | ||
501 | msgstr "" | ||
502 | |||
503 | msgid "login failed: you have to fill all fields" | ||
504 | msgstr "" | ||
505 | |||
506 | msgid "welcome to your wallabag" | ||
507 | msgstr "" | ||
508 | |||
509 | msgid "login failed: bad login or password" | ||
510 | msgstr "" | ||
511 | |||
512 | #, fuzzy | ||
513 | msgid "import from instapaper completed" | ||
514 | msgstr "importación desde Instapaper" | ||
515 | |||
516 | #, fuzzy | ||
517 | msgid "import from pocket completed" | ||
518 | msgstr "importación desde Pocket" | ||
519 | |||
520 | #, fuzzy | ||
521 | msgid "import from Readability completed. " | ||
522 | msgstr "importación desde Readability" | ||
523 | |||
524 | #, fuzzy | ||
525 | msgid "import from Poche completed. " | ||
526 | msgstr "importación desde Pocket" | ||
527 | |||
528 | msgid "Unknown import provider." | ||
529 | msgstr "" | ||
530 | |||
531 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
532 | msgstr "" | ||
533 | |||
534 | msgid "Could not find required \"" | ||
535 | msgstr "" | ||
536 | |||
537 | msgid "Uh, there is a problem while generating feeds." | ||
538 | msgstr "" | ||
539 | |||
540 | #, fuzzy | ||
541 | msgid "Cache deleted." | ||
542 | msgstr "eliminar" | ||
543 | |||
544 | msgid "Oops, it seems you don't have PHP 5." | ||
545 | msgstr "" | ||
546 | |||
547 | #~ msgid "poche it!" | ||
548 | #~ msgstr "pochéalo!" | ||
549 | |||
550 | #~ msgid "Updating poche" | ||
551 | #~ msgstr "Actualizar" | ||
552 | |||
553 | #~ msgid "create an issue" | ||
554 | #~ msgstr "crear un ticket" | ||
555 | |||
556 | #~ msgid "or" | ||
557 | #~ msgstr "o" | ||
558 | |||
559 | #~ msgid "contact us by mail" | ||
560 | #~ msgstr "contactarnos por mail" | ||
561 | |||
562 | #~ msgid "your poche version:" | ||
563 | #~ msgstr "tu versión de Poche:" | ||
diff --git a/locale/fa_IR.utf8/LC_MESSAGES/fa_IR.utf8.po b/locale/fa_IR.utf8/LC_MESSAGES/fa_IR.utf8.po index 74a763c6..f24814d5 100644 --- a/locale/fa_IR.utf8/LC_MESSAGES/fa_IR.utf8.po +++ b/locale/fa_IR.utf8/LC_MESSAGES/fa_IR.utf8.po | |||
@@ -1,51 +1,136 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: \n" | 3 | "Project-Id-Version: \n" |
4 | "POT-Creation-Date: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-02-25 15:15+0300\n" | ||
5 | "PO-Revision-Date: \n" | 6 | "PO-Revision-Date: \n" |
6 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
7 | "Language-Team: \n" | 8 | "Language-Team: \n" |
9 | "Language: \n" | ||
8 | "MIME-Version: 1.0\n" | 10 | "MIME-Version: 1.0\n" |
9 | "Content-Type: text/plain; charset=UTF-8\n" | 11 | "Content-Type: text/plain; charset=UTF-8\n" |
10 | "Content-Transfer-Encoding: 8bit\n" | 12 | "Content-Transfer-Encoding: 8bit\n" |
11 | "X-Generator: Poedit 1.5.4\n" | 13 | "X-Generator: Poedit 1.5.4\n" |
14 | "X-Poedit-Language: Persian\n" | ||
15 | "X-Poedit-Basepath: .\n" | ||
16 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" | ||
17 | |||
18 | msgid "wallabag, a read it later open source system" | ||
19 | msgstr "" | ||
20 | |||
21 | msgid "login failed: user doesn't exist" | ||
22 | msgstr "" | ||
23 | |||
24 | msgid "return home" | ||
25 | msgstr "" | ||
12 | 26 | ||
13 | msgid "config" | 27 | msgid "config" |
14 | msgstr "تنظیمات" | 28 | msgstr "تنظیمات" |
15 | 29 | ||
16 | msgid "Poching a link" | 30 | msgid "Saving articles" |
17 | msgstr "پیوندی را poche کنید" | 31 | msgstr "" |
32 | |||
33 | msgid "There are several ways to save an article:" | ||
34 | msgstr "" | ||
18 | 35 | ||
19 | msgid "read the documentation" | 36 | msgid "read the documentation" |
20 | msgstr "راهنما را بخوانید" | 37 | msgstr "راهنما را بخوانید" |
21 | 38 | ||
22 | msgid "by filling this field" | 39 | msgid "download the extension" |
40 | msgstr "" | ||
41 | |||
42 | msgid "via F-Droid" | ||
43 | msgstr "" | ||
44 | |||
45 | msgid " or " | ||
46 | msgstr "" | ||
47 | |||
48 | msgid "via Google Play" | ||
49 | msgstr "" | ||
50 | |||
51 | msgid "download the application" | ||
52 | msgstr "" | ||
53 | |||
54 | #, fuzzy | ||
55 | msgid "By filling this field" | ||
23 | msgstr "با پرکردن این بخش" | 56 | msgstr "با پرکردن این بخش" |
24 | 57 | ||
25 | msgid "poche it!" | 58 | msgid "bag it!" |
26 | msgstr "poche کنید!" | 59 | msgstr "" |
27 | 60 | ||
28 | msgid "Updating poche" | 61 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
29 | msgstr "بهروزرسانی poche" | 62 | msgstr "" |
30 | 63 | ||
31 | msgid "your version" | 64 | msgid "Upgrading wallabag" |
32 | msgstr "نسخهٔ شما" | 65 | msgstr "" |
33 | 66 | ||
34 | msgid "latest stable version" | 67 | #, fuzzy |
68 | msgid "Installed version" | ||
35 | msgstr "آخرین نسخهٔ پایدار" | 69 | msgstr "آخرین نسخهٔ پایدار" |
36 | 70 | ||
37 | msgid "a more recent stable version is available." | 71 | #, fuzzy |
72 | msgid "Latest stable version" | ||
73 | msgstr "آخرین نسخهٔ پایدار" | ||
74 | |||
75 | #, fuzzy | ||
76 | msgid "A more recent stable version is available." | ||
38 | msgstr "نسخهٔ پایدار تازهای منتشر شده است." | 77 | msgstr "نسخهٔ پایدار تازهای منتشر شده است." |
39 | 78 | ||
40 | msgid "you are up to date." | 79 | #, fuzzy |
80 | msgid "You are up to date." | ||
41 | msgstr "شما بهروز هستید." | 81 | msgstr "شما بهروز هستید." |
42 | 82 | ||
43 | msgid "latest dev version" | 83 | #, fuzzy |
84 | msgid "Latest dev version" | ||
44 | msgstr "آخرین نسخهٔ آزمایشی" | 85 | msgstr "آخرین نسخهٔ آزمایشی" |
45 | 86 | ||
46 | msgid "a more recent development version is available." | 87 | #, fuzzy |
88 | msgid "A more recent development version is available." | ||
47 | msgstr "نسخهٔ آزمایشی تازهای منتشر شده است." | 89 | msgstr "نسخهٔ آزمایشی تازهای منتشر شده است." |
48 | 90 | ||
91 | msgid "Feeds" | ||
92 | msgstr "" | ||
93 | |||
94 | msgid "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
95 | msgstr "" | ||
96 | |||
97 | msgid "Unread feed" | ||
98 | msgstr "" | ||
99 | |||
100 | #, fuzzy | ||
101 | msgid "Favorites feed" | ||
102 | msgstr "بهترینها" | ||
103 | |||
104 | #, fuzzy | ||
105 | msgid "Archive feed" | ||
106 | msgstr "بایگانی" | ||
107 | |||
108 | msgid "Your token:" | ||
109 | msgstr "" | ||
110 | |||
111 | msgid "Your user id:" | ||
112 | msgstr "" | ||
113 | |||
114 | msgid "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
115 | msgstr "" | ||
116 | |||
117 | #, fuzzy | ||
118 | msgid "Change your theme" | ||
119 | msgstr "گذرواژهٔ خود را تغییر دهید" | ||
120 | |||
121 | msgid "Theme:" | ||
122 | msgstr "" | ||
123 | |||
124 | msgid "Update" | ||
125 | msgstr "بهروزرسانی" | ||
126 | |||
127 | #, fuzzy | ||
128 | msgid "Change your language" | ||
129 | msgstr "گذرواژهٔ خود را تغییر دهید" | ||
130 | |||
131 | msgid "Language:" | ||
132 | msgstr "" | ||
133 | |||
49 | msgid "Change your password" | 134 | msgid "Change your password" |
50 | msgstr "گذرواژهٔ خود را تغییر دهید" | 135 | msgstr "گذرواژهٔ خود را تغییر دهید" |
51 | 136 | ||
@@ -58,64 +143,68 @@ msgstr "گذرواژه" | |||
58 | msgid "Repeat your new password:" | 143 | msgid "Repeat your new password:" |
59 | msgstr "گذرواژهٔ تازه را دوباره وارد کنید" | 144 | msgstr "گذرواژهٔ تازه را دوباره وارد کنید" |
60 | 145 | ||
61 | msgid "Update" | ||
62 | msgstr "بهروزرسانی" | ||
63 | |||
64 | msgid "Import" | 146 | msgid "Import" |
65 | msgstr "درونریزی" | 147 | msgstr "درونریزی" |
66 | 148 | ||
67 | msgid "Please execute the import script locally, it can take a very long time." | 149 | #, fuzzy |
150 | msgid "Please execute the import script locally as it can take a very long time." | ||
68 | msgstr "لطفاً برنامهٔ درونریزی را بهطور محلی اجرا کنید، شاید خیلی طول بکشد." | 151 | msgstr "لطفاً برنامهٔ درونریزی را بهطور محلی اجرا کنید، شاید خیلی طول بکشد." |
69 | 152 | ||
70 | msgid "More info in the official doc:" | 153 | #, fuzzy |
154 | msgid "More info in the official documentation:" | ||
71 | msgstr "اطلاعات بیشتر در راهنمای رسمی:" | 155 | msgstr "اطلاعات بیشتر در راهنمای رسمی:" |
72 | 156 | ||
73 | msgid "import from Pocket" | 157 | #, fuzzy |
158 | msgid "Import from Pocket" | ||
74 | msgstr "درونریزی از Pocket" | 159 | msgstr "درونریزی از Pocket" |
75 | 160 | ||
76 | msgid "import from Readability" | 161 | #, php-format |
162 | msgid "(you must have a %s file on your server)" | ||
163 | msgstr "" | ||
164 | |||
165 | #, fuzzy | ||
166 | msgid "Import from Readability" | ||
77 | msgstr "درونریزی از Readability" | 167 | msgstr "درونریزی از Readability" |
78 | 168 | ||
79 | msgid "import from Instapaper" | 169 | #, fuzzy |
170 | msgid "Import from Instapaper" | ||
80 | msgstr "درونریزی از Instapaper" | 171 | msgstr "درونریزی از Instapaper" |
81 | 172 | ||
82 | msgid "Export your poche data" | 173 | #, fuzzy |
174 | msgid "Import from wallabag" | ||
175 | msgstr "درونریزی از Readability" | ||
176 | |||
177 | #, fuzzy | ||
178 | msgid "Export your wallabag data" | ||
83 | msgstr "دادههای poche خود را برونبری کنید" | 179 | msgstr "دادههای poche خود را برونبری کنید" |
84 | 180 | ||
85 | msgid "Click here" | 181 | msgid "Click here" |
86 | msgstr "اینجا را کلیک کنید" | 182 | msgstr "اینجا را کلیک کنید" |
87 | 183 | ||
88 | msgid "to export your poche data." | 184 | msgid "to download your database." |
89 | msgstr "برای برونبری دادههای poche شما" | 185 | msgstr "" |
90 | |||
91 | msgid "back to home" | ||
92 | msgstr "بازگشت به خانه" | ||
93 | 186 | ||
94 | msgid "installation" | 187 | #, fuzzy |
95 | msgstr "نصب" | 188 | msgid "to export your wallabag data." |
189 | msgstr "برای برونبری دادههای poche شما" | ||
96 | 190 | ||
97 | msgid "install your poche" | 191 | msgid "Cache" |
98 | msgstr "poche خود را نصب کنید" | 192 | msgstr "" |
99 | 193 | ||
100 | msgid "" | 194 | msgid "to delete cache." |
101 | "poche is still not installed. Please fill the below form to install it. " | ||
102 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
103 | "on poche website</a>." | ||
104 | msgstr "" | 195 | msgstr "" |
105 | "poche هنوز نصب نیست. برای نصب لطفاً فرم زیر را پر کنید. خواندن <a " | ||
106 | "href='http://doc.inthepoche.com'>راهنما در وبگاه poche</a> را از یاد نبرید." | ||
107 | 196 | ||
108 | msgid "Login" | 197 | msgid "You can enter multiple tags, separated by commas." |
109 | msgstr "ورود" | 198 | msgstr "" |
110 | 199 | ||
111 | msgid "Repeat your password" | 200 | msgid "return to article" |
112 | msgstr "گذرواژه را دوباره وارد کنید" | 201 | msgstr "" |
113 | 202 | ||
114 | msgid "Install" | 203 | msgid "plop" |
115 | msgstr "نصب" | 204 | msgstr "plop" |
116 | 205 | ||
117 | msgid "back to top" | 206 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
118 | msgstr "بازگشت به بالای صفحه" | 207 | msgstr "" |
119 | 208 | ||
120 | msgid "favoris" | 209 | msgid "favoris" |
121 | msgstr "بهترینها" | 210 | msgstr "بهترینها" |
@@ -144,10 +233,14 @@ msgstr "با عنوان" | |||
144 | msgid "by title desc" | 233 | msgid "by title desc" |
145 | msgstr "با عنوان (الفبایی معکوس)" | 234 | msgstr "با عنوان (الفبایی معکوس)" |
146 | 235 | ||
147 | msgid "No link available here!" | 236 | msgid "Tag" |
148 | msgstr "اینجا پیوندی موجود نیست!" | 237 | msgstr "" |
149 | 238 | ||
150 | msgid "toggle mark as read" | 239 | msgid "No articles found." |
240 | msgstr "" | ||
241 | |||
242 | #, fuzzy | ||
243 | msgid "Toggle mark as read" | ||
151 | msgstr "خواندهشده/خواندهنشده" | 244 | msgstr "خواندهشده/خواندهنشده" |
152 | 245 | ||
153 | msgid "toggle favorite" | 246 | msgid "toggle favorite" |
@@ -159,13 +252,95 @@ msgstr "پاککردن" | |||
159 | msgid "original" | 252 | msgid "original" |
160 | msgstr "اصلی" | 253 | msgstr "اصلی" |
161 | 254 | ||
255 | msgid "estimated reading time:" | ||
256 | msgstr "" | ||
257 | |||
258 | msgid "mark all the entries as read" | ||
259 | msgstr "" | ||
260 | |||
162 | msgid "results" | 261 | msgid "results" |
163 | msgstr "نتایج" | 262 | msgstr "نتایج" |
164 | 263 | ||
165 | msgid "tweet" | 264 | msgid "installation" |
265 | msgstr "نصب" | ||
266 | |||
267 | #, fuzzy | ||
268 | msgid "install your wallabag" | ||
269 | msgstr "poche خود را نصب کنید" | ||
270 | |||
271 | #, fuzzy | ||
272 | msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
273 | msgstr "poche هنوز نصب نیست. برای نصب لطفاً فرم زیر را پر کنید. خواندن <a href='http://doc.inthepoche.com'>راهنما در وبگاه poche</a> را از یاد نبرید." | ||
274 | |||
275 | msgid "Login" | ||
276 | msgstr "ورود" | ||
277 | |||
278 | msgid "Repeat your password" | ||
279 | msgstr "گذرواژه را دوباره وارد کنید" | ||
280 | |||
281 | msgid "Install" | ||
282 | msgstr "نصب" | ||
283 | |||
284 | #, fuzzy | ||
285 | msgid "login to your wallabag" | ||
286 | msgstr "به poche خود وارد شوید" | ||
287 | |||
288 | msgid "Login to wallabag" | ||
289 | msgstr "" | ||
290 | |||
291 | msgid "you are in demo mode, some features may be disabled." | ||
292 | msgstr "این تنها نسخهٔ نمایشی است، برخی از ویژگیها کار نمیکنند." | ||
293 | |||
294 | msgid "Username" | ||
295 | msgstr "" | ||
296 | |||
297 | msgid "Stay signed in" | ||
298 | msgstr "مرا به خاطر بسپار" | ||
299 | |||
300 | msgid "(Do not check on public computers)" | ||
301 | msgstr "(روی رایانههای عمومی این کار را نکنید)" | ||
302 | |||
303 | msgid "Sign in" | ||
304 | msgstr "ورود" | ||
305 | |||
306 | msgid "favorites" | ||
307 | msgstr "بهترینها" | ||
308 | |||
309 | msgid "estimated reading time :" | ||
310 | msgstr "" | ||
311 | |||
312 | msgid "Mark all the entries as read" | ||
313 | msgstr "" | ||
314 | |||
315 | msgid "Return home" | ||
316 | msgstr "" | ||
317 | |||
318 | #, fuzzy | ||
319 | msgid "Back to top" | ||
320 | msgstr "بازگشت به بالای صفحه" | ||
321 | |||
322 | #, fuzzy | ||
323 | msgid "Mark as read" | ||
324 | msgstr "خواندهشده/خواندهنشده" | ||
325 | |||
326 | #, fuzzy | ||
327 | msgid "Favorite" | ||
328 | msgstr "بهترینها" | ||
329 | |||
330 | #, fuzzy | ||
331 | msgid "Toggle favorite" | ||
332 | msgstr "جزء بهترینها هست/نیست" | ||
333 | |||
334 | #, fuzzy | ||
335 | msgid "Delete" | ||
336 | msgstr "پاککردن" | ||
337 | |||
338 | #, fuzzy | ||
339 | msgid "Tweet" | ||
166 | msgstr "توییت" | 340 | msgstr "توییت" |
167 | 341 | ||
168 | msgid "email" | 342 | #, fuzzy |
343 | msgid "Email" | ||
169 | msgstr "ایمیل" | 344 | msgstr "ایمیل" |
170 | 345 | ||
171 | msgid "shaarli" | 346 | msgid "shaarli" |
@@ -174,26 +349,24 @@ msgstr "shaarli" | |||
174 | msgid "flattr" | 349 | msgid "flattr" |
175 | msgstr "flattr" | 350 | msgstr "flattr" |
176 | 351 | ||
177 | msgid "this article appears wrong?" | 352 | #, fuzzy |
353 | msgid "Does this article appear wrong?" | ||
178 | msgstr "این مطلب اشتباه نمایش داده شده؟" | 354 | msgstr "این مطلب اشتباه نمایش داده شده؟" |
179 | 355 | ||
180 | msgid "create an issue" | 356 | msgid "tags:" |
181 | msgstr "یک درخواست رفعمشکل بنویسید" | 357 | msgstr "" |
182 | |||
183 | msgid "or" | ||
184 | msgstr "یا" | ||
185 | 358 | ||
186 | msgid "contact us by mail" | 359 | msgid "Edit tags" |
187 | msgstr "به ما ایمیل بزنید" | 360 | msgstr "" |
188 | 361 | ||
189 | msgid "plop" | 362 | msgid "save link!" |
190 | msgstr "plop" | 363 | msgstr "" |
191 | 364 | ||
192 | msgid "home" | 365 | msgid "home" |
193 | msgstr "خانه" | 366 | msgstr "خانه" |
194 | 367 | ||
195 | msgid "favorites" | 368 | msgid "tags" |
196 | msgstr "بهترینها" | 369 | msgstr "" |
197 | 370 | ||
198 | msgid "logout" | 371 | msgid "logout" |
199 | msgstr "بیرون رفتن" | 372 | msgstr "بیرون رفتن" |
@@ -204,23 +377,187 @@ msgstr "نیروگرفته از" | |||
204 | msgid "debug mode is on so cache is off." | 377 | msgid "debug mode is on so cache is off." |
205 | msgstr "حالت عیبیابی فعال است، پس کاشه خاموش است." | 378 | msgstr "حالت عیبیابی فعال است، پس کاشه خاموش است." |
206 | 379 | ||
207 | msgid "your poche version:" | 380 | #, fuzzy |
208 | msgstr "نسخهٔ poche شما:" | 381 | msgid "your wallabag version:" |
382 | msgstr "نسخهٔ شما" | ||
209 | 383 | ||
210 | msgid "storage:" | 384 | msgid "storage:" |
211 | msgstr "ذخیرهسازی:" | 385 | msgstr "ذخیرهسازی:" |
212 | 386 | ||
213 | msgid "login to your poche" | 387 | msgid "save a link" |
214 | msgstr "به poche خود وارد شوید" | 388 | msgstr "" |
215 | 389 | ||
216 | msgid "you are in demo mode, some features may be disabled." | 390 | msgid "back to home" |
217 | msgstr "ین نه نهٔ نمایی س، برخی از یژگیا ک نیکنند." | 391 | msgstr "اگشت به ن" |
218 | 392 | ||
219 | msgid "Stay signed in" | 393 | msgid "toggle mark as read" |
220 | msgstr "ما ه س" | 394 | msgstr "وانهه/وننه" |
221 | 395 | ||
222 | msgid "(Do not check on public computers)" | 396 | msgid "tweet" |
223 | msgstr "(وی راینههای عمومی این کار را نکنید)" | 397 | msgstr "ویی" |
224 | 398 | ||
225 | msgid "Sign in" | 399 | msgid "email" |
226 | msgstr "ورود" | 400 | msgstr "ایمیل" |
401 | |||
402 | msgid "this article appears wrong?" | ||
403 | msgstr "این مطلب اشتباه نمایش داده شده؟" | ||
404 | |||
405 | msgid "No link available here!" | ||
406 | msgstr "اینجا پیوندی موجود نیست!" | ||
407 | |||
408 | msgid "Poching a link" | ||
409 | msgstr "پیوندی را poche کنید" | ||
410 | |||
411 | msgid "by filling this field" | ||
412 | msgstr "با پرکردن این بخش" | ||
413 | |||
414 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
415 | msgstr "" | ||
416 | |||
417 | msgid "your version" | ||
418 | msgstr "نسخهٔ شما" | ||
419 | |||
420 | msgid "latest stable version" | ||
421 | msgstr "آخرین نسخهٔ پایدار" | ||
422 | |||
423 | msgid "a more recent stable version is available." | ||
424 | msgstr "نسخهٔ پایدار تازهای منتشر شده است." | ||
425 | |||
426 | msgid "you are up to date." | ||
427 | msgstr "شما بهروز هستید." | ||
428 | |||
429 | msgid "latest dev version" | ||
430 | msgstr "آخرین نسخهٔ آزمایشی" | ||
431 | |||
432 | msgid "a more recent development version is available." | ||
433 | msgstr "نسخهٔ آزمایشی تازهای منتشر شده است." | ||
434 | |||
435 | msgid "Please execute the import script locally, it can take a very long time." | ||
436 | msgstr "لطفاً برنامهٔ درونریزی را بهطور محلی اجرا کنید، شاید خیلی طول بکشد." | ||
437 | |||
438 | #, fuzzy | ||
439 | msgid "More infos in the official doc:" | ||
440 | msgstr "اطلاعات بیشتر در راهنمای رسمی:" | ||
441 | |||
442 | msgid "import from Pocket" | ||
443 | msgstr "درونریزی از Pocket" | ||
444 | |||
445 | msgid "import from Readability" | ||
446 | msgstr "درونریزی از Readability" | ||
447 | |||
448 | msgid "import from Instapaper" | ||
449 | msgstr "درونریزی از Instapaper" | ||
450 | |||
451 | msgid "Tags" | ||
452 | msgstr "" | ||
453 | |||
454 | #, fuzzy | ||
455 | msgid "Untitled" | ||
456 | msgstr "با عنوان" | ||
457 | |||
458 | msgid "the link has been added successfully" | ||
459 | msgstr "" | ||
460 | |||
461 | msgid "error during insertion : the link wasn't added" | ||
462 | msgstr "" | ||
463 | |||
464 | msgid "the link has been deleted successfully" | ||
465 | msgstr "" | ||
466 | |||
467 | msgid "the link wasn't deleted" | ||
468 | msgstr "" | ||
469 | |||
470 | msgid "Article not found!" | ||
471 | msgstr "" | ||
472 | |||
473 | msgid "previous" | ||
474 | msgstr "" | ||
475 | |||
476 | msgid "next" | ||
477 | msgstr "" | ||
478 | |||
479 | msgid "in demo mode, you can't update your password" | ||
480 | msgstr "" | ||
481 | |||
482 | msgid "your password has been updated" | ||
483 | msgstr "" | ||
484 | |||
485 | msgid "the two fields have to be filled & the password must be the same in the two fields" | ||
486 | msgstr "" | ||
487 | |||
488 | msgid "still using the \"" | ||
489 | msgstr "" | ||
490 | |||
491 | msgid "that theme does not seem to be installed" | ||
492 | msgstr "" | ||
493 | |||
494 | msgid "you have changed your theme preferences" | ||
495 | msgstr "" | ||
496 | |||
497 | msgid "that language does not seem to be installed" | ||
498 | msgstr "" | ||
499 | |||
500 | msgid "you have changed your language preferences" | ||
501 | msgstr "" | ||
502 | |||
503 | msgid "login failed: you have to fill all fields" | ||
504 | msgstr "" | ||
505 | |||
506 | msgid "welcome to your wallabag" | ||
507 | msgstr "" | ||
508 | |||
509 | msgid "login failed: bad login or password" | ||
510 | msgstr "" | ||
511 | |||
512 | #, fuzzy | ||
513 | msgid "import from instapaper completed" | ||
514 | msgstr "درونریزی از Instapaper" | ||
515 | |||
516 | #, fuzzy | ||
517 | msgid "import from pocket completed" | ||
518 | msgstr "درونریزی از Pocket" | ||
519 | |||
520 | #, fuzzy | ||
521 | msgid "import from Readability completed. " | ||
522 | msgstr "درونریزی از Readability" | ||
523 | |||
524 | #, fuzzy | ||
525 | msgid "import from Poche completed. " | ||
526 | msgstr "درونریزی از Pocket" | ||
527 | |||
528 | msgid "Unknown import provider." | ||
529 | msgstr "" | ||
530 | |||
531 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
532 | msgstr "" | ||
533 | |||
534 | msgid "Could not find required \"" | ||
535 | msgstr "" | ||
536 | |||
537 | msgid "Uh, there is a problem while generating feeds." | ||
538 | msgstr "" | ||
539 | |||
540 | #, fuzzy | ||
541 | msgid "Cache deleted." | ||
542 | msgstr "پاککردن" | ||
543 | |||
544 | msgid "Oops, it seems you don't have PHP 5." | ||
545 | msgstr "" | ||
546 | |||
547 | #~ msgid "poche it!" | ||
548 | #~ msgstr "poche کنید!" | ||
549 | |||
550 | #~ msgid "Updating poche" | ||
551 | #~ msgstr "بهروزرسانی poche" | ||
552 | |||
553 | #~ msgid "create an issue" | ||
554 | #~ msgstr "یک درخواست رفعمشکل بنویسید" | ||
555 | |||
556 | #~ msgid "or" | ||
557 | #~ msgstr "یا" | ||
558 | |||
559 | #~ msgid "contact us by mail" | ||
560 | #~ msgstr "به ما ایمیل بزنید" | ||
561 | |||
562 | #~ msgid "your poche version:" | ||
563 | #~ msgstr "نسخهٔ poche شما:" | ||
diff --git a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo index 76872b5a..b625e346 100644 --- a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo +++ b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po index f105d180..0343bfec 100644 --- a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po +++ b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po | |||
@@ -1,50 +1,228 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: \n" | 3 | "Project-Id-Version: wallabag 1.7\n" |
4 | "POT-Creation-Date: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-05-10 20:09+0100\n" | ||
5 | "PO-Revision-Date: \n" | 6 | "PO-Revision-Date: \n" |
6 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 7 | "Last-Translator: Gilles Wittezaële <gilles.wittezaele@laposte.net>\n" |
7 | "Language-Team: \n" | 8 | "Language-Team: \n" |
9 | "Language: fr_FR\n" | ||
8 | "MIME-Version: 1.0\n" | 10 | "MIME-Version: 1.0\n" |
9 | "Content-Type: text/plain; charset=UTF-8\n" | 11 | "Content-Type: text/plain; charset=UTF-8\n" |
10 | "Content-Transfer-Encoding: 8bit\n" | 12 | "Content-Transfer-Encoding: 8bit\n" |
13 | "X-Poedit-KeywordsList: _;gettext;gettext_noop\n" | ||
14 | "X-Poedit-SourceCharset: UTF-8\n" | ||
11 | "X-Generator: Poedit 1.5.4\n" | 15 | "X-Generator: Poedit 1.5.4\n" |
16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||
17 | |||
18 | msgid "wallabag, a read it later open source system" | ||
19 | msgstr "wallabag, un système open source de lecture différé" | ||
20 | |||
21 | msgid "login failed: user doesn't exist" | ||
22 | msgstr "échec de l'identification : cet utilisateur n'existe pas" | ||
23 | |||
24 | msgid "save link!" | ||
25 | msgstr "enregistrer le lien !" | ||
26 | |||
27 | msgid "plop" | ||
28 | msgstr "plop" | ||
29 | |||
30 | msgid "powered by" | ||
31 | msgstr "propulsé par" | ||
32 | |||
33 | msgid "debug mode is on so cache is off." | ||
34 | msgstr "le mode de debug est actif, le cache est donc désactivé." | ||
35 | |||
36 | msgid "your wallabag version:" | ||
37 | msgstr "votre version de wallabag :" | ||
38 | |||
39 | msgid "storage:" | ||
40 | msgstr "stockage :" | ||
41 | |||
42 | msgid "login to your wallabag" | ||
43 | msgstr "se connecter à votre wallabag" | ||
44 | |||
45 | msgid "Login to wallabag" | ||
46 | msgstr "Se connecter à wallabag" | ||
47 | |||
48 | msgid "you are in demo mode, some features may be disabled." | ||
49 | msgstr "" | ||
50 | "vous êtes en mode démo, certaines fonctionnalités peuvent être désactivées." | ||
51 | |||
52 | msgid "Username" | ||
53 | msgstr "Nom d'utilisateur" | ||
54 | |||
55 | msgid "Password" | ||
56 | msgstr "Mot de passe" | ||
57 | |||
58 | msgid "Stay signed in" | ||
59 | msgstr "Rester connecté" | ||
60 | |||
61 | msgid "(Do not check on public computers)" | ||
62 | msgstr "(Ne pas cocher sur un ordinateur public)" | ||
63 | |||
64 | msgid "Sign in" | ||
65 | msgstr "Se connecter" | ||
66 | |||
67 | msgid "back to home" | ||
68 | msgstr "retour à l'accueil" | ||
69 | |||
70 | msgid "favorites" | ||
71 | msgstr "favoris" | ||
72 | |||
73 | msgid "archive" | ||
74 | msgstr "archive" | ||
75 | |||
76 | msgid "unread" | ||
77 | msgstr "non lus" | ||
78 | |||
79 | msgid "Tag" | ||
80 | msgstr "Tag" | ||
81 | |||
82 | msgid "No articles found." | ||
83 | msgstr "Aucun article trouvé." | ||
84 | |||
85 | msgid "estimated reading time:" | ||
86 | msgstr "temps de lecture estimé :" | ||
87 | |||
88 | msgid "estimated reading time :" | ||
89 | msgstr "temps de lecture estimé :" | ||
90 | |||
91 | msgid "Toggle mark as read" | ||
92 | msgstr "Marquer comme lu / non lu" | ||
93 | |||
94 | msgid "toggle favorite" | ||
95 | msgstr "marquer / enlever comme favori" | ||
96 | |||
97 | msgid "delete" | ||
98 | msgstr "supprimer" | ||
99 | |||
100 | msgid "original" | ||
101 | msgstr "original" | ||
102 | |||
103 | msgid "Mark all the entries as read" | ||
104 | msgstr "Marquer tous les articles comme lus" | ||
105 | |||
106 | msgid "results" | ||
107 | msgstr "résultats" | ||
108 | |||
109 | msgid " found for « " | ||
110 | msgstr "trouvé pour « " | ||
111 | |||
112 | msgid "Only one result found for " | ||
113 | msgstr "Seulement un résultat trouvé pour " | ||
12 | 114 | ||
13 | msgid "config" | 115 | msgid "config" |
14 | msgstr "configuration" | 116 | msgstr "configuration" |
15 | 117 | ||
16 | msgid "Poching a link" | 118 | msgid "Saving articles" |
17 | msgstr "Pocher un lien" | 119 | msgstr "Sauvegarde des articles" |
120 | |||
121 | msgid "There are several ways to save an article:" | ||
122 | msgstr "Il y a plusieurs façons d'enregistrer un article :" | ||
18 | 123 | ||
19 | msgid "read the documentation" | 124 | msgid "read the documentation" |
20 | msgstr "lisez la documentation" | 125 | msgstr "lisez la documentation" |
21 | 126 | ||
22 | msgid "by filling this field" | 127 | msgid "download the extension" |
23 | msgstr "en remplissant ce champ" | 128 | msgstr "téléchargez l'extension" |
24 | 129 | ||
25 | msgid "poche it!" | 130 | msgid "via F-Droid" |
26 | msgstr "pochez-le !" | 131 | msgstr "via F-Droid" |
27 | 132 | ||
28 | msgid "Updating poche" | 133 | msgid " or " |
29 | msgstr "Mettre à jour poche" | 134 | msgstr " ou " |
30 | 135 | ||
31 | msgid "your version" | 136 | msgid "via Google Play" |
32 | msgstr "votre version" | 137 | msgstr "via Google PlayStore" |
33 | 138 | ||
34 | msgid "latest stable version" | 139 | msgid "download the application" |
35 | msgstr "dernire version stable" | 140 | msgstr "tléchargez l'application" |
36 | 141 | ||
37 | msgid "a more recent stable version is available." | 142 | msgid "By filling this field" |
38 | msgstr "une version stable plus récente est disponible." | 143 | msgstr "En remplissant ce champ" |
39 | 144 | ||
40 | msgid "you are up to date." | 145 | msgid "bag it!" |
41 | msgstr "vous êtes à jour." | 146 | msgstr "bag it !" |
42 | 147 | ||
43 | msgid "latest dev version" | 148 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
44 | msgstr "dernire version de développement" | 149 | msgstr "Bookmarklet : glissez-dposez ce lien dans votre barre de favoris" |
45 | 150 | ||
46 | msgid "a more recent development version is available." | 151 | msgid "Upgrading wallabag" |
47 | msgstr "une version de développement plus récente est disponible." | 152 | msgstr "Mise à jour de wallabag" |
153 | |||
154 | msgid "Installed version" | ||
155 | msgstr "Version installée" | ||
156 | |||
157 | msgid "Latest stable version" | ||
158 | msgstr "Dernière version stable" | ||
159 | |||
160 | msgid "A more recent stable version is available." | ||
161 | msgstr "Une version stable plus récente est disponible." | ||
162 | |||
163 | msgid "You are up to date." | ||
164 | msgstr "Vous êtes à jour." | ||
165 | |||
166 | msgid "Last check:" | ||
167 | msgstr "Dernière vérification: " | ||
168 | |||
169 | msgid "Latest dev version" | ||
170 | msgstr "Dernière version de développement" | ||
171 | |||
172 | msgid "A more recent development version is available." | ||
173 | msgstr "Une version de développement plus récente est disponible." | ||
174 | |||
175 | msgid "You can clear cache to check the latest release." | ||
176 | msgstr "" | ||
177 | "Vous pouvez vider le cache pour vérifier que vous avez la dernière version." | ||
178 | |||
179 | msgid "Feeds" | ||
180 | msgstr "Flux" | ||
181 | |||
182 | msgid "" | ||
183 | "Your feed token is currently empty and must first be generated to enable " | ||
184 | "feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
185 | msgstr "" | ||
186 | "Votre jeton de flux est actuellement vide et doit d'abord être généré pour " | ||
187 | "activer les flux. Cliquez <a href='?feed&action=generate'>ici</a> pour " | ||
188 | "le générer." | ||
189 | |||
190 | msgid "Unread feed" | ||
191 | msgstr "Flux des non lus" | ||
192 | |||
193 | msgid "Favorites feed" | ||
194 | msgstr "Flux des favoris" | ||
195 | |||
196 | msgid "Archive feed" | ||
197 | msgstr "Flux des archives" | ||
198 | |||
199 | msgid "Your token:" | ||
200 | msgstr "Votre jeton :" | ||
201 | |||
202 | msgid "Your user id:" | ||
203 | msgstr "Votre ID utilisateur :" | ||
204 | |||
205 | msgid "" | ||
206 | "You can regenerate your token: <a href='?feed&action=generate'>generate!" | ||
207 | "</a>." | ||
208 | msgstr "" | ||
209 | "Vous pouvez regénérer votre jeton : <a href='?feed&" | ||
210 | "action=generate'>génération !</a>." | ||
211 | |||
212 | msgid "Change your theme" | ||
213 | msgstr "Changer votre thème" | ||
214 | |||
215 | msgid "Theme:" | ||
216 | msgstr "Thème :" | ||
217 | |||
218 | msgid "Update" | ||
219 | msgstr "Mettre à jour" | ||
220 | |||
221 | msgid "Change your language" | ||
222 | msgstr "Changer votre langue" | ||
223 | |||
224 | msgid "Language:" | ||
225 | msgstr "Langue :" | ||
48 | 226 | ||
49 | msgid "Change your password" | 227 | msgid "Change your password" |
50 | msgstr "Modifier votre mot de passe" | 228 | msgstr "Modifier votre mot de passe" |
@@ -52,80 +230,214 @@ msgstr "Modifier votre mot de passe" | |||
52 | msgid "New password:" | 230 | msgid "New password:" |
53 | msgstr "Nouveau mot de passe :" | 231 | msgstr "Nouveau mot de passe :" |
54 | 232 | ||
55 | msgid "Password" | ||
56 | msgstr "Mot de passe" | ||
57 | |||
58 | msgid "Repeat your new password:" | 233 | msgid "Repeat your new password:" |
59 | msgstr "Répétez votre nouveau mot de passe :" | 234 | msgstr "Répétez votre nouveau mot de passe :" |
60 | 235 | ||
61 | msgid "Update" | ||
62 | msgstr "Mettre à jour" | ||
63 | |||
64 | msgid "Import" | 236 | msgid "Import" |
65 | msgstr "Importer" | 237 | msgstr "Importer" |
66 | 238 | ||
67 | msgid "Please execute the import script locally, it can take a very long time." | 239 | msgid "" |
68 | msgstr "Merci d'exécuter l'import en local, cela peut prendre du temps." | 240 | "You can import your Pocket, Readability, Instapaper, Wallabag or any data in " |
241 | "appropriate json or html format." | ||
242 | msgstr "" | ||
243 | "Vous pouvez importer depuis Pocket, Readability, Instapaper, wallabag, ou " | ||
244 | "n'importe quel fichier au format JSON ou HTML approprié." | ||
69 | 245 | ||
70 | msgid "More info in the official doc:" | 246 | msgid "" |
71 | msgstr "Plus d'infos sur la documentation officielle" | 247 | "Please select export file on your computer and press \"Import\" button below." |
248 | "<br>Wallabag will parse your file, insert all URLs and start fetching of " | ||
249 | "articles if required.<br>Fetching process is controlled by two constants in " | ||
250 | "your config file: IMPORT_LIMIT (how many articles are fetched at once) and " | ||
251 | "IMPORT_DELAY (delay between fetch of next batch of articles)." | ||
252 | msgstr "" | ||
253 | "Sélectionner le fichier à importer sur votre disque dur, et pressez la " | ||
254 | "bouton « Importer » ci-dessous.<br />wallabag analysera votre fichier, " | ||
255 | "ajoutera toutes les URL trouvées et commencera à télécharger les contenus si " | ||
256 | "nécessaire.<br />Le processus de téléchargement est contrôlé par deux " | ||
257 | "constantes dans votre fichier de configuration:<wbr /><code>IMPORT_LIMIT</" | ||
258 | "code> (nombre d'éléments téléchargés à la fois) et <code>IMPORT_DELAY</code> " | ||
259 | "(le délai d'attente entre deux séquences de téléchargement)." | ||
260 | |||
261 | msgid "File:" | ||
262 | msgstr "Fichier : " | ||
263 | |||
264 | msgid "You can click here to fetch content for articles with no content." | ||
265 | msgstr "" | ||
266 | "Vous pouvez cliquer ici pour télécharger le contenu des articles vides." | ||
72 | 267 | ||
73 | msgid "import from Pocket" | 268 | msgid "Export your wallabag data" |
74 | msgstr "import depuis Pocket" | 269 | msgstr "Exporter vos données de wallabag" |
75 | 270 | ||
76 | msgid "import from Readability" | 271 | msgid "Click here" |
77 | msgstr "import depuis Readability" | 272 | msgstr "Cliquez ici" |
78 | 273 | ||
79 | msgid "import from Instapaper" | 274 | msgid "to download your database." |
80 | msgstr "import depuis Instapaper" | 275 | msgstr "pour télécharger votre base de données." |
81 | 276 | ||
82 | msgid "Export your poche data" | 277 | msgid "to export your wallabag data." |
83 | msgstr "Exporter vos données de poche" | 278 | msgstr "pour exporter vos données de wallabag." |
84 | 279 | ||
85 | msgid "Click here" | 280 | msgid "Cache" |
86 | msgstr "Cliquez-ici" | 281 | msgstr "Cache" |
87 | 282 | ||
88 | msgid "to export your poche data." | 283 | msgid "to delete cache." |
89 | msgstr "pour exporter vos données de poche." | 284 | msgstr "pour effacer le cache." |
90 | 285 | ||
91 | msgid "back to home" | 286 | msgid "Add user" |
92 | msgstr "retour à l'accueil" | 287 | msgstr "Ajouter un utilisateur" |
93 | 288 | ||
94 | msgid "installation" | 289 | msgid "Add a new user :" |
95 | msgstr "installation" | 290 | msgstr "Ajouter un nouvel utilisateur : " |
291 | |||
292 | msgid "Login for new user" | ||
293 | msgstr "Identifiant du nouvel utilisateur" | ||
294 | |||
295 | msgid "Login" | ||
296 | msgstr "Nom d'utilisateur" | ||
297 | |||
298 | msgid "Password for new user" | ||
299 | msgstr "Mot de passe du nouvel utilisateur" | ||
300 | |||
301 | msgid "Send" | ||
302 | msgstr "Envoyer" | ||
303 | |||
304 | msgid "Delete account" | ||
305 | msgstr "Supprimer le compte" | ||
96 | 306 | ||
97 | msgid "install your poche" | 307 | msgid "You can delete your account by entering your password and validating." |
98 | msgstr "installez votre poche" | 308 | msgstr "" |
309 | "Vous pouvez supprimer votre compte en entrant votre mot de passe et en " | ||
310 | "validant." | ||
311 | |||
312 | msgid "Be careful, data will be erased forever (that is a very long time)." | ||
313 | msgstr "Attention, les données seront perdues pour toujours." | ||
314 | |||
315 | msgid "Type here your password" | ||
316 | msgstr "Entrez votre mot de passe ici" | ||
317 | |||
318 | msgid "You are the only user, you cannot delete your own account." | ||
319 | msgstr "" | ||
320 | "Vous êtes l'unique utilisateur, vous ne pouvez pas supprimer votre compte." | ||
99 | 321 | ||
100 | msgid "" | 322 | msgid "" |
101 | "poche is still not installed. Please fill the below form to install it. " | 323 | "To completely remove wallabag, delete the wallabag folder on your web server." |
102 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
103 | "on poche website</a>." | ||
104 | msgstr "" | 324 | msgstr "" |
105 | "poche n'est pas encore installé. Merci de remplir le formulaire suivant pour " | 325 | "Pour désinstaller complètement wallabag, supprimez le répertoire " |
106 | "l'installer. N'hésitez pas à <a href='http://doc.inthepoche.com'>lire la " | 326 | "<code>wallabag</code> de votre serveur Web." |
107 | "documentation sur le site de poche</a>." | ||
108 | 327 | ||
109 | msgid "Login" | 328 | msgid "Save a link" |
110 | msgstr "Nom d'utilisateur" | 329 | msgstr "Ajouter un lien" |
111 | 330 | ||
112 | msgid "Repeat your password" | 331 | msgid "Return home" |
113 | msgstr "Répétez votre mot de passe" | 332 | msgstr "Retour accueil" |
114 | 333 | ||
115 | msgid "Install" | 334 | msgid "Back to top" |
116 | msgstr "Installer" | 335 | msgstr "Haut de page" |
336 | |||
337 | msgid "Mark as read" | ||
338 | msgstr "Marquer comme lu" | ||
339 | |||
340 | msgid "Favorite" | ||
341 | msgstr "Favoris" | ||
342 | |||
343 | msgid "Toggle favorite" | ||
344 | msgstr "Marquer / enlever comme favori" | ||
117 | 345 | ||
118 | msgid "back to top" | 346 | msgid "Delete" |
119 | msgstr "retour en haut de page" | 347 | msgstr "Effacer" |
348 | |||
349 | msgid "Tweet" | ||
350 | msgstr "Tweet" | ||
351 | |||
352 | msgid "Email" | ||
353 | msgstr "E-mail" | ||
354 | |||
355 | msgid "shaarli" | ||
356 | msgstr "Shaarli" | ||
357 | |||
358 | msgid "flattr" | ||
359 | msgstr "Flattr" | ||
360 | |||
361 | msgid "Print" | ||
362 | msgstr "Imprimer" | ||
363 | |||
364 | msgid "Does this article appear wrong?" | ||
365 | msgstr "Cet article s'affiche mal ?" | ||
366 | |||
367 | msgid "tags:" | ||
368 | msgstr "tags :" | ||
369 | |||
370 | msgid "Edit tags" | ||
371 | msgstr "Modifier les tags" | ||
120 | 372 | ||
121 | msgid "favoris" | 373 | msgid "favoris" |
122 | msgstr "favoris" | 374 | msgstr "favoris" |
123 | 375 | ||
124 | msgid "archive" | 376 | msgid "mark all the entries as read" |
125 | msgstr "archive" | 377 | msgstr "marquer tous les articles comme lus" |
126 | 378 | ||
127 | msgid "unread" | 379 | msgid "toggle view mode" |
128 | msgstr "non lus" | 380 | msgstr "changer de mode de visualisation" |
381 | |||
382 | msgid "return home" | ||
383 | msgstr "retour à l'accueil" | ||
384 | |||
385 | msgid "Poching a link" | ||
386 | msgstr "Enregistrer un lien" | ||
387 | |||
388 | msgid "by filling this field" | ||
389 | msgstr "en remplissant ce champ" | ||
390 | |||
391 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
392 | msgstr "bookmarklet : glissez-déposez ce lien dans votre barre de favoris" | ||
393 | |||
394 | msgid "your version" | ||
395 | msgstr "votre version" | ||
396 | |||
397 | msgid "latest stable version" | ||
398 | msgstr "dernière version stable" | ||
399 | |||
400 | msgid "a more recent stable version is available." | ||
401 | msgstr "une version stable plus récente est disponible." | ||
402 | |||
403 | msgid "you are up to date." | ||
404 | msgstr "vous êtes à jour." | ||
405 | |||
406 | msgid "latest dev version" | ||
407 | msgstr "dernière version de développement" | ||
408 | |||
409 | msgid "a more recent development version is available." | ||
410 | msgstr "une version de développement plus récente est disponible." | ||
411 | |||
412 | msgid "Please execute the import script locally, it can take a very long time." | ||
413 | msgstr "" | ||
414 | "Merci d'exécuter le script d'importation en local car cela peut prendre du " | ||
415 | "temps." | ||
416 | |||
417 | msgid "More infos in the official doc:" | ||
418 | msgstr "Plus d'infos dans la documentation officielle :" | ||
419 | |||
420 | msgid "import from Pocket" | ||
421 | msgstr "importer depuis Pocket" | ||
422 | |||
423 | #, php-format | ||
424 | msgid "(you must have a %s file on your server)" | ||
425 | msgstr "(le fichier %s doit être présent sur le serveur)" | ||
426 | |||
427 | msgid "import from Readability" | ||
428 | msgstr "importer depuis Readability" | ||
429 | |||
430 | msgid "import from Instapaper" | ||
431 | msgstr "importer depuis Instapaper" | ||
432 | |||
433 | msgid "Start typing for auto complete." | ||
434 | msgstr "Commencez à taper pour activer l'auto-complétion." | ||
435 | |||
436 | msgid "You can enter multiple tags, separated by commas." | ||
437 | msgstr "Vous pouvez entrer plusieurs tags, séparés par des virgules." | ||
438 | |||
439 | msgid "return to article" | ||
440 | msgstr "retourner à l'article" | ||
129 | 441 | ||
130 | msgid "by date asc" | 442 | msgid "by date asc" |
131 | msgstr "par date asc" | 443 | msgstr "par date asc" |
@@ -145,84 +457,196 @@ msgstr "par titre" | |||
145 | msgid "by title desc" | 457 | msgid "by title desc" |
146 | msgstr "par titre desc" | 458 | msgstr "par titre desc" |
147 | 459 | ||
460 | msgid "home" | ||
461 | msgstr "accueil" | ||
462 | |||
463 | msgid "tags" | ||
464 | msgstr "tags" | ||
465 | |||
466 | msgid "save a link" | ||
467 | msgstr "sauver un lien" | ||
468 | |||
469 | msgid "search" | ||
470 | msgstr "rechercher" | ||
471 | |||
472 | msgid "logout" | ||
473 | msgstr "déconnexion" | ||
474 | |||
475 | msgid "installation" | ||
476 | msgstr "installation" | ||
477 | |||
478 | msgid "install your wallabag" | ||
479 | msgstr "installez votre wallabag" | ||
480 | |||
481 | msgid "" | ||
482 | "wallabag is still not installed. Please fill the below form to install it. " | ||
483 | "Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation " | ||
484 | "on wallabag website</a>." | ||
485 | msgstr "" | ||
486 | "wallabag n'est pas encore installé. Merci de remplir le formulaire suivant " | ||
487 | "pour l'installer. N'hésitez pas à <a href='http://doc.wallabag.org'>lire la " | ||
488 | "documentation sur le site de wallabag</a>." | ||
489 | |||
490 | msgid "Repeat your password" | ||
491 | msgstr "Répétez votre mot de passe" | ||
492 | |||
493 | msgid "Install" | ||
494 | msgstr "Installer" | ||
495 | |||
496 | msgid "" | ||
497 | "You can <a href='wallabag_compatibility_test.php'>check your configuration " | ||
498 | "here</a>." | ||
499 | msgstr "" | ||
500 | "Vous pouvez vérifier votre configuration <a " | ||
501 | "href='wallabag_compatibility_test.php'>ici</a>." | ||
502 | |||
503 | msgid "Tags" | ||
504 | msgstr "Tags" | ||
505 | |||
148 | msgid "No link available here!" | 506 | msgid "No link available here!" |
149 | msgstr "Aucun lien n'est disponible ici !" | 507 | msgstr "Aucun lien n'est disponible ici !" |
150 | 508 | ||
151 | msgid "toggle mark as read" | 509 | msgid "toggle mark as read" |
152 | msgstr "marquer comme lu / non lu" | 510 | msgstr "marquer comme lu / non lu" |
153 | 511 | ||
154 | msgid "toggle favorite" | 512 | msgid "tweet" |
155 | msgstr "marquer comme favori" | 513 | msgstr "tweet" |
156 | 514 | ||
157 | msgid "delete" | 515 | msgid "email" |
158 | msgstr "supprimer" | 516 | msgstr "e-mail" |
159 | 517 | ||
160 | msgid "original" | 518 | msgid "this article appears wrong?" |
161 | msgstr "original" | 519 | msgstr "cet article s'affiche mal ?" |
162 | 520 | ||
163 | msgid "results" | 521 | msgid "Search" |
164 | msgstr "résultats" | 522 | msgstr "Rechercher" |
165 | 523 | ||
166 | msgid "tweet" | 524 | msgid "Download required for " |
167 | msgstr "tweet" | 525 | msgstr "Téléchargement requis pour " |
168 | 526 | ||
169 | msgid "email" | 527 | msgid "records" |
170 | msgstr "email" | 528 | msgstr " articles" |
171 | 529 | ||
172 | msgid "shaarli" | 530 | msgid "Downloading next " |
173 | msgstr "shaarli" | 531 | msgstr "Téléchargement des " |
174 | 532 | ||
175 | msgid "flattr" | 533 | msgid "articles, please wait" |
176 | msgstr "flattr" | 534 | msgstr " articles suivants, veuillez patienter..." |
177 | 535 | ||
178 | msgid "this article appears wrong?" | 536 | msgid "Enter your search here" |
179 | msgstr "cet article s'affiche mal ?" | 537 | msgstr "Entrez votre recherche ici" |
180 | 538 | ||
181 | msgid "create an issue" | 539 | #, php-format |
182 | msgstr "créez un ticket" | 540 | msgid "" |
541 | "The new user %s has been installed. Do you want to <a href=\"?logout" | ||
542 | "\">logout ?</a>" | ||
543 | msgstr "" | ||
544 | "Le nouvel utilisateur « %s » a été ajouté. Voulez-vous vous <a href=\"?" | ||
545 | "logout\">déconnecter ?</a>" | ||
183 | 546 | ||
184 | msgid "or" | 547 | #, php-format |
185 | msgstr "ou" | 548 | msgid "Error : An user with the name %s already exists !" |
549 | msgstr "Erreur : Un utilisateur avec le nom « %s » existe déjà !" | ||
186 | 550 | ||
187 | msgid "contact us by mail" | 551 | #, php-format |
188 | msgstr "contactez-nous par email" | 552 | msgid "User %s has been successfully deleted !" |
553 | msgstr "L'utilisateur « %s » a été supprimé avec succès !" | ||
189 | 554 | ||
190 | msgid "plop" | 555 | msgid "Error : The password is wrong !" |
191 | msgstr "plop" | 556 | msgstr "Erreur : le mot de passe est incorrect !" |
192 | 557 | ||
193 | msgid "home" | 558 | msgid "Error : You are the only user, you cannot delete your account !" |
194 | msgstr "accueil" | 559 | msgstr "" |
560 | "Erreur : Vous êtes l'unique utilisateur, vous ne pouvez pas supprimer votre " | ||
561 | "compte !" | ||
195 | 562 | ||
196 | msgid "favorites" | 563 | msgid "Untitled" |
197 | msgstr "favoris" | 564 | msgstr "Sans titre" |
198 | 565 | ||
199 | msgid "logout" | 566 | msgid "the link has been added successfully" |
200 | msgstr "déconnexion" | 567 | msgstr "le lien a été ajouté avec succès" |
201 | 568 | ||
202 | msgid "powered by" | 569 | msgid "error during insertion : the link wasn't added" |
203 | msgstr "propulsé par" | 570 | msgstr "erreur pendant l'insertion : le lien n'a pas été ajouté" |
204 | 571 | ||
205 | msgid "debug mode is on so cache is off." | 572 | msgid "the link has been deleted successfully" |
206 | msgstr "le mode de debug est actif, le cache est donc désactiv." | 573 | msgstr "le lien a été effacé avec succs" |
207 | 574 | ||
208 | msgid "your poche version:" | 575 | msgid "the link wasn't deleted" |
209 | msgstr "votre version de poche :" | 576 | msgstr "le lien n'a pas été effacé" |
210 | 577 | ||
211 | msgid "storage:" | 578 | msgid "Article not found!" |
212 | msgstr "stockage :" | 579 | msgstr "Article non trouvé !" |
213 | 580 | ||
214 | msgid "login to your poche" | 581 | msgid "previous" |
215 | msgstr "se connecter votre poche" | 582 | msgstr "prcédent" |
216 | 583 | ||
217 | msgid "you are in demo mode, some features may be disabled." | 584 | msgid "next" |
585 | msgstr "suivant" | ||
586 | |||
587 | msgid "in demo mode, you can't update your password" | ||
588 | msgstr "en mode démo, vous ne pouvez pas mettre à jour votre mot de passe" | ||
589 | |||
590 | msgid "your password has been updated" | ||
591 | msgstr "votre mot de passe a été mis à jour" | ||
592 | |||
593 | msgid "" | ||
594 | "the two fields have to be filled & the password must be the same in the two " | ||
595 | "fields" | ||
218 | msgstr "" | 596 | msgstr "" |
219 | "vous êtes en mode démo, certaines fonctionnalités peuvent être désactivées." | 597 | "les deux champs doivent être remplis & le mot de passe doit être le même " |
598 | "dans les deux" | ||
220 | 599 | ||
221 | msgid "Stay signed in" | 600 | msgid "still using the \"" |
222 | msgstr "Rester connecté" | 601 | msgstr "vous utilisez toujours \"" |
223 | 602 | ||
224 | msgid "(Do not check on public computers)" | 603 | msgid "that theme does not seem to be installed" |
225 | msgstr "(ne pas cocher sur un ordinateur public)" | 604 | msgstr "ce thème ne semble pas installé" |
226 | 605 | ||
227 | msgid "Sign in" | 606 | msgid "you have changed your theme preferences" |
228 | msgstr "Se connecter" | 607 | msgstr "vous avez changé vos préférences de thème" |
608 | |||
609 | msgid "that language does not seem to be installed" | ||
610 | msgstr "cette langue ne semble pas être installée" | ||
611 | |||
612 | msgid "you have changed your language preferences" | ||
613 | msgstr "vous avez changé vos préférences de langue" | ||
614 | |||
615 | msgid "login failed: you have to fill all fields" | ||
616 | msgstr "échec de l'identification : vous devez remplir tous les champs" | ||
617 | |||
618 | msgid "welcome to your wallabag" | ||
619 | msgstr "bienvenue dans votre wallabag" | ||
620 | |||
621 | msgid "login failed: bad login or password" | ||
622 | msgstr "échec de l'identification : mauvais identifiant ou mot de passe" | ||
623 | |||
624 | msgid "Untitled - Import - " | ||
625 | msgstr "Sans titre - Importer - " | ||
626 | |||
627 | msgid "click to finish import" | ||
628 | msgstr "cliquez pour terminer l'importation" | ||
629 | |||
630 | msgid "Articles inserted: " | ||
631 | msgstr "Articles ajoutés : " | ||
632 | |||
633 | msgid ". Please note, that some may be marked as \"read\"." | ||
634 | msgstr ". Notez que certains pourraient être marqués comme \"lus\"." | ||
635 | |||
636 | msgid "Import finished." | ||
637 | msgstr "Importation terminée." | ||
638 | |||
639 | msgid "Undefined" | ||
640 | msgstr "Non défini" | ||
641 | |||
642 | msgid "User with this id (" | ||
643 | msgstr "Utilisateur avec cet identifiant (" | ||
644 | |||
645 | msgid "Uh, there is a problem while generating feeds." | ||
646 | msgstr "Hum, il y a un problème lors de la génération des flux." | ||
647 | |||
648 | msgid "Cache deleted." | ||
649 | msgstr "Cache effacé." | ||
650 | |||
651 | msgid "Oops, it seems you don't have PHP 5." | ||
652 | msgstr "Oups, vous ne semblez pas avoir PHP 5." | ||
diff --git a/locale/it_IT.utf8/LC_MESSAGES/it_IT.utf8.po b/locale/it_IT.utf8/LC_MESSAGES/it_IT.utf8.po index 429d5d55..41cc01e0 100644 --- a/locale/it_IT.utf8/LC_MESSAGES/it_IT.utf8.po +++ b/locale/it_IT.utf8/LC_MESSAGES/it_IT.utf8.po | |||
@@ -4,54 +4,138 @@ | |||
4 | msgid "" | 4 | msgid "" |
5 | msgstr "" | 5 | msgstr "" |
6 | "Project-Id-Version: poche\n" | 6 | "Project-Id-Version: poche\n" |
7 | "POT-Creation-Date: \n" | 7 | "Report-Msgid-Bugs-To: \n" |
8 | "PO-Revision-Date: 2013-11-25 09:47+0100\n" | 8 | "POT-Creation-Date: 2014-02-25 15:13+0300\n" |
9 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 9 | "PO-Revision-Date: 2014-02-25 15:13+0300\n" |
10 | "Language-Team: Italian (http://www.transifex.com/projects/p/poche/language/" | 10 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
11 | "it/)\n" | 11 | "Language-Team: Italian (http://www.transifex.com/projects/p/poche/language/it/)\n" |
12 | "Language: it\n" | ||
12 | "MIME-Version: 1.0\n" | 13 | "MIME-Version: 1.0\n" |
13 | "Content-Type: text/plain; charset=UTF-8\n" | 14 | "Content-Type: text/plain; charset=UTF-8\n" |
14 | "Content-Transfer-Encoding: 8bit\n" | 15 | "Content-Transfer-Encoding: 8bit\n" |
15 | "Language: it\n" | ||
16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" | 16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" |
17 | "X-Generator: Poedit 1.5.4\n" | 17 | "X-Generator: Poedit 1.5.4\n" |
18 | "X-Poedit-Language: Italian\n" | ||
19 | "X-Poedit-Country: ITALY\n" | ||
20 | "X-Poedit-Basepath: .\n" | ||
21 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" | ||
22 | |||
23 | msgid "wallabag, a read it later open source system" | ||
24 | msgstr "" | ||
25 | |||
26 | msgid "login failed: user doesn't exist" | ||
27 | msgstr "" | ||
28 | |||
29 | msgid "return home" | ||
30 | msgstr "" | ||
18 | 31 | ||
19 | msgid "config" | 32 | msgid "config" |
20 | msgstr "configurazione" | 33 | msgstr "configurazione" |
21 | 34 | ||
22 | msgid "Poching a link" | 35 | msgid "Saving articles" |
23 | msgstr "Pochare un link" | 36 | msgstr "" |
37 | |||
38 | msgid "There are several ways to save an article:" | ||
39 | msgstr "" | ||
24 | 40 | ||
25 | msgid "read the documentation" | 41 | msgid "read the documentation" |
26 | msgstr "leggi la documentazione" | 42 | msgstr "leggi la documentazione" |
27 | 43 | ||
28 | msgid "by filling this field" | 44 | msgid "download the extension" |
45 | msgstr "" | ||
46 | |||
47 | msgid "via F-Droid" | ||
48 | msgstr "" | ||
49 | |||
50 | msgid " or " | ||
51 | msgstr "" | ||
52 | |||
53 | msgid "via Google Play" | ||
54 | msgstr "" | ||
55 | |||
56 | msgid "download the application" | ||
57 | msgstr "" | ||
58 | |||
59 | #, fuzzy | ||
60 | msgid "By filling this field" | ||
29 | msgstr "compilando questo campo" | 61 | msgstr "compilando questo campo" |
30 | 62 | ||
31 | msgid "poche it!" | 63 | msgid "bag it!" |
32 | msgstr "pochalo!" | 64 | msgstr "" |
33 | 65 | ||
34 | msgid "Updating poche" | 66 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
35 | msgstr "Aggiornamento poche" | 67 | msgstr "" |
36 | 68 | ||
37 | msgid "your version" | 69 | msgid "Upgrading wallabag" |
38 | msgstr "la tua versione" | 70 | msgstr "" |
39 | 71 | ||
40 | msgid "latest stable version" | 72 | #, fuzzy |
73 | msgid "Installed version" | ||
41 | msgstr "ultima versione stabile" | 74 | msgstr "ultima versione stabile" |
42 | 75 | ||
43 | msgid "a more recent stable version is available." | 76 | #, fuzzy |
77 | msgid "Latest stable version" | ||
78 | msgstr "ultima versione stabile" | ||
79 | |||
80 | #, fuzzy | ||
81 | msgid "A more recent stable version is available." | ||
44 | msgstr "è disponibile una versione stabile più recente." | 82 | msgstr "è disponibile una versione stabile più recente." |
45 | 83 | ||
46 | msgid "you are up to date." | 84 | #, fuzzy |
85 | msgid "You are up to date." | ||
47 | msgstr "sei aggiornato." | 86 | msgstr "sei aggiornato." |
48 | 87 | ||
49 | msgid "latest dev version" | 88 | #, fuzzy |
89 | msgid "Latest dev version" | ||
50 | msgstr "ultima versione di sviluppo" | 90 | msgstr "ultima versione di sviluppo" |
51 | 91 | ||
52 | msgid "a more recent development version is available." | 92 | #, fuzzy |
93 | msgid "A more recent development version is available." | ||
53 | msgstr "è disponibile una versione di sviluppo più recente." | 94 | msgstr "è disponibile una versione di sviluppo più recente." |
54 | 95 | ||
96 | msgid "Feeds" | ||
97 | msgstr "" | ||
98 | |||
99 | msgid "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
100 | msgstr "" | ||
101 | |||
102 | msgid "Unread feed" | ||
103 | msgstr "" | ||
104 | |||
105 | #, fuzzy | ||
106 | msgid "Favorites feed" | ||
107 | msgstr "preferiti" | ||
108 | |||
109 | #, fuzzy | ||
110 | msgid "Archive feed" | ||
111 | msgstr "archivio" | ||
112 | |||
113 | msgid "Your token:" | ||
114 | msgstr "" | ||
115 | |||
116 | msgid "Your user id:" | ||
117 | msgstr "" | ||
118 | |||
119 | msgid "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
120 | msgstr "" | ||
121 | |||
122 | #, fuzzy | ||
123 | msgid "Change your theme" | ||
124 | msgstr "Cambia la tua password" | ||
125 | |||
126 | msgid "Theme:" | ||
127 | msgstr "" | ||
128 | |||
129 | msgid "Update" | ||
130 | msgstr "Aggiorna" | ||
131 | |||
132 | #, fuzzy | ||
133 | msgid "Change your language" | ||
134 | msgstr "Cambia la tua password" | ||
135 | |||
136 | msgid "Language:" | ||
137 | msgstr "" | ||
138 | |||
55 | msgid "Change your password" | 139 | msgid "Change your password" |
56 | msgstr "Cambia la tua password" | 140 | msgstr "Cambia la tua password" |
57 | 141 | ||
@@ -64,67 +148,68 @@ msgstr "Password" | |||
64 | msgid "Repeat your new password:" | 148 | msgid "Repeat your new password:" |
65 | msgstr "Ripeti la nuova password:" | 149 | msgstr "Ripeti la nuova password:" |
66 | 150 | ||
67 | msgid "Update" | ||
68 | msgstr "Aggiorna" | ||
69 | |||
70 | msgid "Import" | 151 | msgid "Import" |
71 | msgstr "Importa" | 152 | msgstr "Importa" |
72 | 153 | ||
73 | msgid "Please execute the import script locally, it can take a very long time." | 154 | #, fuzzy |
74 | msgstr "" | 155 | msgid "Please execute the import script locally as it can take a very long time." |
75 | "Si prega di eseguire lo script di importazione a livello locale, può " | 156 | msgstr "Si prega di eseguire lo script di importazione a livello locale, può richiedere un tempo molto lungo." |
76 | "richiedere un tempo molto lungo." | ||
77 | 157 | ||
78 | msgid "More info in the official doc:" | 158 | #, fuzzy |
159 | msgid "More info in the official documentation:" | ||
79 | msgstr "Maggiori info nella documentazione ufficiale" | 160 | msgstr "Maggiori info nella documentazione ufficiale" |
80 | 161 | ||
81 | msgid "import from Pocket" | 162 | #, fuzzy |
163 | msgid "Import from Pocket" | ||
82 | msgstr "Importa da Pocket" | 164 | msgstr "Importa da Pocket" |
83 | 165 | ||
84 | msgid "import from Readability" | 166 | #, php-format |
167 | msgid "(you must have a %s file on your server)" | ||
168 | msgstr "" | ||
169 | |||
170 | #, fuzzy | ||
171 | msgid "Import from Readability" | ||
85 | msgstr "Importa da Readability" | 172 | msgstr "Importa da Readability" |
86 | 173 | ||
87 | msgid "import from Instapaper" | 174 | #, fuzzy |
175 | msgid "Import from Instapaper" | ||
88 | msgstr "Importa da Instapaper" | 176 | msgstr "Importa da Instapaper" |
89 | 177 | ||
90 | msgid "Export your poche data" | 178 | #, fuzzy |
179 | msgid "Import from wallabag" | ||
180 | msgstr "Importa da Readability" | ||
181 | |||
182 | #, fuzzy | ||
183 | msgid "Export your wallabag data" | ||
91 | msgstr "Esporta i tuoi dati di poche" | 184 | msgstr "Esporta i tuoi dati di poche" |
92 | 185 | ||
93 | msgid "Click here" | 186 | msgid "Click here" |
94 | msgstr "Fai clic qui" | 187 | msgstr "Fai clic qui" |
95 | 188 | ||
96 | msgid "to export your poche data." | 189 | msgid "to download your database." |
97 | msgstr "per esportare i tuoi dati di poche." | 190 | msgstr "" |
98 | |||
99 | msgid "back to home" | ||
100 | msgstr "torna alla home" | ||
101 | 191 | ||
102 | msgid "installation" | 192 | #, fuzzy |
103 | msgstr "installazione" | 193 | msgid "to export your wallabag data." |
194 | msgstr "per esportare i tuoi dati di poche." | ||
104 | 195 | ||
105 | msgid "install your poche" | 196 | msgid "Cache" |
106 | msgstr "installa il tuo poche" | 197 | msgstr "" |
107 | 198 | ||
108 | msgid "" | 199 | msgid "to delete cache." |
109 | "poche is still not installed. Please fill the below form to install it. " | ||
110 | "Don't hesitate to <a href='http://doc.inthepoche.com'>read the documentation " | ||
111 | "on poche website</a>." | ||
112 | msgstr "" | 200 | msgstr "" |
113 | "poche non è ancora installato. Si prega di riempire il modulo sottostante " | ||
114 | "per completare l'installazione. <a href='http://doc.inthepoche.com'>Leggere " | ||
115 | "la documentazione sul sito di poche</a>." | ||
116 | 201 | ||
117 | msgid "Login" | 202 | msgid "You can enter multiple tags, separated by commas." |
118 | msgstr "Nome utente" | 203 | msgstr "" |
119 | 204 | ||
120 | msgid "Repeat your password" | 205 | msgid "return to article" |
121 | msgstr "Ripeti la tua password" | 206 | msgstr "" |
122 | 207 | ||
123 | msgid "Install" | 208 | msgid "plop" |
124 | msgstr "Installa" | 209 | msgstr "plop" |
125 | 210 | ||
126 | msgid "back to top" | 211 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
127 | msgstr "torna a inizio pagina" | 212 | msgstr "" |
128 | 213 | ||
129 | msgid "favoris" | 214 | msgid "favoris" |
130 | msgstr "preferiti" | 215 | msgstr "preferiti" |
@@ -153,10 +238,14 @@ msgstr "per titolo" | |||
153 | msgid "by title desc" | 238 | msgid "by title desc" |
154 | msgstr "per titolo decr" | 239 | msgstr "per titolo decr" |
155 | 240 | ||
156 | msgid "No link available here!" | 241 | msgid "Tag" |
157 | msgstr "Nessun link disponibile!" | 242 | msgstr "" |
158 | 243 | ||
159 | msgid "toggle mark as read" | 244 | msgid "No articles found." |
245 | msgstr "" | ||
246 | |||
247 | #, fuzzy | ||
248 | msgid "Toggle mark as read" | ||
160 | msgstr "segna come letto / non letto" | 249 | msgstr "segna come letto / non letto" |
161 | 250 | ||
162 | msgid "toggle favorite" | 251 | msgid "toggle favorite" |
@@ -168,13 +257,95 @@ msgstr "elimina" | |||
168 | msgid "original" | 257 | msgid "original" |
169 | msgstr "originale" | 258 | msgstr "originale" |
170 | 259 | ||
260 | msgid "estimated reading time:" | ||
261 | msgstr "" | ||
262 | |||
263 | msgid "mark all the entries as read" | ||
264 | msgstr "" | ||
265 | |||
171 | msgid "results" | 266 | msgid "results" |
172 | msgstr "risultati" | 267 | msgstr "risultati" |
173 | 268 | ||
174 | msgid "tweet" | 269 | msgid "installation" |
270 | msgstr "installazione" | ||
271 | |||
272 | #, fuzzy | ||
273 | msgid "install your wallabag" | ||
274 | msgstr "installa il tuo poche" | ||
275 | |||
276 | #, fuzzy | ||
277 | msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
278 | msgstr "poche non è ancora installato. Si prega di riempire il modulo sottostante per completare l'installazione. <a href='http://doc.inthepoche.com'>Leggere la documentazione sul sito di poche</a>." | ||
279 | |||
280 | msgid "Login" | ||
281 | msgstr "Nome utente" | ||
282 | |||
283 | msgid "Repeat your password" | ||
284 | msgstr "Ripeti la tua password" | ||
285 | |||
286 | msgid "Install" | ||
287 | msgstr "Installa" | ||
288 | |||
289 | #, fuzzy | ||
290 | msgid "login to your wallabag" | ||
291 | msgstr "accedi al tuo poche" | ||
292 | |||
293 | msgid "Login to wallabag" | ||
294 | msgstr "" | ||
295 | |||
296 | msgid "you are in demo mode, some features may be disabled." | ||
297 | msgstr "sei in modalità dimostrazione, alcune funzionalità potrebbero essere disattivate." | ||
298 | |||
299 | msgid "Username" | ||
300 | msgstr "" | ||
301 | |||
302 | msgid "Stay signed in" | ||
303 | msgstr "Resta connesso" | ||
304 | |||
305 | msgid "(Do not check on public computers)" | ||
306 | msgstr "(non selezionare su computer pubblici)" | ||
307 | |||
308 | msgid "Sign in" | ||
309 | msgstr "Accedi" | ||
310 | |||
311 | msgid "favorites" | ||
312 | msgstr "preferiti" | ||
313 | |||
314 | msgid "estimated reading time :" | ||
315 | msgstr "" | ||
316 | |||
317 | msgid "Mark all the entries as read" | ||
318 | msgstr "" | ||
319 | |||
320 | msgid "Return home" | ||
321 | msgstr "" | ||
322 | |||
323 | #, fuzzy | ||
324 | msgid "Back to top" | ||
325 | msgstr "torna a inizio pagina" | ||
326 | |||
327 | #, fuzzy | ||
328 | msgid "Mark as read" | ||
329 | msgstr "segna come letto / non letto" | ||
330 | |||
331 | #, fuzzy | ||
332 | msgid "Favorite" | ||
333 | msgstr "preferiti" | ||
334 | |||
335 | #, fuzzy | ||
336 | msgid "Toggle favorite" | ||
337 | msgstr "segna come preferito" | ||
338 | |||
339 | #, fuzzy | ||
340 | msgid "Delete" | ||
341 | msgstr "elimina" | ||
342 | |||
343 | #, fuzzy | ||
344 | msgid "Tweet" | ||
175 | msgstr "twitta" | 345 | msgstr "twitta" |
176 | 346 | ||
177 | msgid "email" | 347 | #, fuzzy |
348 | msgid "Email" | ||
178 | msgstr "email" | 349 | msgstr "email" |
179 | 350 | ||
180 | msgid "shaarli" | 351 | msgid "shaarli" |
@@ -183,26 +354,24 @@ msgstr "shaarli" | |||
183 | msgid "flattr" | 354 | msgid "flattr" |
184 | msgstr "flattr" | 355 | msgstr "flattr" |
185 | 356 | ||
186 | msgid "this article appears wrong?" | 357 | #, fuzzy |
358 | msgid "Does this article appear wrong?" | ||
187 | msgstr "articolo non visualizzato correttamente?" | 359 | msgstr "articolo non visualizzato correttamente?" |
188 | 360 | ||
189 | msgid "create an issue" | 361 | msgid "tags:" |
190 | msgstr "crea una segnalazione" | 362 | msgstr "" |
191 | |||
192 | msgid "or" | ||
193 | msgstr "oppure" | ||
194 | 363 | ||
195 | msgid "contact us by mail" | 364 | msgid "Edit tags" |
196 | msgstr "contattaci via email" | 365 | msgstr "" |
197 | 366 | ||
198 | msgid "plop" | 367 | msgid "save link!" |
199 | msgstr "plop" | 368 | msgstr "" |
200 | 369 | ||
201 | msgid "home" | 370 | msgid "home" |
202 | msgstr "home" | 371 | msgstr "home" |
203 | 372 | ||
204 | msgid "favorites" | 373 | msgid "tags" |
205 | msgstr "preferiti" | 374 | msgstr "" |
206 | 375 | ||
207 | msgid "logout" | 376 | msgid "logout" |
208 | msgstr "esci" | 377 | msgstr "esci" |
@@ -213,25 +382,187 @@ msgstr "realizzato con" | |||
213 | msgid "debug mode is on so cache is off." | 382 | msgid "debug mode is on so cache is off." |
214 | msgstr "modalità di debug attiva, cache disattivata." | 383 | msgstr "modalità di debug attiva, cache disattivata." |
215 | 384 | ||
216 | msgid "your poche version:" | 385 | #, fuzzy |
217 | msgstr "la tua versione di poche:" | 386 | msgid "your wallabag version:" |
387 | msgstr "la tua versione" | ||
218 | 388 | ||
219 | msgid "storage:" | 389 | msgid "storage:" |
220 | msgstr "memoria:" | 390 | msgstr "memoria:" |
221 | 391 | ||
222 | msgid "login to your poche" | 392 | msgid "save a link" |
223 | msgstr "accedi al tuo poche" | 393 | msgstr "" |
224 | 394 | ||
225 | msgid "you are in demo mode, some features may be disabled." | 395 | msgid "back to home" |
396 | msgstr "torna alla home" | ||
397 | |||
398 | msgid "toggle mark as read" | ||
399 | msgstr "segna come letto / non letto" | ||
400 | |||
401 | msgid "tweet" | ||
402 | msgstr "twitta" | ||
403 | |||
404 | msgid "email" | ||
405 | msgstr "email" | ||
406 | |||
407 | msgid "this article appears wrong?" | ||
408 | msgstr "articolo non visualizzato correttamente?" | ||
409 | |||
410 | msgid "No link available here!" | ||
411 | msgstr "Nessun link disponibile!" | ||
412 | |||
413 | msgid "Poching a link" | ||
414 | msgstr "Pochare un link" | ||
415 | |||
416 | msgid "by filling this field" | ||
417 | msgstr "compilando questo campo" | ||
418 | |||
419 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
226 | msgstr "" | 420 | msgstr "" |
227 | "sei in modalità dimostrazione, alcune funzionalità potrebbero essere " | ||
228 | "disattivate." | ||
229 | 421 | ||
230 | msgid "Stay signed in" | 422 | msgid "your version" |
231 | msgstr "Resta connesso" | 423 | msgstr "la tua versione" |
232 | 424 | ||
233 | msgid "(Do not check on public computers)" | 425 | msgid "latest stable version" |
234 | msgstr "(non selezionare su computer pubblici)" | 426 | msgstr "ultima versione stabile" |
235 | 427 | ||
236 | msgid "Sign in" | 428 | msgid "a more recent stable version is available." |
237 | msgstr "Accedi" | 429 | msgstr "è disponibile una versione stabile più recente." |
430 | |||
431 | msgid "you are up to date." | ||
432 | msgstr "sei aggiornato." | ||
433 | |||
434 | msgid "latest dev version" | ||
435 | msgstr "ultima versione di sviluppo" | ||
436 | |||
437 | msgid "a more recent development version is available." | ||
438 | msgstr "è disponibile una versione di sviluppo più recente." | ||
439 | |||
440 | msgid "Please execute the import script locally, it can take a very long time." | ||
441 | msgstr "Si prega di eseguire lo script di importazione a livello locale, può richiedere un tempo molto lungo." | ||
442 | |||
443 | #, fuzzy | ||
444 | msgid "More infos in the official doc:" | ||
445 | msgstr "Maggiori info nella documentazione ufficiale" | ||
446 | |||
447 | msgid "import from Pocket" | ||
448 | msgstr "Importa da Pocket" | ||
449 | |||
450 | msgid "import from Readability" | ||
451 | msgstr "Importa da Readability" | ||
452 | |||
453 | msgid "import from Instapaper" | ||
454 | msgstr "Importa da Instapaper" | ||
455 | |||
456 | msgid "Tags" | ||
457 | msgstr "" | ||
458 | |||
459 | #, fuzzy | ||
460 | msgid "Untitled" | ||
461 | msgstr "per titolo" | ||
462 | |||
463 | msgid "the link has been added successfully" | ||
464 | msgstr "" | ||
465 | |||
466 | msgid "error during insertion : the link wasn't added" | ||
467 | msgstr "" | ||
468 | |||
469 | msgid "the link has been deleted successfully" | ||
470 | msgstr "" | ||
471 | |||
472 | msgid "the link wasn't deleted" | ||
473 | msgstr "" | ||
474 | |||
475 | msgid "Article not found!" | ||
476 | msgstr "" | ||
477 | |||
478 | msgid "previous" | ||
479 | msgstr "" | ||
480 | |||
481 | msgid "next" | ||
482 | msgstr "" | ||
483 | |||
484 | msgid "in demo mode, you can't update your password" | ||
485 | msgstr "" | ||
486 | |||
487 | msgid "your password has been updated" | ||
488 | msgstr "" | ||
489 | |||
490 | msgid "the two fields have to be filled & the password must be the same in the two fields" | ||
491 | msgstr "" | ||
492 | |||
493 | msgid "still using the \"" | ||
494 | msgstr "" | ||
495 | |||
496 | msgid "that theme does not seem to be installed" | ||
497 | msgstr "" | ||
498 | |||
499 | msgid "you have changed your theme preferences" | ||
500 | msgstr "" | ||
501 | |||
502 | msgid "that language does not seem to be installed" | ||
503 | msgstr "" | ||
504 | |||
505 | msgid "you have changed your language preferences" | ||
506 | msgstr "" | ||
507 | |||
508 | msgid "login failed: you have to fill all fields" | ||
509 | msgstr "" | ||
510 | |||
511 | msgid "welcome to your wallabag" | ||
512 | msgstr "" | ||
513 | |||
514 | msgid "login failed: bad login or password" | ||
515 | msgstr "" | ||
516 | |||
517 | #, fuzzy | ||
518 | msgid "import from instapaper completed" | ||
519 | msgstr "Importa da Instapaper" | ||
520 | |||
521 | #, fuzzy | ||
522 | msgid "import from pocket completed" | ||
523 | msgstr "Importa da Pocket" | ||
524 | |||
525 | #, fuzzy | ||
526 | msgid "import from Readability completed. " | ||
527 | msgstr "Importa da Readability" | ||
528 | |||
529 | #, fuzzy | ||
530 | msgid "import from Poche completed. " | ||
531 | msgstr "Importa da Pocket" | ||
532 | |||
533 | msgid "Unknown import provider." | ||
534 | msgstr "" | ||
535 | |||
536 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
537 | msgstr "" | ||
538 | |||
539 | msgid "Could not find required \"" | ||
540 | msgstr "" | ||
541 | |||
542 | msgid "Uh, there is a problem while generating feeds." | ||
543 | msgstr "" | ||
544 | |||
545 | #, fuzzy | ||
546 | msgid "Cache deleted." | ||
547 | msgstr "elimina" | ||
548 | |||
549 | msgid "Oops, it seems you don't have PHP 5." | ||
550 | msgstr "" | ||
551 | |||
552 | #~ msgid "poche it!" | ||
553 | #~ msgstr "pochalo!" | ||
554 | |||
555 | #~ msgid "Updating poche" | ||
556 | #~ msgstr "Aggiornamento poche" | ||
557 | |||
558 | #~ msgid "create an issue" | ||
559 | #~ msgstr "crea una segnalazione" | ||
560 | |||
561 | #~ msgid "or" | ||
562 | #~ msgstr "oppure" | ||
563 | |||
564 | #~ msgid "contact us by mail" | ||
565 | #~ msgstr "contattaci via email" | ||
566 | |||
567 | #~ msgid "your poche version:" | ||
568 | #~ msgstr "la tua versione di poche:" | ||
diff --git a/locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.mo b/locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.mo index 297516c0..297516c0 100755..100644 --- a/locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.mo +++ b/locale/pl_PL.utf8/LC_MESSAGES/pl_PL.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/pt_BR.utf8/LC_MESSAGES/pt_BR.utf8.mo b/locale/pt_BR.utf8/LC_MESSAGES/pt_BR.utf8.mo new file mode 100644 index 00000000..c1ce317f --- /dev/null +++ b/locale/pt_BR.utf8/LC_MESSAGES/pt_BR.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/pt_BR.utf8/LC_MESSAGES/pt_BR.utf8.po b/locale/pt_BR.utf8/LC_MESSAGES/pt_BR.utf8.po new file mode 100644 index 00000000..e1453922 --- /dev/null +++ b/locale/pt_BR.utf8/LC_MESSAGES/pt_BR.utf8.po | |||
@@ -0,0 +1,549 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Project-Id-Version: wallabag\n" | ||
4 | "Report-Msgid-Bugs-To: \n" | ||
5 | "POT-Creation-Date: 2014-02-25 15:17+0300\n" | ||
6 | "PO-Revision-Date: \n" | ||
7 | "Last-Translator: @iancamporez <ian@camporez.com>\n" | ||
8 | "Language-Team: @iancamporez <ian@camporez.com>\n" | ||
9 | "Language: pt_BR\n" | ||
10 | "MIME-Version: 1.0\n" | ||
11 | "Content-Type: text/plain; charset=UTF-8\n" | ||
12 | "Content-Transfer-Encoding: 8bit\n" | ||
13 | "X-Generator: Poedit 1.6.4\n" | ||
14 | "X-Poedit-Basepath: .\n" | ||
15 | "X-Poedit-SearchPath-0: /home/ian/Projetos/wallabag/locale/en_EN.utf8/" | ||
16 | "LC_MESSAGES\n" | ||
17 | |||
18 | msgid "wallabag, a read it later open source system" | ||
19 | msgstr "wallabag, um \"read it later\" de código aberto" | ||
20 | |||
21 | msgid "login failed: user doesn't exist" | ||
22 | msgstr "falha ao entrar: o usuário não existe" | ||
23 | |||
24 | msgid "return home" | ||
25 | msgstr "retornar à página inicial" | ||
26 | |||
27 | msgid "config" | ||
28 | msgstr "configurar" | ||
29 | |||
30 | msgid "Saving articles" | ||
31 | msgstr "Salvando artigos" | ||
32 | |||
33 | msgid "There are several ways to save an article:" | ||
34 | msgstr "Existem várias maneiras de salvar um artigo:" | ||
35 | |||
36 | msgid "read the documentation" | ||
37 | msgstr "ler a documentação" | ||
38 | |||
39 | msgid "download the extension" | ||
40 | msgstr "baixar a extensão" | ||
41 | |||
42 | msgid "via F-Droid" | ||
43 | msgstr "via F-Droid" | ||
44 | |||
45 | msgid " or " | ||
46 | msgstr "ou " | ||
47 | |||
48 | msgid "via Google Play" | ||
49 | msgstr "via Google Play" | ||
50 | |||
51 | msgid "download the application" | ||
52 | msgstr "baixar o aplicativo" | ||
53 | |||
54 | msgid "By filling this field" | ||
55 | msgstr "Preenchendo esse campo" | ||
56 | |||
57 | msgid "bag it!" | ||
58 | msgstr "adicionar ao wallabag" | ||
59 | |||
60 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" | ||
61 | msgstr "Bookmarklet: arraste esse link para sua barra de favoritos" | ||
62 | |||
63 | msgid "Upgrading wallabag" | ||
64 | msgstr "Atualizando o wallabag" | ||
65 | |||
66 | msgid "Installed version" | ||
67 | msgstr "Versão instalada" | ||
68 | |||
69 | msgid "Latest stable version" | ||
70 | msgstr "Última versão estável" | ||
71 | |||
72 | msgid "A more recent stable version is available." | ||
73 | msgstr "Uma versão estável mais nova está disponível" | ||
74 | |||
75 | msgid "You are up to date." | ||
76 | msgstr "Você está atualizado." | ||
77 | |||
78 | msgid "Latest dev version" | ||
79 | msgstr "Última versão em desenvolvimento" | ||
80 | |||
81 | msgid "A more recent development version is available." | ||
82 | msgstr "Uma versão em desenvolvimento mais recente está disponível" | ||
83 | |||
84 | msgid "Feeds" | ||
85 | msgstr "Feeds" | ||
86 | |||
87 | msgid "" | ||
88 | "Your feed token is currently empty and must first be generated to enable " | ||
89 | "feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
90 | msgstr "" | ||
91 | "Seu token do feed e precisa ser gerado para ativar os feeds. Clique <a " | ||
92 | "href='?feed&action=generate'>aqui para gerar um token</a>." | ||
93 | |||
94 | msgid "Unread feed" | ||
95 | msgstr "Feed de artigos não lidos" | ||
96 | |||
97 | msgid "Favorites feed" | ||
98 | msgstr "Feed dos favoritos" | ||
99 | |||
100 | msgid "Archive feed" | ||
101 | msgstr "Feed de arquivados" | ||
102 | |||
103 | msgid "Your token:" | ||
104 | msgstr "Seu token:" | ||
105 | |||
106 | msgid "Your user id:" | ||
107 | msgstr "Seu código de usuário:" | ||
108 | |||
109 | msgid "" | ||
110 | "You can regenerate your token: <a href='?feed&action=generate'>generate!" | ||
111 | "</a>." | ||
112 | msgstr "" | ||
113 | "Você pode regerar seu token: <a href='?feed&action=generate'>gerar!</a>." | ||
114 | |||
115 | msgid "Change your theme" | ||
116 | msgstr "Mudar seu tema" | ||
117 | |||
118 | msgid "Theme:" | ||
119 | msgstr "Tema:" | ||
120 | |||
121 | msgid "Update" | ||
122 | msgstr "Atualizar" | ||
123 | |||
124 | msgid "Change your language" | ||
125 | msgstr "Alterar seu idioma" | ||
126 | |||
127 | msgid "Language:" | ||
128 | msgstr "Idioma:" | ||
129 | |||
130 | msgid "Change your password" | ||
131 | msgstr "Alterar sua senha" | ||
132 | |||
133 | msgid "New password:" | ||
134 | msgstr "Nova senha:" | ||
135 | |||
136 | msgid "Password" | ||
137 | msgstr "Senha" | ||
138 | |||
139 | msgid "Repeat your new password:" | ||
140 | msgstr "Repetir sua nova senha:" | ||
141 | |||
142 | msgid "Import" | ||
143 | msgstr "Importar" | ||
144 | |||
145 | msgid "" | ||
146 | "Please execute the import script locally as it can take a very long time." | ||
147 | msgstr "" | ||
148 | "Por favor, execute o script de importação localmente, pois pode demorar " | ||
149 | "muito tempo." | ||
150 | |||
151 | msgid "More info in the official documentation:" | ||
152 | msgstr "Mais informações na documentação oficial:" | ||
153 | |||
154 | msgid "Import from Pocket" | ||
155 | msgstr "Importar do Pocket" | ||
156 | |||
157 | #, php-format | ||
158 | msgid "(you must have a %s file on your server)" | ||
159 | msgstr "(você deve ter um arquivo %s no seu servidor)" | ||
160 | |||
161 | msgid "Import from Readability" | ||
162 | msgstr "Importar do Readability" | ||
163 | |||
164 | msgid "Import from Instapaper" | ||
165 | msgstr "Importar do Instapaper" | ||
166 | |||
167 | msgid "Import from wallabag" | ||
168 | msgstr "Importar do wallabag" | ||
169 | |||
170 | msgid "Export your wallabag data" | ||
171 | msgstr "Exportar seus dados do wallabag" | ||
172 | |||
173 | msgid "Click here" | ||
174 | msgstr "Clique aqui" | ||
175 | |||
176 | msgid "to download your database." | ||
177 | msgstr "para baixar seu banco de dados." | ||
178 | |||
179 | msgid "to export your wallabag data." | ||
180 | msgstr "para exportar seus dados do wallabag." | ||
181 | |||
182 | msgid "Cache" | ||
183 | msgstr "Cache" | ||
184 | |||
185 | msgid "to delete cache." | ||
186 | msgstr "para apagar o cache." | ||
187 | |||
188 | msgid "You can enter multiple tags, separated by commas." | ||
189 | msgstr "Você pode inserir várias tags, separadas por vírgulas." | ||
190 | |||
191 | msgid "return to article" | ||
192 | msgstr "retornar ao artigo" | ||
193 | |||
194 | msgid "plop" | ||
195 | msgstr "plop" | ||
196 | |||
197 | msgid "" | ||
198 | "You can <a href='wallabag_compatibility_test.php'>check your configuration " | ||
199 | "here</a>." | ||
200 | msgstr "" | ||
201 | "Você pode <a href='wallabag_compatibility_test.php'>checar suas " | ||
202 | "configurações aqui</a>." | ||
203 | |||
204 | msgid "favoris" | ||
205 | msgstr "favoritos" | ||
206 | |||
207 | msgid "archive" | ||
208 | msgstr "arquivo" | ||
209 | |||
210 | msgid "unread" | ||
211 | msgstr "não lidos" | ||
212 | |||
213 | msgid "by date asc" | ||
214 | msgstr "por data (cresc.)" | ||
215 | |||
216 | msgid "by date" | ||
217 | msgstr "por data" | ||
218 | |||
219 | msgid "by date desc" | ||
220 | msgstr "por data (decr.)" | ||
221 | |||
222 | msgid "by title asc" | ||
223 | msgstr "por título (cresc.)" | ||
224 | |||
225 | msgid "by title" | ||
226 | msgstr "por título" | ||
227 | |||
228 | msgid "by title desc" | ||
229 | msgstr "por título (decr.)" | ||
230 | |||
231 | msgid "Tag" | ||
232 | msgstr "Tag" | ||
233 | |||
234 | msgid "No articles found." | ||
235 | msgstr "Nenhum artigo encontrado." | ||
236 | |||
237 | msgid "Toggle mark as read" | ||
238 | msgstr "Alterar \"marcar como lido\"" | ||
239 | |||
240 | msgid "toggle favorite" | ||
241 | msgstr "alterar \"favorito\"" | ||
242 | |||
243 | msgid "delete" | ||
244 | msgstr "deletar" | ||
245 | |||
246 | msgid "original" | ||
247 | msgstr "original" | ||
248 | |||
249 | msgid "estimated reading time:" | ||
250 | msgstr "tempo estimado de leitura:" | ||
251 | |||
252 | msgid "mark all the entries as read" | ||
253 | msgstr "marcar todas as entradas como lidas" | ||
254 | |||
255 | msgid "results" | ||
256 | msgstr "resultados" | ||
257 | |||
258 | msgid "installation" | ||
259 | msgstr "instalação" | ||
260 | |||
261 | msgid "install your wallabag" | ||
262 | msgstr "instalar seu wallabag" | ||
263 | |||
264 | msgid "" | ||
265 | "wallabag is still not installed. Please fill the below form to install it. " | ||
266 | "Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation " | ||
267 | "on wallabag website</a>." | ||
268 | msgstr "" | ||
269 | "O wallabag ainda não está instalado. Preencha o formulário abaixo para " | ||
270 | "instalá-lo. Não hesite em <a href='http://doc.wallabag.org/'>ler a " | ||
271 | "documentação no site do wallabag</a>." | ||
272 | |||
273 | msgid "Login" | ||
274 | msgstr "Login" | ||
275 | |||
276 | msgid "Repeat your password" | ||
277 | msgstr "Repetir sua senha" | ||
278 | |||
279 | msgid "Install" | ||
280 | msgstr "Instalar" | ||
281 | |||
282 | msgid "login to your wallabag" | ||
283 | msgstr "entrar no seu wallabag" | ||
284 | |||
285 | msgid "Login to wallabag" | ||
286 | msgstr "Entrar no wallabag" | ||
287 | |||
288 | msgid "you are in demo mode, some features may be disabled." | ||
289 | msgstr "você está no modo demo, alguns recursos podem estar desativados." | ||
290 | |||
291 | msgid "Username" | ||
292 | msgstr "Nome de usuário" | ||
293 | |||
294 | msgid "Stay signed in" | ||
295 | msgstr "Continuar conectado" | ||
296 | |||
297 | msgid "(Do not check on public computers)" | ||
298 | msgstr "(Não marque em computadores públicos)" | ||
299 | |||
300 | msgid "Sign in" | ||
301 | msgstr "Entrar" | ||
302 | |||
303 | msgid "favorites" | ||
304 | msgstr "favoritos" | ||
305 | |||
306 | msgid "estimated reading time :" | ||
307 | msgstr "tempo estimado de leitura :" | ||
308 | |||
309 | msgid "Mark all the entries as read" | ||
310 | msgstr "Marcar todas as entradas como lidas" | ||
311 | |||
312 | msgid "Return home" | ||
313 | msgstr "Retornar à página inicial" | ||
314 | |||
315 | msgid "Back to top" | ||
316 | msgstr "Voltar ao topo" | ||
317 | |||
318 | msgid "Mark as read" | ||
319 | msgstr "Marcar como lido" | ||
320 | |||
321 | msgid "Favorite" | ||
322 | msgstr "Favoritar" | ||
323 | |||
324 | msgid "Toggle favorite" | ||
325 | msgstr "Alterar \"favorito\"" | ||
326 | |||
327 | msgid "Delete" | ||
328 | msgstr "Deletar" | ||
329 | |||
330 | msgid "Tweet" | ||
331 | msgstr "Tweetar" | ||
332 | |||
333 | msgid "Email" | ||
334 | msgstr "Email" | ||
335 | |||
336 | msgid "shaarli" | ||
337 | msgstr "shaarli" | ||
338 | |||
339 | msgid "flattr" | ||
340 | msgstr "flattr" | ||
341 | |||
342 | msgid "Does this article appear wrong?" | ||
343 | msgstr "Esse artigo está sendo exibido incorretamente?" | ||
344 | |||
345 | msgid "tags:" | ||
346 | msgstr "tags:" | ||
347 | |||
348 | msgid "Edit tags" | ||
349 | msgstr "Editar tags" | ||
350 | |||
351 | msgid "save link!" | ||
352 | msgstr "salvar link!" | ||
353 | |||
354 | msgid "home" | ||
355 | msgstr "início" | ||
356 | |||
357 | msgid "tags" | ||
358 | msgstr "tags" | ||
359 | |||
360 | msgid "logout" | ||
361 | msgstr "sair" | ||
362 | |||
363 | msgid "powered by" | ||
364 | msgstr "powered by" | ||
365 | |||
366 | msgid "debug mode is on so cache is off." | ||
367 | msgstr "o modo debug está ativo, então o cache foi desativado." | ||
368 | |||
369 | msgid "your wallabag version:" | ||
370 | msgstr "sua versão do wallabag:" | ||
371 | |||
372 | msgid "storage:" | ||
373 | msgstr "armazenamento:" | ||
374 | |||
375 | msgid "save a link" | ||
376 | msgstr "salvar link" | ||
377 | |||
378 | msgid "back to home" | ||
379 | msgstr "voltar à página inicial" | ||
380 | |||
381 | msgid "toggle mark as read" | ||
382 | msgstr "alterar \"marcar como lido\"" | ||
383 | |||
384 | msgid "tweet" | ||
385 | msgstr "tweetar" | ||
386 | |||
387 | msgid "email" | ||
388 | msgstr "email" | ||
389 | |||
390 | msgid "this article appears wrong?" | ||
391 | msgstr "esse artigo está sendo mostrado incorretamente?" | ||
392 | |||
393 | msgid "No link available here!" | ||
394 | msgstr "Nenhum link disponível aqui!" | ||
395 | |||
396 | msgid "Poching a link" | ||
397 | msgstr "Pocheando um link" | ||
398 | |||
399 | msgid "by filling this field" | ||
400 | msgstr "preenchendo esse campo" | ||
401 | |||
402 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
403 | msgstr "bookmarklet: arraste esse link para a sua barra de favoritos" | ||
404 | |||
405 | msgid "your version" | ||
406 | msgstr "sua versão" | ||
407 | |||
408 | msgid "latest stable version" | ||
409 | msgstr "última versão estável" | ||
410 | |||
411 | msgid "a more recent stable version is available." | ||
412 | msgstr "uma versão estável mais nova está disponível" | ||
413 | |||
414 | msgid "you are up to date." | ||
415 | msgstr "você está atualizado." | ||
416 | |||
417 | msgid "latest dev version" | ||
418 | msgstr "última versão em desenvolvimento" | ||
419 | |||
420 | msgid "a more recent development version is available." | ||
421 | msgstr "uma versão em desenvolvimento mais nova está disponível" | ||
422 | |||
423 | msgid "Please execute the import script locally, it can take a very long time." | ||
424 | msgstr "" | ||
425 | "Por favor, execute o script de importação localmente, pois pode demorar " | ||
426 | "muito tempo." | ||
427 | |||
428 | msgid "More infos in the official doc:" | ||
429 | msgstr "Mais informações na documentação oficial:" | ||
430 | |||
431 | msgid "import from Pocket" | ||
432 | msgstr "importar do Pocket" | ||
433 | |||
434 | msgid "import from Readability" | ||
435 | msgstr "importar do Readability" | ||
436 | |||
437 | msgid "import from Instapaper" | ||
438 | msgstr "importar do Instapaper" | ||
439 | |||
440 | msgid "Tags" | ||
441 | msgstr "Tags" | ||
442 | |||
443 | msgid "Untitled" | ||
444 | msgstr "Sem título" | ||
445 | |||
446 | msgid "the link has been added successfully" | ||
447 | msgstr "o link foi adicionado com sucesso" | ||
448 | |||
449 | msgid "error during insertion : the link wasn't added" | ||
450 | msgstr "erro durante a inserção: o link não foi adicionado" | ||
451 | |||
452 | msgid "the link has been deleted successfully" | ||
453 | msgstr "o link foi deletado com sucesso" | ||
454 | |||
455 | msgid "the link wasn't deleted" | ||
456 | msgstr "o link não foi deletado" | ||
457 | |||
458 | msgid "Article not found!" | ||
459 | msgstr "Artigo não encontrado!" | ||
460 | |||
461 | msgid "previous" | ||
462 | msgstr "anterior" | ||
463 | |||
464 | msgid "next" | ||
465 | msgstr "próximo" | ||
466 | |||
467 | msgid "in demo mode, you can't update your password" | ||
468 | msgstr "você não pode alterar a senha no modo demo" | ||
469 | |||
470 | msgid "your password has been updated" | ||
471 | msgstr "sua senha foi atualizada" | ||
472 | |||
473 | msgid "" | ||
474 | "the two fields have to be filled & the password must be the same in the two " | ||
475 | "fields" | ||
476 | msgstr "" | ||
477 | "os dois campos devem estar preenchidos e as senhas devem ser iguais em ambos" | ||
478 | |||
479 | msgid "still using the \"" | ||
480 | msgstr "ainda usando o \"" | ||
481 | |||
482 | msgid "that theme does not seem to be installed" | ||
483 | msgstr "esse tema aparentemente não está instalado" | ||
484 | |||
485 | msgid "you have changed your theme preferences" | ||
486 | msgstr "você alterou suas preferências de tema" | ||
487 | |||
488 | msgid "that language does not seem to be installed" | ||
489 | msgstr "esse idioma aparentemente não está instalado" | ||
490 | |||
491 | msgid "you have changed your language preferences" | ||
492 | msgstr "você alterou suas preferências de idioma" | ||
493 | |||
494 | msgid "login failed: you have to fill all fields" | ||
495 | msgstr "falha ao entrar: você deve preencher todos os campos" | ||
496 | |||
497 | msgid "welcome to your wallabag" | ||
498 | msgstr "bem-vindo ao seu wallabag" | ||
499 | |||
500 | msgid "login failed: bad login or password" | ||
501 | msgstr "falha ao entrar: login ou senha incorretos" | ||
502 | |||
503 | msgid "import from instapaper completed" | ||
504 | msgstr "importação do instapaper completa" | ||
505 | |||
506 | msgid "import from pocket completed" | ||
507 | msgstr "importação do pocket completa" | ||
508 | |||
509 | msgid "import from Readability completed. " | ||
510 | msgstr "importação do Readability completa. " | ||
511 | |||
512 | msgid "import from Poche completed. " | ||
513 | msgstr "importação do Poche completa. " | ||
514 | |||
515 | msgid "Unknown import provider." | ||
516 | msgstr "Serviço de importação desconhecido." | ||
517 | |||
518 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
519 | msgstr "Arquivo inc/poche/define.inc.php incompleto, por favor defina \"" | ||
520 | |||
521 | msgid "Could not find required \"" | ||
522 | msgstr "Não foi possível encontrar o requerido \"" | ||
523 | |||
524 | msgid "Uh, there is a problem while generating feeds." | ||
525 | msgstr "Uh, houve um problema ao gerar os feeds." | ||
526 | |||
527 | msgid "Cache deleted." | ||
528 | msgstr "Cache deletado." | ||
529 | |||
530 | msgid "Oops, it seems you don't have PHP 5." | ||
531 | msgstr "Oops, parece que você não tem o PHP 5." | ||
532 | |||
533 | #~ msgid "poche it!" | ||
534 | #~ msgstr "poche it!" | ||
535 | |||
536 | #~ msgid "Updating poche" | ||
537 | #~ msgstr "Updating poche" | ||
538 | |||
539 | #~ msgid "create an issue" | ||
540 | #~ msgstr "create an issue" | ||
541 | |||
542 | #~ msgid "or" | ||
543 | #~ msgstr "or" | ||
544 | |||
545 | #~ msgid "contact us by mail" | ||
546 | #~ msgstr "contact us by mail" | ||
547 | |||
548 | #~ msgid "your poche version:" | ||
549 | #~ msgstr "your poche version:" | ||
diff --git a/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.mo b/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.mo index c9cf1a55..5cbfad16 100755 --- a/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.mo +++ b/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.po b/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.po index aa1769cf..08f12b7c 100755 --- a/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.po +++ b/locale/ru_RU.utf8/LC_MESSAGES/ru_RU.utf8.po | |||
@@ -2,7 +2,7 @@ msgid "" | |||
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: \n" | 3 | "Project-Id-Version: \n" |
4 | "Report-Msgid-Bugs-To: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-02-07 12:40+0300\n" | 5 | "POT-Creation-Date: 2014-02-25 15:09+0300\n" |
6 | "PO-Revision-Date: \n" | 6 | "PO-Revision-Date: \n" |
7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" | 7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
8 | "Language-Team: \n" | 8 | "Language-Team: \n" |
@@ -14,135 +14,25 @@ msgstr "" | |||
14 | "X-Poedit-Language: Russian\n" | 14 | "X-Poedit-Language: Russian\n" |
15 | "X-Poedit-Country: RUSSIA\n" | 15 | "X-Poedit-Country: RUSSIA\n" |
16 | "X-Poedit-SourceCharset: utf-8\n" | 16 | "X-Poedit-SourceCharset: utf-8\n" |
17 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag\n" | 17 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" |
18 | 18 | ||
19 | msgid "poche, a read it later open source system" | 19 | msgid "wallabag, a read it later open source system" |
20 | msgstr "poche, сервис отложенного чтения с открытым исходным кодом" | 20 | msgstr "wallabag, сервис отложенного чтения с открытым исходным кодом" |
21 | 21 | ||
22 | msgid "login failed: user doesn't exist" | 22 | msgid "login failed: user doesn't exist" |
23 | msgstr "войти не удалось: пользователь не существует" | 23 | msgstr "войти не удалось: пользователь не существует" |
24 | 24 | ||
25 | msgid "Return home" | ||
26 | msgstr "На главную" | ||
27 | |||
28 | msgid "Back to top" | ||
29 | msgstr "Наверх" | ||
30 | |||
31 | msgid "original" | ||
32 | msgstr "источник" | ||
33 | |||
34 | msgid "Mark as read" | ||
35 | msgstr "Отметить как прочитанное" | ||
36 | |||
37 | msgid "Toggle mark as read" | ||
38 | msgstr "Изменить отметку 'прочитано'" | ||
39 | |||
40 | msgid "Favorite" | ||
41 | msgstr "Избранное" | ||
42 | |||
43 | msgid "Toggle favorite" | ||
44 | msgstr "Изменить метку избранного" | ||
45 | |||
46 | msgid "Delete" | ||
47 | msgstr "Удалить" | ||
48 | |||
49 | msgid "Tweet" | ||
50 | msgstr "Твитнуть" | ||
51 | |||
52 | msgid "Email" | ||
53 | msgstr "Отправить по почте" | ||
54 | |||
55 | msgid "shaarli" | ||
56 | msgstr "shaarli" | ||
57 | |||
58 | msgid "flattr" | ||
59 | msgstr "проспонсировать" | ||
60 | |||
61 | msgid "Does this article appear wrong?" | ||
62 | msgstr "Статья выглядит криво?" | ||
63 | |||
64 | msgid "tags:" | ||
65 | msgstr "теги:" | ||
66 | |||
67 | msgid "Edit tags" | ||
68 | msgstr "Редактировать теги" | ||
69 | |||
70 | msgid "return home" | 25 | msgid "return home" |
71 | msgstr "на главную" | 26 | msgstr "на главную" |
72 | 27 | ||
73 | msgid "powered by" | ||
74 | msgstr "при поддержке" | ||
75 | |||
76 | msgid "debug mode is on so cache is off." | ||
77 | msgstr "включён режим отладки - кеш выключен." | ||
78 | |||
79 | msgid "your poche version:" | ||
80 | msgstr "ваша версия poche:" | ||
81 | |||
82 | msgid "storage:" | ||
83 | msgstr "хранилище:" | ||
84 | |||
85 | msgid "favoris" | ||
86 | msgstr "избранное" | ||
87 | |||
88 | msgid "archive" | ||
89 | msgstr "архив" | ||
90 | |||
91 | msgid "unread" | ||
92 | msgstr "непрочитанное" | ||
93 | |||
94 | msgid "by date asc" | ||
95 | msgstr "по дате, сперва старые" | ||
96 | |||
97 | msgid "by date" | ||
98 | msgstr "по дате" | ||
99 | |||
100 | msgid "by date desc" | ||
101 | msgstr "по дате, сперва новые" | ||
102 | |||
103 | msgid "by title asc" | ||
104 | msgstr "по заголовку (прямой)" | ||
105 | |||
106 | msgid "by title" | ||
107 | msgstr "по заголовку" | ||
108 | |||
109 | msgid "by title desc" | ||
110 | msgstr "по заголовку (обратный)" | ||
111 | |||
112 | msgid "No articles found." | ||
113 | msgstr "Статей не найдено." | ||
114 | |||
115 | msgid "toggle favorite" | ||
116 | msgstr "изменить метку избранного" | ||
117 | |||
118 | msgid "delete" | ||
119 | msgstr "удалить" | ||
120 | |||
121 | msgid "estimated reading time:" | ||
122 | msgstr "ориентировочное время чтения:" | ||
123 | |||
124 | msgid "results" | ||
125 | msgstr "найдено" | ||
126 | |||
127 | msgid "home" | ||
128 | msgstr "главная" | ||
129 | |||
130 | msgid "favorites" | ||
131 | msgstr "избранное" | ||
132 | |||
133 | msgid "tags" | ||
134 | msgstr "теги" | ||
135 | |||
136 | msgid "config" | 28 | msgid "config" |
137 | msgstr "настройки" | 29 | msgstr "настройки" |
138 | 30 | ||
139 | msgid "logout" | 31 | msgid "Saving articles" |
140 | msgstr "выход" | 32 | msgstr "Сохранение статей" |
141 | |||
142 | msgid "Poching links" | ||
143 | msgstr "Сохранение ссылок" | ||
144 | 33 | ||
145 | msgid "There are several ways to poche a link:" | 34 | #, fuzzy |
35 | msgid "There are several ways to save an article:" | ||
146 | msgstr "Существует несколько способов сохранить ссылку:" | 36 | msgstr "Существует несколько способов сохранить ссылку:" |
147 | 37 | ||
148 | msgid "read the documentation" | 38 | msgid "read the documentation" |
@@ -166,14 +56,14 @@ msgstr "скачать приложение" | |||
166 | msgid "By filling this field" | 56 | msgid "By filling this field" |
167 | msgstr "Заполнением этого поля" | 57 | msgstr "Заполнением этого поля" |
168 | 58 | ||
169 | msgid "poche it!" | 59 | msgid "bag it!" |
170 | msgstr "прикарманить!" | 60 | msgstr "прикарманить!" |
171 | 61 | ||
172 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" | 62 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
173 | msgstr "Закладка: перетащите и опустите ссылку на панель закладок" | 63 | msgstr "Закладка: перетащите и опустите ссылку на панель закладок" |
174 | 64 | ||
175 | msgid "Updating poche" | 65 | msgid "Upgrading wallabag" |
176 | msgstr "Обновления poche" | 66 | msgstr "Обновление wallabag" |
177 | 67 | ||
178 | msgid "Installed version" | 68 | msgid "Installed version" |
179 | msgstr "Установленная версия" | 69 | msgstr "Установленная версия" |
@@ -187,15 +77,14 @@ msgstr "Доступна новая стабильная версия." | |||
187 | msgid "You are up to date." | 77 | msgid "You are up to date." |
188 | msgstr "У вас всё самое новое." | 78 | msgstr "У вас всё самое новое." |
189 | 79 | ||
190 | msgid "latest dev version" | 80 | #, fuzzy |
81 | msgid "Latest dev version" | ||
191 | msgstr "последняя версия в разработке" | 82 | msgstr "последняя версия в разработке" |
192 | 83 | ||
193 | msgid "a more recent development version is available." | 84 | #, fuzzy |
85 | msgid "A more recent development version is available." | ||
194 | msgstr "есть более свежая версия в разработке." | 86 | msgstr "есть более свежая версия в разработке." |
195 | 87 | ||
196 | msgid "you are up to date." | ||
197 | msgstr "у вас всё самое новое." | ||
198 | |||
199 | msgid "Feeds" | 88 | msgid "Feeds" |
200 | msgstr "Ленты (feeds)" | 89 | msgstr "Ленты (feeds)" |
201 | 90 | ||
@@ -253,7 +142,8 @@ msgstr "Импортировать" | |||
253 | msgid "Please execute the import script locally as it can take a very long time." | 142 | msgid "Please execute the import script locally as it can take a very long time." |
254 | msgstr "Пожалуйста, выполните сценарий импорта локально - это может занять слишком много времени." | 143 | msgstr "Пожалуйста, выполните сценарий импорта локально - это может занять слишком много времени." |
255 | 144 | ||
256 | msgid "More info in the official docs:" | 145 | #, fuzzy |
146 | msgid "More info in the official documentation:" | ||
257 | msgstr "Больше сведений в официальной документации:" | 147 | msgstr "Больше сведений в официальной документации:" |
258 | 148 | ||
259 | msgid "Import from Pocket" | 149 | msgid "Import from Pocket" |
@@ -269,11 +159,11 @@ msgstr "Импортировать из Readability" | |||
269 | msgid "Import from Instapaper" | 159 | msgid "Import from Instapaper" |
270 | msgstr "Импортировать из Instapaper" | 160 | msgstr "Импортировать из Instapaper" |
271 | 161 | ||
272 | msgid "Import from poche" | 162 | msgid "Import from wallabag" |
273 | msgstr "Импортировать из poche" | 163 | msgstr "Импортировать из wallabag" |
274 | 164 | ||
275 | msgid "Export your poche data" | 165 | msgid "Export your wallabag data" |
276 | msgstr "Экспортировать данные poche" | 166 | msgstr "Экспортировать данные wallabag" |
277 | 167 | ||
278 | msgid "Click here" | 168 | msgid "Click here" |
279 | msgstr "Кликните здесь" | 169 | msgstr "Кликните здесь" |
@@ -281,17 +171,14 @@ msgstr "Кликните здесь" | |||
281 | msgid "to download your database." | 171 | msgid "to download your database." |
282 | msgstr "чтобы скачать вашу базу данных" | 172 | msgstr "чтобы скачать вашу базу данных" |
283 | 173 | ||
284 | msgid "to export your poche data." | 174 | msgid "to export your wallabag data." |
285 | msgstr "чтобы экспортировать свои записи из poche." | 175 | msgstr "чтобы экспортировать свои записи из wallabag." |
286 | 176 | ||
287 | msgid "Tag" | 177 | msgid "Cache" |
288 | msgstr "Тег" | 178 | msgstr "Кэш" |
289 | |||
290 | msgid "No link available here!" | ||
291 | msgstr "Здесь нет ссылки!" | ||
292 | 179 | ||
293 | msgid "toggle mark as read" | 180 | msgid "to delete cache." |
294 | msgstr "измени оку 'роитно'" | 181 | msgstr "т сроить эш." |
295 | 182 | ||
296 | msgid "You can enter multiple tags, separated by commas." | 183 | msgid "You can enter multiple tags, separated by commas." |
297 | msgstr "Вы можете ввести несколько тегов, разделяя их запятой." | 184 | msgstr "Вы можете ввести несколько тегов, разделяя их запятой." |
@@ -305,6 +192,60 @@ msgstr "plop" | |||
305 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." | 192 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
306 | msgstr "Вы можете <a href='wallabag_compatibility_test.php'>проверить конфигурацию здесь</a>." | 193 | msgstr "Вы можете <a href='wallabag_compatibility_test.php'>проверить конфигурацию здесь</a>." |
307 | 194 | ||
195 | msgid "favoris" | ||
196 | msgstr "избранное" | ||
197 | |||
198 | msgid "archive" | ||
199 | msgstr "архив" | ||
200 | |||
201 | msgid "unread" | ||
202 | msgstr "непрочитанное" | ||
203 | |||
204 | msgid "by date asc" | ||
205 | msgstr "по дате, сперва старые" | ||
206 | |||
207 | msgid "by date" | ||
208 | msgstr "по дате" | ||
209 | |||
210 | msgid "by date desc" | ||
211 | msgstr "по дате, сперва новые" | ||
212 | |||
213 | msgid "by title asc" | ||
214 | msgstr "по заголовку (прямой)" | ||
215 | |||
216 | msgid "by title" | ||
217 | msgstr "по заголовку" | ||
218 | |||
219 | msgid "by title desc" | ||
220 | msgstr "по заголовку (обратный)" | ||
221 | |||
222 | msgid "Tag" | ||
223 | msgstr "Тег" | ||
224 | |||
225 | msgid "No articles found." | ||
226 | msgstr "Статей не найдено." | ||
227 | |||
228 | msgid "Toggle mark as read" | ||
229 | msgstr "Изменить отметку 'прочитано'" | ||
230 | |||
231 | msgid "toggle favorite" | ||
232 | msgstr "изменить метку избранного" | ||
233 | |||
234 | msgid "delete" | ||
235 | msgstr "удалить" | ||
236 | |||
237 | msgid "original" | ||
238 | msgstr "источник" | ||
239 | |||
240 | msgid "estimated reading time:" | ||
241 | msgstr "ориентировочное время чтения:" | ||
242 | |||
243 | msgid "mark all the entries as read" | ||
244 | msgstr "отметить все статьи как прочитанные " | ||
245 | |||
246 | msgid "results" | ||
247 | msgstr "найдено" | ||
248 | |||
308 | msgid "installation" | 249 | msgid "installation" |
309 | msgstr "установка" | 250 | msgstr "установка" |
310 | 251 | ||
@@ -341,6 +282,159 @@ msgstr "Запомнить меня" | |||
341 | msgid "(Do not check on public computers)" | 282 | msgid "(Do not check on public computers)" |
342 | msgstr "(Не отмечайте на чужих компьютерах)" | 283 | msgstr "(Не отмечайте на чужих компьютерах)" |
343 | 284 | ||
285 | msgid "Sign in" | ||
286 | msgstr "Зарегистрироваться" | ||
287 | |||
288 | msgid "favorites" | ||
289 | msgstr "избранное" | ||
290 | |||
291 | #, fuzzy | ||
292 | msgid "estimated reading time :" | ||
293 | msgstr "ориентировочное время чтения:" | ||
294 | |||
295 | msgid "Mark all the entries as read" | ||
296 | msgstr "Отметить все как прочитанное" | ||
297 | |||
298 | msgid "Return home" | ||
299 | msgstr "На главную" | ||
300 | |||
301 | msgid "Back to top" | ||
302 | msgstr "Наверх" | ||
303 | |||
304 | msgid "Mark as read" | ||
305 | msgstr "Отметить как прочитанное" | ||
306 | |||
307 | msgid "Favorite" | ||
308 | msgstr "Избранное" | ||
309 | |||
310 | msgid "Toggle favorite" | ||
311 | msgstr "Изменить метку избранного" | ||
312 | |||
313 | msgid "Delete" | ||
314 | msgstr "Удалить" | ||
315 | |||
316 | msgid "Tweet" | ||
317 | msgstr "Твитнуть" | ||
318 | |||
319 | msgid "Email" | ||
320 | msgstr "Отправить по почте" | ||
321 | |||
322 | msgid "shaarli" | ||
323 | msgstr "shaarli" | ||
324 | |||
325 | msgid "flattr" | ||
326 | msgstr "проспонсировать" | ||
327 | |||
328 | msgid "Does this article appear wrong?" | ||
329 | msgstr "Статья выглядит криво?" | ||
330 | |||
331 | msgid "tags:" | ||
332 | msgstr "теги:" | ||
333 | |||
334 | msgid "Edit tags" | ||
335 | msgstr "Редактировать теги" | ||
336 | |||
337 | msgid "save link!" | ||
338 | msgstr "сохранить ссылку!" | ||
339 | |||
340 | msgid "home" | ||
341 | msgstr "главная" | ||
342 | |||
343 | msgid "tags" | ||
344 | msgstr "теги" | ||
345 | |||
346 | msgid "logout" | ||
347 | msgstr "выход" | ||
348 | |||
349 | msgid "powered by" | ||
350 | msgstr "при поддержке" | ||
351 | |||
352 | msgid "debug mode is on so cache is off." | ||
353 | msgstr "включён режим отладки - кеш выключен." | ||
354 | |||
355 | msgid "your wallabag version:" | ||
356 | msgstr "Ваша версия wallabag:" | ||
357 | |||
358 | msgid "storage:" | ||
359 | msgstr "хранилище:" | ||
360 | |||
361 | msgid "save a link" | ||
362 | msgstr "сохранить ссылку" | ||
363 | |||
364 | msgid "back to home" | ||
365 | msgstr "домой" | ||
366 | |||
367 | msgid "toggle mark as read" | ||
368 | msgstr "изменить отметку 'прочитано'" | ||
369 | |||
370 | msgid "tweet" | ||
371 | msgstr "твитнуть" | ||
372 | |||
373 | msgid "email" | ||
374 | msgstr "email" | ||
375 | |||
376 | #, fuzzy | ||
377 | msgid "this article appears wrong?" | ||
378 | msgstr "Статья выглядит криво?" | ||
379 | |||
380 | msgid "No link available here!" | ||
381 | msgstr "Здесь нет ссылки!" | ||
382 | |||
383 | #, fuzzy | ||
384 | msgid "Poching a link" | ||
385 | msgstr "Сохранение ссылок" | ||
386 | |||
387 | #, fuzzy | ||
388 | msgid "by filling this field" | ||
389 | msgstr "Заполнением этого поля" | ||
390 | |||
391 | #, fuzzy | ||
392 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
393 | msgstr "Закладка: перетащите и опустите ссылку на панель закладок" | ||
394 | |||
395 | msgid "your version" | ||
396 | msgstr "Ваша версия" | ||
397 | |||
398 | #, fuzzy | ||
399 | msgid "latest stable version" | ||
400 | msgstr "Последняя стабильная версия" | ||
401 | |||
402 | #, fuzzy | ||
403 | msgid "a more recent stable version is available." | ||
404 | msgstr "Доступна новая стабильная версия." | ||
405 | |||
406 | msgid "you are up to date." | ||
407 | msgstr "у вас всё самое новое." | ||
408 | |||
409 | msgid "latest dev version" | ||
410 | msgstr "последняя версия в разработке" | ||
411 | |||
412 | msgid "a more recent development version is available." | ||
413 | msgstr "есть более свежая версия в разработке." | ||
414 | |||
415 | #, fuzzy | ||
416 | msgid "Please execute the import script locally, it can take a very long time." | ||
417 | msgstr "Пожалуйста, выполните сценарий импорта локально - это может занять слишком много времени." | ||
418 | |||
419 | #, fuzzy | ||
420 | msgid "More infos in the official doc:" | ||
421 | msgstr "Больше сведений в официальной документации:" | ||
422 | |||
423 | #, fuzzy | ||
424 | msgid "import from Pocket" | ||
425 | msgstr "Импортировать из Pocket" | ||
426 | |||
427 | #, fuzzy | ||
428 | msgid "import from Readability" | ||
429 | msgstr "Импортировать из Readability" | ||
430 | |||
431 | #, fuzzy | ||
432 | msgid "import from Instapaper" | ||
433 | msgstr "Импортировать из Instapaper" | ||
434 | |||
435 | msgid "Tags" | ||
436 | msgstr "Теги" | ||
437 | |||
344 | msgid "Untitled" | 438 | msgid "Untitled" |
345 | msgstr "Без названия" | 439 | msgstr "Без названия" |
346 | 440 | ||
@@ -356,6 +450,9 @@ msgstr "ссылка успешно удалена" | |||
356 | msgid "the link wasn't deleted" | 450 | msgid "the link wasn't deleted" |
357 | msgstr "ссылка не удалена" | 451 | msgstr "ссылка не удалена" |
358 | 452 | ||
453 | msgid "Article not found!" | ||
454 | msgstr "Статью не найдено." | ||
455 | |||
359 | msgid "previous" | 456 | msgid "previous" |
360 | msgstr "предыдущая" | 457 | msgstr "предыдущая" |
361 | 458 | ||
@@ -389,15 +486,12 @@ msgstr "вы изменили свои настройки языка" | |||
389 | msgid "login failed: you have to fill all fields" | 486 | msgid "login failed: you have to fill all fields" |
390 | msgstr "войти не удалось: вы должны заполнить все поля" | 487 | msgstr "войти не удалось: вы должны заполнить все поля" |
391 | 488 | ||
392 | msgid "welcome to your poche" | 489 | msgid "welcome to your wallabag" |
393 | msgstr "добро пожаловать в ваш poche" | 490 | msgstr "добро пожаловать в wallabag" |
394 | 491 | ||
395 | msgid "login failed: bad login or password" | 492 | msgid "login failed: bad login or password" |
396 | msgstr "войти не удалось: неправильное имя пользователя или пароль" | 493 | msgstr "войти не удалось: неправильное имя пользователя или пароль" |
397 | 494 | ||
398 | msgid "see you soon!" | ||
399 | msgstr "увидимся!" | ||
400 | |||
401 | msgid "import from instapaper completed" | 495 | msgid "import from instapaper completed" |
402 | msgstr "импорт из instapaper завершен" | 496 | msgstr "импорт из instapaper завершен" |
403 | 497 | ||
@@ -422,14 +516,40 @@ msgstr "Не удалось найти требуемый \"" | |||
422 | msgid "Uh, there is a problem while generating feeds." | 516 | msgid "Uh, there is a problem while generating feeds." |
423 | msgstr "Ох, возникла проблема при создании ленты." | 517 | msgstr "Ох, возникла проблема при создании ленты." |
424 | 518 | ||
519 | msgid "Cache deleted." | ||
520 | msgstr "Кэш очищен. " | ||
521 | |||
425 | msgid "Oops, it seems you don't have PHP 5." | 522 | msgid "Oops, it seems you don't have PHP 5." |
426 | msgstr "Упс, кажется у вас не установлен PHP 5." | 523 | msgstr "Упс, кажется у вас не установлен PHP 5." |
427 | 524 | ||
428 | #~ msgid "your version" | 525 | #~ msgid "You can poche a link by several methods:" |
429 | #~ msgstr "Ваша версия" | 526 | #~ msgstr "Вы можете сохранить ссылку несколькими путями:" |
527 | |||
528 | #~ msgid "poche it!" | ||
529 | #~ msgstr "прикарманить!" | ||
530 | |||
531 | #~ msgid "Updating poche" | ||
532 | #~ msgstr "Обновления poche" | ||
533 | |||
534 | #, fuzzy | ||
535 | #~ msgid "Export your poche datas" | ||
536 | #~ msgstr "Экспортировать данные poche" | ||
537 | |||
538 | #, fuzzy | ||
539 | #~ msgid "to export your poche datas." | ||
540 | #~ msgstr "чтобы экспортировать свои записи из poche." | ||
430 | 541 | ||
431 | #~ msgid "back to home" | 542 | #~ msgid "your poche version:" |
432 | #~ msgstr "домой" | 543 | #~ msgstr "ваша версия poche:" |
544 | |||
545 | #~ msgid "Import from poche" | ||
546 | #~ msgstr "Импортировать из poche" | ||
547 | |||
548 | #~ msgid "welcome to your poche" | ||
549 | #~ msgstr "добро пожаловать в ваш poche" | ||
550 | |||
551 | #~ msgid "see you soon!" | ||
552 | #~ msgstr "увидимся!" | ||
433 | 553 | ||
434 | #~ msgid "create an issue" | 554 | #~ msgid "create an issue" |
435 | #~ msgstr "оповестить об ошибке" | 555 | #~ msgstr "оповестить об ошибке" |
@@ -439,6 +559,3 @@ msgstr "Упс, кажется у вас не установлен PHP 5." | |||
439 | 559 | ||
440 | #~ msgid "contact us by mail" | 560 | #~ msgid "contact us by mail" |
441 | #~ msgstr "связаться по почте" | 561 | #~ msgstr "связаться по почте" |
442 | |||
443 | #~ msgid "Sign in" | ||
444 | #~ msgstr "Зарегистрироваться" | ||
diff --git a/locale/sl_SI.utf8/LC_MESSAGES/sl_SI.utf8.po b/locale/sl_SI.utf8/LC_MESSAGES/sl_SI.utf8.po index 6eac8e01..8ad2eded 100644 --- a/locale/sl_SI.utf8/LC_MESSAGES/sl_SI.utf8.po +++ b/locale/sl_SI.utf8/LC_MESSAGES/sl_SI.utf8.po | |||
@@ -4,55 +4,138 @@ | |||
4 | msgid "" | 4 | msgid "" |
5 | msgstr "" | 5 | msgstr "" |
6 | "Project-Id-Version: wallabag\n" | 6 | "Project-Id-Version: wallabag\n" |
7 | "POT-Creation-Date: \n" | 7 | "Report-Msgid-Bugs-To: \n" |
8 | "PO-Revision-Date: 2014-02-21 15:09+0100\n" | 8 | "POT-Creation-Date: 2014-02-25 15:12+0300\n" |
9 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 9 | "PO-Revision-Date: 2014-02-25 15:12+0300\n" |
10 | "Language-Team: Slovenian (Slovenia) (http://www.transifex.com/projects/p/" | 10 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
11 | "wallabag/language/sl_SI/)\n" | 11 | "Language-Team: Slovenian (Slovenia) (http://www.transifex.com/projects/p/wallabag/language/sl_SI/)\n" |
12 | "Language: sl_SI\n" | ||
12 | "MIME-Version: 1.0\n" | 13 | "MIME-Version: 1.0\n" |
13 | "Content-Type: text/plain; charset=UTF-8\n" | 14 | "Content-Type: text/plain; charset=UTF-8\n" |
14 | "Content-Transfer-Encoding: 8bit\n" | 15 | "Content-Transfer-Encoding: 8bit\n" |
15 | "Language: sl_SI\n" | 16 | "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" |
16 | "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" | ||
17 | "%100==4 ? 2 : 3);\n" | ||
18 | "X-Generator: Poedit 1.5.4\n" | 17 | "X-Generator: Poedit 1.5.4\n" |
18 | "X-Poedit-Language: Slovenian\n" | ||
19 | "X-Poedit-Country: SLOVENIA\n" | ||
20 | "X-Poedit-Basepath: .\n" | ||
21 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" | ||
22 | |||
23 | msgid "wallabag, a read it later open source system" | ||
24 | msgstr "" | ||
25 | |||
26 | msgid "login failed: user doesn't exist" | ||
27 | msgstr "" | ||
28 | |||
29 | msgid "return home" | ||
30 | msgstr "" | ||
19 | 31 | ||
20 | msgid "config" | 32 | msgid "config" |
21 | msgstr "nastavitve" | 33 | msgstr "nastavitve" |
22 | 34 | ||
23 | msgid "Poching a link" | 35 | msgid "Saving articles" |
24 | msgstr "Shrani povezavo" | 36 | msgstr "" |
37 | |||
38 | msgid "There are several ways to save an article:" | ||
39 | msgstr "" | ||
25 | 40 | ||
26 | msgid "read the documentation" | 41 | msgid "read the documentation" |
27 | msgstr "preberite dokumentacijo" | 42 | msgstr "preberite dokumentacijo" |
28 | 43 | ||
29 | msgid "by filling this field" | 44 | msgid "download the extension" |
45 | msgstr "" | ||
46 | |||
47 | msgid "via F-Droid" | ||
48 | msgstr "" | ||
49 | |||
50 | msgid " or " | ||
51 | msgstr "" | ||
52 | |||
53 | msgid "via Google Play" | ||
54 | msgstr "" | ||
55 | |||
56 | msgid "download the application" | ||
57 | msgstr "" | ||
58 | |||
59 | #, fuzzy | ||
60 | msgid "By filling this field" | ||
30 | msgstr "z vnosom v to polje" | 61 | msgstr "z vnosom v to polje" |
31 | 62 | ||
32 | msgid "poche it!" | 63 | msgid "bag it!" |
33 | msgstr "shrani!" | 64 | msgstr "" |
34 | 65 | ||
35 | msgid "Updating poche" | 66 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
36 | msgstr "Posodabljam Poche" | 67 | msgstr "" |
37 | 68 | ||
38 | msgid "your version" | 69 | msgid "Upgrading wallabag" |
39 | msgstr "vaša različica" | 70 | msgstr "" |
40 | 71 | ||
41 | msgid "latest stable version" | 72 | #, fuzzy |
73 | msgid "Installed version" | ||
42 | msgstr "zadnja stabilna različica" | 74 | msgstr "zadnja stabilna različica" |
43 | 75 | ||
44 | msgid "a more recent stable version is available." | 76 | #, fuzzy |
77 | msgid "Latest stable version" | ||
78 | msgstr "zadnja stabilna različica" | ||
79 | |||
80 | #, fuzzy | ||
81 | msgid "A more recent stable version is available." | ||
45 | msgstr "na voljo je nova stabilna različica." | 82 | msgstr "na voljo je nova stabilna različica." |
46 | 83 | ||
47 | msgid "you are up to date." | 84 | #, fuzzy |
85 | msgid "You are up to date." | ||
48 | msgstr "imate najnovejšo različico." | 86 | msgstr "imate najnovejšo različico." |
49 | 87 | ||
50 | msgid "latest dev version" | 88 | #, fuzzy |
89 | msgid "Latest dev version" | ||
51 | msgstr "zadnja razvojna različica" | 90 | msgstr "zadnja razvojna različica" |
52 | 91 | ||
53 | msgid "a more recent development version is available." | 92 | #, fuzzy |
93 | msgid "A more recent development version is available." | ||
54 | msgstr "na voljo je nova razvojna različica." | 94 | msgstr "na voljo je nova razvojna različica." |
55 | 95 | ||
96 | msgid "Feeds" | ||
97 | msgstr "" | ||
98 | |||
99 | msgid "Your feed token is currently empty and must first be generated to enable feeds. Click <a href='?feed&action=generate'>here to generate it</a>." | ||
100 | msgstr "" | ||
101 | |||
102 | msgid "Unread feed" | ||
103 | msgstr "" | ||
104 | |||
105 | #, fuzzy | ||
106 | msgid "Favorites feed" | ||
107 | msgstr "priljubljeni" | ||
108 | |||
109 | #, fuzzy | ||
110 | msgid "Archive feed" | ||
111 | msgstr "arhiv" | ||
112 | |||
113 | msgid "Your token:" | ||
114 | msgstr "" | ||
115 | |||
116 | msgid "Your user id:" | ||
117 | msgstr "" | ||
118 | |||
119 | msgid "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." | ||
120 | msgstr "" | ||
121 | |||
122 | #, fuzzy | ||
123 | msgid "Change your theme" | ||
124 | msgstr "Zamenjava gesla" | ||
125 | |||
126 | msgid "Theme:" | ||
127 | msgstr "" | ||
128 | |||
129 | msgid "Update" | ||
130 | msgstr "Posodobi" | ||
131 | |||
132 | #, fuzzy | ||
133 | msgid "Change your language" | ||
134 | msgstr "Zamenjava gesla" | ||
135 | |||
136 | msgid "Language:" | ||
137 | msgstr "" | ||
138 | |||
56 | msgid "Change your password" | 139 | msgid "Change your password" |
57 | msgstr "Zamenjava gesla" | 140 | msgstr "Zamenjava gesla" |
58 | 141 | ||
@@ -65,67 +148,69 @@ msgstr "Geslo" | |||
65 | msgid "Repeat your new password:" | 148 | msgid "Repeat your new password:" |
66 | msgstr "Ponovite novo geslo:" | 149 | msgstr "Ponovite novo geslo:" |
67 | 150 | ||
68 | msgid "Update" | ||
69 | msgstr "Posodobi" | ||
70 | |||
71 | msgid "Import" | 151 | msgid "Import" |
72 | msgstr "Uvozi" | 152 | msgstr "Uvozi" |
73 | 153 | ||
74 | msgid "Please execute the import script locally, it can take a very long time." | 154 | #, fuzzy |
75 | msgstr "" | 155 | msgid "Please execute the import script locally as it can take a very long time." |
76 | "Prosimo poženite skripto za uvoz lokalno, saj lahko postopek traja precej " | 156 | msgstr "Prosimo poženite skripto za uvoz lokalno, saj lahko postopek traja precej časa." |
77 | "časa." | ||
78 | 157 | ||
79 | msgid "More infos in the official doc:" | 158 | #, fuzzy |
159 | msgid "More info in the official documentation:" | ||
80 | msgstr "Več informacij v uradni dokumentaciji:" | 160 | msgstr "Več informacij v uradni dokumentaciji:" |
81 | 161 | ||
82 | msgid "import from Pocket" | 162 | #, fuzzy |
163 | msgid "Import from Pocket" | ||
83 | msgstr "Uvoz iz aplikacije Pocket" | 164 | msgstr "Uvoz iz aplikacije Pocket" |
84 | 165 | ||
85 | msgid "import from Readability" | 166 | #, php-format |
167 | msgid "(you must have a %s file on your server)" | ||
168 | msgstr "" | ||
169 | |||
170 | #, fuzzy | ||
171 | msgid "Import from Readability" | ||
86 | msgstr "Uvoz iz aplikacije Readability" | 172 | msgstr "Uvoz iz aplikacije Readability" |
87 | 173 | ||
88 | msgid "import from Instapaper" | 174 | #, fuzzy |
175 | msgid "Import from Instapaper" | ||
89 | msgstr "Uvoz iz aplikacije Instapaper" | 176 | msgstr "Uvoz iz aplikacije Instapaper" |
90 | 177 | ||
91 | msgid "Export your poche datas" | 178 | #, fuzzy |
179 | msgid "Import from wallabag" | ||
180 | msgstr "Uvoz iz aplikacije Readability" | ||
181 | |||
182 | #, fuzzy | ||
183 | msgid "Export your wallabag data" | ||
92 | msgstr "Izvoz vsebine" | 184 | msgstr "Izvoz vsebine" |
93 | 185 | ||
94 | msgid "Click here" | 186 | msgid "Click here" |
95 | msgstr "Kliknite tukaj" | 187 | msgstr "Kliknite tukaj" |
96 | 188 | ||
97 | msgid "to export your poche datas." | 189 | #, fuzzy |
190 | msgid "to download your database." | ||
98 | msgstr "za izvoz vsebine aplikacije Poche." | 191 | msgstr "za izvoz vsebine aplikacije Poche." |
99 | 192 | ||
100 | msgid "back to home" | 193 | #, fuzzy |
101 | msgstr "Nazaj domov" | 194 | msgid "to export your wallabag data." |
102 | 195 | msgstr "za izvoz vsebine aplikacije Poche." | |
103 | msgid "installation" | ||
104 | msgstr "Namestitev" | ||
105 | 196 | ||
106 | msgid "install your poche" | 197 | msgid "Cache" |
107 | msgstr "Namestitev aplikacije Poche" | 198 | msgstr "" |
108 | 199 | ||
109 | msgid "" | 200 | msgid "to delete cache." |
110 | "poche is still not installed. Please fill the below form to install it. " | ||
111 | "Don't hesitate to <a href='http://inthepoche.com/doc'>read the documentation " | ||
112 | "on poche website</a>." | ||
113 | msgstr "" | 201 | msgstr "" |
114 | "Poche še vedno ni nameščen. Za namestitev izpolnite spodnji obrazec. Za več " | ||
115 | "informacij <a href='http://inthepoche.com/doc'>preberite dokumentacijo na " | ||
116 | "spletni strani</a>." | ||
117 | 202 | ||
118 | msgid "Login" | 203 | msgid "You can enter multiple tags, separated by commas." |
119 | msgstr "Prijava" | 204 | msgstr "" |
120 | 205 | ||
121 | msgid "Repeat your password" | 206 | msgid "return to article" |
122 | msgstr "Ponovite geslo" | 207 | msgstr "" |
123 | 208 | ||
124 | msgid "Install" | 209 | msgid "plop" |
125 | msgstr "Namesti" | 210 | msgstr "štrbunk" |
126 | 211 | ||
127 | msgid "back to top" | 212 | msgid "You can <a href='wallabag_compatibility_test.php'>check your configuration here</a>." |
128 | msgstr "nazaj na vrh" | 213 | msgstr "" |
129 | 214 | ||
130 | msgid "favoris" | 215 | msgid "favoris" |
131 | msgstr "priljubljeni" | 216 | msgstr "priljubljeni" |
@@ -154,10 +239,14 @@ msgstr "po naslovu" | |||
154 | msgid "by title desc" | 239 | msgid "by title desc" |
155 | msgstr "po naslovu - padajoče" | 240 | msgstr "po naslovu - padajoče" |
156 | 241 | ||
157 | msgid "No link available here!" | 242 | msgid "Tag" |
158 | msgstr "Povezava ni na voljo!" | 243 | msgstr "" |
159 | 244 | ||
160 | msgid "toggle mark as read" | 245 | msgid "No articles found." |
246 | msgstr "" | ||
247 | |||
248 | #, fuzzy | ||
249 | msgid "Toggle mark as read" | ||
161 | msgstr "označi kot prebrano" | 250 | msgstr "označi kot prebrano" |
162 | 251 | ||
163 | msgid "toggle favorite" | 252 | msgid "toggle favorite" |
@@ -169,13 +258,95 @@ msgstr "zavrzi" | |||
169 | msgid "original" | 258 | msgid "original" |
170 | msgstr "izvirnik" | 259 | msgstr "izvirnik" |
171 | 260 | ||
261 | msgid "estimated reading time:" | ||
262 | msgstr "" | ||
263 | |||
264 | msgid "mark all the entries as read" | ||
265 | msgstr "" | ||
266 | |||
172 | msgid "results" | 267 | msgid "results" |
173 | msgstr "rezultati" | 268 | msgstr "rezultati" |
174 | 269 | ||
175 | msgid "tweet" | 270 | msgid "installation" |
271 | msgstr "Namestitev" | ||
272 | |||
273 | #, fuzzy | ||
274 | msgid "install your wallabag" | ||
275 | msgstr "Namestitev aplikacije Poche" | ||
276 | |||
277 | #, fuzzy | ||
278 | msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://doc.wallabag.org/'>read the documentation on wallabag website</a>." | ||
279 | msgstr "Poche še vedno ni nameščen. Za namestitev izpolnite spodnji obrazec. Za več informacij <a href='http://inthepoche.com/doc'>preberite dokumentacijo na spletni strani</a>." | ||
280 | |||
281 | msgid "Login" | ||
282 | msgstr "Prijava" | ||
283 | |||
284 | msgid "Repeat your password" | ||
285 | msgstr "Ponovite geslo" | ||
286 | |||
287 | msgid "Install" | ||
288 | msgstr "Namesti" | ||
289 | |||
290 | #, fuzzy | ||
291 | msgid "login to your wallabag" | ||
292 | msgstr "prijavite se v svoj Poche" | ||
293 | |||
294 | msgid "Login to wallabag" | ||
295 | msgstr "" | ||
296 | |||
297 | msgid "you are in demo mode, some features may be disabled." | ||
298 | msgstr "uporabljate vzorčno različico programa, zato so lahko nekatere funkcije izklopljene." | ||
299 | |||
300 | msgid "Username" | ||
301 | msgstr "" | ||
302 | |||
303 | msgid "Stay signed in" | ||
304 | msgstr "Ostani prijavljen" | ||
305 | |||
306 | msgid "(Do not check on public computers)" | ||
307 | msgstr "(Ne označi na javnih napravah)" | ||
308 | |||
309 | msgid "Sign in" | ||
310 | msgstr "Prijava" | ||
311 | |||
312 | msgid "favorites" | ||
313 | msgstr "priljubljeni" | ||
314 | |||
315 | msgid "estimated reading time :" | ||
316 | msgstr "" | ||
317 | |||
318 | msgid "Mark all the entries as read" | ||
319 | msgstr "" | ||
320 | |||
321 | msgid "Return home" | ||
322 | msgstr "" | ||
323 | |||
324 | #, fuzzy | ||
325 | msgid "Back to top" | ||
326 | msgstr "nazaj na vrh" | ||
327 | |||
328 | #, fuzzy | ||
329 | msgid "Mark as read" | ||
330 | msgstr "označi kot prebrano" | ||
331 | |||
332 | #, fuzzy | ||
333 | msgid "Favorite" | ||
334 | msgstr "priljubljeni" | ||
335 | |||
336 | #, fuzzy | ||
337 | msgid "Toggle favorite" | ||
338 | msgstr "označi kot priljubljeno" | ||
339 | |||
340 | #, fuzzy | ||
341 | msgid "Delete" | ||
342 | msgstr "zavrzi" | ||
343 | |||
344 | #, fuzzy | ||
345 | msgid "Tweet" | ||
176 | msgstr "tvitni" | 346 | msgstr "tvitni" |
177 | 347 | ||
178 | msgid "email" | 348 | #, fuzzy |
349 | msgid "Email" | ||
179 | msgstr "pošlji po e-pošti" | 350 | msgstr "pošlji po e-pošti" |
180 | 351 | ||
181 | msgid "shaarli" | 352 | msgid "shaarli" |
@@ -184,26 +355,24 @@ msgstr "shaarli" | |||
184 | msgid "flattr" | 355 | msgid "flattr" |
185 | msgstr "flattr" | 356 | msgstr "flattr" |
186 | 357 | ||
187 | msgid "this article appears wrong?" | 358 | #, fuzzy |
359 | msgid "Does this article appear wrong?" | ||
188 | msgstr "napaka?" | 360 | msgstr "napaka?" |
189 | 361 | ||
190 | msgid "create an issue" | 362 | msgid "tags:" |
191 | msgstr "prijavi napako" | 363 | msgstr "" |
192 | |||
193 | msgid "or" | ||
194 | msgstr "ali" | ||
195 | 364 | ||
196 | msgid "contact us by mail" | 365 | msgid "Edit tags" |
197 | msgstr "pošlji e-pošto razvijalcem" | 366 | msgstr "" |
198 | 367 | ||
199 | msgid "plop" | 368 | msgid "save link!" |
200 | msgstr "štrbunk" | 369 | msgstr "" |
201 | 370 | ||
202 | msgid "home" | 371 | msgid "home" |
203 | msgstr "domov" | 372 | msgstr "domov" |
204 | 373 | ||
205 | msgid "favorites" | 374 | msgid "tags" |
206 | msgstr "priljubljeni" | 375 | msgstr "" |
207 | 376 | ||
208 | msgid "logout" | 377 | msgid "logout" |
209 | msgstr "odjava" | 378 | msgstr "odjava" |
@@ -212,28 +381,188 @@ msgid "powered by" | |||
212 | msgstr "stran poganja" | 381 | msgstr "stran poganja" |
213 | 382 | ||
214 | msgid "debug mode is on so cache is off." | 383 | msgid "debug mode is on so cache is off." |
215 | msgstr "" | 384 | msgstr "vklopljen je način odpravljanja napak, zato je predpomnilnik izključen." |
216 | "vklopljen je način odpravljanja napak, zato je predpomnilnik izključen." | ||
217 | 385 | ||
218 | msgid "your poche version:" | 386 | #, fuzzy |
219 | msgstr "vaša verzija Poche:" | 387 | msgid "your wallabag version:" |
388 | msgstr "vaša različica" | ||
220 | 389 | ||
221 | msgid "storage:" | 390 | msgid "storage:" |
222 | msgstr "pomnilnik:" | 391 | msgstr "pomnilnik:" |
223 | 392 | ||
224 | msgid "login to your poche" | 393 | msgid "save a link" |
225 | msgstr "prijavite se v svoj Poche" | 394 | msgstr "" |
226 | 395 | ||
227 | msgid "you are in demo mode, some features may be disabled." | 396 | msgid "back to home" |
397 | msgstr "Nazaj domov" | ||
398 | |||
399 | msgid "toggle mark as read" | ||
400 | msgstr "označi kot prebrano" | ||
401 | |||
402 | msgid "tweet" | ||
403 | msgstr "tvitni" | ||
404 | |||
405 | msgid "email" | ||
406 | msgstr "pošlji po e-pošti" | ||
407 | |||
408 | msgid "this article appears wrong?" | ||
409 | msgstr "napaka?" | ||
410 | |||
411 | msgid "No link available here!" | ||
412 | msgstr "Povezava ni na voljo!" | ||
413 | |||
414 | msgid "Poching a link" | ||
415 | msgstr "Shrani povezavo" | ||
416 | |||
417 | msgid "by filling this field" | ||
418 | msgstr "z vnosom v to polje" | ||
419 | |||
420 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
228 | msgstr "" | 421 | msgstr "" |
229 | "uporabljate vzorčno različico programa, zato so lahko nekatere funkcije " | ||
230 | "izklopljene." | ||
231 | 422 | ||
232 | msgid "Stay signed in" | 423 | msgid "your version" |
233 | msgstr "Ostani prijavljen" | 424 | msgstr "vaša različica" |
234 | 425 | ||
235 | msgid "(Do not check on public computers)" | 426 | msgid "latest stable version" |
236 | msgstr "(Ne označi na javnih napravah)" | 427 | msgstr "zadnja stabilna različica" |
237 | 428 | ||
238 | msgid "Sign in" | 429 | msgid "a more recent stable version is available." |
239 | msgstr "Prijava" | 430 | msgstr "na voljo je nova stabilna različica." |
431 | |||
432 | msgid "you are up to date." | ||
433 | msgstr "imate najnovejšo različico." | ||
434 | |||
435 | msgid "latest dev version" | ||
436 | msgstr "zadnja razvojna različica" | ||
437 | |||
438 | msgid "a more recent development version is available." | ||
439 | msgstr "na voljo je nova razvojna različica." | ||
440 | |||
441 | msgid "Please execute the import script locally, it can take a very long time." | ||
442 | msgstr "Prosimo poženite skripto za uvoz lokalno, saj lahko postopek traja precej časa." | ||
443 | |||
444 | msgid "More infos in the official doc:" | ||
445 | msgstr "Več informacij v uradni dokumentaciji:" | ||
446 | |||
447 | msgid "import from Pocket" | ||
448 | msgstr "Uvoz iz aplikacije Pocket" | ||
449 | |||
450 | msgid "import from Readability" | ||
451 | msgstr "Uvoz iz aplikacije Readability" | ||
452 | |||
453 | msgid "import from Instapaper" | ||
454 | msgstr "Uvoz iz aplikacije Instapaper" | ||
455 | |||
456 | msgid "Tags" | ||
457 | msgstr "" | ||
458 | |||
459 | #, fuzzy | ||
460 | msgid "Untitled" | ||
461 | msgstr "po naslovu" | ||
462 | |||
463 | msgid "the link has been added successfully" | ||
464 | msgstr "" | ||
465 | |||
466 | msgid "error during insertion : the link wasn't added" | ||
467 | msgstr "" | ||
468 | |||
469 | msgid "the link has been deleted successfully" | ||
470 | msgstr "" | ||
471 | |||
472 | msgid "the link wasn't deleted" | ||
473 | msgstr "" | ||
474 | |||
475 | msgid "Article not found!" | ||
476 | msgstr "" | ||
477 | |||
478 | msgid "previous" | ||
479 | msgstr "" | ||
480 | |||
481 | msgid "next" | ||
482 | msgstr "" | ||
483 | |||
484 | msgid "in demo mode, you can't update your password" | ||
485 | msgstr "" | ||
486 | |||
487 | msgid "your password has been updated" | ||
488 | msgstr "" | ||
489 | |||
490 | msgid "the two fields have to be filled & the password must be the same in the two fields" | ||
491 | msgstr "" | ||
492 | |||
493 | msgid "still using the \"" | ||
494 | msgstr "" | ||
495 | |||
496 | msgid "that theme does not seem to be installed" | ||
497 | msgstr "" | ||
498 | |||
499 | msgid "you have changed your theme preferences" | ||
500 | msgstr "" | ||
501 | |||
502 | msgid "that language does not seem to be installed" | ||
503 | msgstr "" | ||
504 | |||
505 | msgid "you have changed your language preferences" | ||
506 | msgstr "" | ||
507 | |||
508 | msgid "login failed: you have to fill all fields" | ||
509 | msgstr "" | ||
510 | |||
511 | msgid "welcome to your wallabag" | ||
512 | msgstr "" | ||
513 | |||
514 | msgid "login failed: bad login or password" | ||
515 | msgstr "" | ||
516 | |||
517 | #, fuzzy | ||
518 | msgid "import from instapaper completed" | ||
519 | msgstr "Uvoz iz aplikacije Instapaper" | ||
520 | |||
521 | #, fuzzy | ||
522 | msgid "import from pocket completed" | ||
523 | msgstr "Uvoz iz aplikacije Pocket" | ||
524 | |||
525 | #, fuzzy | ||
526 | msgid "import from Readability completed. " | ||
527 | msgstr "Uvoz iz aplikacije Readability" | ||
528 | |||
529 | #, fuzzy | ||
530 | msgid "import from Poche completed. " | ||
531 | msgstr "Uvoz iz aplikacije Pocket" | ||
532 | |||
533 | msgid "Unknown import provider." | ||
534 | msgstr "" | ||
535 | |||
536 | msgid "Incomplete inc/poche/define.inc.php file, please define \"" | ||
537 | msgstr "" | ||
538 | |||
539 | msgid "Could not find required \"" | ||
540 | msgstr "" | ||
541 | |||
542 | msgid "Uh, there is a problem while generating feeds." | ||
543 | msgstr "" | ||
544 | |||
545 | #, fuzzy | ||
546 | msgid "Cache deleted." | ||
547 | msgstr "zavrzi" | ||
548 | |||
549 | msgid "Oops, it seems you don't have PHP 5." | ||
550 | msgstr "" | ||
551 | |||
552 | #~ msgid "poche it!" | ||
553 | #~ msgstr "shrani!" | ||
554 | |||
555 | #~ msgid "Updating poche" | ||
556 | #~ msgstr "Posodabljam Poche" | ||
557 | |||
558 | #~ msgid "create an issue" | ||
559 | #~ msgstr "prijavi napako" | ||
560 | |||
561 | #~ msgid "or" | ||
562 | #~ msgstr "ali" | ||
563 | |||
564 | #~ msgid "contact us by mail" | ||
565 | #~ msgstr "pošlji e-pošto razvijalcem" | ||
566 | |||
567 | #~ msgid "your poche version:" | ||
568 | #~ msgstr "vaša verzija Poche:" | ||
diff --git a/locale/tools/fillCache.php b/locale/tools/fillCache.php new file mode 100755 index 00000000..bdd9cc58 --- /dev/null +++ b/locale/tools/fillCache.php | |||
@@ -0,0 +1,59 @@ | |||
1 | <?php | ||
2 | |||
3 | // this script compile all twig templates and put it in cahce to get Poedit (or xgettext) to extract phrases fron chached templates. | ||
4 | |||
5 | // gettext command line tools: | ||
6 | // msgunfmt - get po from mo | ||
7 | // msgfmt - get mo from po | ||
8 | // xgettext - extract phrases from files | ||
9 | |||
10 | |||
11 | $siteRoot = dirname(__FILE__) . '/../..'; | ||
12 | |||
13 | require_once $siteRoot . '/vendor/twig/twig/lib/Twig/Autoloader.php'; | ||
14 | Twig_Autoloader::register(); | ||
15 | |||
16 | require_once $siteRoot . '/vendor/twig/extensions/lib/Twig/Extensions/Autoloader.php'; | ||
17 | Twig_Extensions_Autoloader::register(); | ||
18 | |||
19 | //$tplDir = $siteRoot.'/themes/default'; | ||
20 | $tplDirRoot = $siteRoot.'/themes/'; | ||
21 | $tmpDir = $siteRoot. '/cache/'; | ||
22 | |||
23 | foreach (new IteratorIterator(new DirectoryIterator($tplDirRoot)) as $tplDir) { | ||
24 | |||
25 | if ($tplDir->isDir() and $tplDir!='.' and $tplDir!='..') { | ||
26 | echo "\n$tplDir\n"; | ||
27 | |||
28 | $loader = new Twig_Loader_Filesystem($tplDirRoot.$tplDir); | ||
29 | |||
30 | // force auto-reload to always have the latest version of the template | ||
31 | $twig = new Twig_Environment($loader, array( | ||
32 | 'cache' => $tmpDir, | ||
33 | 'auto_reload' => true | ||
34 | )); | ||
35 | |||
36 | $twig->addExtension(new Twig_Extensions_Extension_I18n()); | ||
37 | |||
38 | $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain'); | ||
39 | $twig->addFilter($filter); | ||
40 | |||
41 | $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime'); | ||
42 | $twig->addFilter($filter); | ||
43 | |||
44 | $filter = new Twig_SimpleFilter('getPrettyFilename', function($string) { return str_replace($siteRoot, '', $string); }); | ||
45 | $twig->addFilter($filter); | ||
46 | |||
47 | // // iterate over all your templates | ||
48 | foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tplDirRoot.$tplDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { | ||
49 | // force compilation | ||
50 | if ($file->isFile() and pathinfo($file, PATHINFO_EXTENSION)=='twig') { | ||
51 | echo "\t$file\n"; | ||
52 | $twig->loadTemplate(str_replace($tplDirRoot.$tplDir.'/', '', $file)); | ||
53 | } | ||
54 | } | ||
55 | |||
56 | } | ||
57 | |||
58 | } | ||
59 | |||
diff --git a/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.mo b/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.mo index 80972b65..4884abf5 100755 --- a/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.mo +++ b/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.mo | |||
Binary files differ | |||
diff --git a/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.po b/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.po index 6092cbab..08797705 100755 --- a/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.po +++ b/locale/uk_UA.utf8/LC_MESSAGES/uk_UA.utf8.po | |||
@@ -2,8 +2,8 @@ msgid "" | |||
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: wballabag\n" | 3 | "Project-Id-Version: wballabag\n" |
4 | "Report-Msgid-Bugs-To: \n" | 4 | "Report-Msgid-Bugs-To: \n" |
5 | "POT-Creation-Date: 2014-02-06 19:23+0300\n" | 5 | "POT-Creation-Date: 2014-02-25 15:06+0300\n" |
6 | "PO-Revision-Date: 2014-02-06 19:24+0300\n" | 6 | "PO-Revision-Date: 2014-02-25 15:08+0300\n" |
7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" | 7 | "Last-Translator: Maryana <mariroz@mr.lviv.ua>\n" |
8 | "Language-Team: \n" | 8 | "Language-Team: \n" |
9 | "Language: \n" | 9 | "Language: \n" |
@@ -15,52 +15,25 @@ msgstr "" | |||
15 | "X-Poedit-Language: Ukrainian\n" | 15 | "X-Poedit-Language: Ukrainian\n" |
16 | "X-Poedit-Country: UKRAINE\n" | 16 | "X-Poedit-Country: UKRAINE\n" |
17 | "X-Poedit-SourceCharset: utf-8\n" | 17 | "X-Poedit-SourceCharset: utf-8\n" |
18 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag\n" | 18 | "X-Poedit-SearchPath-0: /home/mariroz/_DEV/web/wallabag/wallabag-master-testing\n" |
19 | 19 | ||
20 | msgid "poche, a read it later open source system" | 20 | msgid "wallabag, a read it later open source system" |
21 | msgstr "poche, сервіс відкладеного читання з відкритим кодом" | 21 | msgstr "wallabag, сервіс відкладеного читання з відкритим кодом" |
22 | 22 | ||
23 | msgid "login failed: user doesn't exist" | 23 | msgid "login failed: user doesn't exist" |
24 | msgstr "увійти не вдалося: користувач не існує" | 24 | msgstr "увійти не вдалося: користувач не існує" |
25 | 25 | ||
26 | msgid "powered by" | 26 | msgid "return home" |
27 | msgstr "за підтримки" | 27 | msgstr "повернутися на головну" |
28 | |||
29 | msgid "debug mode is on so cache is off." | ||
30 | msgstr "режим відладки включено, отже кеш виключено." | ||
31 | |||
32 | msgid "your poche version:" | ||
33 | msgstr "версія вашої poche:" | ||
34 | |||
35 | msgid "storage:" | ||
36 | msgstr "сховище:" | ||
37 | |||
38 | msgid "home" | ||
39 | msgstr "головна" | ||
40 | |||
41 | msgid "favorites" | ||
42 | msgstr "вибране" | ||
43 | |||
44 | msgid "archive" | ||
45 | msgstr "архів" | ||
46 | |||
47 | msgid "tags" | ||
48 | msgstr "теги" | ||
49 | 28 | ||
50 | msgid "config" | 29 | msgid "config" |
51 | msgstr "налаштування" | 30 | msgstr "налаштування" |
52 | 31 | ||
53 | msgid "logout" | 32 | msgid "Saving articles" |
54 | msgstr "вихід" | ||
55 | |||
56 | msgid "return home" | ||
57 | msgstr "повернутися на головну" | ||
58 | |||
59 | msgid "Poching links" | ||
60 | msgstr "Зберігання посилань" | 33 | msgstr "Зберігання посилань" |
61 | 34 | ||
62 | msgid "There are several ways to poche a link:" | 35 | msgid "There are several ways to save an article:" |
63 | msgstr "Є кілька способів зберегти послнн:" | 36 | msgstr "Є кілька способів зберегти статю:" |
64 | 37 | ||
65 | msgid "read the documentation" | 38 | msgid "read the documentation" |
66 | msgstr "читати документацію" | 39 | msgstr "читати документацію" |
@@ -83,14 +56,14 @@ msgstr "завантажити додаток" | |||
83 | msgid "By filling this field" | 56 | msgid "By filling this field" |
84 | msgstr "Заповнивши це поле" | 57 | msgstr "Заповнивши це поле" |
85 | 58 | ||
86 | msgid "poche it!" | 59 | msgid "bag it!" |
87 | msgstr "зберегти!" | 60 | msgstr "зберегти!" |
88 | 61 | ||
89 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" | 62 | msgid "Bookmarklet: drag & drop this link to your bookmarks bar" |
90 | msgstr "З допомогою закладки: перетягніть і відпустіть посилання на панель закладок" | 63 | msgstr "З допомогою закладки: перетягніть і відпустіть посилання на панель закладок" |
91 | 64 | ||
92 | msgid "Updating poche" | 65 | msgid "Upgrading wallabag" |
93 | msgstr "Оновлення poche" | 66 | msgstr "Оновлення wallabag" |
94 | 67 | ||
95 | msgid "Installed version" | 68 | msgid "Installed version" |
96 | msgstr "Встановлено версію" | 69 | msgstr "Встановлено версію" |
@@ -104,14 +77,11 @@ msgstr "Є новіша стабільна версія." | |||
104 | msgid "You are up to date." | 77 | msgid "You are up to date." |
105 | msgstr "У вас остання версія." | 78 | msgstr "У вас остання версія." |
106 | 79 | ||
107 | msgid "latest dev version" | 80 | msgid "Latest dev version" |
108 | msgstr "остання версія в розробці" | 81 | msgstr "Остання версія в розробці" |
109 | |||
110 | msgid "a more recent development version is available." | ||
111 | msgstr "доступна новіша версія в розробці." | ||
112 | 82 | ||
113 | msgid "you are up to date." | 83 | msgid "A more recent development version is available." |
114 | msgstr "у с стнн версія." | 84 | msgstr "остун овша версія в розробці." |
115 | 85 | ||
116 | msgid "Feeds" | 86 | msgid "Feeds" |
117 | msgstr "Завантаження (feeds)" | 87 | msgstr "Завантаження (feeds)" |
@@ -170,8 +140,8 @@ msgstr "Імпортування" | |||
170 | msgid "Please execute the import script locally as it can take a very long time." | 140 | msgid "Please execute the import script locally as it can take a very long time." |
171 | msgstr "Будь ласка, виконайте сценарій імпорту локально, оскільки це може тривати досить довго." | 141 | msgstr "Будь ласка, виконайте сценарій імпорту локально, оскільки це може тривати досить довго." |
172 | 142 | ||
173 | msgid "More info in the official docs:" | 143 | msgid "More info in the official documentation:" |
174 | msgstr "Більш тальна інформаці в офіційній документації:" | 144 | msgstr "Більше інформаці в офіційній документації:" |
175 | 145 | ||
176 | msgid "Import from Pocket" | 146 | msgid "Import from Pocket" |
177 | msgstr "Імпорт з Pocket-а" | 147 | msgstr "Імпорт з Pocket-а" |
@@ -186,11 +156,11 @@ msgstr "Імпорт з Readability" | |||
186 | msgid "Import from Instapaper" | 156 | msgid "Import from Instapaper" |
187 | msgstr "Імпорт з Instapaper" | 157 | msgstr "Імпорт з Instapaper" |
188 | 158 | ||
189 | msgid "Import from poche" | 159 | msgid "Import from wallabag" |
190 | msgstr "Імпорт з poche" | 160 | msgstr "Імпорт з wallabag" |
191 | 161 | ||
192 | msgid "Export your poche data" | 162 | msgid "Export your wallabag data" |
193 | msgstr "Експортувати ваші дані з poche" | 163 | msgstr "Експортувати ваші дані з wallabag" |
194 | 164 | ||
195 | msgid "Click here" | 165 | msgid "Click here" |
196 | msgstr "Клікніть тут" | 166 | msgstr "Клікніть тут" |
@@ -198,32 +168,14 @@ msgstr "Клікніть тут" | |||
198 | msgid "to download your database." | 168 | msgid "to download your database." |
199 | msgstr "щоб завантажити вашу базу даних." | 169 | msgstr "щоб завантажити вашу базу даних." |
200 | 170 | ||
201 | msgid "to export your poche data." | 171 | msgid "to export your wallabag data." |
202 | msgstr "щоб експортувати ваші дані poche." | 172 | msgstr "щоб експортувати ваші дані wallabag." |
203 | 173 | ||
204 | msgid "Tag" | 174 | msgid "Cache" |
205 | msgstr "ег" | 175 | msgstr "еш" |
206 | 176 | ||
207 | msgid "No link available here!" | 177 | msgid "to delete cache." |
208 | msgstr "Немає доступних посилань!" | 178 | msgstr "щоб очистити кеш." |
209 | |||
210 | msgid "toggle mark as read" | ||
211 | msgstr "змінити мітку на прочитано" | ||
212 | |||
213 | msgid "toggle favorite" | ||
214 | msgstr "змінити мітку вибраного" | ||
215 | |||
216 | msgid "delete" | ||
217 | msgstr "видалити" | ||
218 | |||
219 | msgid "original" | ||
220 | msgstr "оригінал" | ||
221 | |||
222 | msgid "estimated reading time:" | ||
223 | msgstr "приблизний час читання:" | ||
224 | |||
225 | msgid "results" | ||
226 | msgstr "результат(ів)" | ||
227 | 179 | ||
228 | msgid "You can enter multiple tags, separated by commas." | 180 | msgid "You can enter multiple tags, separated by commas." |
229 | msgstr "Ви можете ввести декілька тегів, розділених комами." | 181 | msgstr "Ви можете ввести декілька тегів, розділених комами." |
@@ -240,6 +192,9 @@ msgstr "Ви можете <a href='wallabag_compatibility_test.php'>переві | |||
240 | msgid "favoris" | 192 | msgid "favoris" |
241 | msgstr "вибране" | 193 | msgstr "вибране" |
242 | 194 | ||
195 | msgid "archive" | ||
196 | msgstr "архів" | ||
197 | |||
243 | msgid "unread" | 198 | msgid "unread" |
244 | msgstr "непрочитане" | 199 | msgstr "непрочитане" |
245 | 200 | ||
@@ -261,12 +216,33 @@ msgstr "за назвою" | |||
261 | msgid "by title desc" | 216 | msgid "by title desc" |
262 | msgstr "за назвою по спаданню" | 217 | msgstr "за назвою по спаданню" |
263 | 218 | ||
219 | msgid "Tag" | ||
220 | msgstr "Тег" | ||
221 | |||
264 | msgid "No articles found." | 222 | msgid "No articles found." |
265 | msgstr "Статей не знайдено." | 223 | msgstr "Статей не знайдено." |
266 | 224 | ||
267 | msgid "Toggle mark as read" | 225 | msgid "Toggle mark as read" |
268 | msgstr "змінити мітку прочитаного" | 226 | msgstr "змінити мітку прочитаного" |
269 | 227 | ||
228 | msgid "toggle favorite" | ||
229 | msgstr "змінити мітку вибраного" | ||
230 | |||
231 | msgid "delete" | ||
232 | msgstr "видалити" | ||
233 | |||
234 | msgid "original" | ||
235 | msgstr "оригінал" | ||
236 | |||
237 | msgid "estimated reading time:" | ||
238 | msgstr "приблизний час читання:" | ||
239 | |||
240 | msgid "mark all the entries as read" | ||
241 | msgstr "відмітити всі статті як прочитані" | ||
242 | |||
243 | msgid "results" | ||
244 | msgstr "результат(ів)" | ||
245 | |||
270 | msgid "installation" | 246 | msgid "installation" |
271 | msgstr "інсталяція" | 247 | msgstr "інсталяція" |
272 | 248 | ||
@@ -303,6 +279,18 @@ msgstr "Запам'ятати мене" | |||
303 | msgid "(Do not check on public computers)" | 279 | msgid "(Do not check on public computers)" |
304 | msgstr "(Не відмічайте на загальнодоступних комп'ютерах)" | 280 | msgstr "(Не відмічайте на загальнодоступних комп'ютерах)" |
305 | 281 | ||
282 | msgid "Sign in" | ||
283 | msgstr "Увійти" | ||
284 | |||
285 | msgid "favorites" | ||
286 | msgstr "вибране" | ||
287 | |||
288 | msgid "estimated reading time :" | ||
289 | msgstr "приблизний час читання:" | ||
290 | |||
291 | msgid "Mark all the entries as read" | ||
292 | msgstr "Відмітити все як прочитане" | ||
293 | |||
306 | msgid "Return home" | 294 | msgid "Return home" |
307 | msgstr "Повернутися на головну" | 295 | msgstr "Повернутися на головну" |
308 | 296 | ||
@@ -342,11 +330,95 @@ msgstr "теги:" | |||
342 | msgid "Edit tags" | 330 | msgid "Edit tags" |
343 | msgstr "Редагувати теги" | 331 | msgstr "Редагувати теги" |
344 | 332 | ||
345 | msgid "previous" | 333 | msgid "save link!" |
346 | msgstr "ре" | 334 | msgstr "ерет лнк!" |
347 | 335 | ||
348 | msgid "next" | 336 | msgid "home" |
349 | msgstr "наступна" | 337 | msgstr "головна" |
338 | |||
339 | msgid "tags" | ||
340 | msgstr "теги" | ||
341 | |||
342 | msgid "logout" | ||
343 | msgstr "вихід" | ||
344 | |||
345 | msgid "powered by" | ||
346 | msgstr "за підтримки" | ||
347 | |||
348 | msgid "debug mode is on so cache is off." | ||
349 | msgstr "режим відладки включено, отже кеш виключено." | ||
350 | |||
351 | msgid "your wallabag version:" | ||
352 | msgstr "версія вашого wallabag:" | ||
353 | |||
354 | msgid "storage:" | ||
355 | msgstr "сховище:" | ||
356 | |||
357 | msgid "save a link" | ||
358 | msgstr "зберегти лінк" | ||
359 | |||
360 | msgid "back to home" | ||
361 | msgstr "назад на головну" | ||
362 | |||
363 | msgid "toggle mark as read" | ||
364 | msgstr "змінити мітку на прочитано" | ||
365 | |||
366 | msgid "tweet" | ||
367 | msgstr "твітнути" | ||
368 | |||
369 | msgid "email" | ||
370 | msgstr "email" | ||
371 | |||
372 | msgid "this article appears wrong?" | ||
373 | msgstr "ця стаття виглядає не так, як треба?" | ||
374 | |||
375 | msgid "No link available here!" | ||
376 | msgstr "Немає доступних посилань!" | ||
377 | |||
378 | msgid "Poching a link" | ||
379 | msgstr "Зберігання посилання" | ||
380 | |||
381 | msgid "by filling this field" | ||
382 | msgstr "заповнивши це поле" | ||
383 | |||
384 | msgid "bookmarklet: drag & drop this link to your bookmarks bar" | ||
385 | msgstr "з допомогою закладки: перетягніть і відпустіть посилання на панель закладок" | ||
386 | |||
387 | msgid "your version" | ||
388 | msgstr "ваша версія:" | ||
389 | |||
390 | msgid "latest stable version" | ||
391 | msgstr "остання стабільна версія" | ||
392 | |||
393 | msgid "a more recent stable version is available." | ||
394 | msgstr "є новіша стабільна версія." | ||
395 | |||
396 | msgid "you are up to date." | ||
397 | msgstr "у вас остання версія." | ||
398 | |||
399 | msgid "latest dev version" | ||
400 | msgstr "остання версія в розробці" | ||
401 | |||
402 | msgid "a more recent development version is available." | ||
403 | msgstr "доступна новіша версія в розробці." | ||
404 | |||
405 | msgid "Please execute the import script locally, it can take a very long time." | ||
406 | msgstr "Будь ласка, виконайте сценарій імпорту локально, оскільки це може тривати досить довго." | ||
407 | |||
408 | msgid "More infos in the official doc:" | ||
409 | msgstr "Більше інформації в офіційній документації:" | ||
410 | |||
411 | msgid "import from Pocket" | ||
412 | msgstr "імпорт з Pocket-а" | ||
413 | |||
414 | msgid "import from Readability" | ||
415 | msgstr "імпорт з Readability" | ||
416 | |||
417 | msgid "import from Instapaper" | ||
418 | msgstr "імпорт з Instapaper" | ||
419 | |||
420 | msgid "Tags" | ||
421 | msgstr "Теги" | ||
350 | 422 | ||
351 | msgid "Untitled" | 423 | msgid "Untitled" |
352 | msgstr "Без назви" | 424 | msgstr "Без назви" |
@@ -363,6 +435,15 @@ msgstr "посилання успішно видалено" | |||
363 | msgid "the link wasn't deleted" | 435 | msgid "the link wasn't deleted" |
364 | msgstr "посилання не було видалено" | 436 | msgstr "посилання не було видалено" |
365 | 437 | ||
438 | msgid "Article not found!" | ||
439 | msgstr "Статтю не знайдено!" | ||
440 | |||
441 | msgid "previous" | ||
442 | msgstr "попередня" | ||
443 | |||
444 | msgid "next" | ||
445 | msgstr "наступна" | ||
446 | |||
366 | msgid "in demo mode, you can't update your password" | 447 | msgid "in demo mode, you can't update your password" |
367 | msgstr "в демонстраційному режимі ви не можете змінювати свій пароль" | 448 | msgstr "в демонстраційному режимі ви не можете змінювати свій пароль" |
368 | 449 | ||
@@ -390,15 +471,12 @@ msgstr "ви змінили свої налаштування мови" | |||
390 | msgid "login failed: you have to fill all fields" | 471 | msgid "login failed: you have to fill all fields" |
391 | msgstr "увійти не вдалося: ви повинні заповнити всі поля" | 472 | msgstr "увійти не вдалося: ви повинні заповнити всі поля" |
392 | 473 | ||
393 | msgid "welcome to your poche" | 474 | msgid "welcome to your wallabag" |
394 | msgstr "ласкаво просимо до вашого poche" | 475 | msgstr "ласкаво просимо до вашого wallabag" |
395 | 476 | ||
396 | msgid "login failed: bad login or password" | 477 | msgid "login failed: bad login or password" |
397 | msgstr "увійти не вдалося: не вірний логін або пароль" | 478 | msgstr "увійти не вдалося: не вірний логін або пароль" |
398 | 479 | ||
399 | msgid "see you soon!" | ||
400 | msgstr "бувайте, ще побачимось!" | ||
401 | |||
402 | msgid "import from instapaper completed" | 480 | msgid "import from instapaper completed" |
403 | msgstr "імпорт з instapaper-а завершено" | 481 | msgstr "імпорт з instapaper-а завершено" |
404 | 482 | ||
@@ -423,6 +501,34 @@ msgstr "Не вдалося знайти потрібний \"" | |||
423 | msgid "Uh, there is a problem while generating feeds." | 501 | msgid "Uh, there is a problem while generating feeds." |
424 | msgstr "Ох, є проблема при створенні завантажень (feeds)." | 502 | msgstr "Ох, є проблема при створенні завантажень (feeds)." |
425 | 503 | ||
504 | msgid "Cache deleted." | ||
505 | msgstr "Кеш очищено." | ||
506 | |||
426 | msgid "Oops, it seems you don't have PHP 5." | 507 | msgid "Oops, it seems you don't have PHP 5." |
427 | msgstr "Упс, здається, у вас немає PHP 5." | 508 | msgstr "Упс, здається, у вас немає PHP 5." |
428 | 509 | ||
510 | #~ msgid "You can poche a link by several methods:" | ||
511 | #~ msgstr "Ви можете зберегти посилання кількома способами:" | ||
512 | |||
513 | #~ msgid "poche it!" | ||
514 | #~ msgstr "зберегти!" | ||
515 | |||
516 | #~ msgid "Updating poche" | ||
517 | #~ msgstr "Оновлення poche" | ||
518 | |||
519 | #, fuzzy | ||
520 | #~ msgid "Export your poche datas" | ||
521 | #~ msgstr "Експортувати ваші дані з poche" | ||
522 | |||
523 | #, fuzzy | ||
524 | #~ msgid "to export your poche datas." | ||
525 | #~ msgstr "щоб експортувати ваші дані poche." | ||
526 | |||
527 | #~ msgid "Import from poche" | ||
528 | #~ msgstr "Імпорт з poche" | ||
529 | |||
530 | #~ msgid "welcome to your poche" | ||
531 | #~ msgstr "ласкаво просимо до вашого poche" | ||
532 | |||
533 | #~ msgid "see you soon!" | ||
534 | #~ msgstr "бувайте, ще побачимось!" | ||
diff --git a/themes/baggy/_display-mode.twig b/themes/baggy/_display-mode.twig new file mode 100755 index 00000000..382dd5f1 --- /dev/null +++ b/themes/baggy/_display-mode.twig | |||
@@ -0,0 +1,5 @@ | |||
1 | <div id="display-mode"> | ||
2 | <a href="javascript: void(null);" id="listmode" class="listmode"> | ||
3 | <img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/blank.png" alt="{% trans "toggle view mode" %}" title="{% trans "toggle view mode" %}" width="16" height="16"> | ||
4 | </a> | ||
5 | </div> | ||
diff --git a/themes/baggy/_head.twig b/themes/baggy/_head.twig index 206d5aae..be11673f 100644..100755 --- a/themes/baggy/_head.twig +++ b/themes/baggy/_head.twig | |||
@@ -1,12 +1,16 @@ | |||
1 | <link rel="shortcut icon" type="image/x-icon" href="{{ poche_url }}/themes/{{theme}}/img/favicon.ico" /> | 1 | <link rel="shortcut icon" type="image/x-icon" href="{{ poche_url }}themes/{{theme}}/img/favicon.ico" /> |
2 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ poche_url }}/themes/{{theme}}/img/apple-touch-icon-144x144-precomposed.png"> | 2 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ poche_url }}themes/{{theme}}/img/apple-touch-icon-144x144-precomposed.png"> |
3 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ poche_url }}/themes/{{theme}}/img/apple-touch-icon-72x72-precomposed.png"> | 3 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ poche_url }}themes/{{theme}}/img/apple-touch-icon-72x72-precomposed.png"> |
4 | <link rel="apple-touch-icon-precomposed" href="{{ poche_url }}/themes/{{theme}}/img/apple-touch-icon-precomposed.png"> | 4 | <link rel="apple-touch-icon-precomposed" href="{{ poche_url }}themes/{{theme}}/img/apple-touch-icon-precomposed.png"> |
5 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/ratatouille.css" media="all"> | 5 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/ratatouille.css" media="all"> |
6 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/font.css" media="all"> | 6 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/font.css" media="all"> |
7 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/main.css" media="all"> | 7 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/main.css" media="all"> |
8 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/messages.css" media="all"> | 8 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/messages.css" media="all"> |
9 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/print.css" media="print"> | 9 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/print.css" media="print"> |
10 | <script src="{{ poche_url }}/themes/{{theme}}/js/jquery-2.0.3.min.js"></script> | 10 | <script src="{{ poche_url }}themes/default/js/jquery-2.0.3.min.js"></script> |
11 | <script src="{{ poche_url }}/themes/{{theme}}/js/init.js"></script> | 11 | <script src="{{ poche_url }}themes/default/js/autoClose.js"></script> |
12 | <script src="{{ poche_url }}/themes/{{ constant('DEFAULT_THEME') }}/js/closeMessage.js"></script> | 12 | <script src="{{ poche_url }}themes/{{theme}}/js/jquery.cookie.js"></script> |
13 | <script src="{{ poche_url }}themes/{{theme}}/js/init.js"></script> | ||
14 | <script src="{{ poche_url }}themes/default/js/saveLink.js"></script> | ||
15 | <script src="{{ poche_url }}themes/default/js/popupForm.js"></script> | ||
16 | <script src="{{ poche_url }}themes/{{theme}}/js/closeMessage.js"></script> | ||
diff --git a/themes/baggy/_menu.twig b/themes/baggy/_menu.twig index e9cd9d4a..f1b75cd5 100644 --- a/themes/baggy/_menu.twig +++ b/themes/baggy/_menu.twig | |||
@@ -4,9 +4,13 @@ | |||
4 | <li><a href="./?view=fav" {% if view == 'fav' %}class="current"{% endif %}>{% trans "favorites" %}</a></li> | 4 | <li><a href="./?view=fav" {% if view == 'fav' %}class="current"{% endif %}>{% trans "favorites" %}</a></li> |
5 | <li><a href="./?view=archive" {% if view == 'archive' %}class="current"{% endif %}>{% trans "archive" %}</a></li> | 5 | <li><a href="./?view=archive" {% if view == 'archive' %}class="current"{% endif %}>{% trans "archive" %}</a></li> |
6 | <li><a href="./?view=tags" {% if view == 'tags' %}class="current"{% endif %}>{% trans "tags" %}</a></li> | 6 | <li><a href="./?view=tags" {% if view == 'tags' %}class="current"{% endif %}>{% trans "tags" %}</a></li> |
7 | <li><a href="javascript: void(null);" id="pocheit">{% trans "save a link" %}</a></li> | 7 | <li style="position: relative;"><a href="javascript: void(null);" id="bagit">{% trans "save a link" %}</a> |
8 | {% include '_pocheit-form.twig' %} | ||
9 | </li> | ||
10 | <li style="position: relative;"><a href="javascript: void(null);" id="search">{% trans "search" %}</a> | ||
11 | {% include '_search-form.twig' %} | ||
12 | </li> | ||
8 | <li><a href="./?view=config" {% if view == 'config' %}class="current"{% endif %}>{% trans "config" %}</a></li> | 13 | <li><a href="./?view=config" {% if view == 'config' %}class="current"{% endif %}>{% trans "config" %}</a></li> |
9 | <li><a class="icon icon-power" href="./?logout" title="{% trans "logout" %}">{% trans "logout" %}</a></li> | 14 | <li><a class="icon icon-power" href="./?logout" title="{% trans "logout" %}">{% trans "logout" %}</a></li> |
10 | </ul> | 15 | </ul> |
11 | 16 | ||
12 | {% include '_pocheit-form.twig' %} | ||
diff --git a/themes/baggy/_pocheit-form.twig b/themes/baggy/_pocheit-form.twig new file mode 100755 index 00000000..505ec368 --- /dev/null +++ b/themes/baggy/_pocheit-form.twig | |||
@@ -0,0 +1,10 @@ | |||
1 | <div id="bagit-form" class="messages info"> | ||
2 | <form method="get" action="index.php" target="_blank" id="bagit-form-form"> | ||
3 | <h2><a href="javascript: void(null);" id="bagit-form-close">X</a> | ||
4 | {% trans "Save a link" %}</h2> | ||
5 | <input type="hidden" name="autoclose" value="1" /> | ||
6 | <input required placeholder="example.com/article" class="addurl" id="plainurl" name="plainurl" type="url" /> | ||
7 | <input type="submit" value="{% trans "save link!" %}" /> | ||
8 | <div id="add-link-result"></div> | ||
9 | </form> | ||
10 | </div> | ||
diff --git a/themes/baggy/_search-form.twig b/themes/baggy/_search-form.twig new file mode 100644 index 00000000..b8ac3bfa --- /dev/null +++ b/themes/baggy/_search-form.twig | |||
@@ -0,0 +1,7 @@ | |||
1 | <div id="search-form" class="messages info"> | ||
2 | <form method="get" action="index.php"> | ||
3 | <input type="hidden" name="view" value="search"></input> | ||
4 | <label><a href="javascript: void(null);" id="search-form-close">X</a>{% trans "Search" %}</label> : <input type="text" name="search" /> | ||
5 | <input id="submit-search" type="submit" value="{% trans "Search" %} !"></input> | ||
6 | </form> | ||
7 | </div> \ No newline at end of file | ||
diff --git a/themes/baggy/_top.twig b/themes/baggy/_top.twig index 4f476a37..a31c0925 100644..100755 --- a/themes/baggy/_top.twig +++ b/themes/baggy/_top.twig | |||
@@ -1,6 +1,6 @@ | |||
1 | <header class="w600p center mbm"> | 1 | <header class="w600p center mbm"> |
2 | <h1 class="logo"> | 2 | <h1 class="logo"> |
3 | {% if view == 'home' %}{% block logo %}<img width="100" height="100" src="{{ poche_url }}/themes/{{theme}}/img/logo-w.png" alt="logo poche" />{% endblock %} | 3 | {% if view == 'home' %}{% block logo %}<img width="100" height="100" src="{{ poche_url }}themes/{{theme}}/img/logo-w.png" alt="wallabag logo" />{% endblock %} |
4 | {% else %}<a href="./" title="{% trans "return home" %}" >{{ block('logo') }}</a> | 4 | {% else %}<a href="./" title="{% trans "return home" %}" >{{ block('logo') }}</a> |
5 | {% endif %} | 5 | {% endif %} |
6 | </h1> | 6 | </h1> |
diff --git a/themes/baggy/config.twig b/themes/baggy/config.twig index b37ac115..46735f07 100644..100755 --- a/themes/baggy/config.twig +++ b/themes/baggy/config.twig | |||
@@ -8,10 +8,11 @@ | |||
8 | <h2>{% trans "Saving articles" %}</h2> | 8 | <h2>{% trans "Saving articles" %}</h2> |
9 | <p>{% trans "There are several ways to save an article:" %} (<a href="http://doc.wallabag.org/" title="{% trans "read the documentation" %}">?</a>)</p> | 9 | <p>{% trans "There are several ways to save an article:" %} (<a href="http://doc.wallabag.org/" title="{% trans "read the documentation" %}">?</a>)</p> |
10 | <ul> | 10 | <ul> |
11 | <li>Firefox: <a href="https://addons.mozilla.org/firefox/addon/poche/" title="download the firefox extension">{% trans "download the extension" %}</a></li> | 11 | <li>Firefox: <a href="https://addons.mozilla.org/firefox/addon/wallabag/" title="download the firefox extension">{% trans "download the extension" %}</a></li> |
12 | <li>Chrome: <a href="http://doc.wallabag.org/doku.php?id=users:chrome_extension" title="download the chrome extension">{% trans "download the extension" %}</a></li> | 12 | <li>Chrome: <a href="http://doc.wallabag.org/doku.php?id=users:chrome_extension" title="download the chrome extension">{% trans "download the extension" %}</a></li> |
13 | <li>Android: <a href="https://f-droid.org/repository/browse/?fdid=fr.gaulupeau.apps.Poche" title="download the application">{% trans "via F-Droid" %}</a> {% trans " or " %} <a href="https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via Google Play" %}</a></li> | 13 | <li>Android: <a href="https://f-droid.org/app/fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via F-Droid" %}</a> {% trans " or " %} <a href="https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via Google Play" %}</a></li> |
14 | <li>Windows Phone: <a href="https://www.windowsphone.com/en-us/store/app/poche/334de2f0-51b5-4826-8549-a3d805a37e83" title="download the window phone application">{% trans "download the application" %}</a></li> | 14 | <li>iOS: <a href="https://itunes.apple.com/app/wallabag/id828331015?mt=8" title="download the iOS application">{% trans "download the application" %}</a></li> |
15 | <li>Windows Phone: <a href="http://www.windowsphone.com/en-us/store/app/wallabag/ff890514-348c-4d0b-9b43-153fff3f7450" title="download the window phone application">{% trans "download the application" %}</a></li> | ||
15 | <li> | 16 | <li> |
16 | <form method="get" action="index.php"> | 17 | <form method="get" action="index.php"> |
17 | <label class="addurl" for="config_plainurl">{% trans "By filling this field" %}:</label> | 18 | <label class="addurl" for="config_plainurl">{% trans "By filling this field" %}:</label> |
@@ -25,9 +26,10 @@ | |||
25 | <h2>{% trans "Upgrading wallabag" %}</h2> | 26 | <h2>{% trans "Upgrading wallabag" %}</h2> |
26 | <ul> | 27 | <ul> |
27 | <li>{% trans "Installed version" %} : <strong>{{ constant('POCHE') }}</strong></li> | 28 | <li>{% trans "Installed version" %} : <strong>{{ constant('POCHE') }}</strong></li> |
28 | <li>{% trans "Latest stable version" %} : {{ prod }}. {% if compare_prod == -1 %}<strong><a href="http://wallabag.org/">{% trans "A more recent stable version is available." %}</a></strong>{% else %}{% trans "You are up to date." %}{% endif %}</li> | 29 | <li>{% trans "Latest stable version" %} : {{ prod }}. {% if compare_prod == -1 %}<strong><a href="http://wallabag.org/">{% trans "A more recent stable version is available." %}</a></strong>{% else %}{% trans "You are up to date." %}{% endif %} ({% trans "Last check:" %} {{ check_time_prod }})</li> |
29 | {% if constant('DEBUG_POCHE') == 1 %}<li>{% trans "Latest dev version" %} : {{ dev }}. {% if compare_dev == -1 %}<strong><a href="http://wallabag.org/">{% trans "A more recent development version is available." %}</a></strong>{% else %}{% trans "You are up to date." %}{% endif %}</li>{% endif %} | 30 | {% if constant('DEBUG_POCHE') == 1 %}<li>{% trans "Latest dev version" %} : {{ dev }}. {% if compare_dev == -1 %}<strong><a href="http://wallabag.org/">{% trans "A more recent development version is available." %}</a></strong>{% else %}{% trans "You are up to date." %}{% endif %} ({% trans "Last check:" %} {{ check_time_dev }}){% endif %}</li> |
30 | </ul> | 31 | </ul> |
32 | <p>{% trans "You can clear cache to check the latest release." %}</p> | ||
31 | 33 | ||
32 | <h2>{% trans "Feeds" %}</h2> | 34 | <h2>{% trans "Feeds" %}</h2> |
33 | {% if token == '' %} | 35 | {% if token == '' %} |
@@ -42,7 +44,7 @@ | |||
42 | <p>{% trans "Your user id:" %} <strong>{{user_id}}</strong></p> | 44 | <p>{% trans "Your user id:" %} <strong>{{user_id}}</strong></p> |
43 | <p>{% trans "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." %}</p> | 45 | <p>{% trans "You can regenerate your token: <a href='?feed&action=generate'>generate!</a>." %}</p> |
44 | {% endif %} | 46 | {% endif %} |
45 | 47 | ||
46 | <h2>{% trans "Change your theme" %}</h2> | 48 | <h2>{% trans "Change your theme" %}</h2> |
47 | <form method="post" action="?updatetheme" name="changethemeform"> | 49 | <form method="post" action="?updatetheme" name="changethemeform"> |
48 | <fieldset class="w500p inline"> | 50 | <fieldset class="w500p inline"> |
@@ -103,21 +105,63 @@ | |||
103 | {% endif %} | 105 | {% endif %} |
104 | 106 | ||
105 | <h2>{% trans "Import" %}</h2> | 107 | <h2>{% trans "Import" %}</h2> |
106 | <p>{% trans "Please execute the import script locally as it can take a very long time." %}</p> | 108 | <p>{% trans "You can import your Pocket, Readability, Instapaper, Wallabag or any data in appropriate json or html format." %}</p> |
107 | <p>{% trans "More info in the official documentation:" %} <a href="http://doc.wallabag.org/doku.php?id=users:migrate">wallabag.org</a></p> | 109 | <p>{% trans "Please select export file on your computer and press \"Import\" button below.<br>Wallabag will parse your file, insert all URLs and start fetching of articles if required.<br>Fetching process is controlled by two constants in your config file: IMPORT_LIMIT (how many articles are fetched at once) and IMPORT_DELAY (delay between fetch of next batch of articles)." %}</p> |
108 | <ul> | 110 | <form method="post" action="?import" name="uploadfile" enctype="multipart/form-data"> |
109 | <li><a href="./?import&from=pocket">{% trans "Import from Pocket" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('POCKET_FILE')) }}</li> | 111 | <fieldset class="w500p"> |
110 | <li><a href="./?import&from=readability">{% trans "Import from Readability" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('READABILITY_FILE')) }}</li> | 112 | <div class="row"> |
111 | <li><a href="./?import&from=instapaper">{% trans "Import from Instapaper" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('INSTAPAPER_FILE')) }}</li> | 113 | <label class="col w150p" for="file">{% trans "File:" %}</label> |
112 | <li><a href="./?import&from=poche">{% trans "Import from wallabag" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('POCHE_FILE')) }}</li> | 114 | <input class="col" type="file" id="file" name="file" tabindex="4"> |
113 | </ul> | 115 | </div> |
114 | 116 | <div class="row mts txtcenter"> | |
117 | <button class="bouton" type="submit" tabindex="4">{% trans "Import" %}</button> | ||
118 | </div> | ||
119 | </fieldset> | ||
120 | </form> | ||
121 | <p><a href="?import">{% trans "You can click here to fetch content for articles with no content." %}</a></p> | ||
122 | |||
115 | <h2>{% trans "Export your wallabag data" %}</h2> | 123 | <h2>{% trans "Export your wallabag data" %}</h2> |
116 | {% if constant('STORAGE') == 'sqlite' %} | 124 | {% if constant('STORAGE') == 'sqlite' %} |
117 | <p><a href="?download" target="_blank">{% trans "Click here" %}</a> {% trans "to download your database." %}</p>{% endif %} | 125 | <p><a href="?download" target="_blank">{% trans "Click here" %}</a> {% trans "to download your database." %}</p>{% endif %} |
118 | <p><a href="?export" target="_blank">{% trans "Click here" %}</a> {% trans "to export your wallabag data." %}</p> | 126 | <p><a href="?export" target="_blank">{% trans "Click here" %}</a> {% trans "to export your wallabag data." %}</p> |
127 | |||
128 | <h2>{% trans "Fancy an E-Book ?" %}</h2> | ||
129 | <p>{% trans "Click on <a href=\"./?epub&method=all\" title=\"Generate ePub\">this link</a> to get all your articles in one ebook (ePub 3 format)." %} | ||
130 | <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> | ||
119 | 131 | ||
120 | <h2>{% trans "Cache" %}</h2> | 132 | <h2>{% trans "Cache" %}</h2> |
121 | <p><a href="?empty-cache">{% trans "Click here" %}</a> {% trans "to delete cache." %}</p> | 133 | <p><a href="?empty-cache">{% trans "Click here" %}</a> {% trans "to delete cache." %}</p> |
122 | 134 | ||
135 | <h2>{% trans 'Add user' %}</h2> | ||
136 | <p>{% trans 'Add a new user :' %}</p> | ||
137 | <form method="post" action="?newuser"> | ||
138 | <fieldset class="w500p"> | ||
139 | <div class="row"> | ||
140 | <label class="col w150p" for="newusername">{% trans 'Login for new user' %}</label> | ||
141 | <input class="col" type="text" id="newusername" name="newusername" placeholder="{% trans 'Login' %}"> | ||
142 | </div> | ||
143 | <div class="row"> | ||
144 | <label class="col w150p" for="password4newuser">{% trans "Password for new user" %}</label> | ||
145 | <input class="col" type="password" id="password4newuser" name="password4newuser" placeholder="{% trans "Password" %}"> | ||
146 | </div> | ||
147 | <div class="row mts txtcenter"> | ||
148 | <button type="submit">{% trans "Send" %}</button> | ||
149 | </div> | ||
150 | </fieldset> | ||
151 | </form> | ||
152 | |||
153 | <h2>{% trans "Delete account" %}</h2> | ||
154 | {% if not only_user %}<form method="post" action="?deluser"> | ||
155 | <p>{% trans "You can delete your account by entering your password and validating." %}<br /><b>{% trans "Be careful, data will be erased forever (that is a very long time)." %}</b></p> | ||
156 | <fieldset class="w500p"> | ||
157 | <div class="row"> | ||
158 | <label class="col w150p" for="password4deletinguser">{% trans "Type here your password" %}</label> | ||
159 | <input class="col" type="password" id="password4deletinguser" name="password4deletinguser" placeholder="{% trans "Password" %}"> | ||
160 | </div> | ||
161 | <div class="row mts txtcenter"> | ||
162 | <button type="submit">{% trans "Send" %}</button> | ||
163 | </div> | ||
164 | </form> | ||
165 | {% else %}<p>{% trans "You are the only user, you cannot delete your own account." %}<br /> | ||
166 | {% trans "To completely remove wallabag, delete the wallabag folder on your web server." %}</p>{% endif %} | ||
123 | {% endblock %} | 167 | {% endblock %} |
diff --git a/themes/baggy/css/main.css b/themes/baggy/css/main.css index 3e128b08..6d320cd2 100755 --- a/themes/baggy/css/main.css +++ b/themes/baggy/css/main.css | |||
@@ -173,21 +173,22 @@ h2:after { | |||
173 | #links { | 173 | #links { |
174 | position: fixed; | 174 | position: fixed; |
175 | top: 0; | 175 | top: 0; |
176 | width: 9em; | 176 | width: 10em; |
177 | left: 0; | 177 | left: 0; |
178 | text-align: right; | 178 | text-align: right; |
179 | background: #333; | 179 | background: #333; |
180 | padding-top: 9em; | 180 | padding-top: 9.5em; |
181 | height: 100%; | 181 | height: 100%; |
182 | box-shadow:inset -4px 0 20px rgba(0,0,0,0.6); | 182 | box-shadow:inset -4px 0 20px rgba(0,0,0,0.6); |
183 | z-index: 10; | 183 | z-index: 10; |
184 | } | 184 | } |
185 | 185 | ||
186 | #main { | 186 | #main { |
187 | margin-left: 12em; | 187 | margin-left: 13em; |
188 | position: relative; | 188 | position: relative; |
189 | z-index: 10; | 189 | z-index: 10; |
190 | padding-right: 5%; | 190 | padding-right: 5%; |
191 | padding-bottom: 1em; | ||
191 | } | 192 | } |
192 | 193 | ||
193 | #links a { | 194 | #links a { |
@@ -227,7 +228,7 @@ h2:after { | |||
227 | #links li:last-child { | 228 | #links li:last-child { |
228 | position: fixed; | 229 | position: fixed; |
229 | bottom: 1em; | 230 | bottom: 1em; |
230 | width: 10%; | 231 | width: 10em; |
231 | } | 232 | } |
232 | 233 | ||
233 | #links li:last-child a:before { | 234 | #links li:last-child a:before { |
@@ -237,6 +238,61 @@ h2:after { | |||
237 | } | 238 | } |
238 | 239 | ||
239 | 240 | ||
241 | #sort { | ||
242 | padding: 0; | ||
243 | list-style-type: none; | ||
244 | opacity: 0.5; | ||
245 | display: inline-block; | ||
246 | } | ||
247 | |||
248 | #sort li { | ||
249 | display: inline; | ||
250 | font-size: 0.9em; | ||
251 | } | ||
252 | |||
253 | #sort li + li { | ||
254 | margin-left: 10px; | ||
255 | } | ||
256 | |||
257 | #sort a { | ||
258 | padding: 2px 2px 0; | ||
259 | vertical-align: middle; | ||
260 | } | ||
261 | |||
262 | #sort img { | ||
263 | vertical-align: baseline; | ||
264 | } | ||
265 | #sort img:hover { | ||
266 | cursor: pointer; | ||
267 | } | ||
268 | |||
269 | #display-mode { | ||
270 | float: right; | ||
271 | vertical-align: middle; | ||
272 | margin-top: 10px; | ||
273 | margin-bottom: 10px; | ||
274 | opacity: 0.5; | ||
275 | } | ||
276 | #listmode { | ||
277 | width: 16px; | ||
278 | display: inline-block; | ||
279 | text-decoration: none; | ||
280 | } | ||
281 | #listmode a:hover { | ||
282 | opacity: 1; | ||
283 | } | ||
284 | .tablemode { | ||
285 | background-image: url("../img/baggy/table.png"); | ||
286 | background-repeat: no-repeat; | ||
287 | background-position: bottom; | ||
288 | } | ||
289 | .listmode { | ||
290 | background-image: url("../img/baggy/list.png"); | ||
291 | background-repeat: no-repeat; | ||
292 | background-position: bottom; | ||
293 | } | ||
294 | |||
295 | |||
240 | /* ========================================================================== | 296 | /* ========================================================================== |
241 | 2 = Layout | 297 | 2 = Layout |
242 | ========================================================================== */ | 298 | ========================================================================== */ |
@@ -248,7 +304,7 @@ h2:after { | |||
248 | 304 | ||
249 | footer { | 305 | footer { |
250 | text-align: right; | 306 | text-align: right; |
251 | position: fixed; | 307 | position: relative; |
252 | bottom: 0; | 308 | bottom: 0; |
253 | right: 5em; | 309 | right: 5em; |
254 | color: #999; | 310 | color: #999; |
@@ -266,6 +322,15 @@ footer a { | |||
266 | letter-spacing:-5px; | 322 | letter-spacing:-5px; |
267 | } | 323 | } |
268 | 324 | ||
325 | .listmode .entrie { | ||
326 | width: 100%!important; | ||
327 | margin-left: 0!important; | ||
328 | } | ||
329 | |||
330 | .listmode .entrie p { | ||
331 | display: none; | ||
332 | } | ||
333 | |||
269 | .list-entries + .results { | 334 | .list-entries + .results { |
270 | margin-bottom: 2em; | 335 | margin-bottom: 2em; |
271 | } | 336 | } |
@@ -359,6 +424,7 @@ footer a { | |||
359 | content: none; | 424 | content: none; |
360 | } | 425 | } |
361 | 426 | ||
427 | |||
362 | .entrie h2 a { | 428 | .entrie h2 a { |
363 | display: block; | 429 | display: block; |
364 | text-decoration: none; | 430 | text-decoration: none; |
@@ -370,7 +436,7 @@ footer a { | |||
370 | -o-transition: all 0.5s ease; | 436 | -o-transition: all 0.5s ease; |
371 | transition: all 0.5s ease; | 437 | transition: all 0.5s ease; |
372 | } | 438 | } |
373 | 439 | /* | |
374 | .entrie h2 a:after { | 440 | .entrie h2 a:after { |
375 | content: ""; | 441 | content: ""; |
376 | position: absolute; | 442 | position: absolute; |
@@ -379,6 +445,7 @@ footer a { | |||
379 | height: 100%; | 445 | height: 100%; |
380 | left: 0; | 446 | left: 0; |
381 | } | 447 | } |
448 | */ | ||
382 | 449 | ||
383 | .entrie p { | 450 | .entrie p { |
384 | color: #666; | 451 | color: #666; |
@@ -442,6 +509,7 @@ footer a { | |||
442 | 509 | ||
443 | .pagination { | 510 | .pagination { |
444 | text-align: right; | 511 | text-align: right; |
512 | margin-bottom:50px; | ||
445 | } | 513 | } |
446 | 514 | ||
447 | .nb-results { | 515 | .nb-results { |
@@ -469,6 +537,153 @@ footer a { | |||
469 | } | 537 | } |
470 | 538 | ||
471 | /* ========================================================================== | 539 | /* ========================================================================== |
540 | 2.1 = "save a link" related styles | ||
541 | ========================================================================== */ | ||
542 | |||
543 | #bagit-form, #search-form { | ||
544 | background: rgba(0,0,0,0.5); | ||
545 | position: absolute; | ||
546 | top: 0; | ||
547 | left: 10em; | ||
548 | z-index: 20; | ||
549 | height: 100%; | ||
550 | width: 100%; | ||
551 | margin: 0; | ||
552 | margin-top: -30%; | ||
553 | padding: 2em; | ||
554 | display: none; | ||
555 | border-left: 1px #EEE solid; | ||
556 | } | ||
557 | |||
558 | #bagit-form form, #search-form form { | ||
559 | background: #FFF; | ||
560 | position: absolute; | ||
561 | top: 0; | ||
562 | left: 0; | ||
563 | z-index: 20; | ||
564 | border: 10px solid #000; | ||
565 | width: 400px; | ||
566 | height: 200px; | ||
567 | /* margin: -150px 0 0 -300px; */ | ||
568 | padding: 2em; | ||
569 | } | ||
570 | |||
571 | a#bagit-form-close, a#search-form-close { | ||
572 | background: #000; | ||
573 | color: #FFF; | ||
574 | padding: 0.2em 0.5em; | ||
575 | text-decoration: none; | ||
576 | display: inline-block; | ||
577 | float: right; | ||
578 | font-size: 0.6em; | ||
579 | } | ||
580 | a#bagit-form-close:hover, a#search-form-close:hover { | ||
581 | background: #999; | ||
582 | color: #000; | ||
583 | } | ||
584 | |||
585 | .active-current { | ||
586 | background-color: #999; | ||
587 | } | ||
588 | |||
589 | .active-current:after { | ||
590 | content: ""; | ||
591 | width: 0; | ||
592 | height: 0; | ||
593 | position: absolute; | ||
594 | border-style: solid; | ||
595 | border-width: 10px; | ||
596 | border-color: transparent #EEE transparent transparent; | ||
597 | right: 0; | ||
598 | top: 50%; | ||
599 | margin-top: -10px; | ||
600 | } | ||
601 | |||
602 | .opacity03 { | ||
603 | opacity: 0.3; | ||
604 | } | ||
605 | |||
606 | .add-to-wallabag-link-after { | ||
607 | background-color: #000; | ||
608 | color: #fff; | ||
609 | padding: 0 3px 2px 3px; | ||
610 | } | ||
611 | |||
612 | a.add-to-wallabag-link-after { | ||
613 | visibility: hidden; | ||
614 | position: absolute; | ||
615 | opacity: 0; | ||
616 | transition-duration: 2s; | ||
617 | transition-timing-function: ease-out; | ||
618 | } | ||
619 | |||
620 | #article article a:hover + a.add-to-wallabag-link-after, a.add-to-wallabag-link-after:hover { | ||
621 | opacity: 1; | ||
622 | visibility: visible; | ||
623 | transition-duration: .3s; | ||
624 | transition-timing-function: ease-in; | ||
625 | } | ||
626 | |||
627 | a.add-to-wallabag-link-after:after { | ||
628 | content: "w"; | ||
629 | } | ||
630 | |||
631 | #add-link-result { | ||
632 | font-weight: bold; | ||
633 | margin-top: 10px; | ||
634 | } | ||
635 | |||
636 | /* ========================================================================== | ||
637 | 2.2 = "search for articles" popup div related styles | ||
638 | ========================================================================== */ | ||
639 | #search-form { | ||
640 | background: rgba(0,0,0,0.5); | ||
641 | position: absolute; | ||
642 | top: 0; | ||
643 | left: 10em; | ||
644 | z-index: 20; | ||
645 | height: 100%; | ||
646 | width: 100%; | ||
647 | margin: 0; | ||
648 | margin-top: -30%; | ||
649 | padding: 2em; | ||
650 | display: none; | ||
651 | border-left: 1px #EEE solid; | ||
652 | } | ||
653 | |||
654 | #search-form form { | ||
655 | background: #FFF; | ||
656 | position: absolute; | ||
657 | top: 0; | ||
658 | left: 0; | ||
659 | z-index: 20; | ||
660 | border: 10px solid #000; | ||
661 | width: 400px; | ||
662 | height: 200px; | ||
663 | /* margin: -150px 0 0 -300px; */ | ||
664 | padding: 2em; | ||
665 | } | ||
666 | |||
667 | a#search-form-close { | ||
668 | background: #000; | ||
669 | color: #FFF; | ||
670 | padding: 0.2em 0.5em; | ||
671 | text-decoration: none; | ||
672 | display: inline-block; | ||
673 | float: right; | ||
674 | font-size: 1.2em; | ||
675 | } | ||
676 | a#search-form-close:hover { | ||
677 | background: #999; | ||
678 | color: #000; | ||
679 | } | ||
680 | |||
681 | #submit-search{ | ||
682 | margin-left: 4em; | ||
683 | margin-top:1em; | ||
684 | } | ||
685 | |||
686 | /* ========================================================================== | ||
472 | 3 = Pictos | 687 | 3 = Pictos |
473 | ========================================================================== */ | 688 | ========================================================================== */ |
474 | 689 | ||
@@ -550,6 +765,9 @@ footer a { | |||
550 | .icon-rss:before { | 765 | .icon-rss:before { |
551 | content: "\e808"; | 766 | content: "\e808"; |
552 | } | 767 | } |
768 | .icon-print:before { | ||
769 | content: "\e80d"; | ||
770 | } | ||
553 | 771 | ||
554 | /* ========================================================================== | 772 | /* ========================================================================== |
555 | Icon selected | 773 | Icon selected |
@@ -583,7 +801,7 @@ footer a { | |||
583 | } | 801 | } |
584 | 802 | ||
585 | .warning { | 803 | .warning { |
586 | font-size: 3em; | 804 | /* font-size: 3em; |
587 | color: #999; | 805 | color: #999; |
588 | font-style: italic; | 806 | font-style: italic; |
589 | position: absolute; | 807 | position: absolute; |
@@ -592,7 +810,10 @@ footer a { | |||
592 | width: 100%; | 810 | width: 100%; |
593 | text-align: center; | 811 | text-align: center; |
594 | padding-right: 5%; | 812 | padding-right: 5%; |
595 | margin-top: -2em; | 813 | margin-top: -2em;*/ |
814 | font-weight: bold; | ||
815 | display: block; | ||
816 | width: 100%; | ||
596 | } | 817 | } |
597 | 818 | ||
598 | /* ========================================================================== | 819 | /* ========================================================================== |
@@ -602,6 +823,7 @@ footer a { | |||
602 | #article { | 823 | #article { |
603 | width: 70%; | 824 | width: 70%; |
604 | margin-bottom: 3em; | 825 | margin-bottom: 3em; |
826 | text-align: justify; | ||
605 | } | 827 | } |
606 | 828 | ||
607 | #article .tags { | 829 | #article .tags { |
@@ -731,6 +953,9 @@ blockquote { | |||
731 | width: 100%; | 953 | width: 100%; |
732 | margin-left: 0; | 954 | margin-left: 0; |
733 | } | 955 | } |
956 | #display-mode { | ||
957 | display: none; | ||
958 | } | ||
734 | } | 959 | } |
735 | 960 | ||
736 | @media screen and (max-width: 500px) { | 961 | @media screen and (max-width: 500px) { |
@@ -754,8 +979,8 @@ blockquote { | |||
754 | content: none; | 979 | content: none; |
755 | } | 980 | } |
756 | .logo { | 981 | .logo { |
757 | width: 1.5em; | 982 | width: 1.25em; |
758 | height: 1.5em; | 983 | height: 1.25em; |
759 | left: 0; | 984 | left: 0; |
760 | top: 0; | 985 | top: 0; |
761 | } | 986 | } |
@@ -805,6 +1030,7 @@ blockquote { | |||
805 | margin-left: 1.5em; | 1030 | margin-left: 1.5em; |
806 | padding-right: 1.5em; | 1031 | padding-right: 1.5em; |
807 | position: static; | 1032 | position: static; |
1033 | margin-top: 3em; | ||
808 | } | 1034 | } |
809 | #article_toolbar .topPosF { | 1035 | #article_toolbar .topPosF { |
810 | display: none; | 1036 | display: none; |
@@ -820,4 +1046,12 @@ blockquote { | |||
820 | #article_toolbar a { | 1046 | #article_toolbar a { |
821 | padding: 0.3em 0.4em 0.2em; | 1047 | padding: 0.3em 0.4em 0.2em; |
822 | } | 1048 | } |
1049 | |||
1050 | #display-mode { | ||
1051 | display: none; | ||
1052 | } | ||
1053 | |||
1054 | #bagit-form, #search-form { | ||
1055 | left: 0; | ||
1056 | } | ||
823 | } | 1057 | } |
diff --git a/themes/baggy/css/print.css b/themes/baggy/css/print.css index 9aefa779..9dd6d295 100755 --- a/themes/baggy/css/print.css +++ b/themes/baggy/css/print.css | |||
@@ -17,6 +17,7 @@ img { | |||
17 | 17 | ||
18 | /* Hide useless blocks */ | 18 | /* Hide useless blocks */ |
19 | body > header, | 19 | body > header, |
20 | #article_toolbar, | ||
20 | #links, | 21 | #links, |
21 | #sort, | 22 | #sort, |
22 | body > footer, | 23 | body > footer, |
@@ -46,3 +47,16 @@ abbr[title]:after { | |||
46 | .pagination span.current { | 47 | .pagination span.current { |
47 | border-style: dashed; | 48 | border-style: dashed; |
48 | } | 49 | } |
50 | |||
51 | #main { | ||
52 | width: 100%; | ||
53 | padding: 0; | ||
54 | margin: 0; | ||
55 | margin-left: 0; | ||
56 | padding-right: 0; | ||
57 | padding-bottom: 0; | ||
58 | } | ||
59 | |||
60 | #article { | ||
61 | width: 100%; | ||
62 | } | ||
diff --git a/themes/baggy/edit-tags.twig b/themes/baggy/edit-tags.twig index 9e9012ee..2113e964 100644..100755 --- a/themes/baggy/edit-tags.twig +++ b/themes/baggy/edit-tags.twig | |||
@@ -4,6 +4,11 @@ | |||
4 | {% include '_menu.twig' %} | 4 | {% include '_menu.twig' %} |
5 | {% endblock %} | 5 | {% endblock %} |
6 | {% block content %} | 6 | {% block content %} |
7 | |||
8 | <script src="{{ poche_url }}themes/default/js/jquery-ui-1.10.4.custom.min.js"></script> | ||
9 | <script src="{{ poche_url }}themes/default/js/autoCompleteTags.js"></script> | ||
10 | <link rel="stylesheet" href="{{ poche_url }}themes/default/css/jquery-ui-1.10.4.custom.min.css" media="all"> | ||
11 | |||
7 | <div id="article"> | 12 | <div id="article"> |
8 | <h2>{{ entry.title|raw }}</21> | 13 | <h2>{{ entry.title|raw }}</21> |
9 | </div> | 14 | </div> |
@@ -17,7 +22,8 @@ | |||
17 | <input type="hidden" name="entry_id" value="{{ entry_id }}" /> | 22 | <input type="hidden" name="entry_id" value="{{ entry_id }}" /> |
18 | <label for="value">Add tags: </label><input type="text" placeholder="interview, editorial, video" id="value" name="value" required="required" /> | 23 | <label for="value">Add tags: </label><input type="text" placeholder="interview, editorial, video" id="value" name="value" required="required" /> |
19 | <input type="submit" value="Tag" /> | 24 | <input type="submit" value="Tag" /> |
20 | <p>{% trans "You can enter multiple tags, separated by commas." %}</p> | 25 | <p>{% trans "Start typing for auto complete." %}<br> |
26 | {% trans "You can enter multiple tags, separated by commas." %}</p> | ||
21 | </form> | 27 | </form> |
22 | <a class="icon icon-reply return" href="./?view=view&id={{ entry_id }}">{% trans "return to article" %}</a> | 28 | <a class="icon icon-reply return" href="./?view=view&id={{ entry_id }}">{% trans "return to article" %}</a> |
23 | {% endblock %} | 29 | {% endblock %} |
diff --git a/themes/baggy/fonts/icomoon.eot b/themes/baggy/fonts/icomoon.eot index 02f53b36..56323516 100755..100644 --- a/themes/baggy/fonts/icomoon.eot +++ b/themes/baggy/fonts/icomoon.eot | |||
Binary files differ | |||
diff --git a/themes/baggy/fonts/icomoon.svg b/themes/baggy/fonts/icomoon.svg index d9c35b98..b4586136 100755..100644 --- a/themes/baggy/fonts/icomoon.svg +++ b/themes/baggy/fonts/icomoon.svg | |||
@@ -1,7 +1,21 @@ | |||
1 | <?xml version="1.0" standalone="no"?> | 1 | <?xml version="1.0" standalone="no"?> |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > | 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > |
3 | <svg xmlns="http://www.w3.org/2000/svg"> | 3 | <svg xmlns="http://www.w3.org/2000/svg"> |
4 | <metadata>Generated by IcoMoon</metadata> | 4 | <metadata> |
5 | <json> | ||
6 | { | ||
7 | "fontFamily": "icomoon", | ||
8 | "majorVersion": 1, | ||
9 | "minorVersion": 0, | ||
10 | "version": "Version 1.0", | ||
11 | "fontId": "icomoon", | ||
12 | "psName": "icomoon", | ||
13 | "subFamily": "Regular", | ||
14 | "fullName": "icomoon", | ||
15 | "description": "Generated by IcoMoon" | ||
16 | } | ||
17 | </json> | ||
18 | </metadata> | ||
5 | <defs> | 19 | <defs> |
6 | <font id="icomoon" horiz-adv-x="512"> | 20 | <font id="icomoon" horiz-adv-x="512"> |
7 | <font-face units-per-em="512" ascent="480" descent="-32" /> | 21 | <font-face units-per-em="512" ascent="480" descent="-32" /> |
@@ -23,4 +37,5 @@ | |||
23 | <glyph unicode="" d="M475.648 50.432v219.136q-9.216-10.24-19.968-18.944-76.288-58.368-121.856-96.256-14.336-12.288-23.552-19.456t-24.576-13.824-29.184-6.656h-1.024q-13.312 0-29.184 6.656t-24.576 13.824-23.552 19.456q-45.056 37.888-121.856 96.256-10.752 8.704-19.968 18.944v-219.136q0-4.096 3.072-6.656t6.144-2.56h420.864q3.584 0 6.144 2.56t3.072 6.656zM475.648 350.464v7.168t-0.512 3.584-0.512 3.584-1.536 2.56-2.56 2.048-4.096 1.024h-420.864q-3.584 0-6.144-3.072t-3.072-6.144q0-48.128 41.984-81.408 55.296-43.52 114.688-90.624 2.048-1.024 10.24-8.192t12.8-10.752 12.8-9.216 14.336-7.68 12.288-2.56h1.024q5.632 0 12.288 2.56t14.336 7.68 12.8 9.216 12.8 10.752 10.24 8.192q59.392 47.104 114.688 90.624 15.36 12.288 28.672 33.28t13.312 37.376zM512 361.216v-310.784q0-18.944-13.312-32.256t-32.256-13.824h-420.864q-18.432 0-32.256 13.824t-13.312 32.256v310.784q0 18.944 13.312 32.256t32.256 13.312h420.864q18.944 0 32.256-13.312t13.312-32.256z" /> | 37 | <glyph unicode="" d="M475.648 50.432v219.136q-9.216-10.24-19.968-18.944-76.288-58.368-121.856-96.256-14.336-12.288-23.552-19.456t-24.576-13.824-29.184-6.656h-1.024q-13.312 0-29.184 6.656t-24.576 13.824-23.552 19.456q-45.056 37.888-121.856 96.256-10.752 8.704-19.968 18.944v-219.136q0-4.096 3.072-6.656t6.144-2.56h420.864q3.584 0 6.144 2.56t3.072 6.656zM475.648 350.464v7.168t-0.512 3.584-0.512 3.584-1.536 2.56-2.56 2.048-4.096 1.024h-420.864q-3.584 0-6.144-3.072t-3.072-6.144q0-48.128 41.984-81.408 55.296-43.52 114.688-90.624 2.048-1.024 10.24-8.192t12.8-10.752 12.8-9.216 14.336-7.68 12.288-2.56h1.024q5.632 0 12.288 2.56t14.336 7.68 12.8 9.216 12.8 10.752 10.24 8.192q59.392 47.104 114.688 90.624 15.36 12.288 28.672 33.28t13.312 37.376zM512 361.216v-310.784q0-18.944-13.312-32.256t-32.256-13.824h-420.864q-18.432 0-32.256 13.824t-13.312 32.256v310.784q0 18.944 13.312 32.256t32.256 13.312h420.864q18.944 0 32.256-13.312t13.312-32.256z" /> |
24 | <glyph unicode="" d="M0 133.888l256 256 256-256-75.776-75.776-180.224 179.712-180.224-179.712z" /> | 38 | <glyph unicode="" d="M0 133.888l256 256 256-256-75.776-75.776-180.224 179.712-180.224-179.712z" /> |
25 | <glyph unicode="" d="M25.6 279.296q62.464-35.84 168.96-35.84t168.96 35.84l-27.648-248.832q-1.024-7.168-17.92-18.432t-51.2-22.016-72.192-10.752-71.68 10.752-51.2 22.016-18.432 18.432zM275.456 432.896q48.128-9.216 80.896-28.16t32.768-36.352v-5.12q0-29.696-57.344-50.688t-137.216-20.992-137.216 20.992-57.344 50.688v5.12q0 17.408 32.768 36.352t80.896 28.16l21.504 24.576q11.264 13.312 35.84 13.312h47.104q26.624 0 35.84-13.312zM247.808 375.552h43.008q-47.104 56.32-53.248 64.512-7.168 8.192-16.384 8.192h-52.224q-11.264 0-16.384-8.192l-54.272-64.512h43.008l32.768 33.792h41.984z" horiz-adv-x="389" /> | 39 | <glyph unicode="" d="M25.6 279.296q62.464-35.84 168.96-35.84t168.96 35.84l-27.648-248.832q-1.024-7.168-17.92-18.432t-51.2-22.016-72.192-10.752-71.68 10.752-51.2 22.016-18.432 18.432zM275.456 432.896q48.128-9.216 80.896-28.16t32.768-36.352v-5.12q0-29.696-57.344-50.688t-137.216-20.992-137.216 20.992-57.344 50.688v5.12q0 17.408 32.768 36.352t80.896 28.16l21.504 24.576q11.264 13.312 35.84 13.312h47.104q26.624 0 35.84-13.312zM247.808 375.552h43.008q-47.104 56.32-53.248 64.512-7.168 8.192-16.384 8.192h-52.224q-11.264 0-16.384-8.192l-54.272-64.512h43.008l32.768 33.792h41.984z" horiz-adv-x="389" /> |
40 | <glyph unicode="" d="M128 448h256v-64h-256zM480 352h-448c-17.6 0-32-14.4-32-32v-160c0-17.6 14.398-32 32-32h96v-128h256v128h96c17.6 0 32 14.4 32 32v160c0 17.6-14.4 32-32 32zM352 32h-192v160h192v-160zM487.2 304c0-12.813-10.387-23.2-23.199-23.2-12.813 0-23.201 10.387-23.201 23.2s10.388 23.2 23.201 23.2c12.813 0 23.199-10.387 23.199-23.2z" /> | ||
26 | </font></defs></svg> \ No newline at end of file | 41 | </font></defs></svg> \ No newline at end of file |
diff --git a/themes/baggy/fonts/icomoon.ttf b/themes/baggy/fonts/icomoon.ttf index 3860dd04..bb1f21f8 100755..100644 --- a/themes/baggy/fonts/icomoon.ttf +++ b/themes/baggy/fonts/icomoon.ttf | |||
Binary files differ | |||
diff --git a/themes/baggy/fonts/icomoon.woff b/themes/baggy/fonts/icomoon.woff index c3a91ed7..bab13779 100755..100644 --- a/themes/baggy/fonts/icomoon.woff +++ b/themes/baggy/fonts/icomoon.woff | |||
Binary files differ | |||
diff --git a/themes/baggy/home.twig b/themes/baggy/home.twig index 4f9db063..3942d3bf 100644..100755 --- a/themes/baggy/home.twig +++ b/themes/baggy/home.twig | |||
@@ -18,17 +18,27 @@ | |||
18 | {% if entries is empty %} | 18 | {% if entries is empty %} |
19 | <div class="messages warning"><p>{% trans "No articles found." %}</p></div> | 19 | <div class="messages warning"><p>{% trans "No articles found." %}</p></div> |
20 | {% else %} | 20 | {% else %} |
21 | <div> | ||
22 | {% include '_display-mode.twig' %} | ||
23 | {% include '_sorting.twig' %} | ||
24 | </div> | ||
21 | {% block pager %} | 25 | {% block pager %} |
22 | {% if nb_results > 1 %} | 26 | {% if nb_results > 1 %} |
23 | <div class="results"> | 27 | <div class="results"> |
24 | <div class="nb-results">{{ nb_results }} {% trans "results" %}</div> | 28 | <div class="nb-results">{{ nb_results }} {% trans "results" %}{% if search_term is defined %} {% trans %}found for « {{ search_term }} »{% endtrans %}{% endif %}</div> |
25 | {{ page_links | raw }} | 29 | {{ page_links | raw }} |
26 | </div> | 30 | </div> |
31 | {% elseif nb_results == 1 %} | ||
32 | {% if search_term is defined %} | ||
33 | <div class="results"> | ||
34 | <div class="nb-results">{% trans "Only one result found for " %} « {{ search_term }} »</div> | ||
35 | </div> | ||
36 | {% endif %} | ||
27 | {% endif %} | 37 | {% endif %} |
28 | {% endblock %} | 38 | {% endblock %} |
29 | <div class="list-entries"> | 39 | <div id="list-entries" class="list-entries"> |
30 | {% for entry in entries %} | 40 | {% for entry in entries %} |
31 | <div id="entry-{{ entry.id|e }}" class="entrie"> | 41 | <div id="entry-{{ entry.id|e }}" class="entrie"{% if listmode %} style="width:100%; margin-left:0;"{% endif %}> |
32 | <h2><a href="index.php?view=view&id={{ entry.id|e }}">{{ entry.title|raw }}</a></h2> | 42 | <h2><a href="index.php?view=view&id={{ entry.id|e }}">{{ entry.title|raw }}</a></h2> |
33 | {% if entry.content| getReadingTime > 0 %} | 43 | {% if entry.content| getReadingTime > 0 %} |
34 | <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> | 44 | <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> |
@@ -46,7 +56,14 @@ | |||
46 | 56 | ||
47 | {% endfor %} | 57 | {% endfor %} |
48 | </div> | 58 | </div> |
59 | {{ block('pager') }} | ||
49 | {% 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 %} | 60 | {% 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 %} |
61 | |||
62 | {% 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 %} | ||
63 | |||
64 | {% if tag %}<a title="{% trans "Download the articles from this tag in an epub" %}" href="./?epub&method=tag&tag={{ tag.value }}">{% trans "Download the articles from this tag in an epub" %}</a> | ||
65 | {% elseif search_term is defined %}<a title="{% trans "Download the articles from this search in an epub" %}" href="./?epub&method=search&search={{ search_term }}">{% trans "Download the articles from this search in an epub" %}</a> | ||
66 | {% else %}<a title="{% trans "Download the articles from this category in an epub" %}" href="./?epub&method=category&category={{ view }}">{% trans "Download the articles from this category in an epub" %}</a>{% endif %} | ||
67 | |||
50 | {% endif %} | 68 | {% endif %} |
51 | {{ block('pager') }} | ||
52 | {% endblock %} | 69 | {% endblock %} |
diff --git a/themes/baggy/img/baggy/blank.png b/themes/baggy/img/baggy/blank.png new file mode 100755 index 00000000..63e09844 --- /dev/null +++ b/themes/baggy/img/baggy/blank.png | |||
Binary files differ | |||
diff --git a/themes/baggy/img/baggy/down.png b/themes/baggy/img/baggy/down.png new file mode 100644 index 00000000..b9d536a7 --- /dev/null +++ b/themes/baggy/img/baggy/down.png | |||
Binary files differ | |||
diff --git a/themes/baggy/img/baggy/list.png b/themes/baggy/img/baggy/list.png new file mode 100755 index 00000000..bd5aff5a --- /dev/null +++ b/themes/baggy/img/baggy/list.png | |||
Binary files differ | |||
diff --git a/themes/baggy/img/baggy/table.png b/themes/baggy/img/baggy/table.png new file mode 100755 index 00000000..859c4cd8 --- /dev/null +++ b/themes/baggy/img/baggy/table.png | |||
Binary files differ | |||
diff --git a/themes/baggy/img/baggy/top.png b/themes/baggy/img/baggy/top.png new file mode 100644 index 00000000..954a8c0a --- /dev/null +++ b/themes/baggy/img/baggy/top.png | |||
Binary files differ | |||
diff --git a/themes/baggy/js/autoClose.js b/themes/baggy/js/autoClose.js deleted file mode 100644 index e9145b7e..00000000 --- a/themes/baggy/js/autoClose.js +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | $(document).ready(function() { | ||
2 | current_url = window.location.href | ||
3 | if (current_url.match("&closewin=true")) { | ||
4 | window.close(); | ||
5 | } | ||
6 | }); | ||
diff --git a/themes/baggy/js/init.js b/themes/baggy/js/init.js index c1d3c0ec..00470fbf 100755 --- a/themes/baggy/js/init.js +++ b/themes/baggy/js/init.js | |||
@@ -1,12 +1,48 @@ | |||
1 | document.addEventListener('DOMContentLoaded', function() { | 1 | $.fn.ready(function() { |
2 | var menu = document.getElementById('menu'); | 2 | |
3 | 3 | var $listmode = $('#listmode'), | |
4 | menu.addEventListener('click', function(){ | 4 | $listentries = $("#list-entries"); |
5 | if(this.nextElementSibling.style.display === "block") { | 5 | |
6 | this.nextElementSibling.style.display = "none"; | 6 | /* ========================================================================== |
7 | }else { | 7 | Menu |
8 | this.nextElementSibling.style.display = "block"; | 8 | ========================================================================== */ |
9 | |||
10 | $("#menu").click(function(){ | ||
11 | $("#links").toggle(); | ||
12 | }); | ||
13 | |||
14 | /* ========================================================================== | ||
15 | List mode or Table Mode | ||
16 | ========================================================================== */ | ||
17 | |||
18 | $listmode.click(function(){ | ||
19 | if ( $.cookie("listmode") == 1 ) { | ||
20 | // Cookie | ||
21 | $.removeCookie("listmode"); | ||
22 | |||
23 | $listentries.removeClass("listmode"); | ||
24 | $listmode.removeClass("tablemode"); | ||
25 | $listmode.addClass("listmode"); | ||
9 | } | 26 | } |
10 | 27 | else { | |
28 | // Cookie | ||
29 | $.cookie("listmode", 1, {expires: 365}); | ||
30 | |||
31 | $listentries.addClass("listmode"); | ||
32 | $listmode.removeClass("listmode"); | ||
33 | $listmode.addClass("tablemode"); | ||
34 | } | ||
35 | |||
11 | }); | 36 | }); |
12 | }); \ No newline at end of file | 37 | |
38 | /* ========================================================================== | ||
39 | Cookie listmode | ||
40 | ========================================================================== */ | ||
41 | |||
42 | if ( $.cookie("listmode") == 1 ) { | ||
43 | $listentries.addClass("listmode"); | ||
44 | $listmode.removeClass("listmode"); | ||
45 | $listmode.addClass("tablemode"); | ||
46 | } | ||
47 | |||
48 | }); | ||
diff --git a/themes/baggy/js/jquery-2.0.3.min.js b/themes/baggy/js/jquery-2.0.3.min.js deleted file mode 100644 index a4dd0a2f..00000000 --- a/themes/baggy/js/jquery-2.0.3.min.js +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | (function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ct={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1></$2>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1></$2>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(xt[0].contentWindow||xt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=Mt(e,t),xt.detach()),Nt[e]=n),n}function Mt(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,t){x.cssHooks[t]={get:function(e,n,r){return n?0===e.offsetWidth&&bt.test(x.css(e,"display"))?x.swap(e,Et,function(){return Pt(e,t,r)}):Pt(e,t,r):undefined},set:function(e,n,r){var i=r&&qt(e);return Ot(e,n,r?Ft(e,t,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,t){return t?x.swap(e,{display:"inline-block"},vt,[e,"marginRight"]):undefined}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,t){x.cssHooks[t]={get:function(e,n){return n?(n=vt(e,t),Ct.test(n)?x(e).position()[t]+"px":n):undefined}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+jt[r]+t]=o[r]||o[r-2]||o[0];return i}},wt.test(e)||(x.cssHooks[e+t].set=Ot)});var Wt=/%20/g,$t=/\[\]$/,Bt=/\r?\n/g,It=/^(?:submit|button|image|reset|file)$/i,zt=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&zt.test(this.nodeName)&&!It.test(e)&&(this.checked||!ot.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(Bt,"\r\n")}}):{name:t.name,value:n.replace(Bt,"\r\n")}}).get()}}),x.param=function(e,t){var n,r=[],i=function(e,t){t=x.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(t===undefined&&(t=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){i(this.name,this.value)});else for(n in e)_t(n,e[n],t,i);return r.join("&").replace(Wt,"+")};function _t(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||$t.test(e)?r(e,i):_t(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)_t(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var Xt,Ut,Yt=x.now(),Vt=/\?/,Gt=/#.*$/,Jt=/([?&])_=[^&]*/,Qt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Kt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Zt=/^(?:GET|HEAD)$/,en=/^\/\//,tn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,nn=x.fn.load,rn={},on={},sn="*/".concat("*");try{Ut=i.href}catch(an){Ut=o.createElement("a"),Ut.href="",Ut=Ut.href}Xt=tn.exec(Ut.toLowerCase())||[];function un(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function ln(e,t,n,r){var i={},o=e===on;function s(a){var u;return i[a]=!0,x.each(e[a]||[],function(e,a){var l=a(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):undefined:(t.dataTypes.unshift(l),s(l),!1)}),u}return s(t.dataTypes[0])||!i["*"]&&s("*")}function cn(e,t){var n,r,i=x.ajaxSettings.flatOptions||{};for(n in t)t[n]!==undefined&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,t,n){if("string"!=typeof e&&nn)return nn.apply(this,arguments);var r,i,o,s=this,a=e.indexOf(" ");return a>=0&&(r=e.slice(a),e=e.slice(0,a)),x.isFunction(t)?(n=t,t=undefined):t&&"object"==typeof t&&(i="POST"),s.length>0&&x.ajax({url:e,type:i,dataType:"html",data:t}).done(function(e){o=arguments,s.html(r?x("<div>").append(x.parseHTML(e)).find(r):e)}).complete(n&&function(e,t){s.each(n,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ut,type:"GET",isLocal:Kt.test(Xt[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":sn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?cn(cn(e,x.ajaxSettings),t):cn(x.ajaxSettings,e)},ajaxPrefilter:un(rn),ajaxTransport:un(on),ajax:function(e,t){"object"==typeof e&&(t=e,e=undefined),t=t||{};var n,r,i,o,s,a,u,l,c=x.ajaxSetup({},t),p=c.context||c,f=c.context&&(p.nodeType||p.jquery)?x(p):x.event,h=x.Deferred(),d=x.Callbacks("once memory"),g=c.statusCode||{},m={},y={},v=0,b="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===v){if(!o){o={};while(t=Qt.exec(i))o[t[1].toLowerCase()]=t[2]}t=o[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===v?i:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return v||(e=y[n]=y[n]||e,m[e]=t),this},overrideMimeType:function(e){return v||(c.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>v)for(t in e)g[t]=[g[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||b;return n&&n.abort(t),k(0,t),this}};if(h.promise(T).complete=d.add,T.success=T.done,T.error=T.fail,c.url=((e||c.url||Ut)+"").replace(Gt,"").replace(en,Xt[1]+"//"),c.type=t.method||t.type||c.method||c.type,c.dataTypes=x.trim(c.dataType||"*").toLowerCase().match(w)||[""],null==c.crossDomain&&(a=tn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===Xt[1]&&a[2]===Xt[2]&&(a[3]||("http:"===a[1]?"80":"443"))===(Xt[3]||("http:"===Xt[1]?"80":"443")))),c.data&&c.processData&&"string"!=typeof c.data&&(c.data=x.param(c.data,c.traditional)),ln(rn,c,t,T),2===v)return T;u=c.global,u&&0===x.active++&&x.event.trigger("ajaxStart"),c.type=c.type.toUpperCase(),c.hasContent=!Zt.test(c.type),r=c.url,c.hasContent||(c.data&&(r=c.url+=(Vt.test(r)?"&":"?")+c.data,delete c.data),c.cache===!1&&(c.url=Jt.test(r)?r.replace(Jt,"$1_="+Yt++):r+(Vt.test(r)?"&":"?")+"_="+Yt++)),c.ifModified&&(x.lastModified[r]&&T.setRequestHeader("If-Modified-Since",x.lastModified[r]),x.etag[r]&&T.setRequestHeader("If-None-Match",x.etag[r])),(c.data&&c.hasContent&&c.contentType!==!1||t.contentType)&&T.setRequestHeader("Content-Type",c.contentType),T.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+("*"!==c.dataTypes[0]?", "+sn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)T.setRequestHeader(l,c.headers[l]);if(c.beforeSend&&(c.beforeSend.call(p,T,c)===!1||2===v))return T.abort();b="abort";for(l in{success:1,error:1,complete:1})T[l](c[l]);if(n=ln(on,c,t,T)){T.readyState=1,u&&f.trigger("ajaxSend",[T,c]),c.async&&c.timeout>0&&(s=setTimeout(function(){T.abort("timeout")},c.timeout));try{v=1,n.send(m,k)}catch(C){if(!(2>v))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,t,o,a){var l,m,y,b,w,C=t;2!==v&&(v=2,s&&clearTimeout(s),n=undefined,i=a||"",T.readyState=e>0?4:0,l=e>=200&&300>e||304===e,o&&(b=pn(c,T,o)),b=fn(c,b,T,l),l?(c.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(x.lastModified[r]=w),w=T.getResponseHeader("etag"),w&&(x.etag[r]=w)),204===e||"HEAD"===c.type?C="nocontent":304===e?C="notmodified":(C=b.state,m=b.data,y=b.error,l=!y)):(y=C,(e||!C)&&(C="error",0>e&&(e=0))),T.status=e,T.statusText=(t||C)+"",l?h.resolveWith(p,[m,C,T]):h.rejectWith(p,[T,C,y]),T.statusCode(g),g=undefined,u&&f.trigger(l?"ajaxSuccess":"ajaxError",[T,c,l?m:y]),d.fireWith(p,[T,C]),u&&(f.trigger("ajaxComplete",[T,c]),--x.active||x.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,t){return x.get(e,undefined,t,"script")}}),x.each(["get","post"],function(e,t){x[t]=function(e,n,r,i){return x.isFunction(n)&&(i=i||r,r=n,n=undefined),x.ajax({url:e,type:t,dataType:i,data:n,success:r})}});function pn(e,t,n){var r,i,o,s,a=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),r===undefined&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}s||(s=i)}o=o||s}return o?(o!==u[0]&&u.unshift(o),n[o]):undefined}function fn(e,t,n,r){var i,o,s,a,u,l={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)l[s.toLowerCase()]=e.converters[s];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(s=l[u+" "+o]||l["* "+o],!s)for(i in l)if(a=i.split(" "),a[1]===o&&(s=l[u+" "+a[0]]||l["* "+a[0]])){s===!0?s=l[i]:l[i]!==!0&&(o=a[0],c.unshift(a[1]));break}if(s!==!0)if(s&&e["throws"])t=s(t);else try{t=s(t)}catch(p){return{state:"parsererror",error:s?p:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===undefined&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),x.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,i){t=x("<script>").prop({async:!0,charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&i("error"===e.type?404:200,e.type)}),o.head.appendChild(t[0])},abort:function(){n&&n()}}}});var hn=[],dn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=hn.pop()||x.expando+"_"+Yt++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,s,a=t.jsonp!==!1&&(dn.test(t.url)?"url":"string"==typeof t.data&&!(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&dn.test(t.data)&&"data");return a||"jsonp"===t.dataTypes[0]?(i=t.jsonpCallback=x.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,a?t[a]=t[a].replace(dn,"$1"+i):t.jsonp!==!1&&(t.url+=(Vt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return s||x.error(i+" was not called"),s[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){s=arguments},r.always(function(){e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,hn.push(i)),s&&x.isFunction(o)&&o(s[0]),s=o=undefined}),"script"):undefined}),x.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(e){}};var gn=x.ajaxSettings.xhr(),mn={0:200,1223:204},yn=0,vn={};e.ActiveXObject&&x(e).on("unload",function(){for(var e in vn)vn[e]();vn=undefined}),x.support.cors=!!gn&&"withCredentials"in gn,x.support.ajax=gn=!!gn,x.ajaxTransport(function(e){var t;return x.support.cors||gn&&!e.crossDomain?{send:function(n,r){var i,o,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(i in e.xhrFields)s[i]=e.xhrFields[i];e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(i in n)s.setRequestHeader(i,n[i]);t=function(e){return function(){t&&(delete vn[o],t=s.onload=s.onerror=null,"abort"===e?s.abort():"error"===e?r(s.status||404,s.statusText):r(mn[s.status]||s.status,s.statusText,"string"==typeof s.responseText?{text:s.responseText}:undefined,s.getAllResponseHeaders()))}},s.onload=t(),s.onerror=t("error"),t=vn[o=yn++]=t("abort"),s.send(e.hasContent&&e.data||null)},abort:function(){t&&t()}}:undefined});var xn,bn,wn=/^(?:toggle|show|hide)$/,Tn=RegExp("^(?:([+-])=|)("+b+")([a-z%]*)$","i"),Cn=/queueHooks$/,kn=[An],Nn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Tn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),s=(x.cssNumber[e]||"px"!==o&&+r)&&Tn.exec(x.css(n.elem,e)),a=1,u=20;if(s&&s[3]!==o){o=o||s[3],i=i||[],s=+r||1;do a=a||".5",s/=a,x.style(n.elem,e,s+o);while(a!==(a=n.cur()/r)&&1!==a&&--u)}return i&&(s=n.start=+s||+r||0,n.unit=o,n.end=i[1]?s+(i[1]+1)*i[2]:+i[2]),n}]};function En(){return setTimeout(function(){xn=undefined}),xn=x.now()}function Sn(e,t,n){var r,i=(Nn[t]||[]).concat(Nn["*"]),o=0,s=i.length;for(;s>o;o++)if(r=i[o].call(n,t,e))return r}function jn(e,t,n){var r,i,o=0,s=kn.length,a=x.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=xn||En(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,s=0,u=l.tweens.length;for(;u>s;s++)l.tweens[s].run(o);return a.notifyWith(e,[l,o,n]),1>o&&u?n:(a.resolveWith(e,[l]),!1)},l=a.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:xn||En(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?a.resolveWith(e,[l,t]):a.rejectWith(e,[l,t]),this}}),c=l.props;for(Dn(c,l.opts.specialEasing);s>o;o++)if(r=kn[o].call(l,e,c,l.opts))return r;return x.map(c,Sn,l),x.isFunction(l.opts.start)&&l.opts.start.call(e,l),x.fx.timer(x.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function Dn(e,t){var n,r,i,o,s;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),s=x.cssHooks[r],s&&"expand"in s){o=s.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(jn,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Nn[n]=Nn[n]||[],Nn[n].unshift(t)},prefilter:function(e,t){t?kn.unshift(e):kn.push(e)}});function An(e,t,n){var r,i,o,s,a,u,l=this,c={},p=e.style,f=e.nodeType&&Lt(e),h=q.get(e,"fxshow");n.queue||(a=x._queueHooks(e,"fx"),null==a.unqueued&&(a.unqueued=0,u=a.empty.fire,a.empty.fire=function(){a.unqueued||u()}),a.unqueued++,l.always(function(){l.always(function(){a.unqueued--,x.queue(e,"fx").length||a.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(p.display="inline-block")),n.overflow&&(p.overflow="hidden",l.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],wn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show")){if("show"!==i||!h||h[r]===undefined)continue;f=!0}c[r]=h&&h[r]||x.style(e,r)}if(!x.isEmptyObject(c)){h?"hidden"in h&&(f=h.hidden):h=q.access(e,"fxshow",{}),o&&(h.hidden=!f),f?x(e).show():l.done(function(){x(e).hide()}),l.done(function(){var t;q.remove(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)s=Sn(f?h[r]:0,r,l),r in h||(h[r]=s.start,f&&(s.end=s.start,s.start="width"===r||"height"===r?1:0))}}function Ln(e,t,n,r,i){return new Ln.prototype.init(e,t,n,r,i)}x.Tween=Ln,Ln.prototype={constructor:Ln,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=Ln.propHooks[this.prop];return e&&e.get?e.get(this):Ln.propHooks._default.get(this)},run:function(e){var t,n=Ln.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ln.propHooks._default.set(this),this}},Ln.prototype.init.prototype=Ln.prototype,Ln.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Ln.propHooks.scrollTop=Ln.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(qn(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Lt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),s=function(){var t=jn(this,x.extend({},e),o);(i||q.get(this,"finish"))&&t.stop(!0)};return s.finish=s,i||o.queue===!1?this.each(s):this.queue(o.queue,s)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=undefined),t&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=x.timers,s=q.get(this);if(i)s[i]&&s[i].stop&&r(s[i]);else for(i in s)s[i]&&s[i].stop&&Cn.test(i)&&r(s[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));(t||!n)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=q.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,s=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;s>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function qn(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=jt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:qn("show"),slideUp:qn("hide"),slideToggle:qn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=Ln.prototype.init,x.fx.tick=function(){var e,t=x.timers,n=0;for(xn=x.now();t.length>n;n++)e=t[n],e()||t[n]!==e||t.splice(n--,1);t.length||x.fx.stop(),xn=undefined},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){bn||(bn=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(bn),bn=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===undefined?this:this.each(function(t){x.offset.setOffset(this,e,t)});var t,n,i=this[0],o={top:0,left:0},s=i&&i.ownerDocument;if(s)return t=s.documentElement,x.contains(t,i)?(typeof i.getBoundingClientRect!==r&&(o=i.getBoundingClientRect()),n=Hn(s),{top:o.top+n.pageYOffset-t.clientTop,left:o.left+n.pageXOffset-t.clientLeft}):o},x.offset={setOffset:function(e,t,n){var r,i,o,s,a,u,l,c=x.css(e,"position"),p=x(e),f={};"static"===c&&(e.style.position="relative"),a=p.offset(),o=x.css(e,"top"),u=x.css(e,"left"),l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1,l?(r=p.position(),s=r.top,i=r.left):(s=parseFloat(o)||0,i=parseFloat(u)||0),x.isFunction(t)&&(t=t.call(e,n,a)),null!=t.top&&(f.top=t.top-a.top+s),null!=t.left&&(f.left=t.left-a.left+i),"using"in t?t.using.call(e,f):p.css(f)}},x.fn.extend({position:function(){if(this[0]){var e,t,n=this[0],r={top:0,left:0};return"fixed"===x.css(n,"position")?t=n.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(r=e.offset()),r.top+=x.css(e[0],"borderTopWidth",!0),r.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-r.top-x.css(n,"marginTop",!0),left:t.left-r.left-x.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,n){var r="pageYOffset"===n;x.fn[t]=function(i){return x.access(this,function(t,i,o){var s=Hn(t);return o===undefined?s?s[n]:t[i]:(s?s.scrollTo(r?e.pageXOffset:o,r?o:e.pageYOffset):t[i]=o,undefined)},t,i,arguments.length,null)}});function Hn(e){return x.isWindow(e)?e:9===e.nodeType&&e.defaultView}x.each({Height:"height",Width:"width"},function(e,t){x.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){x.fn[r]=function(r,i){var o=arguments.length&&(n||"boolean"!=typeof r),s=n||(r===!0||i===!0?"margin":"border");return x.access(this,function(t,n,r){var i;return x.isWindow(t)?t.document.documentElement["client"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):r===undefined?x.css(t,n,s):x.style(t,n,r,s)},t,o?r:undefined,o,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}),"object"==typeof e&&"object"==typeof e.document&&(e.jQuery=e.$=x)})(window); \ No newline at end of file | ||
diff --git a/themes/baggy/js/jquery.cookie.js b/themes/baggy/js/jquery.cookie.js new file mode 100755 index 00000000..92719000 --- /dev/null +++ b/themes/baggy/js/jquery.cookie.js | |||
@@ -0,0 +1,117 @@ | |||
1 | /*! | ||
2 | * jQuery Cookie Plugin v1.4.0 | ||
3 | * https://github.com/carhartl/jquery-cookie | ||
4 | * | ||
5 | * Copyright 2013 Klaus Hartl | ||
6 | * Released under the MIT license | ||
7 | */ | ||
8 | (function (factory) { | ||
9 | if (typeof define === 'function' && define.amd) { | ||
10 | // AMD. Register as anonymous module. | ||
11 | define(['jquery'], factory); | ||
12 | } else { | ||
13 | // Browser globals. | ||
14 | factory(jQuery); | ||
15 | } | ||
16 | }(function ($) { | ||
17 | |||
18 | var pluses = /\+/g; | ||
19 | |||
20 | function encode(s) { | ||
21 | return config.raw ? s : encodeURIComponent(s); | ||
22 | } | ||
23 | |||
24 | function decode(s) { | ||
25 | return config.raw ? s : decodeURIComponent(s); | ||
26 | } | ||
27 | |||
28 | function stringifyCookieValue(value) { | ||
29 | return encode(config.json ? JSON.stringify(value) : String(value)); | ||
30 | } | ||
31 | |||
32 | function parseCookieValue(s) { | ||
33 | if (s.indexOf('"') === 0) { | ||
34 | // This is a quoted cookie as according to RFC2068, unescape... | ||
35 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); | ||
36 | } | ||
37 | |||
38 | try { | ||
39 | // Replace server-side written pluses with spaces. | ||
40 | // If we can't decode the cookie, ignore it, it's unusable. | ||
41 | s = decodeURIComponent(s.replace(pluses, ' ')); | ||
42 | } catch(e) { | ||
43 | return; | ||
44 | } | ||
45 | |||
46 | try { | ||
47 | // If we can't parse the cookie, ignore it, it's unusable. | ||
48 | return config.json ? JSON.parse(s) : s; | ||
49 | } catch(e) {} | ||
50 | } | ||
51 | |||
52 | function read(s, converter) { | ||
53 | var value = config.raw ? s : parseCookieValue(s); | ||
54 | return $.isFunction(converter) ? converter(value) : value; | ||
55 | } | ||
56 | |||
57 | var config = $.cookie = function (key, value, options) { | ||
58 | |||
59 | // Write | ||
60 | if (value !== undefined && !$.isFunction(value)) { | ||
61 | options = $.extend({}, config.defaults, options); | ||
62 | |||
63 | if (typeof options.expires === 'number') { | ||
64 | var days = options.expires, t = options.expires = new Date(); | ||
65 | t.setDate(t.getDate() + days); | ||
66 | } | ||
67 | |||
68 | return (document.cookie = [ | ||
69 | encode(key), '=', stringifyCookieValue(value), | ||
70 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE | ||
71 | options.path ? '; path=' + options.path : '', | ||
72 | options.domain ? '; domain=' + options.domain : '', | ||
73 | options.secure ? '; secure' : '' | ||
74 | ].join('')); | ||
75 | } | ||
76 | |||
77 | // Read | ||
78 | |||
79 | var result = key ? undefined : {}; | ||
80 | |||
81 | // To prevent the for loop in the first place assign an empty array | ||
82 | // in case there are no cookies at all. Also prevents odd result when | ||
83 | // calling $.cookie(). | ||
84 | var cookies = document.cookie ? document.cookie.split('; ') : []; | ||
85 | |||
86 | for (var i = 0, l = cookies.length; i < l; i++) { | ||
87 | var parts = cookies[i].split('='); | ||
88 | var name = decode(parts.shift()); | ||
89 | var cookie = parts.join('='); | ||
90 | |||
91 | if (key && key === name) { | ||
92 | // If second argument (value) is a function it's a converter... | ||
93 | result = read(cookie, value); | ||
94 | break; | ||
95 | } | ||
96 | |||
97 | // Prevent storing a cookie that we couldn't decode. | ||
98 | if (!key && (cookie = read(cookie)) !== undefined) { | ||
99 | result[name] = cookie; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | return result; | ||
104 | }; | ||
105 | |||
106 | config.defaults = {}; | ||
107 | |||
108 | $.removeCookie = function (key, options) { | ||
109 | if ($.cookie(key) !== undefined) { | ||
110 | // Must not alter options, thus extending a fresh object... | ||
111 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); | ||
112 | return true; | ||
113 | } | ||
114 | return false; | ||
115 | }; | ||
116 | |||
117 | })); | ||
diff --git a/themes/baggy/layout.twig b/themes/baggy/layout.twig index dfebc3ea..8de12749 100644..100755 --- a/themes/baggy/layout.twig +++ b/themes/baggy/layout.twig | |||
@@ -21,6 +21,9 @@ | |||
21 | {% block precontent %}{% endblock %} | 21 | {% block precontent %}{% endblock %} |
22 | {% block messages %} | 22 | {% block messages %} |
23 | {% include '_messages.twig' %} | 23 | {% include '_messages.twig' %} |
24 | {% if includeImport %} | ||
25 | {% include '_import.twig' %} | ||
26 | {% endif %} | ||
24 | {% endblock %} | 27 | {% endblock %} |
25 | <div id="content" class="w600p center"> | 28 | <div id="content" class="w600p center"> |
26 | {% block content %}{% endblock %} | 29 | {% block content %}{% endblock %} |
diff --git a/themes/baggy/tags.twig b/themes/baggy/tags.twig index 9df44bb7..9bb93a45 100644..100755 --- a/themes/baggy/tags.twig +++ b/themes/baggy/tags.twig | |||
@@ -6,7 +6,7 @@ | |||
6 | {% block content %} | 6 | {% block content %} |
7 | <h2>{% trans "Tags" %}</h2> | 7 | <h2>{% trans "Tags" %}</h2> |
8 | <ul class="list-tags"> | 8 | <ul class="list-tags"> |
9 | {% for tag in tags %}<li>{% if token != '' %}<a class="icon icon-rss" href="?feed&type=tag&user_id={{ user_id }}&tag_id={{ tag.id }}&token={{ token }}" target="_blank"><span>rss</span></a>{% endif %} <a href="./?view=tag&id={{ tag.id }}">{{ tag.value }}</a> | 9 | {% for tag in tags %}<li>{% if token != '' %}<a class="icon icon-rss" href="?feed&type=tag&user_id={{ user_id }}&tag_id={{ tag.id }}&token={{ token }}" target="_blank"><span>rss</span></a>{% endif %} <a href="./?view=tag&id={{ tag.id }}">{{ tag.value }}</a> ({{ tag.entriescount }}) |
10 | </li> | 10 | </li> |
11 | {% endfor %} | 11 | {% endfor %} |
12 | </ul> | 12 | </ul> |
diff --git a/themes/baggy/view.twig b/themes/baggy/view.twig index 4751c4cb..af97407d 100644..100755 --- a/themes/baggy/view.twig +++ b/themes/baggy/view.twig | |||
@@ -8,13 +8,15 @@ | |||
8 | <ul class="links"> | 8 | <ul class="links"> |
9 | <li class="topPosF"><a href="#top" title="{% trans "Back to top" %}" class="tool top icon icon-arrow-up-thick"><span>{% trans "Back to top" %}</span></a></li> | 9 | <li class="topPosF"><a href="#top" title="{% trans "Back to top" %}" class="tool top icon icon-arrow-up-thick"><span>{% trans "Back to top" %}</span></a></li> |
10 | <li><a href="{{ entry.url|e }}" target="_blank" title="{% trans "original" %} : {{ entry.title|e }}" class="tool link icon icon-link"><span>{{ entry.url | e | getDomain }}</span></a></li> | 10 | <li><a href="{{ entry.url|e }}" target="_blank" title="{% trans "original" %} : {{ entry.title|e }}" class="tool link icon icon-link"><span>{{ entry.url | e | getDomain }}</span></a></li> |
11 | <li><a title="{% trans "Mark as read" %}" class="tool icon icon-check {% if entry.is_read == 0 %}archive-off{% else %}archive{% endif %}" href="./?action=toggle_archive&id={{ entry.id|e }}"><span>{% trans "Toggle mark as read" %}</span></a></li> | 11 | <li><a title="{% trans "Mark as read" %}" class="tool icon icon-check {% if entry.is_read == 0 %}archive-off{% else %}archive{% endif %}" href="javascript: void(null);" id="markAsRead"><span>{% trans "Toggle mark as read" %}</span></a></li> |
12 | <li><a title="{% trans "Favorite" %}" class="tool icon icon-star {% if entry.is_fav == 0 %}fav-off{% else %}fav{% endif %}" href="./?action=toggle_fav&id={{ entry.id|e }}"><span>{% trans "Toggle favorite" %}</span></a></li> | 12 | <li><a title="{% trans "Favorite" %}" class="tool icon icon-star {% if entry.is_fav == 0 %}fav-off{% else %}fav{% endif %}" href="javascript: void(null);" id="setFav"><span>{% trans "Toggle favorite" %}</span></a></li> |
13 | <li><a title="{% trans "Delete" %}" class="tool delete icon icon-trash" href="./?action=delete&id={{ entry.id|e }}"><span>{% trans "Delete" %}</span></a></li> | 13 | <li><a title="{% trans "Delete" %}" class="tool delete icon icon-trash" href="./?action=delete&id={{ entry.id|e }}"><span>{% trans "Delete" %}</span></a></li> |
14 | {% if constant('SHARE_TWITTER') == 1 %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter icon icon-twitter" title="{% trans "Tweet" %}"><span>{% trans "Tweet" %}</span></a></li>{% endif %} | 14 | {% if constant('SHARE_TWITTER') == 1 %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter icon icon-twitter" title="{% trans "Tweet" %}"><span>{% trans "Tweet" %}</span></a></li>{% endif %} |
15 | {% if constant('SHARE_MAIL') == 1 %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="{% trans "Email" %}"><span>{% trans "Email" %}</span></a></li>{% endif %} | 15 | {% if constant('SHARE_MAIL') == 1 %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="{% trans "Email" %}"><span>{% trans "Email" %}</span></a></li>{% endif %} |
16 | {% if constant('SHARE_SHAARLI') == 1 %}<li><a href="{{ constant('SHAARLI_URL') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" target="_blank" class="tool shaarli" title="{% trans "shaarli" %}"><span>{% trans "shaarli" %}</span></a></li>{% endif %} | 16 | {% if constant('SHARE_SHAARLI') == 1 %}<li><a href="{{ constant('SHAARLI_URL') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" target="_blank" class="tool shaarli" title="{% trans "shaarli" %}"><span>{% trans "shaarli" %}</span></a></li>{% endif %} |
17 | {% 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" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span> ({{ flattr.numflattrs }})</a></li>{% endif %}{% endif %} | 17 | {% 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 %} |
18 | {% 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 %} | ||
19 | <li><a href="./?epub&method=id&id={{ entry.id|e }}" title="Generate epub file">EPUB</a></li> | ||
18 | <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&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> | 20 | <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&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> |
19 | </ul> | 21 | </ul> |
20 | </div> | 22 | </div> |
@@ -29,4 +31,67 @@ | |||
29 | {{ content | raw }} | 31 | {{ content | raw }} |
30 | </article> | 32 | </article> |
31 | </div> | 33 | </div> |
34 | <script src="{{ poche_url }}themes/{{theme}}/js/restoreScroll.js"></script> | ||
35 | <script type="text/javascript"> | ||
36 | $(document).ready(function() { | ||
37 | |||
38 | // toggle read property of current article | ||
39 | $('#markAsRead').click(function(){ | ||
40 | $("body").css("cursor", "wait"); | ||
41 | $.ajax( { url: './?action=toggle_archive&id={{ entry.id|e }}' }).done( | ||
42 | function( data ) { | ||
43 | if ( data == '1' ) { | ||
44 | if ( $('#markAsRead').hasClass("archive-off") ) { | ||
45 | $('#markAsRead').removeClass("archive-off"); | ||
46 | $('#markAsRead').addClass("archive"); | ||
47 | } | ||
48 | else { | ||
49 | $('#markAsRead').removeClass("archive"); | ||
50 | $('#markAsRead').addClass("archive-off"); | ||
51 | } | ||
52 | } | ||
53 | else { | ||
54 | alert('Error! Pls check if you are logged in.'); | ||
55 | } | ||
56 | }); | ||
57 | $("body").css("cursor", "auto"); | ||
58 | }); | ||
59 | |||
60 | // toggle favorite property of current article | ||
61 | $('#setFav').click(function(){ | ||
62 | $("body").css("cursor", "wait"); | ||
63 | $.ajax( { url: './?action=toggle_fav&id={{ entry.id|e }}' }).done( | ||
64 | function( data ) { | ||
65 | if ( data == '1' ) { | ||
66 | if ( $('#setFav').hasClass("fav-off") ) { | ||
67 | $('#setFav').removeClass("fav-off"); | ||
68 | $('#setFav').addClass("fav"); | ||
69 | } | ||
70 | else { | ||
71 | $('#setFav').removeClass("fav"); | ||
72 | $('#setFav').addClass("fav-off"); | ||
73 | } | ||
74 | } | ||
75 | else { | ||
76 | alert('Error! Pls check if you are logged in.'); | ||
77 | } | ||
78 | }); | ||
79 | $("body").css("cursor", "auto"); | ||
80 | }); | ||
81 | |||
82 | $(window).scroll(function(e){ | ||
83 | var scrollTop = $(window).scrollTop(); | ||
84 | var docHeight = $(document).height(); | ||
85 | var scrollPercent = (scrollTop) / (docHeight); | ||
86 | var scrollPercentRounded = Math.round(scrollPercent*100)/100; | ||
87 | savePercent({{ entry.id|e }}, scrollPercentRounded); | ||
88 | }); | ||
89 | |||
90 | retrievePercent({{ entry.id|e }}); | ||
91 | |||
92 | $(window).resize(function(){ | ||
93 | retrievePercent({{ entry.id|e }}); | ||
94 | }); | ||
95 | }); | ||
96 | </script> | ||
32 | {% endblock %} | 97 | {% endblock %} |
diff --git a/themes/courgette/_head.twig b/themes/courgette/_head.twig index 57b40f41..059936d9 100755 --- a/themes/courgette/_head.twig +++ b/themes/courgette/_head.twig | |||
@@ -1,11 +1,11 @@ | |||
1 | <link rel="shortcut icon" type="image/x-icon" href="{{ poche_url }}/themes/{{theme}}/img/favicon.ico" /> | 1 | <link rel="shortcut icon" type="image/x-icon" href="{{ poche_url }}themes/{{theme}}/img/favicon.ico" /> |
2 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ poche_url }}/themes/{{theme}}/img/apple-touch-icon-144x144-precomposed.png"> | 2 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ poche_url }}themes/{{theme}}/img/apple-touch-icon-144x144-precomposed.png"> |
3 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ poche_url }}/themes/{{theme}}/img/apple-touch-icon-72x72-precomposed.png"> | 3 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ poche_url }}themes/{{theme}}/img/apple-touch-icon-72x72-precomposed.png"> |
4 | <link rel="apple-touch-icon-precomposed" href="{{ poche_url }}/themes/{{theme}}/img/apple-touch-icon-precomposed.png"> | 4 | <link rel="apple-touch-icon-precomposed" href="{{ poche_url }}themes/{{theme}}/img/apple-touch-icon-precomposed.png"> |
5 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/font.css" media="all"> | 5 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/font.css" media="all"> |
6 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/style.css" media="all"> | 6 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/style.css" media="all"> |
7 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/messages.css" media="all"> | 7 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/messages.css" media="all"> |
8 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{theme}}/css/print.css" media="print"> | 8 | <link rel="stylesheet" href="{{ poche_url }}themes/{{theme}}/css/print.css" media="print"> |
9 | <link href='//fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'> | 9 | <link href='//fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'> |
10 | <script src="//codeorigin.jquery.com/jquery-2.0.3.min.js"></script> | 10 | <script src="//codeorigin.jquery.com/jquery-2.0.3.min.js"></script> |
11 | <script src="{{ poche_url }}/themes/{{theme}}/js/init.js"></script> | 11 | <script src="{{ poche_url }}themes/{{theme}}/js/init.js"></script> |
diff --git a/themes/courgette/_top.twig b/themes/courgette/_top.twig index 792687c0..2d41db17 100755 --- a/themes/courgette/_top.twig +++ b/themes/courgette/_top.twig | |||
@@ -1,6 +1,6 @@ | |||
1 | <header> | 1 | <header> |
2 | <h1> | 2 | <h1> |
3 | {% if view == 'home' %}{% block logo %}<img src="{{ poche_url }}/themes/{{ constant('DEFAULT_THEME') }}/img/logo.svg" alt="logo poche" />{% endblock %} | 3 | {% if view == 'home' %}{% block logo %}<img src="{{ poche_url }}themes/{{theme}}/img/logo.svg" alt="wallabag logo" />{% endblock %} |
4 | {% elseif view == 'fav' %}<a href="./" title="{% trans "back to home" %}" >{{ block('logo') }} <span>Favoris</span></a> | 4 | {% elseif view == 'fav' %}<a href="./" title="{% trans "back to home" %}" >{{ block('logo') }} <span>Favoris</span></a> |
5 | {% elseif view == 'archive' %}<a href="./" title="{% trans "back to home" %}" >{{ block('logo') }} <span>Archive</span></a> | 5 | {% elseif view == 'archive' %}<a href="./" title="{% trans "back to home" %}" >{{ block('logo') }} <span>Archive</span></a> |
6 | {% else %}<a href="./" title="{% trans "back to home" %}" >{{ block('logo') }}</a> | 6 | {% else %}<a href="./" title="{% trans "back to home" %}" >{{ block('logo') }}</a> |
diff --git a/themes/courgette/_view.twig b/themes/courgette/_view.twig index e80829ef..25479a3d 100755 --- a/themes/courgette/_view.twig +++ b/themes/courgette/_view.twig | |||
@@ -12,6 +12,7 @@ | |||
12 | {% if constant('SHARE_MAIL') == 1 %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email" title="{% trans "email" %}"><span>{% trans "email" %}</span></a></li>{% endif %} | 12 | {% if constant('SHARE_MAIL') == 1 %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email" title="{% trans "email" %}"><span>{% trans "email" %}</span></a></li>{% endif %} |
13 | {% if constant('SHARE_SHAARLI') == 1 %}<li><a href="{{ constant('SHAARLI_URL') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" target="_blank" class="tool shaarli" title="{% trans "shaarli" %}"><span>{% trans "shaarli" %}</span></a></li>{% endif %} | 13 | {% if constant('SHARE_SHAARLI') == 1 %}<li><a href="{{ constant('SHAARLI_URL') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" target="_blank" class="tool shaarli" title="{% trans "shaarli" %}"><span>{% trans "shaarli" %}</span></a></li>{% endif %} |
14 | {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}<li class="flattrli"><a href="http://flattr.com/submit/auto?url={{ entry.url }}" class="tool 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" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span>{{ flattr.numflattrs }}</a></li>{% endif %}{% endif %} | 14 | {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}<li class="flattrli"><a href="http://flattr.com/submit/auto?url={{ entry.url }}" class="tool 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" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span>{{ flattr.numflattrs }}</a></li>{% endif %}{% endif %} |
15 | <li><a href="./?epub&method=id&id={{ entry.id|e }}" title="Generate epub file">EPUB</a></li> | ||
15 | <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&body={{ entry.url|url_encode }}" title="{% trans "this article appears wrong?" %}" class="tool bad-display"><span>{% trans "this article appears wrong?" %}</span></a></li> | 16 | <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&body={{ entry.url|url_encode }}" title="{% trans "this article appears wrong?" %}" class="tool bad-display"><span>{% trans "this article appears wrong?" %}</span></a></li> |
16 | </ul> | 17 | </ul> |
17 | </div> | 18 | </div> |
@@ -23,7 +24,7 @@ | |||
23 | {{ content | raw }} | 24 | {{ content | raw }} |
24 | </article> | 25 | </article> |
25 | </div> | 26 | </div> |
26 | <script src="{{ poche_url }}/themes/{{ constant('DEFAULT_THEME') }}/js/restoreScroll.js"></script> | 27 | <script src="{{ poche_url }}themes/{{theme}}/js/restoreScroll.js"></script> |
27 | <script type="text/javascript"> | 28 | <script type="text/javascript"> |
28 | $(document).ready(function() { | 29 | $(document).ready(function() { |
29 | 30 | ||
diff --git a/themes/courgette/config.twig b/themes/courgette/config.twig index 26da7289..9ab58461 100755 --- a/themes/courgette/config.twig +++ b/themes/courgette/config.twig | |||
@@ -7,22 +7,24 @@ | |||
7 | {% block content %} | 7 | {% block content %} |
8 | <div id="config"> | 8 | <div id="config"> |
9 | <h2>{% trans "Poching a link" %}</h2> | 9 | <h2>{% trans "Poching a link" %}</h2> |
10 | <p>{% trans "You can poche a link by several methods:" %} (<a class="special" href="http://doc.wallabag.org" title="{% trans "read the documentation" %}">?</a>)</p> | 10 | <p>{% trans "There are several ways to save an article:" %} (<a class="special" href="http://doc.wallabag.org" title="{% trans "read the documentation" %}">?</a>)</p> |
11 | <ul> | 11 | <ul> |
12 | <li>firefox: <a href="https://bitbucket.org/jogaulupeau/poche/downloads/poche.xpi" title="download the firefox extension">{% trans "download the extension" %}</a></li> | 12 | <li>Firefox: <a href="https://addons.mozilla.org/firefox/addon/wallabag/" title="download the firefox extension">{% trans "download the extension" %}</a></li> |
13 | <li>chrome: <a href="https://bitbucket.org/jogaulupeau/poche/downloads/poche.crx" title="download the chrome extension">{% trans "download the extension" %}</a></li> | 13 | <li>Chrome: <a href="http://doc.wallabag.org/doku.php?id=users:chrome_extension" title="download the chrome extension">{% trans "download the extension" %}</a></li> |
14 | <li>android: <a href="https://bitbucket.org/jogaulupeau/poche/downloads/Poche.apk" title="download the application">{% trans "download the application" %}</a></li> | 14 | <li>Android: <a href="https://f-droid.org/app/fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via F-Droid" %}</a> {% trans " or " %} <a href="https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via Google Play" %}</a></li> |
15 | <li>iOS: <a href="https://itunes.apple.com/app/wallabag/id828331015?mt=8" title="download the iOS application">{% trans "download the application" %}</a></li> | ||
16 | <li>Windows Phone: <a href="http://www.windowsphone.com/en-us/store/app/wallabag/ff890514-348c-4d0b-9b43-153fff3f7450" title="download the window phone application">{% trans "download the application" %}</a></li> | ||
15 | <li> | 17 | <li> |
16 | <form method="get" action="index.php"> | 18 | <form method="get" action="index.php"> |
17 | <label class="addurl" for="plainurl">{% trans "by filling this field" %}:</label> | 19 | <label class="addurl" for="plainurl">{% trans "by filling this field" %}:</label> |
18 | <input required placeholder="Ex:mywebsite.com/article" class="addurl" id="plainurl" name="plainurl" type="url" /> | 20 | <input required placeholder="Ex:mywebsite.com/article" class="addurl" id="plainurl" name="plainurl" type="url" /> |
19 | <input type="submit" value="{% trans "poche it!" %}" /> | 21 | <input type="submit" value="{% trans "bag it!" %}" /> |
20 | </form> | 22 | </form> |
21 | </li> | 23 | </li> |
22 | <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 "poche it!" %}</a></li> | 24 | <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> |
23 | </ul> | 25 | </ul> |
24 | 26 | ||
25 | <h2>{% trans "Updating poche" %}</h2> | 27 | <h2>{% trans "Upgrading wallabag" %}</h2> |
26 | <ul> | 28 | <ul> |
27 | <li>{% trans "your version" %} : <strong>{{ constant('POCHE') }}</strong></li> | 29 | <li>{% trans "your version" %} : <strong>{{ constant('POCHE') }}</strong></li> |
28 | <li>{% trans "latest stable version" %} : {{ prod }}. {% if compare_prod == -1 %}<strong><a href="http://wallabag.org/">{% trans "a more recent stable version is available." %}</a></strong>{% else %}{% trans "you are up to date." %}{% endif %}</li> | 30 | <li>{% trans "latest stable version" %} : {{ prod }}. {% if compare_prod == -1 %}<strong><a href="http://wallabag.org/">{% trans "a more recent stable version is available." %}</a></strong>{% else %}{% trans "you are up to date." %}{% endif %}</li> |
@@ -76,7 +78,44 @@ | |||
76 | <li><a href="./?import&from=instapaper">{% trans "import from Instapaper" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('INSTAPAPER_FILE')) }}</li> | 78 | <li><a href="./?import&from=instapaper">{% trans "import from Instapaper" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('INSTAPAPER_FILE')) }}</li> |
77 | </ul> | 79 | </ul> |
78 | 80 | ||
79 | <h2>{% trans "Export your poche datas" %}</h2> | 81 | <h2>{% trans "Export your wallabag data" %}</h2> |
80 | <p><a href="./?export" target="_blank">{% trans "Click here" %}</a> {% trans "to export your poche datas." %}</p> | 82 | <p><a href="./?export" target="_blank">{% trans "Click here" %}</a> {% trans "to export your wallabag data." %}</p> |
83 | |||
84 | <h2>{% trans "Fancy an E-Book ?" %}</h2> | ||
85 | <p>{% trans "Click on <a href=\"./?epub&method=all\" title=\"Generate ePub\">this link</a> to get all your articles in one ebook (ePub 3 format)." %} | ||
86 | <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> | ||
87 | |||
88 | <h2>{% trans 'Add user' %}</h2> | ||
89 | <p>{% trans 'Add a new user :' %}</p> | ||
90 | <form method="post" action="?newuser"> | ||
91 | <fieldset class="w500p"> | ||
92 | <div class="row"> | ||
93 | <label class="col w150p" for="newusername">{% trans 'Login for new user' %}</label> | ||
94 | <input class="col" type="text" id="newusername" name="newusername" placeholder="{% trans 'Login' %}"> | ||
95 | </div> | ||
96 | <div class="row"> | ||
97 | <label class="col w150p" for="password4newuser">{% trans "Password for new user" %}</label> | ||
98 | <input class="col" type="password" id="password4newuser" name="password4newuser" placeholder="{% trans "Password" %}"> | ||
99 | </div> | ||
100 | <div class="row mts txtcenter"> | ||
101 | <button type="submit">{% trans "Send" %}</button> | ||
102 | </div> | ||
103 | </fieldset> | ||
104 | </form> | ||
105 | |||
106 | <h2>{% trans "Delete account" %}</h2> | ||
107 | {% if not only_user %}<form method="post" action="?deluser"> | ||
108 | <p>{% trans "You can delete your account by entering your password and validating." %}<br /><b>{% trans "Be careful, data will be erased forever (that is a very long time)." %}</b></p> | ||
109 | <fieldset class="w500p"> | ||
110 | <div class="row"> | ||
111 | <label class="col w150p" for="password4deletinguser">{% trans "Type here your password" %}</label> | ||
112 | <input class="col" type="password" id="password4deletinguser" name="password4deletinguser" placeholder="{% trans "Password" %}"> | ||
113 | </div> | ||
114 | <div class="row mts txtcenter"> | ||
115 | <button type="submit">{% trans "Send" %}</button> | ||
116 | </div> | ||
117 | </form> | ||
118 | {% else %}<p>{% trans "You are the only user, you cannot delete your own account." %}<br /> | ||
119 | {% trans "To completely remove wallabag, delete the wallabag folder on your web server." %}</p>{% endif %} | ||
81 | </div> | 120 | </div> |
82 | {% endblock %} \ No newline at end of file | 121 | {% endblock %} |
diff --git a/themes/courgette/home.twig b/themes/courgette/home.twig index 416cfa43..401f3f20 100755 --- a/themes/courgette/home.twig +++ b/themes/courgette/home.twig | |||
@@ -14,8 +14,8 @@ | |||
14 | {% block precontent %} | 14 | {% block precontent %} |
15 | {% if entries|length > 1 %} | 15 | {% if entries|length > 1 %} |
16 | <ul id="sort"> | 16 | <ul id="sort"> |
17 | <li><a href="./?sort=ia&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by date asc" %}" title="{% trans "by date asc" %}" /></a> {% trans "by date" %} <a href="./?sort=id&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by date desc" %}" title="{% trans "by date desc" %}" /></a></li> | 17 | <li><a href="./?sort=ia&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by date asc" %}" title="{% trans "by date asc" %}" /></a> {% trans "by date" %} <a href="./?sort=id&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by date desc" %}" title="{% trans "by date desc" %}" /></a></li> |
18 | <li><a href="./?sort=ta&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by title asc" %}" title="{% trans "by title asc" %}" /></a> {% trans "by title" %} <a href="./?sort=td&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by title desc" %}" title="{% trans "by title desc" %}" /></a></li> | 18 | <li><a href="./?sort=ta&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by title asc" %}" title="{% trans "by title asc" %}" /></a> {% trans "by title" %} <a href="./?sort=td&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by title desc" %}" title="{% trans "by title desc" %}" /></a></li> |
19 | </ul> | 19 | </ul> |
20 | {% endif %} | 20 | {% endif %} |
21 | {% endblock %} | 21 | {% endblock %} |
@@ -26,9 +26,15 @@ | |||
26 | {% block pager %} | 26 | {% block pager %} |
27 | {% if nb_results > 1 %} | 27 | {% if nb_results > 1 %} |
28 | <div class="results"> | 28 | <div class="results"> |
29 | <div class="nb-results">{{ nb_results }} {% trans "results" %}</div> | 29 | <div class="nb-results">{{ nb_results }} {% trans "results" %}{% if search_term is defined %}{% trans " found for « " %} {{ search_term }} »{% endif %}</div> |
30 | {{ page_links | raw }} | 30 | {{ page_links | raw }} |
31 | </div> | 31 | </div> |
32 | {% elseif nb_results == 1 %} | ||
33 | {% if search_term is defined %} | ||
34 | <div class="results"> | ||
35 | <div class="nb-results">{% trans "Only one result found for " %} « {{ search_term }} »</div> | ||
36 | </div> | ||
37 | {% endif %} | ||
32 | {% endif %} | 38 | {% endif %} |
33 | {% endblock %} | 39 | {% endblock %} |
34 | {% for entry in entries %} | 40 | {% for entry in entries %} |
@@ -44,6 +50,13 @@ | |||
44 | <p>{{ entry.content|striptags|slice(0, 300) }}...</p> | 50 | <p>{{ entry.content|striptags|slice(0, 300) }}...</p> |
45 | </div> | 51 | </div> |
46 | {% endfor %} | 52 | {% endfor %} |
47 | {% endif %} | 53 | |
48 | {{ block('pager') }} | 54 | {{ block('pager') }} |
55 | |||
56 | {% if tag %}<a title="{% trans "Download the articles from this tag in an epub" %}" href="./?epub&method=tag&tag={{ tag.value }}">{% trans "Download the articles from this tag in an epub" %}</a> | ||
57 | {% elseif search_term is defined %}<a title="{% trans "Download the articles from this search in an epub" %}" href="./?epub&method=search&search={{ search_term }}">{% trans "Download the articles from this search in an epub" %}</a> | ||
58 | {% else %}<a title="{% trans "Download the articles from this category in an epub" %}" href="./?epub&method=category&category={{ view }}">{% trans "Download the articles from this category in an epub" %}</a>{% endif %} | ||
59 | |||
60 | {% endif %} | ||
61 | |||
49 | {% endblock %} \ No newline at end of file | 62 | {% endblock %} \ No newline at end of file |
diff --git a/themes/courgette/tags.twig b/themes/courgette/tags.twig index 9dfdab52..b11dce28 100644..100755 --- a/themes/courgette/tags.twig +++ b/themes/courgette/tags.twig | |||
@@ -4,5 +4,5 @@ | |||
4 | {% include '_menu.twig' %} | 4 | {% include '_menu.twig' %} |
5 | {% endblock %} | 5 | {% endblock %} |
6 | {% block content %} | 6 | {% block content %} |
7 | {% for tag in tags %}<a class="tag" href="./?view=tag&id={{ tag.id }}">{{ tag.value }}</a> {% if token != '' %}<a href="?feed&type=tag&user_id={{ user_id }}&tag_id={{ tag.id }}&token={{ token }}" target="_blank"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/rss.png" /></a>{% endif %} {% endfor %} | 7 | {% for tag in tags %}<a class="tag" href="./?view=tag&id={{ tag.id }}">{{ tag.value }}</a> {% if token != '' %}<a href="?feed&type=tag&user_id={{ user_id }}&tag_id={{ tag.id }}&token={{ token }}" target="_blank"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/rss.png" /></a>{% endif %} {% endfor %} |
8 | {% endblock %} \ No newline at end of file | 8 | {% endblock %} \ No newline at end of file |
diff --git a/themes/dark/img/dark/rss.png b/themes/dark/img/dark/rss.png new file mode 100644 index 00000000..21bad1a1 --- /dev/null +++ b/themes/dark/img/dark/rss.png | |||
Binary files differ | |||
diff --git a/themes/default/_bookmarklet.twig b/themes/default/_bookmarklet.twig index 2e3071ad..61996353 100644 --- a/themes/default/_bookmarklet.twig +++ b/themes/default/_bookmarklet.twig | |||
@@ -1,3 +1,3 @@ | |||
1 | <script type="text/javascript"> | 1 | <script type="text/javascript"> |
2 | top["bookmarklet-url@wallabag.org"]=""+"<!DOCTYPE html>"+"<html>"+"<head>"+"<title>poche it !</title>"+'<link rel="icon" href="{{poche_url}}tpl/img/favicon.ico" />'+"</head>"+"<body>"+"<script>"+"window.onload=function(){"+"window.setTimeout(function(){"+"history.back();"+"},250);"+"};"+"</scr"+"ipt>"+"</body>"+"</html>" | 2 | top["bookmarklet-url@wallabag.org"]=""+"<!DOCTYPE html>"+"<html>"+"<head>"+"<title>bag it!</title>"+'<link rel="icon" href="{{poche_url}}tpl/img/favicon.ico" />'+"</head>"+"<body>"+"<script>"+"window.onload=function(){"+"window.setTimeout(function(){"+"history.back();"+"},250);"+"};"+"</scr"+"ipt>"+"</body>"+"</html>" |
3 | </script> \ No newline at end of file | 3 | </script> \ No newline at end of file |
diff --git a/themes/default/_head.twig b/themes/default/_head.twig index f310e420..8c939e30 100644..100755 --- a/themes/default/_head.twig +++ b/themes/default/_head.twig | |||
@@ -1,12 +1,14 @@ | |||
1 | <link rel="shortcut icon" type="image/x-icon" href="{{ poche_url }}/themes/default/img/favicon.ico" /> | 1 | <link rel="shortcut icon" type="image/x-icon" href="{{ poche_url }}themes/default/img/favicon.ico" /> |
2 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ poche_url }}/themes/default/img/apple-touch-icon-144x144-precomposed.png"> | 2 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ poche_url }}themes/default/img/apple-touch-icon-144x144-precomposed.png"> |
3 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ poche_url }}/themes/default/img/apple-touch-icon-72x72-precomposed.png"> | 3 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ poche_url }}themes/default/img/apple-touch-icon-72x72-precomposed.png"> |
4 | <link rel="apple-touch-icon-precomposed" href="{{ poche_url }}/themes/default/img/apple-touch-icon-precomposed.png"> | 4 | <link rel="apple-touch-icon-precomposed" href="{{ poche_url }}themes/default/img/apple-touch-icon-precomposed.png"> |
5 | <link rel="stylesheet" href="{{ poche_url }}/themes/default/css/knacss.css" media="all"> | 5 | <link rel="stylesheet" href="{{ poche_url }}themes/default/css/knacss.css" media="all"> |
6 | <link rel="stylesheet" href="{{ poche_url }}/themes/default/css/style.css" media="all"> | 6 | <link rel="stylesheet" href="{{ poche_url }}themes/default/css/style.css" media="all"> |
7 | <link rel="stylesheet" href="{{ poche_url }}/themes/{{ theme }}/css/style-{{ theme }}.css" media="all" title="{{ theme }} theme"> | 7 | <link rel="stylesheet" href="{{ poche_url }}themes/{{ theme }}/css/style-{{ theme }}.css" media="all" title="{{ theme }} theme"> |
8 | <link rel="stylesheet" href="{{ poche_url }}/themes/default/css/messages.css" media="all"> | 8 | <link rel="stylesheet" href="{{ poche_url }}themes/default/css/messages.css" media="all"> |
9 | <link rel="stylesheet" href="{{ poche_url }}/themes/default/css/print.css" media="print"> | 9 | <link rel="stylesheet" href="{{ poche_url }}themes/default/css/print.css" media="print"> |
10 | <script src="{{ poche_url }}/themes/default/js/jquery-2.0.3.min.js"></script> | 10 | <script src="{{ poche_url }}themes/default/js/jquery-2.0.3.min.js"></script> |
11 | <script src="{{ poche_url }}/themes/default/js/autoClose.js"></script> | 11 | <script src="{{ poche_url }}themes/default/js/autoClose.js"></script> |
12 | <script src="{{ poche_url }}/themes/default/js/closeMessage.js"></script> \ No newline at end of file | 12 | <script src="{{ poche_url }}themes/default/js/closeMessage.js"></script> |
13 | <script src="{{ poche_url }}themes/default/js/saveLink.js"></script> | ||
14 | <script src="{{ poche_url }}themes/default/js/popupForm.js"></script> \ No newline at end of file | ||
diff --git a/themes/default/_import.twig b/themes/default/_import.twig new file mode 100755 index 00000000..c59b7a15 --- /dev/null +++ b/themes/default/_import.twig | |||
@@ -0,0 +1,15 @@ | |||
1 | <script type="text/javascript"> | ||
2 | <!-- | ||
3 | $(document).ready(function() { | ||
4 | $("body").css("cursor", "wait"); | ||
5 | |||
6 | setTimeout(function(){ | ||
7 | window.location = './?import'; | ||
8 | }, {{ import.delay }} ); | ||
9 | }); | ||
10 | //--> | ||
11 | </script> | ||
12 | <div class="messages warning"> | ||
13 | <p>{% trans "Download required for " %} {{ import.recordsDownloadRequired }} {% trans "records" %}.</p> | ||
14 | <p>{% trans "Downloading next " %} {{ import.recordsUnderDownload }} {% trans "articles, please wait" %}...</p> | ||
15 | </div> | ||
diff --git a/themes/default/_menu.twig b/themes/default/_menu.twig index 55583b3d..0daa0b03 100644 --- a/themes/default/_menu.twig +++ b/themes/default/_menu.twig | |||
@@ -3,9 +3,11 @@ | |||
3 | <li><a href="./?view=fav" {% if view == 'fav' %}class="current"{% endif %}>{% trans "favorites" %}</a></li> | 3 | <li><a href="./?view=fav" {% if view == 'fav' %}class="current"{% endif %}>{% trans "favorites" %}</a></li> |
4 | <li><a href="./?view=archive" {% if view == 'archive' %}class="current"{% endif %}>{% trans "archive" %}</a></li> | 4 | <li><a href="./?view=archive" {% if view == 'archive' %}class="current"{% endif %}>{% trans "archive" %}</a></li> |
5 | <li><a href="./?view=tags" {% if view == 'tags' %}class="current"{% endif %}>{% trans "tags" %}</a></li> | 5 | <li><a href="./?view=tags" {% if view == 'tags' %}class="current"{% endif %}>{% trans "tags" %}</a></li> |
6 | <li><a href="javascript: void(null);" id="pocheit">{% trans "save a link" %}</a><span id="pocheit-arrow"></span></li> | 6 | <li><a href="javascript: void(null);" id="bagit">{% trans "save a link" %}</a><span id="bagit-arrow"></span></li> |
7 | <li><a href="javascript: void(null);" id="search">{% trans "search" %}</a><span id="search-arrow"></span></li> | ||
7 | <li><a href="./?view=config" {% if view == 'config' %}class="current"{% endif %}>{% trans "config" %}</a></li> | 8 | <li><a href="./?view=config" {% if view == 'config' %}class="current"{% endif %}>{% trans "config" %}</a></li> |
8 | <li><a href="./?logout" title="{% trans "logout" %}">{% trans "logout" %}</a></li> | 9 | <li><a href="./?logout" title="{% trans "logout" %}">{% trans "logout" %}</a></li> |
9 | </ul> | 10 | </ul> |
10 | {% include '_pocheit-form.twig' %} | 11 | {% include '_pocheit-form.twig' %} |
12 | {% include '_search-form.twig' %} | ||
11 | 13 | ||
diff --git a/themes/default/_pocheit-form.twig b/themes/default/_pocheit-form.twig index 13096159..8c982db0 100755 --- a/themes/default/_pocheit-form.twig +++ b/themes/default/_pocheit-form.twig | |||
@@ -1,22 +1,8 @@ | |||
1 | <div id="pocheit-form" class="messages info"> | 1 | <div id="bagit-form" class="messages info"> |
2 | <center> | 2 | <a href="javascript: void(null);" id="bagit-form-close"> </a> |
3 | <form method="get" action="index.php"> | 3 | <form method="get" action="index.php" id="bagit-form-form"> |
4 | <input required placeholder="example.com/article" class="addurl" id="plainurl" name="plainurl" type="url" /> | 4 | <input required placeholder="example.com/article" class="addurl" id="plainurl" name="plainurl" type="url" /> |
5 | <input type="submit" value="{% trans "save link!" %}" /> | 5 | <input type="submit" value="{% trans "save link!" %}" /> |
6 | <div id="add-link-result"></div> | ||
6 | </form> | 7 | </form> |
7 | </center> | ||
8 | </div> | 8 | </div> |
9 | <script type="text/javascript"> | ||
10 | $(document).ready(function() { | ||
11 | |||
12 | $("#pocheit-form").hide(); | ||
13 | |||
14 | $("#pocheit").click(function(){ | ||
15 | $("#pocheit-form").toggle(); | ||
16 | $("#pocheit").toggleClass("current"); | ||
17 | $("#pocheit-arrow").toggleClass("arrow-down"); | ||
18 | }); | ||
19 | |||
20 | |||
21 | }); | ||
22 | </script> | ||
diff --git a/themes/default/_search-form.twig b/themes/default/_search-form.twig new file mode 100755 index 00000000..33bea20d --- /dev/null +++ b/themes/default/_search-form.twig | |||
@@ -0,0 +1,9 @@ | |||
1 | <div id="search-form" class="messages info"> | ||
2 | <form method="get" action="index.php"> | ||
3 | <p> | ||
4 | <input type="hidden" name="view" value="search"></input> | ||
5 | <label>{% trans "Search" %}</label> : <input type="text" placeholder="{% trans "Enter your search here" %}" name="search" /> | ||
6 | <input type="submit" value="{% trans "Search" %} !"></input> | ||
7 | </p> | ||
8 | </form> | ||
9 | </div> | ||
diff --git a/themes/default/_sorting.twig b/themes/default/_sorting.twig new file mode 100755 index 00000000..ce3d38bc --- /dev/null +++ b/themes/default/_sorting.twig | |||
@@ -0,0 +1,6 @@ | |||
1 | {% if entries|length > 1 %} | ||
2 | <ul id="sort"> | ||
3 | <li><a href="./?sort=ia&view={{ view }}{% if search_term is defined %}&search={{ search_term }}{% endif %}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by date asc" %}" title="{% trans "by date asc" %}" /></a> {% trans "by date" %} <a href="./?sort=id&view={{ view }}{% if search_term is defined %}&search={{ search_term }}{% endif %}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by date desc" %}" title="{% trans "by date desc" %}" /></a></li> | ||
4 | <li><a href="./?sort=ta&view={{ view }}{% if search_term is defined %}&search={{ search_term }}{% endif %}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by title asc" %}" title="{% trans "by title asc" %}" /></a> {% trans "by title" %} <a href="./?sort=td&view={{ view }}{% if search_term is defined %}&search={{ search_term }}{% endif %}&id={{ id }}"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by title desc" %}" title="{% trans "by title desc" %}" /></a></li> | ||
5 | </ul> | ||
6 | {% endif %} | ||
diff --git a/themes/default/_top.twig b/themes/default/_top.twig index 0711acdc..45806f01 100644..100755 --- a/themes/default/_top.twig +++ b/themes/default/_top.twig | |||
@@ -1,6 +1,6 @@ | |||
1 | <header class="w600p center mbm"> | 1 | <header class="w600p center mbm"> |
2 | <h1> | 2 | <h1> |
3 | {% if view == 'home' %}{% block logo %}<img width="100" height="100" src="{{ poche_url }}/themes/baggy/img/logo-other_themes.png" alt="logo poche" />{% endblock %} | 3 | {% if view == 'home' %}{% block logo %}<img width="100" height="100" src="{{ poche_url }}themes/baggy/img/logo-other_themes.png" alt="wallabag logo" />{% endblock %} |
4 | {% else %}<a href="./" title="{% trans "return home" %}" >{{ block('logo') }}</a> | 4 | {% else %}<a href="./" title="{% trans "return home" %}" >{{ block('logo') }}</a> |
5 | {% endif %} | 5 | {% endif %} |
6 | </h1> | 6 | </h1> |
diff --git a/themes/default/config.twig b/themes/default/config.twig index cd4d074d..160f6046 100644..100755 --- a/themes/default/config.twig +++ b/themes/default/config.twig | |||
@@ -8,10 +8,11 @@ | |||
8 | <h2>{% trans "Saving articles" %}</h2> | 8 | <h2>{% trans "Saving articles" %}</h2> |
9 | <p>{% trans "There are several ways to save an article:" %} (<a href="http://doc.wallabag.org/" title="{% trans "read the documentation" %}">?</a>)</p> | 9 | <p>{% trans "There are several ways to save an article:" %} (<a href="http://doc.wallabag.org/" title="{% trans "read the documentation" %}">?</a>)</p> |
10 | <ul> | 10 | <ul> |
11 | <li>Firefox: <a href="https://addons.mozilla.org/firefox/addon/poche/" title="download the firefox extension">{% trans "download the extension" %}</a></li> | 11 | <li>Firefox: <a href="https://addons.mozilla.org/firefox/addon/wallabag/" title="download the firefox extension">{% trans "download the extension" %}</a></li> |
12 | <li>Chrome: <a href="http://doc.wallabag.org/doku.php?id=users:chrome_extension" title="download the chrome extension">{% trans "download the extension" %}</a></li> | 12 | <li>Chrome: <a href="http://doc.wallabag.org/doku.php?id=users:chrome_extension" title="download the chrome extension">{% trans "download the extension" %}</a></li> |
13 | <li>Android: <a href="https://f-droid.org/repository/browse/?fdid=fr.gaulupeau.apps.Poche" title="download the application">{% trans "via F-Droid" %}</a> {% trans " or " %} <a href="https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via Google Play" %}</a></li> | 13 | <li>Android: <a href="https://f-droid.org/app/fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via F-Droid" %}</a> {% trans " or " %} <a href="https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche" title="download the application">{% trans "via Google Play" %}</a></li> |
14 | <li>Windows Phone: <a href="https://www.windowsphone.com/en-us/store/app/poche/334de2f0-51b5-4826-8549-a3d805a37e83" title="download the window phone application">{% trans "download the application" %}</a></li> | 14 | <li>iOS: <a href="https://itunes.apple.com/app/wallabag/id828331015?mt=8" title="download the iOS application">{% trans "download the application" %}</a></li> |
15 | <li>Windows Phone: <a href="http://www.windowsphone.com/en-us/store/app/wallabag/ff890514-348c-4d0b-9b43-153fff3f7450" title="download the window phone application">{% trans "download the application" %}</a></li> | ||
15 | <li> | 16 | <li> |
16 | <form method="get" action="index.php"> | 17 | <form method="get" action="index.php"> |
17 | <label class="addurl" for="config_plainurl">{% trans "By filling this field" %}:</label> | 18 | <label class="addurl" for="config_plainurl">{% trans "By filling this field" %}:</label> |
@@ -103,14 +104,20 @@ | |||
103 | {% endif %} | 104 | {% endif %} |
104 | 105 | ||
105 | <h2>{% trans "Import" %}</h2> | 106 | <h2>{% trans "Import" %}</h2> |
106 | <p>{% trans "Please execute the import script locally as it can take a very long time." %}</p> | 107 | <p>{% trans "You can import your Pocket, Readability, Instapaper, Wallabag or any data in appropriate json or html format." %}</p> |
107 | <p>{% trans "More info in the official documentation:" %} <a href="http://doc.wallabag.org/doku.php?id=users:migrate">wallabag.org</a></p> | 108 | <p>{% trans "Please select export file on your computer and press \"Import\" button below.<br>Wallabag will parse your file, insert all URLs and start fetching of articles if required.<br>Fetching process is controlled by two constants in your config file: IMPORT_LIMIT (how many articles are fetched at once) and IMPORT_DELAY (delay between fetch of next batch of articles)." %}</p> |
108 | <ul> | 109 | <form method="post" action="?import" name="uploadfile" enctype="multipart/form-data"> |
109 | <li><a href="./?import&from=pocket">{% trans "Import from Pocket" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('POCKET_FILE')) }}</li> | 110 | <fieldset class="w500p"> |
110 | <li><a href="./?import&from=readability">{% trans "Import from Readability" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('READABILITY_FILE')) }}</li> | 111 | <div class="row"> |
111 | <li><a href="./?import&from=instapaper">{% trans "Import from Instapaper" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('INSTAPAPER_FILE')) }}</li> | 112 | <label class="col w150p" for="file">{% trans "File:" %}</label> |
112 | <li><a href="./?import&from=poche">{% trans "Import from wallabag" %}</a> {{ '(you must have a %s file on your server)'|trans|format(constant('POCHE_FILE')) }}</li> | 113 | <input class="col" type="file" id="file" name="file" tabindex="4"> |
113 | </ul> | 114 | </div> |
115 | <div class="row mts txtcenter"> | ||
116 | <button class="bouton" type="submit" tabindex="4">{% trans "Import" %}</button> | ||
117 | </div> | ||
118 | </fieldset> | ||
119 | </form> | ||
120 | <p><a href="?import">{% trans "You can click here to fetch content for articles with no content." %}</a></p> | ||
114 | 121 | ||
115 | <h2>{% trans "Export your wallabag data" %}</h2> | 122 | <h2>{% trans "Export your wallabag data" %}</h2> |
116 | {% if constant('STORAGE') == 'sqlite' %} | 123 | {% if constant('STORAGE') == 'sqlite' %} |
@@ -119,4 +126,41 @@ | |||
119 | 126 | ||
120 | <h2>{% trans "Cache" %}</h2> | 127 | <h2>{% trans "Cache" %}</h2> |
121 | <p><a href="?empty-cache">{% trans "Click here" %}</a> {% trans "to delete cache." %}</p> | 128 | <p><a href="?empty-cache">{% trans "Click here" %}</a> {% trans "to delete cache." %}</p> |
129 | |||
130 | <h2>{% trans "Fancy an E-Book ?" %}</h2> | ||
131 | <p>{% trans "Click on <a href=\"./?epub&method=all\" title=\"Generate ePub\">this link</a> to get all your articles in one ebook (ePub 3 format)." %} | ||
132 | <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> | ||
133 | |||
134 | <h2>{% trans 'Add user' %}</h2> | ||
135 | <p>{% trans 'Add a new user :' %}</p> | ||
136 | <form method="post" action="?newuser"> | ||
137 | <fieldset class="w500p"> | ||
138 | <div class="row"> | ||
139 | <label class="col w150p" for="newusername">{% trans 'Login for new user' %}</label> | ||
140 | <input class="col" type="text" id="newusername" name="newusername" placeholder="{% trans 'Login' %}"> | ||
141 | </div> | ||
142 | <div class="row"> | ||
143 | <label class="col w150p" for="password4newuser">{% trans "Password for new user" %}</label> | ||
144 | <input class="col" type="password" id="password4newuser" name="password4newuser" placeholder="{% trans "Password" %}"> | ||
145 | </div> | ||
146 | <div class="row mts txtcenter"> | ||
147 | <button type="submit">{% trans "Send" %}</button> | ||
148 | </div> | ||
149 | </fieldset> | ||
150 | </form> | ||
151 | |||
152 | <h2>{% trans "Delete account" %}</h2> | ||
153 | {% if not only_user %}<form method="post" action="?deluser"> | ||
154 | <p>{% trans "You can delete your account by entering your password and validating." %}<br /><b>{% trans "Be careful, data will be erased forever (that is a very long time)." %}</b></p> | ||
155 | <fieldset class="w500p"> | ||
156 | <div class="row"> | ||
157 | <label class="col w150p" for="password4deletinguser">{% trans "Type here your password" %}</label> | ||
158 | <input class="col" type="password" id="password4deletinguser" name="password4deletinguser" placeholder="{% trans "Password" %}"> | ||
159 | </div> | ||
160 | <div class="row mts txtcenter"> | ||
161 | <button type="submit">{% trans "Send" %}</button> | ||
162 | </div> | ||
163 | </form> | ||
164 | {% else %}<p>{% trans "You are the only user, you cannot delete your own account." %}<br /> | ||
165 | {% trans "To completely remove wallabag, delete the wallabag folder on your web server." %}</p>{% endif %} | ||
122 | {% endblock %} | 166 | {% endblock %} |
diff --git a/themes/default/css/images/animated-overlay.gif b/themes/default/css/images/animated-overlay.gif new file mode 100644 index 00000000..d441f75e --- /dev/null +++ b/themes/default/css/images/animated-overlay.gif | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/themes/default/css/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 00000000..9f10cb65 --- /dev/null +++ b/themes/default/css/images/ui-bg_flat_0_aaaaaa_40x100.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_flat_75_ffffff_40x100.png b/themes/default/css/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 00000000..b89b914b --- /dev/null +++ b/themes/default/css/images/ui-bg_flat_75_ffffff_40x100.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/themes/default/css/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..780d3ffd --- /dev/null +++ b/themes/default/css/images/ui-bg_glass_55_fbf9ee_1x400.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_glass_65_ffffff_1x400.png b/themes/default/css/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..da53255b --- /dev/null +++ b/themes/default/css/images/ui-bg_glass_65_ffffff_1x400.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_glass_75_dadada_1x400.png b/themes/default/css/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..f6306608 --- /dev/null +++ b/themes/default/css/images/ui-bg_glass_75_dadada_1x400.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/themes/default/css/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..7d347065 --- /dev/null +++ b/themes/default/css/images/ui-bg_glass_75_e6e6e6_1x400.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_glass_95_fef1ec_1x400.png b/themes/default/css/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..533c4900 --- /dev/null +++ b/themes/default/css/images/ui-bg_glass_95_fef1ec_1x400.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/themes/default/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..bf3a9481 --- /dev/null +++ b/themes/default/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-icons_222222_256x240.png b/themes/default/css/images/ui-icons_222222_256x240.png new file mode 100644 index 00000000..c1cb1170 --- /dev/null +++ b/themes/default/css/images/ui-icons_222222_256x240.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-icons_2e83ff_256x240.png b/themes/default/css/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..84b601bf --- /dev/null +++ b/themes/default/css/images/ui-icons_2e83ff_256x240.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-icons_454545_256x240.png b/themes/default/css/images/ui-icons_454545_256x240.png new file mode 100644 index 00000000..b6db1acd --- /dev/null +++ b/themes/default/css/images/ui-icons_454545_256x240.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-icons_888888_256x240.png b/themes/default/css/images/ui-icons_888888_256x240.png new file mode 100644 index 00000000..feea0e20 --- /dev/null +++ b/themes/default/css/images/ui-icons_888888_256x240.png | |||
Binary files differ | |||
diff --git a/themes/default/css/images/ui-icons_cd0a0a_256x240.png b/themes/default/css/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..ed5b6b09 --- /dev/null +++ b/themes/default/css/images/ui-icons_cd0a0a_256x240.png | |||
Binary files differ | |||
diff --git a/themes/default/css/jquery-ui-1.10.4.custom.css b/themes/default/css/jquery-ui-1.10.4.custom.css new file mode 100644 index 00000000..5690172f --- /dev/null +++ b/themes/default/css/jquery-ui-1.10.4.custom.css | |||
@@ -0,0 +1,560 @@ | |||
1 | /*! jQuery UI - v1.10.4 - 2014-03-09 | ||
2 | * http://jqueryui.com | ||
3 | * Includes: jquery.ui.core.css, jquery.ui.autocomplete.css, jquery.ui.menu.css, jquery.ui.theme.css | ||
4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px | ||
5 | * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ | ||
6 | |||
7 | /* Layout helpers | ||
8 | ----------------------------------*/ | ||
9 | .ui-helper-hidden { | ||
10 | display: none; | ||
11 | } | ||
12 | .ui-helper-hidden-accessible { | ||
13 | border: 0; | ||
14 | clip: rect(0 0 0 0); | ||
15 | height: 1px; | ||
16 | margin: -1px; | ||
17 | overflow: hidden; | ||
18 | padding: 0; | ||
19 | position: absolute; | ||
20 | width: 1px; | ||
21 | } | ||
22 | .ui-helper-reset { | ||
23 | margin: 0; | ||
24 | padding: 0; | ||
25 | border: 0; | ||
26 | outline: 0; | ||
27 | line-height: 1.3; | ||
28 | text-decoration: none; | ||
29 | font-size: 100%; | ||
30 | list-style: none; | ||
31 | } | ||
32 | .ui-helper-clearfix:before, | ||
33 | .ui-helper-clearfix:after { | ||
34 | content: ""; | ||
35 | display: table; | ||
36 | border-collapse: collapse; | ||
37 | } | ||
38 | .ui-helper-clearfix:after { | ||
39 | clear: both; | ||
40 | } | ||
41 | .ui-helper-clearfix { | ||
42 | min-height: 0; /* support: IE7 */ | ||
43 | } | ||
44 | .ui-helper-zfix { | ||
45 | width: 100%; | ||
46 | height: 100%; | ||
47 | top: 0; | ||
48 | left: 0; | ||
49 | position: absolute; | ||
50 | opacity: 0; | ||
51 | filter:Alpha(Opacity=0); | ||
52 | } | ||
53 | |||
54 | .ui-front { | ||
55 | z-index: 100; | ||
56 | } | ||
57 | |||
58 | |||
59 | /* Interaction Cues | ||
60 | ----------------------------------*/ | ||
61 | .ui-state-disabled { | ||
62 | cursor: default !important; | ||
63 | } | ||
64 | |||
65 | |||
66 | /* Icons | ||
67 | ----------------------------------*/ | ||
68 | |||
69 | /* states and images */ | ||
70 | .ui-icon { | ||
71 | display: block; | ||
72 | text-indent: -99999px; | ||
73 | overflow: hidden; | ||
74 | background-repeat: no-repeat; | ||
75 | } | ||
76 | |||
77 | |||
78 | /* Misc visuals | ||
79 | ----------------------------------*/ | ||
80 | |||
81 | /* Overlays */ | ||
82 | .ui-widget-overlay { | ||
83 | position: fixed; | ||
84 | top: 0; | ||
85 | left: 0; | ||
86 | width: 100%; | ||
87 | height: 100%; | ||
88 | } | ||
89 | .ui-autocomplete { | ||
90 | position: absolute; | ||
91 | top: 0; | ||
92 | left: 0; | ||
93 | cursor: default; | ||
94 | } | ||
95 | .ui-menu { | ||
96 | list-style: none; | ||
97 | padding: 2px; | ||
98 | margin: 0; | ||
99 | display: block; | ||
100 | outline: none; | ||
101 | } | ||
102 | .ui-menu .ui-menu { | ||
103 | margin-top: -3px; | ||
104 | position: absolute; | ||
105 | } | ||
106 | .ui-menu .ui-menu-item { | ||
107 | margin: 0; | ||
108 | padding: 0; | ||
109 | width: 100%; | ||
110 | /* support: IE10, see #8844 */ | ||
111 | list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); | ||
112 | } | ||
113 | .ui-menu .ui-menu-divider { | ||
114 | margin: 5px -2px 5px -2px; | ||
115 | height: 0; | ||
116 | font-size: 0; | ||
117 | line-height: 0; | ||
118 | border-width: 1px 0 0 0; | ||
119 | } | ||
120 | .ui-menu .ui-menu-item a { | ||
121 | text-decoration: none; | ||
122 | display: block; | ||
123 | padding: 2px .4em; | ||
124 | line-height: 1.5; | ||
125 | min-height: 0; /* support: IE7 */ | ||
126 | font-weight: normal; | ||
127 | } | ||
128 | .ui-menu .ui-menu-item a.ui-state-focus, | ||
129 | .ui-menu .ui-menu-item a.ui-state-active { | ||
130 | font-weight: normal; | ||
131 | margin: -1px; | ||
132 | } | ||
133 | |||
134 | .ui-menu .ui-state-disabled { | ||
135 | font-weight: normal; | ||
136 | margin: .4em 0 .2em; | ||
137 | line-height: 1.5; | ||
138 | } | ||
139 | .ui-menu .ui-state-disabled a { | ||
140 | cursor: default; | ||
141 | } | ||
142 | |||
143 | /* icon support */ | ||
144 | .ui-menu-icons { | ||
145 | position: relative; | ||
146 | } | ||
147 | .ui-menu-icons .ui-menu-item a { | ||
148 | position: relative; | ||
149 | padding-left: 2em; | ||
150 | } | ||
151 | |||
152 | /* left-aligned */ | ||
153 | .ui-menu .ui-icon { | ||
154 | position: absolute; | ||
155 | top: .2em; | ||
156 | left: .2em; | ||
157 | } | ||
158 | |||
159 | /* right-aligned */ | ||
160 | .ui-menu .ui-menu-icon { | ||
161 | position: static; | ||
162 | float: right; | ||
163 | } | ||
164 | |||
165 | /* Component containers | ||
166 | ----------------------------------*/ | ||
167 | .ui-widget { | ||
168 | font-family: Verdana,Arial,sans-serif; | ||
169 | font-size: 1.1em; | ||
170 | } | ||
171 | .ui-widget .ui-widget { | ||
172 | font-size: 1em; | ||
173 | } | ||
174 | .ui-widget input, | ||
175 | .ui-widget select, | ||
176 | .ui-widget textarea, | ||
177 | .ui-widget button { | ||
178 | font-family: Verdana,Arial,sans-serif; | ||
179 | font-size: 1em; | ||
180 | } | ||
181 | .ui-widget-content { | ||
182 | border: 1px solid #aaaaaa; | ||
183 | background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; | ||
184 | color: #222222; | ||
185 | } | ||
186 | .ui-widget-content a { | ||
187 | color: #222222; | ||
188 | } | ||
189 | .ui-widget-header { | ||
190 | border: 1px solid #aaaaaa; | ||
191 | background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; | ||
192 | color: #222222; | ||
193 | font-weight: bold; | ||
194 | } | ||
195 | .ui-widget-header a { | ||
196 | color: #222222; | ||
197 | } | ||
198 | |||
199 | /* Interaction states | ||
200 | ----------------------------------*/ | ||
201 | .ui-state-default, | ||
202 | .ui-widget-content .ui-state-default, | ||
203 | .ui-widget-header .ui-state-default { | ||
204 | border: 1px solid #d3d3d3; | ||
205 | background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; | ||
206 | font-weight: normal; | ||
207 | color: #555555; | ||
208 | } | ||
209 | .ui-state-default a, | ||
210 | .ui-state-default a:link, | ||
211 | .ui-state-default a:visited { | ||
212 | color: #555555; | ||
213 | text-decoration: none; | ||
214 | } | ||
215 | .ui-state-hover, | ||
216 | .ui-widget-content .ui-state-hover, | ||
217 | .ui-widget-header .ui-state-hover, | ||
218 | .ui-state-focus, | ||
219 | .ui-widget-content .ui-state-focus, | ||
220 | .ui-widget-header .ui-state-focus { | ||
221 | border: 1px solid #999999; | ||
222 | background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; | ||
223 | font-weight: normal; | ||
224 | color: #212121; | ||
225 | } | ||
226 | .ui-state-hover a, | ||
227 | .ui-state-hover a:hover, | ||
228 | .ui-state-hover a:link, | ||
229 | .ui-state-hover a:visited, | ||
230 | .ui-state-focus a, | ||
231 | .ui-state-focus a:hover, | ||
232 | .ui-state-focus a:link, | ||
233 | .ui-state-focus a:visited { | ||
234 | color: #212121; | ||
235 | text-decoration: none; | ||
236 | } | ||
237 | .ui-state-active, | ||
238 | .ui-widget-content .ui-state-active, | ||
239 | .ui-widget-header .ui-state-active { | ||
240 | border: 1px solid #aaaaaa; | ||
241 | background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; | ||
242 | font-weight: normal; | ||
243 | color: #212121; | ||
244 | } | ||
245 | .ui-state-active a, | ||
246 | .ui-state-active a:link, | ||
247 | .ui-state-active a:visited { | ||
248 | color: #212121; | ||
249 | text-decoration: none; | ||
250 | } | ||
251 | |||
252 | /* Interaction Cues | ||
253 | ----------------------------------*/ | ||
254 | .ui-state-highlight, | ||
255 | .ui-widget-content .ui-state-highlight, | ||
256 | .ui-widget-header .ui-state-highlight { | ||
257 | border: 1px solid #fcefa1; | ||
258 | background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; | ||
259 | color: #363636; | ||
260 | } | ||
261 | .ui-state-highlight a, | ||
262 | .ui-widget-content .ui-state-highlight a, | ||
263 | .ui-widget-header .ui-state-highlight a { | ||
264 | color: #363636; | ||
265 | } | ||
266 | .ui-state-error, | ||
267 | .ui-widget-content .ui-state-error, | ||
268 | .ui-widget-header .ui-state-error { | ||
269 | border: 1px solid #cd0a0a; | ||
270 | background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; | ||
271 | color: #cd0a0a; | ||
272 | } | ||
273 | .ui-state-error a, | ||
274 | .ui-widget-content .ui-state-error a, | ||
275 | .ui-widget-header .ui-state-error a { | ||
276 | color: #cd0a0a; | ||
277 | } | ||
278 | .ui-state-error-text, | ||
279 | .ui-widget-content .ui-state-error-text, | ||
280 | .ui-widget-header .ui-state-error-text { | ||
281 | color: #cd0a0a; | ||
282 | } | ||
283 | .ui-priority-primary, | ||
284 | .ui-widget-content .ui-priority-primary, | ||
285 | .ui-widget-header .ui-priority-primary { | ||
286 | font-weight: bold; | ||
287 | } | ||
288 | .ui-priority-secondary, | ||
289 | .ui-widget-content .ui-priority-secondary, | ||
290 | .ui-widget-header .ui-priority-secondary { | ||
291 | opacity: .7; | ||
292 | filter:Alpha(Opacity=70); | ||
293 | font-weight: normal; | ||
294 | } | ||
295 | .ui-state-disabled, | ||
296 | .ui-widget-content .ui-state-disabled, | ||
297 | .ui-widget-header .ui-state-disabled { | ||
298 | opacity: .35; | ||
299 | filter:Alpha(Opacity=35); | ||
300 | background-image: none; | ||
301 | } | ||
302 | .ui-state-disabled .ui-icon { | ||
303 | filter:Alpha(Opacity=35); /* For IE8 - See #6059 */ | ||
304 | } | ||
305 | |||
306 | /* Icons | ||
307 | ----------------------------------*/ | ||
308 | |||
309 | /* states and images */ | ||
310 | .ui-icon { | ||
311 | width: 16px; | ||
312 | height: 16px; | ||
313 | } | ||
314 | .ui-icon, | ||
315 | .ui-widget-content .ui-icon { | ||
316 | background-image: url(images/ui-icons_222222_256x240.png); | ||
317 | } | ||
318 | .ui-widget-header .ui-icon { | ||
319 | background-image: url(images/ui-icons_222222_256x240.png); | ||
320 | } | ||
321 | .ui-state-default .ui-icon { | ||
322 | background-image: url(images/ui-icons_888888_256x240.png); | ||
323 | } | ||
324 | .ui-state-hover .ui-icon, | ||
325 | .ui-state-focus .ui-icon { | ||
326 | background-image: url(images/ui-icons_454545_256x240.png); | ||
327 | } | ||
328 | .ui-state-active .ui-icon { | ||
329 | background-image: url(images/ui-icons_454545_256x240.png); | ||
330 | } | ||
331 | .ui-state-highlight .ui-icon { | ||
332 | background-image: url(images/ui-icons_2e83ff_256x240.png); | ||
333 | } | ||
334 | .ui-state-error .ui-icon, | ||
335 | .ui-state-error-text .ui-icon { | ||
336 | background-image: url(images/ui-icons_cd0a0a_256x240.png); | ||
337 | } | ||
338 | |||
339 | /* positioning */ | ||
340 | .ui-icon-blank { background-position: 16px 16px; } | ||
341 | .ui-icon-carat-1-n { background-position: 0 0; } | ||
342 | .ui-icon-carat-1-ne { background-position: -16px 0; } | ||
343 | .ui-icon-carat-1-e { background-position: -32px 0; } | ||
344 | .ui-icon-carat-1-se { background-position: -48px 0; } | ||
345 | .ui-icon-carat-1-s { background-position: -64px 0; } | ||
346 | .ui-icon-carat-1-sw { background-position: -80px 0; } | ||
347 | .ui-icon-carat-1-w { background-position: -96px 0; } | ||
348 | .ui-icon-carat-1-nw { background-position: -112px 0; } | ||
349 | .ui-icon-carat-2-n-s { background-position: -128px 0; } | ||
350 | .ui-icon-carat-2-e-w { background-position: -144px 0; } | ||
351 | .ui-icon-triangle-1-n { background-position: 0 -16px; } | ||
352 | .ui-icon-triangle-1-ne { background-position: -16px -16px; } | ||
353 | .ui-icon-triangle-1-e { background-position: -32px -16px; } | ||
354 | .ui-icon-triangle-1-se { background-position: -48px -16px; } | ||
355 | .ui-icon-triangle-1-s { background-position: -64px -16px; } | ||
356 | .ui-icon-triangle-1-sw { background-position: -80px -16px; } | ||
357 | .ui-icon-triangle-1-w { background-position: -96px -16px; } | ||
358 | .ui-icon-triangle-1-nw { background-position: -112px -16px; } | ||
359 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; } | ||
360 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; } | ||
361 | .ui-icon-arrow-1-n { background-position: 0 -32px; } | ||
362 | .ui-icon-arrow-1-ne { background-position: -16px -32px; } | ||
363 | .ui-icon-arrow-1-e { background-position: -32px -32px; } | ||
364 | .ui-icon-arrow-1-se { background-position: -48px -32px; } | ||
365 | .ui-icon-arrow-1-s { background-position: -64px -32px; } | ||
366 | .ui-icon-arrow-1-sw { background-position: -80px -32px; } | ||
367 | .ui-icon-arrow-1-w { background-position: -96px -32px; } | ||
368 | .ui-icon-arrow-1-nw { background-position: -112px -32px; } | ||
369 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; } | ||
370 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } | ||
371 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; } | ||
372 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; } | ||
373 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; } | ||
374 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; } | ||
375 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; } | ||
376 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; } | ||
377 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; } | ||
378 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; } | ||
379 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; } | ||
380 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; } | ||
381 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; } | ||
382 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; } | ||
383 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; } | ||
384 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; } | ||
385 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } | ||
386 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } | ||
387 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } | ||
388 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } | ||
389 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } | ||
390 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } | ||
391 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } | ||
392 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } | ||
393 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } | ||
394 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } | ||
395 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } | ||
396 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } | ||
397 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; } | ||
398 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; } | ||
399 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; } | ||
400 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; } | ||
401 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } | ||
402 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } | ||
403 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } | ||
404 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } | ||
405 | .ui-icon-arrow-4 { background-position: 0 -80px; } | ||
406 | .ui-icon-arrow-4-diag { background-position: -16px -80px; } | ||
407 | .ui-icon-extlink { background-position: -32px -80px; } | ||
408 | .ui-icon-newwin { background-position: -48px -80px; } | ||
409 | .ui-icon-refresh { background-position: -64px -80px; } | ||
410 | .ui-icon-shuffle { background-position: -80px -80px; } | ||
411 | .ui-icon-transfer-e-w { background-position: -96px -80px; } | ||
412 | .ui-icon-transferthick-e-w { background-position: -112px -80px; } | ||
413 | .ui-icon-folder-collapsed { background-position: 0 -96px; } | ||
414 | .ui-icon-folder-open { background-position: -16px -96px; } | ||
415 | .ui-icon-document { background-position: -32px -96px; } | ||
416 | .ui-icon-document-b { background-position: -48px -96px; } | ||
417 | .ui-icon-note { background-position: -64px -96px; } | ||
418 | .ui-icon-mail-closed { background-position: -80px -96px; } | ||
419 | .ui-icon-mail-open { background-position: -96px -96px; } | ||
420 | .ui-icon-suitcase { background-position: -112px -96px; } | ||
421 | .ui-icon-comment { background-position: -128px -96px; } | ||
422 | .ui-icon-person { background-position: -144px -96px; } | ||
423 | .ui-icon-print { background-position: -160px -96px; } | ||
424 | .ui-icon-trash { background-position: -176px -96px; } | ||
425 | .ui-icon-locked { background-position: -192px -96px; } | ||
426 | .ui-icon-unlocked { background-position: -208px -96px; } | ||
427 | .ui-icon-bookmark { background-position: -224px -96px; } | ||
428 | .ui-icon-tag { background-position: -240px -96px; } | ||
429 | .ui-icon-home { background-position: 0 -112px; } | ||
430 | .ui-icon-flag { background-position: -16px -112px; } | ||
431 | .ui-icon-calendar { background-position: -32px -112px; } | ||
432 | .ui-icon-cart { background-position: -48px -112px; } | ||
433 | .ui-icon-pencil { background-position: -64px -112px; } | ||
434 | .ui-icon-clock { background-position: -80px -112px; } | ||
435 | .ui-icon-disk { background-position: -96px -112px; } | ||
436 | .ui-icon-calculator { background-position: -112px -112px; } | ||
437 | .ui-icon-zoomin { background-position: -128px -112px; } | ||
438 | .ui-icon-zoomout { background-position: -144px -112px; } | ||
439 | .ui-icon-search { background-position: -160px -112px; } | ||
440 | .ui-icon-wrench { background-position: -176px -112px; } | ||
441 | .ui-icon-gear { background-position: -192px -112px; } | ||
442 | .ui-icon-heart { background-position: -208px -112px; } | ||
443 | .ui-icon-star { background-position: -224px -112px; } | ||
444 | .ui-icon-link { background-position: -240px -112px; } | ||
445 | .ui-icon-cancel { background-position: 0 -128px; } | ||
446 | .ui-icon-plus { background-position: -16px -128px; } | ||
447 | .ui-icon-plusthick { background-position: -32px -128px; } | ||
448 | .ui-icon-minus { background-position: -48px -128px; } | ||
449 | .ui-icon-minusthick { background-position: -64px -128px; } | ||
450 | .ui-icon-close { background-position: -80px -128px; } | ||
451 | .ui-icon-closethick { background-position: -96px -128px; } | ||
452 | .ui-icon-key { background-position: -112px -128px; } | ||
453 | .ui-icon-lightbulb { background-position: -128px -128px; } | ||
454 | .ui-icon-scissors { background-position: -144px -128px; } | ||
455 | .ui-icon-clipboard { background-position: -160px -128px; } | ||
456 | .ui-icon-copy { background-position: -176px -128px; } | ||
457 | .ui-icon-contact { background-position: -192px -128px; } | ||
458 | .ui-icon-image { background-position: -208px -128px; } | ||
459 | .ui-icon-video { background-position: -224px -128px; } | ||
460 | .ui-icon-script { background-position: -240px -128px; } | ||
461 | .ui-icon-alert { background-position: 0 -144px; } | ||
462 | .ui-icon-info { background-position: -16px -144px; } | ||
463 | .ui-icon-notice { background-position: -32px -144px; } | ||
464 | .ui-icon-help { background-position: -48px -144px; } | ||
465 | .ui-icon-check { background-position: -64px -144px; } | ||
466 | .ui-icon-bullet { background-position: -80px -144px; } | ||
467 | .ui-icon-radio-on { background-position: -96px -144px; } | ||
468 | .ui-icon-radio-off { background-position: -112px -144px; } | ||
469 | .ui-icon-pin-w { background-position: -128px -144px; } | ||
470 | .ui-icon-pin-s { background-position: -144px -144px; } | ||
471 | .ui-icon-play { background-position: 0 -160px; } | ||
472 | .ui-icon-pause { background-position: -16px -160px; } | ||
473 | .ui-icon-seek-next { background-position: -32px -160px; } | ||
474 | .ui-icon-seek-prev { background-position: -48px -160px; } | ||
475 | .ui-icon-seek-end { background-position: -64px -160px; } | ||
476 | .ui-icon-seek-start { background-position: -80px -160px; } | ||
477 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ | ||
478 | .ui-icon-seek-first { background-position: -80px -160px; } | ||
479 | .ui-icon-stop { background-position: -96px -160px; } | ||
480 | .ui-icon-eject { background-position: -112px -160px; } | ||
481 | .ui-icon-volume-off { background-position: -128px -160px; } | ||
482 | .ui-icon-volume-on { background-position: -144px -160px; } | ||
483 | .ui-icon-power { background-position: 0 -176px; } | ||
484 | .ui-icon-signal-diag { background-position: -16px -176px; } | ||
485 | .ui-icon-signal { background-position: -32px -176px; } | ||
486 | .ui-icon-battery-0 { background-position: -48px -176px; } | ||
487 | .ui-icon-battery-1 { background-position: -64px -176px; } | ||
488 | .ui-icon-battery-2 { background-position: -80px -176px; } | ||
489 | .ui-icon-battery-3 { background-position: -96px -176px; } | ||
490 | .ui-icon-circle-plus { background-position: 0 -192px; } | ||
491 | .ui-icon-circle-minus { background-position: -16px -192px; } | ||
492 | .ui-icon-circle-close { background-position: -32px -192px; } | ||
493 | .ui-icon-circle-triangle-e { background-position: -48px -192px; } | ||
494 | .ui-icon-circle-triangle-s { background-position: -64px -192px; } | ||
495 | .ui-icon-circle-triangle-w { background-position: -80px -192px; } | ||
496 | .ui-icon-circle-triangle-n { background-position: -96px -192px; } | ||
497 | .ui-icon-circle-arrow-e { background-position: -112px -192px; } | ||
498 | .ui-icon-circle-arrow-s { background-position: -128px -192px; } | ||
499 | .ui-icon-circle-arrow-w { background-position: -144px -192px; } | ||
500 | .ui-icon-circle-arrow-n { background-position: -160px -192px; } | ||
501 | .ui-icon-circle-zoomin { background-position: -176px -192px; } | ||
502 | .ui-icon-circle-zoomout { background-position: -192px -192px; } | ||
503 | .ui-icon-circle-check { background-position: -208px -192px; } | ||
504 | .ui-icon-circlesmall-plus { background-position: 0 -208px; } | ||
505 | .ui-icon-circlesmall-minus { background-position: -16px -208px; } | ||
506 | .ui-icon-circlesmall-close { background-position: -32px -208px; } | ||
507 | .ui-icon-squaresmall-plus { background-position: -48px -208px; } | ||
508 | .ui-icon-squaresmall-minus { background-position: -64px -208px; } | ||
509 | .ui-icon-squaresmall-close { background-position: -80px -208px; } | ||
510 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; } | ||
511 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } | ||
512 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; } | ||
513 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; } | ||
514 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } | ||
515 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; } | ||
516 | |||
517 | |||
518 | /* Misc visuals | ||
519 | ----------------------------------*/ | ||
520 | |||
521 | /* Corner radius */ | ||
522 | .ui-corner-all, | ||
523 | .ui-corner-top, | ||
524 | .ui-corner-left, | ||
525 | .ui-corner-tl { | ||
526 | border-top-left-radius: 4px; | ||
527 | } | ||
528 | .ui-corner-all, | ||
529 | .ui-corner-top, | ||
530 | .ui-corner-right, | ||
531 | .ui-corner-tr { | ||
532 | border-top-right-radius: 4px; | ||
533 | } | ||
534 | .ui-corner-all, | ||
535 | .ui-corner-bottom, | ||
536 | .ui-corner-left, | ||
537 | .ui-corner-bl { | ||
538 | border-bottom-left-radius: 4px; | ||
539 | } | ||
540 | .ui-corner-all, | ||
541 | .ui-corner-bottom, | ||
542 | .ui-corner-right, | ||
543 | .ui-corner-br { | ||
544 | border-bottom-right-radius: 4px; | ||
545 | } | ||
546 | |||
547 | /* Overlays */ | ||
548 | .ui-widget-overlay { | ||
549 | background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; | ||
550 | opacity: .3; | ||
551 | filter: Alpha(Opacity=30); | ||
552 | } | ||
553 | .ui-widget-shadow { | ||
554 | margin: -8px 0 0 -8px; | ||
555 | padding: 8px; | ||
556 | background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; | ||
557 | opacity: .3; | ||
558 | filter: Alpha(Opacity=30); | ||
559 | border-radius: 8px; | ||
560 | } | ||
diff --git a/themes/default/css/jquery-ui-1.10.4.custom.min.css b/themes/default/css/jquery-ui-1.10.4.custom.min.css new file mode 100644 index 00000000..4dba92a8 --- /dev/null +++ b/themes/default/css/jquery-ui-1.10.4.custom.min.css | |||
@@ -0,0 +1,7 @@ | |||
1 | /*! jQuery UI - v1.10.4 - 2014-03-09 | ||
2 | * http://jqueryui.com | ||
3 | * Includes: jquery.ui.core.css, jquery.ui.autocomplete.css, jquery.ui.menu.css, jquery.ui.theme.css | ||
4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px | ||
5 | * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ | ||
6 | |||
7 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px} \ No newline at end of file | ||
diff --git a/themes/default/css/style-default.css b/themes/default/css/style-default.css index 9dc7c0b0..889a9f5c 100644..100755 --- a/themes/default/css/style-default.css +++ b/themes/default/css/style-default.css | |||
@@ -52,4 +52,8 @@ a.link span { | |||
52 | 52 | ||
53 | a.bad-display span { | 53 | a.bad-display span { |
54 | background-image: url('../img/default/bad-display.png'); | 54 | background-image: url('../img/default/bad-display.png'); |
55 | } | ||
56 | |||
57 | a.print span { | ||
58 | background-image: url('../img/default/print.png'); | ||
55 | } \ No newline at end of file | 59 | } \ No newline at end of file |
diff --git a/themes/default/css/style.css b/themes/default/css/style.css index 28675907..e58ef81a 100644..100755 --- a/themes/default/css/style.css +++ b/themes/default/css/style.css | |||
@@ -331,7 +331,8 @@ a.email span, | |||
331 | a.delete span, | 331 | a.delete span, |
332 | a.link span, | 332 | a.link span, |
333 | a.bad-display span, | 333 | a.bad-display span, |
334 | a.reading-time span { | 334 | a.reading-time span, |
335 | a.print span { | ||
335 | background-repeat: no-repeat; | 336 | background-repeat: no-repeat; |
336 | } | 337 | } |
337 | 338 | ||
@@ -347,3 +348,85 @@ a.reading-time span { | |||
347 | margin-left: -30px; | 348 | margin-left: -30px; |
348 | } | 349 | } |
349 | 350 | ||
351 | .two-column { | ||
352 | display: block; | ||
353 | width: 50%; | ||
354 | paddig-right: 20px; | ||
355 | float: left; | ||
356 | vertical-align: top; | ||
357 | } | ||
358 | |||
359 | |||
360 | /* ========================================================================== | ||
361 | "save a link" popup div related styles | ||
362 | ========================================================================== */ | ||
363 | |||
364 | #bagit-form { | ||
365 | display: none; | ||
366 | padding-left: 30px; | ||
367 | width: 450px; | ||
368 | |||
369 | } | ||
370 | |||
371 | a#bagit-form-close { | ||
372 | color: #FFF; | ||
373 | display: inline-block; | ||
374 | float: right; | ||
375 | background: url("../img/messages/close.png") no-repeat scroll 0 0 rgba(0, 0, 0, 0); | ||
376 | height: 16px; | ||
377 | margin: -14px -8px 0 0; | ||
378 | width: 16px; | ||
379 | text-decoration: none; | ||
380 | } | ||
381 | |||
382 | |||
383 | .add-to-wallabag-link-after { | ||
384 | background-color: #000; | ||
385 | color: #fff; | ||
386 | padding: 0 4px 1px 3px; | ||
387 | font-weight: bold; | ||
388 | font-size: 0.7em; | ||
389 | border-radius: 4px; | ||
390 | } | ||
391 | .add-to-wallabag-link-after:hover, .add-to-wallabag-link-after:active { | ||
392 | color: #fff; | ||
393 | } | ||
394 | .add-to-wallabag-link-after:visited { | ||
395 | color: #999; | ||
396 | } | ||
397 | a.add-to-wallabag-link-after { | ||
398 | visibility: hidden; | ||
399 | position: absolute; | ||
400 | opacity: 0; | ||
401 | transition-duration: 2s; | ||
402 | transition-timing-function: ease-out; | ||
403 | } | ||
404 | #article article a:hover + a.add-to-wallabag-link-after, a.add-to-wallabag-link-after:hover { | ||
405 | opacity: 1; | ||
406 | visibility: visible; | ||
407 | transition-duration: .3s; | ||
408 | transition-timing-function: ease-in; | ||
409 | } | ||
410 | a.add-to-wallabag-link-after:after { | ||
411 | content: "w"; | ||
412 | } | ||
413 | |||
414 | |||
415 | #add-link-result { | ||
416 | display: inline; | ||
417 | padding-left: 10px; | ||
418 | } | ||
419 | |||
420 | .opacity03 { | ||
421 | /*opacity: 0.3;*/ | ||
422 | } | ||
423 | |||
424 | #readLeftPercent { | ||
425 | display: inline-block; | ||
426 | /* Show textual content */ | ||
427 | overflow: visible; | ||
428 | text-align: left; | ||
429 | text-indent: 0; | ||
430 | color: black; | ||
431 | width: 50px; | ||
432 | } \ No newline at end of file | ||
diff --git a/themes/default/edit-tags.twig b/themes/default/edit-tags.twig index 83f04aa0..bd498d26 100644..100755 --- a/themes/default/edit-tags.twig +++ b/themes/default/edit-tags.twig | |||
@@ -5,6 +5,10 @@ | |||
5 | {% endblock %} | 5 | {% endblock %} |
6 | {% block content %} | 6 | {% block content %} |
7 | 7 | ||
8 | <script src="{{ poche_url }}themes/default/js/jquery-ui-1.10.4.custom.min.js"></script> | ||
9 | <script src="{{ poche_url }}themes/default/js/autoCompleteTags.js"></script> | ||
10 | <link rel="stylesheet" href="{{ poche_url }}themes/default/css/jquery-ui-1.10.4.custom.min.css" media="all"> | ||
11 | |||
8 | <div id="article"> | 12 | <div id="article"> |
9 | <header class="mbm"> | 13 | <header class="mbm"> |
10 | <h1>{{ entry.title|raw }}</h1> | 14 | <h1>{{ entry.title|raw }}</h1> |
@@ -17,13 +21,15 @@ no tags | |||
17 | <ul> | 21 | <ul> |
18 | {% for tag in tags %}<li>{{ tag.value }} <a href="./?action=remove_tag&tag_id={{ tag.id }}&id={{ entry_id }}">✘</a></li>{% endfor %} | 22 | {% for tag in tags %}<li>{{ tag.value }} <a href="./?action=remove_tag&tag_id={{ tag.id }}&id={{ entry_id }}">✘</a></li>{% endfor %} |
19 | </ul> | 23 | </ul> |
20 | <form method="post" action="./?action=add_tag"> | 24 | <form method="post" action="./?action=add_tag" id="editTags"> |
21 | <input type="hidden" name="entry_id" value="{{ entry_id }}" /> | 25 | <input type="hidden" name="entry_id" value="{{ entry_id }}" /> |
22 | <label for="value">Add tags: </label> | 26 | <label for="value">Add tags: </label> |
23 | <input type="text" placeholder="interview, editorial, video" id="value" name="value" required="required" /> | 27 | <input type="text" placeholder="interview, editorial, video" id="value" name="value" required="required" /> |
24 | <input type="submit" value="Tag" /> | 28 | <input type="submit" value="Tag" /> |
25 | <p>{% trans "You can enter multiple tags, separated by commas." %}</p> | 29 | <p>{% trans "Start typing for auto complete." %}<br> |
30 | {% trans "You can enter multiple tags, separated by commas." %}</p> | ||
26 | 31 | ||
27 | </form> | 32 | </form> |
33 | <br> | ||
28 | <a href="./?view=view&id={{ entry_id }}">« {% trans "return to article" %}</a> | 34 | <a href="./?view=view&id={{ entry_id }}">« {% trans "return to article" %}</a> |
29 | {% endblock %} | 35 | {% endblock %} |
diff --git a/themes/default/home.twig b/themes/default/home.twig index 165fecc6..e6c781f5 100644..100755 --- a/themes/default/home.twig +++ b/themes/default/home.twig | |||
@@ -12,14 +12,15 @@ | |||
12 | {% include '_menu.twig' %} | 12 | {% include '_menu.twig' %} |
13 | {% endblock %} | 13 | {% endblock %} |
14 | {% block precontent %} | 14 | {% block precontent %} |
15 | {% if entries|length > 1 %} | 15 | |
16 | <ul id="sort"> | 16 | {% include '_sorting.twig' %} |
17 | <li><a href="./?sort=ia&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by date asc" %}" title="{% trans "by date asc" %}" /></a> {% trans "by date" %} <a href="./?sort=id&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by date desc" %}" title="{% trans "by date desc" %}" /></a></li> | ||
18 | <li><a href="./?sort=ta&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/top.png" alt="{% trans "by title asc" %}" title="{% trans "by title asc" %}" /></a> {% trans "by title" %} <a href="./?sort=td&view={{ view }}&id={{ id }}"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/down.png" alt="{% trans "by title desc" %}" title="{% trans "by title desc" %}" /></a></li> | ||
19 | </ul> | ||
20 | {% endif %} | ||
21 | {% endblock %} | 17 | {% endblock %} |
22 | {% block content %} | 18 | {% block content %} |
19 | |||
20 | {% if includeImport %} | ||
21 | {% include '_import.twig' %} | ||
22 | {% endif %} | ||
23 | |||
23 | {% if tag %} | 24 | {% if tag %} |
24 | <h3>{% trans "Tag" %}: <b>{{ tag.value }}</b></h3> | 25 | <h3>{% trans "Tag" %}: <b>{{ tag.value }}</b></h3> |
25 | {% endif %} | 26 | {% endif %} |
@@ -30,9 +31,15 @@ | |||
30 | {% block pager %} | 31 | {% block pager %} |
31 | {% if nb_results > 1 %} | 32 | {% if nb_results > 1 %} |
32 | <div class="results"> | 33 | <div class="results"> |
33 | <div class="nb-results">{{ nb_results }} {% trans "results" %}</div> | 34 | <div class="nb-results">{{ nb_results }} {% trans "results" %}{% if search_term is defined %}{% trans " found for « " %} {{ search_term }} »{% endif %}</div> |
34 | {{ page_links | raw }} | 35 | {{ page_links | raw }} |
35 | </div> | 36 | </div> |
37 | {% elseif nb_results == 1 %} | ||
38 | {% if search_term is defined %} | ||
39 | <div class="results"> | ||
40 | <div class="nb-results">{% trans "Only one result found for " %} « {{ search_term }} »</div> | ||
41 | </div> | ||
42 | {% endif %} | ||
36 | {% endif %} | 43 | {% endif %} |
37 | {% endblock %} | 44 | {% endblock %} |
38 | {% for entry in entries %} | 45 | {% for entry in entries %} |
@@ -48,7 +55,14 @@ | |||
48 | <p>{{ entry.content|striptags|slice(0, 300) }}...</p> | 55 | <p>{{ entry.content|striptags|slice(0, 300) }}...</p> |
49 | </div> | 56 | </div> |
50 | {% endfor %} | 57 | {% endfor %} |
51 | {% endif %} | 58 | |
52 | {{ block('pager') }} | 59 | {{ block('pager') }} |
60 | |||
53 | {% if view == 'home' %}{% if nb_results > 1 %}<a title="{% trans "mark all the entries as read" %}" href="./?action=archive_all">{% trans "mark all the entries as read" %}</a>{% endif %}{% endif %} | 61 | {% if view == 'home' %}{% if nb_results > 1 %}<a title="{% trans "mark all the entries as read" %}" href="./?action=archive_all">{% trans "mark all the entries as read" %}</a>{% endif %}{% endif %} |
62 | |||
63 | {% if tag %}<a title="{% trans "Download the articles from this tag in an epub" %}" href="./?epub&method=tag&tag={{ tag.value }}">{% trans "Download the articles from this tag in an epub" %}</a> | ||
64 | {% elseif search_term is defined %}<a title="{% trans "Download the articles from this search in an epub" %}" href="./?epub&method=search&search={{ search_term }}">{% trans "Download the articles from this search in an epub" %}</a> | ||
65 | {% else %}<a title="{% trans "Download the articles from this category in an epub" %}" href="./?epub&method=category&category={{ view }}">{% trans "Download the articles from this category in an epub" %}</a>{% endif %} | ||
66 | |||
67 | {% endif %} | ||
54 | {% endblock %} | 68 | {% endblock %} |
diff --git a/themes/default/img/default/print.png b/themes/default/img/default/print.png new file mode 100755 index 00000000..83d6445b --- /dev/null +++ b/themes/default/img/default/print.png | |||
Binary files differ | |||
diff --git a/themes/default/js/autoCompleteTags.js b/themes/default/js/autoCompleteTags.js new file mode 100755 index 00000000..90bc982c --- /dev/null +++ b/themes/default/js/autoCompleteTags.js | |||
@@ -0,0 +1,47 @@ | |||
1 | jQuery(function($) { | ||
2 | |||
3 | function split( val ) { | ||
4 | return val.split( /,\s*/ ); | ||
5 | } | ||
6 | function extractLast( term ) { | ||
7 | return split( term ).pop(); | ||
8 | } | ||
9 | |||
10 | |||
11 | $("#value").bind("keydown", function(event) { | ||
12 | if (event.keyCode === $.ui.keyCode.TAB && $(this).data("ui-autocomplete").menu.active) { | ||
13 | event.preventDefault(); | ||
14 | } | ||
15 | }).autocomplete({ | ||
16 | source : function(request, response) { | ||
17 | $.getJSON("./?view=tags", { | ||
18 | term : extractLast(request.term), | ||
19 | //id: $(':hidden#entry_id').val() | ||
20 | }, response); | ||
21 | }, | ||
22 | search : function() { | ||
23 | // custom minLength | ||
24 | var term = extractLast(this.value); | ||
25 | if (term.length < 1) { | ||
26 | return false; | ||
27 | } | ||
28 | }, | ||
29 | focus : function() { | ||
30 | // prevent value inserted on focus | ||
31 | return false; | ||
32 | }, | ||
33 | select : function(event, ui) { | ||
34 | var terms = split(this.value); | ||
35 | // remove the current input | ||
36 | terms.pop(); | ||
37 | // add the selected item | ||
38 | terms.push(ui.item.value); | ||
39 | // add placeholder to get the comma-and-space at the end | ||
40 | terms.push(""); | ||
41 | this.value = terms.join(", "); | ||
42 | return false; | ||
43 | } | ||
44 | }); | ||
45 | |||
46 | |||
47 | }); | ||
diff --git a/themes/default/js/jquery-ui-1.10.4.custom.js b/themes/default/js/jquery-ui-1.10.4.custom.js new file mode 100644 index 00000000..6f599fca --- /dev/null +++ b/themes/default/js/jquery-ui-1.10.4.custom.js | |||
@@ -0,0 +1,2519 @@ | |||
1 | /*! jQuery UI - v1.10.4 - 2014-03-08 | ||
2 | * http://jqueryui.com | ||
3 | * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.menu.js | ||
4 | * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ | ||
5 | |||
6 | (function( $, undefined ) { | ||
7 | |||
8 | var uuid = 0, | ||
9 | runiqueId = /^ui-id-\d+$/; | ||
10 | |||
11 | // $.ui might exist from components with no dependencies, e.g., $.ui.position | ||
12 | $.ui = $.ui || {}; | ||
13 | |||
14 | $.extend( $.ui, { | ||
15 | version: "1.10.4", | ||
16 | |||
17 | keyCode: { | ||
18 | BACKSPACE: 8, | ||
19 | COMMA: 188, | ||
20 | DELETE: 46, | ||
21 | DOWN: 40, | ||
22 | END: 35, | ||
23 | ENTER: 13, | ||
24 | ESCAPE: 27, | ||
25 | HOME: 36, | ||
26 | LEFT: 37, | ||
27 | NUMPAD_ADD: 107, | ||
28 | NUMPAD_DECIMAL: 110, | ||
29 | NUMPAD_DIVIDE: 111, | ||
30 | NUMPAD_ENTER: 108, | ||
31 | NUMPAD_MULTIPLY: 106, | ||
32 | NUMPAD_SUBTRACT: 109, | ||
33 | PAGE_DOWN: 34, | ||
34 | PAGE_UP: 33, | ||
35 | PERIOD: 190, | ||
36 | RIGHT: 39, | ||
37 | SPACE: 32, | ||
38 | TAB: 9, | ||
39 | UP: 38 | ||
40 | } | ||
41 | }); | ||
42 | |||
43 | // plugins | ||
44 | $.fn.extend({ | ||
45 | focus: (function( orig ) { | ||
46 | return function( delay, fn ) { | ||
47 | return typeof delay === "number" ? | ||
48 | this.each(function() { | ||
49 | var elem = this; | ||
50 | setTimeout(function() { | ||
51 | $( elem ).focus(); | ||
52 | if ( fn ) { | ||
53 | fn.call( elem ); | ||
54 | } | ||
55 | }, delay ); | ||
56 | }) : | ||
57 | orig.apply( this, arguments ); | ||
58 | }; | ||
59 | })( $.fn.focus ), | ||
60 | |||
61 | scrollParent: function() { | ||
62 | var scrollParent; | ||
63 | if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { | ||
64 | scrollParent = this.parents().filter(function() { | ||
65 | return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); | ||
66 | }).eq(0); | ||
67 | } else { | ||
68 | scrollParent = this.parents().filter(function() { | ||
69 | return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); | ||
70 | }).eq(0); | ||
71 | } | ||
72 | |||
73 | return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; | ||
74 | }, | ||
75 | |||
76 | zIndex: function( zIndex ) { | ||
77 | if ( zIndex !== undefined ) { | ||
78 | return this.css( "zIndex", zIndex ); | ||
79 | } | ||
80 | |||
81 | if ( this.length ) { | ||
82 | var elem = $( this[ 0 ] ), position, value; | ||
83 | while ( elem.length && elem[ 0 ] !== document ) { | ||
84 | // Ignore z-index if position is set to a value where z-index is ignored by the browser | ||
85 | // This makes behavior of this function consistent across browsers | ||
86 | // WebKit always returns auto if the element is positioned | ||
87 | position = elem.css( "position" ); | ||
88 | if ( position === "absolute" || position === "relative" || position === "fixed" ) { | ||
89 | // IE returns 0 when zIndex is not specified | ||
90 | // other browsers return a string | ||
91 | // we ignore the case of nested elements with an explicit value of 0 | ||
92 | // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> | ||
93 | value = parseInt( elem.css( "zIndex" ), 10 ); | ||
94 | if ( !isNaN( value ) && value !== 0 ) { | ||
95 | return value; | ||
96 | } | ||
97 | } | ||
98 | elem = elem.parent(); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | return 0; | ||
103 | }, | ||
104 | |||
105 | uniqueId: function() { | ||
106 | return this.each(function() { | ||
107 | if ( !this.id ) { | ||
108 | this.id = "ui-id-" + (++uuid); | ||
109 | } | ||
110 | }); | ||
111 | }, | ||
112 | |||
113 | removeUniqueId: function() { | ||
114 | return this.each(function() { | ||
115 | if ( runiqueId.test( this.id ) ) { | ||
116 | $( this ).removeAttr( "id" ); | ||
117 | } | ||
118 | }); | ||
119 | } | ||
120 | }); | ||
121 | |||
122 | // selectors | ||
123 | function focusable( element, isTabIndexNotNaN ) { | ||
124 | var map, mapName, img, | ||
125 | nodeName = element.nodeName.toLowerCase(); | ||
126 | if ( "area" === nodeName ) { | ||
127 | map = element.parentNode; | ||
128 | mapName = map.name; | ||
129 | if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { | ||
130 | return false; | ||
131 | } | ||
132 | img = $( "img[usemap=#" + mapName + "]" )[0]; | ||
133 | return !!img && visible( img ); | ||
134 | } | ||
135 | return ( /input|select|textarea|button|object/.test( nodeName ) ? | ||
136 | !element.disabled : | ||
137 | "a" === nodeName ? | ||
138 | element.href || isTabIndexNotNaN : | ||
139 | isTabIndexNotNaN) && | ||
140 | // the element and all of its ancestors must be visible | ||
141 | visible( element ); | ||
142 | } | ||
143 | |||
144 | function visible( element ) { | ||
145 | return $.expr.filters.visible( element ) && | ||
146 | !$( element ).parents().addBack().filter(function() { | ||
147 | return $.css( this, "visibility" ) === "hidden"; | ||
148 | }).length; | ||
149 | } | ||
150 | |||
151 | $.extend( $.expr[ ":" ], { | ||
152 | data: $.expr.createPseudo ? | ||
153 | $.expr.createPseudo(function( dataName ) { | ||
154 | return function( elem ) { | ||
155 | return !!$.data( elem, dataName ); | ||
156 | }; | ||
157 | }) : | ||
158 | // support: jQuery <1.8 | ||
159 | function( elem, i, match ) { | ||
160 | return !!$.data( elem, match[ 3 ] ); | ||
161 | }, | ||
162 | |||
163 | focusable: function( element ) { | ||
164 | return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); | ||
165 | }, | ||
166 | |||
167 | tabbable: function( element ) { | ||
168 | var tabIndex = $.attr( element, "tabindex" ), | ||
169 | isTabIndexNaN = isNaN( tabIndex ); | ||
170 | return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); | ||
171 | } | ||
172 | }); | ||
173 | |||
174 | // support: jQuery <1.8 | ||
175 | if ( !$( "<a>" ).outerWidth( 1 ).jquery ) { | ||
176 | $.each( [ "Width", "Height" ], function( i, name ) { | ||
177 | var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], | ||
178 | type = name.toLowerCase(), | ||
179 | orig = { | ||
180 | innerWidth: $.fn.innerWidth, | ||
181 | innerHeight: $.fn.innerHeight, | ||
182 | outerWidth: $.fn.outerWidth, | ||
183 | outerHeight: $.fn.outerHeight | ||
184 | }; | ||
185 | |||
186 | function reduce( elem, size, border, margin ) { | ||
187 | $.each( side, function() { | ||
188 | size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; | ||
189 | if ( border ) { | ||
190 | size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; | ||
191 | } | ||
192 | if ( margin ) { | ||
193 | size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; | ||
194 | } | ||
195 | }); | ||
196 | return size; | ||
197 | } | ||
198 | |||
199 | $.fn[ "inner" + name ] = function( size ) { | ||
200 | if ( size === undefined ) { | ||
201 | return orig[ "inner" + name ].call( this ); | ||
202 | } | ||
203 | |||
204 | return this.each(function() { | ||
205 | $( this ).css( type, reduce( this, size ) + "px" ); | ||
206 | }); | ||
207 | }; | ||
208 | |||
209 | $.fn[ "outer" + name] = function( size, margin ) { | ||
210 | if ( typeof size !== "number" ) { | ||
211 | return orig[ "outer" + name ].call( this, size ); | ||
212 | } | ||
213 | |||
214 | return this.each(function() { | ||
215 | $( this).css( type, reduce( this, size, true, margin ) + "px" ); | ||
216 | }); | ||
217 | }; | ||
218 | }); | ||
219 | } | ||
220 | |||
221 | // support: jQuery <1.8 | ||
222 | if ( !$.fn.addBack ) { | ||
223 | $.fn.addBack = function( selector ) { | ||
224 | return this.add( selector == null ? | ||
225 | this.prevObject : this.prevObject.filter( selector ) | ||
226 | ); | ||
227 | }; | ||
228 | } | ||
229 | |||
230 | // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) | ||
231 | if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { | ||
232 | $.fn.removeData = (function( removeData ) { | ||
233 | return function( key ) { | ||
234 | if ( arguments.length ) { | ||
235 | return removeData.call( this, $.camelCase( key ) ); | ||
236 | } else { | ||
237 | return removeData.call( this ); | ||
238 | } | ||
239 | }; | ||
240 | })( $.fn.removeData ); | ||
241 | } | ||
242 | |||
243 | |||
244 | |||
245 | |||
246 | |||
247 | // deprecated | ||
248 | $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); | ||
249 | |||
250 | $.support.selectstart = "onselectstart" in document.createElement( "div" ); | ||
251 | $.fn.extend({ | ||
252 | disableSelection: function() { | ||
253 | return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + | ||
254 | ".ui-disableSelection", function( event ) { | ||
255 | event.preventDefault(); | ||
256 | }); | ||
257 | }, | ||
258 | |||
259 | enableSelection: function() { | ||
260 | return this.unbind( ".ui-disableSelection" ); | ||
261 | } | ||
262 | }); | ||
263 | |||
264 | $.extend( $.ui, { | ||
265 | // $.ui.plugin is deprecated. Use $.widget() extensions instead. | ||
266 | plugin: { | ||
267 | add: function( module, option, set ) { | ||
268 | var i, | ||
269 | proto = $.ui[ module ].prototype; | ||
270 | for ( i in set ) { | ||
271 | proto.plugins[ i ] = proto.plugins[ i ] || []; | ||
272 | proto.plugins[ i ].push( [ option, set[ i ] ] ); | ||
273 | } | ||
274 | }, | ||
275 | call: function( instance, name, args ) { | ||
276 | var i, | ||
277 | set = instance.plugins[ name ]; | ||
278 | if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { | ||
279 | return; | ||
280 | } | ||
281 | |||
282 | for ( i = 0; i < set.length; i++ ) { | ||
283 | if ( instance.options[ set[ i ][ 0 ] ] ) { | ||
284 | set[ i ][ 1 ].apply( instance.element, args ); | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | }, | ||
289 | |||
290 | // only used by resizable | ||
291 | hasScroll: function( el, a ) { | ||
292 | |||
293 | //If overflow is hidden, the element might have extra content, but the user wants to hide it | ||
294 | if ( $( el ).css( "overflow" ) === "hidden") { | ||
295 | return false; | ||
296 | } | ||
297 | |||
298 | var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", | ||
299 | has = false; | ||
300 | |||
301 | if ( el[ scroll ] > 0 ) { | ||
302 | return true; | ||
303 | } | ||
304 | |||
305 | // TODO: determine which cases actually cause this to happen | ||
306 | // if the element doesn't have the scroll set, see if it's possible to | ||
307 | // set the scroll | ||
308 | el[ scroll ] = 1; | ||
309 | has = ( el[ scroll ] > 0 ); | ||
310 | el[ scroll ] = 0; | ||
311 | return has; | ||
312 | } | ||
313 | }); | ||
314 | |||
315 | })( jQuery ); | ||
316 | (function( $, undefined ) { | ||
317 | |||
318 | var uuid = 0, | ||
319 | slice = Array.prototype.slice, | ||
320 | _cleanData = $.cleanData; | ||
321 | $.cleanData = function( elems ) { | ||
322 | for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { | ||
323 | try { | ||
324 | $( elem ).triggerHandler( "remove" ); | ||
325 | // http://bugs.jquery.com/ticket/8235 | ||
326 | } catch( e ) {} | ||
327 | } | ||
328 | _cleanData( elems ); | ||
329 | }; | ||
330 | |||
331 | $.widget = function( name, base, prototype ) { | ||
332 | var fullName, existingConstructor, constructor, basePrototype, | ||
333 | // proxiedPrototype allows the provided prototype to remain unmodified | ||
334 | // so that it can be used as a mixin for multiple widgets (#8876) | ||
335 | proxiedPrototype = {}, | ||
336 | namespace = name.split( "." )[ 0 ]; | ||
337 | |||
338 | name = name.split( "." )[ 1 ]; | ||
339 | fullName = namespace + "-" + name; | ||
340 | |||
341 | if ( !prototype ) { | ||
342 | prototype = base; | ||
343 | base = $.Widget; | ||
344 | } | ||
345 | |||
346 | // create selector for plugin | ||
347 | $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { | ||
348 | return !!$.data( elem, fullName ); | ||
349 | }; | ||
350 | |||
351 | $[ namespace ] = $[ namespace ] || {}; | ||
352 | existingConstructor = $[ namespace ][ name ]; | ||
353 | constructor = $[ namespace ][ name ] = function( options, element ) { | ||
354 | // allow instantiation without "new" keyword | ||
355 | if ( !this._createWidget ) { | ||
356 | return new constructor( options, element ); | ||
357 | } | ||
358 | |||
359 | // allow instantiation without initializing for simple inheritance | ||
360 | // must use "new" keyword (the code above always passes args) | ||
361 | if ( arguments.length ) { | ||
362 | this._createWidget( options, element ); | ||
363 | } | ||
364 | }; | ||
365 | // extend with the existing constructor to carry over any static properties | ||
366 | $.extend( constructor, existingConstructor, { | ||
367 | version: prototype.version, | ||
368 | // copy the object used to create the prototype in case we need to | ||
369 | // redefine the widget later | ||
370 | _proto: $.extend( {}, prototype ), | ||
371 | // track widgets that inherit from this widget in case this widget is | ||
372 | // redefined after a widget inherits from it | ||
373 | _childConstructors: [] | ||
374 | }); | ||
375 | |||
376 | basePrototype = new base(); | ||
377 | // we need to make the options hash a property directly on the new instance | ||
378 | // otherwise we'll modify the options hash on the prototype that we're | ||
379 | // inheriting from | ||
380 | basePrototype.options = $.widget.extend( {}, basePrototype.options ); | ||
381 | $.each( prototype, function( prop, value ) { | ||
382 | if ( !$.isFunction( value ) ) { | ||
383 | proxiedPrototype[ prop ] = value; | ||
384 | return; | ||
385 | } | ||
386 | proxiedPrototype[ prop ] = (function() { | ||
387 | var _super = function() { | ||
388 | return base.prototype[ prop ].apply( this, arguments ); | ||
389 | }, | ||
390 | _superApply = function( args ) { | ||
391 | return base.prototype[ prop ].apply( this, args ); | ||
392 | }; | ||
393 | return function() { | ||
394 | var __super = this._super, | ||
395 | __superApply = this._superApply, | ||
396 | returnValue; | ||
397 | |||
398 | this._super = _super; | ||
399 | this._superApply = _superApply; | ||
400 | |||
401 | returnValue = value.apply( this, arguments ); | ||
402 | |||
403 | this._super = __super; | ||
404 | this._superApply = __superApply; | ||
405 | |||
406 | return returnValue; | ||
407 | }; | ||
408 | })(); | ||
409 | }); | ||
410 | constructor.prototype = $.widget.extend( basePrototype, { | ||
411 | // TODO: remove support for widgetEventPrefix | ||
412 | // always use the name + a colon as the prefix, e.g., draggable:start | ||
413 | // don't prefix for widgets that aren't DOM-based | ||
414 | widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name | ||
415 | }, proxiedPrototype, { | ||
416 | constructor: constructor, | ||
417 | namespace: namespace, | ||
418 | widgetName: name, | ||
419 | widgetFullName: fullName | ||
420 | }); | ||
421 | |||
422 | // If this widget is being redefined then we need to find all widgets that | ||
423 | // are inheriting from it and redefine all of them so that they inherit from | ||
424 | // the new version of this widget. We're essentially trying to replace one | ||
425 | // level in the prototype chain. | ||
426 | if ( existingConstructor ) { | ||
427 | $.each( existingConstructor._childConstructors, function( i, child ) { | ||
428 | var childPrototype = child.prototype; | ||
429 | |||
430 | // redefine the child widget using the same prototype that was | ||
431 | // originally used, but inherit from the new version of the base | ||
432 | $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); | ||
433 | }); | ||
434 | // remove the list of existing child constructors from the old constructor | ||
435 | // so the old child constructors can be garbage collected | ||
436 | delete existingConstructor._childConstructors; | ||
437 | } else { | ||
438 | base._childConstructors.push( constructor ); | ||
439 | } | ||
440 | |||
441 | $.widget.bridge( name, constructor ); | ||
442 | }; | ||
443 | |||
444 | $.widget.extend = function( target ) { | ||
445 | var input = slice.call( arguments, 1 ), | ||
446 | inputIndex = 0, | ||
447 | inputLength = input.length, | ||
448 | key, | ||
449 | value; | ||
450 | for ( ; inputIndex < inputLength; inputIndex++ ) { | ||
451 | for ( key in input[ inputIndex ] ) { | ||
452 | value = input[ inputIndex ][ key ]; | ||
453 | if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { | ||
454 | // Clone objects | ||
455 | if ( $.isPlainObject( value ) ) { | ||
456 | target[ key ] = $.isPlainObject( target[ key ] ) ? | ||
457 | $.widget.extend( {}, target[ key ], value ) : | ||
458 | // Don't extend strings, arrays, etc. with objects | ||
459 | $.widget.extend( {}, value ); | ||
460 | // Copy everything else by reference | ||
461 | } else { | ||
462 | target[ key ] = value; | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | } | ||
467 | return target; | ||
468 | }; | ||
469 | |||
470 | $.widget.bridge = function( name, object ) { | ||
471 | var fullName = object.prototype.widgetFullName || name; | ||
472 | $.fn[ name ] = function( options ) { | ||
473 | var isMethodCall = typeof options === "string", | ||
474 | args = slice.call( arguments, 1 ), | ||
475 | returnValue = this; | ||
476 | |||
477 | // allow multiple hashes to be passed on init | ||
478 | options = !isMethodCall && args.length ? | ||
479 | $.widget.extend.apply( null, [ options ].concat(args) ) : | ||
480 | options; | ||
481 | |||
482 | if ( isMethodCall ) { | ||
483 | this.each(function() { | ||
484 | var methodValue, | ||
485 | instance = $.data( this, fullName ); | ||
486 | if ( !instance ) { | ||
487 | return $.error( "cannot call methods on " + name + " prior to initialization; " + | ||
488 | "attempted to call method '" + options + "'" ); | ||
489 | } | ||
490 | if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { | ||
491 | return $.error( "no such method '" + options + "' for " + name + " widget instance" ); | ||
492 | } | ||
493 | methodValue = instance[ options ].apply( instance, args ); | ||
494 | if ( methodValue !== instance && methodValue !== undefined ) { | ||
495 | returnValue = methodValue && methodValue.jquery ? | ||
496 | returnValue.pushStack( methodValue.get() ) : | ||
497 | methodValue; | ||
498 | return false; | ||
499 | } | ||
500 | }); | ||
501 | } else { | ||
502 | this.each(function() { | ||
503 | var instance = $.data( this, fullName ); | ||
504 | if ( instance ) { | ||
505 | instance.option( options || {} )._init(); | ||
506 | } else { | ||
507 | $.data( this, fullName, new object( options, this ) ); | ||
508 | } | ||
509 | }); | ||
510 | } | ||
511 | |||
512 | return returnValue; | ||
513 | }; | ||
514 | }; | ||
515 | |||
516 | $.Widget = function( /* options, element */ ) {}; | ||
517 | $.Widget._childConstructors = []; | ||
518 | |||
519 | $.Widget.prototype = { | ||
520 | widgetName: "widget", | ||
521 | widgetEventPrefix: "", | ||
522 | defaultElement: "<div>", | ||
523 | options: { | ||
524 | disabled: false, | ||
525 | |||
526 | // callbacks | ||
527 | create: null | ||
528 | }, | ||
529 | _createWidget: function( options, element ) { | ||
530 | element = $( element || this.defaultElement || this )[ 0 ]; | ||
531 | this.element = $( element ); | ||
532 | this.uuid = uuid++; | ||
533 | this.eventNamespace = "." + this.widgetName + this.uuid; | ||
534 | this.options = $.widget.extend( {}, | ||
535 | this.options, | ||
536 | this._getCreateOptions(), | ||
537 | options ); | ||
538 | |||
539 | this.bindings = $(); | ||
540 | this.hoverable = $(); | ||
541 | this.focusable = $(); | ||
542 | |||
543 | if ( element !== this ) { | ||
544 | $.data( element, this.widgetFullName, this ); | ||
545 | this._on( true, this.element, { | ||
546 | remove: function( event ) { | ||
547 | if ( event.target === element ) { | ||
548 | this.destroy(); | ||
549 | } | ||
550 | } | ||
551 | }); | ||
552 | this.document = $( element.style ? | ||
553 | // element within the document | ||
554 | element.ownerDocument : | ||
555 | // element is window or document | ||
556 | element.document || element ); | ||
557 | this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); | ||
558 | } | ||
559 | |||
560 | this._create(); | ||
561 | this._trigger( "create", null, this._getCreateEventData() ); | ||
562 | this._init(); | ||
563 | }, | ||
564 | _getCreateOptions: $.noop, | ||
565 | _getCreateEventData: $.noop, | ||
566 | _create: $.noop, | ||
567 | _init: $.noop, | ||
568 | |||
569 | destroy: function() { | ||
570 | this._destroy(); | ||
571 | // we can probably remove the unbind calls in 2.0 | ||
572 | // all event bindings should go through this._on() | ||
573 | this.element | ||
574 | .unbind( this.eventNamespace ) | ||
575 | // 1.9 BC for #7810 | ||
576 | // TODO remove dual storage | ||
577 | .removeData( this.widgetName ) | ||
578 | .removeData( this.widgetFullName ) | ||
579 | // support: jquery <1.6.3 | ||
580 | // http://bugs.jquery.com/ticket/9413 | ||
581 | .removeData( $.camelCase( this.widgetFullName ) ); | ||
582 | this.widget() | ||
583 | .unbind( this.eventNamespace ) | ||
584 | .removeAttr( "aria-disabled" ) | ||
585 | .removeClass( | ||
586 | this.widgetFullName + "-disabled " + | ||
587 | "ui-state-disabled" ); | ||
588 | |||
589 | // clean up events and states | ||
590 | this.bindings.unbind( this.eventNamespace ); | ||
591 | this.hoverable.removeClass( "ui-state-hover" ); | ||
592 | this.focusable.removeClass( "ui-state-focus" ); | ||
593 | }, | ||
594 | _destroy: $.noop, | ||
595 | |||
596 | widget: function() { | ||
597 | return this.element; | ||
598 | }, | ||
599 | |||
600 | option: function( key, value ) { | ||
601 | var options = key, | ||
602 | parts, | ||
603 | curOption, | ||
604 | i; | ||
605 | |||
606 | if ( arguments.length === 0 ) { | ||
607 | // don't return a reference to the internal hash | ||
608 | return $.widget.extend( {}, this.options ); | ||
609 | } | ||
610 | |||
611 | if ( typeof key === "string" ) { | ||
612 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } | ||
613 | options = {}; | ||
614 | parts = key.split( "." ); | ||
615 | key = parts.shift(); | ||
616 | if ( parts.length ) { | ||
617 | curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); | ||
618 | for ( i = 0; i < parts.length - 1; i++ ) { | ||
619 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; | ||
620 | curOption = curOption[ parts[ i ] ]; | ||
621 | } | ||
622 | key = parts.pop(); | ||
623 | if ( arguments.length === 1 ) { | ||
624 | return curOption[ key ] === undefined ? null : curOption[ key ]; | ||
625 | } | ||
626 | curOption[ key ] = value; | ||
627 | } else { | ||
628 | if ( arguments.length === 1 ) { | ||
629 | return this.options[ key ] === undefined ? null : this.options[ key ]; | ||
630 | } | ||
631 | options[ key ] = value; | ||
632 | } | ||
633 | } | ||
634 | |||
635 | this._setOptions( options ); | ||
636 | |||
637 | return this; | ||
638 | }, | ||
639 | _setOptions: function( options ) { | ||
640 | var key; | ||
641 | |||
642 | for ( key in options ) { | ||
643 | this._setOption( key, options[ key ] ); | ||
644 | } | ||
645 | |||
646 | return this; | ||
647 | }, | ||
648 | _setOption: function( key, value ) { | ||
649 | this.options[ key ] = value; | ||
650 | |||
651 | if ( key === "disabled" ) { | ||
652 | this.widget() | ||
653 | .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) | ||
654 | .attr( "aria-disabled", value ); | ||
655 | this.hoverable.removeClass( "ui-state-hover" ); | ||
656 | this.focusable.removeClass( "ui-state-focus" ); | ||
657 | } | ||
658 | |||
659 | return this; | ||
660 | }, | ||
661 | |||
662 | enable: function() { | ||
663 | return this._setOption( "disabled", false ); | ||
664 | }, | ||
665 | disable: function() { | ||
666 | return this._setOption( "disabled", true ); | ||
667 | }, | ||
668 | |||
669 | _on: function( suppressDisabledCheck, element, handlers ) { | ||
670 | var delegateElement, | ||
671 | instance = this; | ||
672 | |||
673 | // no suppressDisabledCheck flag, shuffle arguments | ||
674 | if ( typeof suppressDisabledCheck !== "boolean" ) { | ||
675 | handlers = element; | ||
676 | element = suppressDisabledCheck; | ||
677 | suppressDisabledCheck = false; | ||
678 | } | ||
679 | |||
680 | // no element argument, shuffle and use this.element | ||
681 | if ( !handlers ) { | ||
682 | handlers = element; | ||
683 | element = this.element; | ||
684 | delegateElement = this.widget(); | ||
685 | } else { | ||
686 | // accept selectors, DOM elements | ||
687 | element = delegateElement = $( element ); | ||
688 | this.bindings = this.bindings.add( element ); | ||
689 | } | ||
690 | |||
691 | $.each( handlers, function( event, handler ) { | ||
692 | function handlerProxy() { | ||
693 | // allow widgets to customize the disabled handling | ||
694 | // - disabled as an array instead of boolean | ||
695 | // - disabled class as method for disabling individual parts | ||
696 | if ( !suppressDisabledCheck && | ||
697 | ( instance.options.disabled === true || | ||
698 | $( this ).hasClass( "ui-state-disabled" ) ) ) { | ||
699 | return; | ||
700 | } | ||
701 | return ( typeof handler === "string" ? instance[ handler ] : handler ) | ||
702 | .apply( instance, arguments ); | ||
703 | } | ||
704 | |||
705 | // copy the guid so direct unbinding works | ||
706 | if ( typeof handler !== "string" ) { | ||
707 | handlerProxy.guid = handler.guid = | ||
708 | handler.guid || handlerProxy.guid || $.guid++; | ||
709 | } | ||
710 | |||
711 | var match = event.match( /^(\w+)\s*(.*)$/ ), | ||
712 | eventName = match[1] + instance.eventNamespace, | ||
713 | selector = match[2]; | ||
714 | if ( selector ) { | ||
715 | delegateElement.delegate( selector, eventName, handlerProxy ); | ||
716 | } else { | ||
717 | element.bind( eventName, handlerProxy ); | ||
718 | } | ||
719 | }); | ||
720 | }, | ||
721 | |||
722 | _off: function( element, eventName ) { | ||
723 | eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; | ||
724 | element.unbind( eventName ).undelegate( eventName ); | ||
725 | }, | ||
726 | |||
727 | _delay: function( handler, delay ) { | ||
728 | function handlerProxy() { | ||
729 | return ( typeof handler === "string" ? instance[ handler ] : handler ) | ||
730 | .apply( instance, arguments ); | ||
731 | } | ||
732 | var instance = this; | ||
733 | return setTimeout( handlerProxy, delay || 0 ); | ||
734 | }, | ||
735 | |||
736 | _hoverable: function( element ) { | ||
737 | this.hoverable = this.hoverable.add( element ); | ||
738 | this._on( element, { | ||
739 | mouseenter: function( event ) { | ||
740 | $( event.currentTarget ).addClass( "ui-state-hover" ); | ||
741 | }, | ||
742 | mouseleave: function( event ) { | ||
743 | $( event.currentTarget ).removeClass( "ui-state-hover" ); | ||
744 | } | ||
745 | }); | ||
746 | }, | ||
747 | |||
748 | _focusable: function( element ) { | ||
749 | this.focusable = this.focusable.add( element ); | ||
750 | this._on( element, { | ||
751 | focusin: function( event ) { | ||
752 | $( event.currentTarget ).addClass( "ui-state-focus" ); | ||
753 | }, | ||
754 | focusout: function( event ) { | ||
755 | $( event.currentTarget ).removeClass( "ui-state-focus" ); | ||
756 | } | ||
757 | }); | ||
758 | }, | ||
759 | |||
760 | _trigger: function( type, event, data ) { | ||
761 | var prop, orig, | ||
762 | callback = this.options[ type ]; | ||
763 | |||
764 | data = data || {}; | ||
765 | event = $.Event( event ); | ||
766 | event.type = ( type === this.widgetEventPrefix ? | ||
767 | type : | ||
768 | this.widgetEventPrefix + type ).toLowerCase(); | ||
769 | // the original event may come from any element | ||
770 | // so we need to reset the target on the new event | ||
771 | event.target = this.element[ 0 ]; | ||
772 | |||
773 | // copy original event properties over to the new event | ||
774 | orig = event.originalEvent; | ||
775 | if ( orig ) { | ||
776 | for ( prop in orig ) { | ||
777 | if ( !( prop in event ) ) { | ||
778 | event[ prop ] = orig[ prop ]; | ||
779 | } | ||
780 | } | ||
781 | } | ||
782 | |||
783 | this.element.trigger( event, data ); | ||
784 | return !( $.isFunction( callback ) && | ||
785 | callback.apply( this.element[0], [ event ].concat( data ) ) === false || | ||
786 | event.isDefaultPrevented() ); | ||
787 | } | ||
788 | }; | ||
789 | |||
790 | $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { | ||
791 | $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { | ||
792 | if ( typeof options === "string" ) { | ||
793 | options = { effect: options }; | ||
794 | } | ||
795 | var hasOptions, | ||
796 | effectName = !options ? | ||
797 | method : | ||
798 | options === true || typeof options === "number" ? | ||
799 | defaultEffect : | ||
800 | options.effect || defaultEffect; | ||
801 | options = options || {}; | ||
802 | if ( typeof options === "number" ) { | ||
803 | options = { duration: options }; | ||
804 | } | ||
805 | hasOptions = !$.isEmptyObject( options ); | ||
806 | options.complete = callback; | ||
807 | if ( options.delay ) { | ||
808 | element.delay( options.delay ); | ||
809 | } | ||
810 | if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { | ||
811 | element[ method ]( options ); | ||
812 | } else if ( effectName !== method && element[ effectName ] ) { | ||
813 | element[ effectName ]( options.duration, options.easing, callback ); | ||
814 | } else { | ||
815 | element.queue(function( next ) { | ||
816 | $( this )[ method ](); | ||
817 | if ( callback ) { | ||
818 | callback.call( element[ 0 ] ); | ||
819 | } | ||
820 | next(); | ||
821 | }); | ||
822 | } | ||
823 | }; | ||
824 | }); | ||
825 | |||
826 | })( jQuery ); | ||
827 | (function( $, undefined ) { | ||
828 | |||
829 | $.ui = $.ui || {}; | ||
830 | |||
831 | var cachedScrollbarWidth, | ||
832 | max = Math.max, | ||
833 | abs = Math.abs, | ||
834 | round = Math.round, | ||
835 | rhorizontal = /left|center|right/, | ||
836 | rvertical = /top|center|bottom/, | ||
837 | roffset = /[\+\-]\d+(\.[\d]+)?%?/, | ||
838 | rposition = /^\w+/, | ||
839 | rpercent = /%$/, | ||
840 | _position = $.fn.position; | ||
841 | |||
842 | function getOffsets( offsets, width, height ) { | ||
843 | return [ | ||
844 | parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), | ||
845 | parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) | ||
846 | ]; | ||
847 | } | ||
848 | |||
849 | function parseCss( element, property ) { | ||
850 | return parseInt( $.css( element, property ), 10 ) || 0; | ||
851 | } | ||
852 | |||
853 | function getDimensions( elem ) { | ||
854 | var raw = elem[0]; | ||
855 | if ( raw.nodeType === 9 ) { | ||
856 | return { | ||
857 | width: elem.width(), | ||
858 | height: elem.height(), | ||
859 | offset: { top: 0, left: 0 } | ||
860 | }; | ||
861 | } | ||
862 | if ( $.isWindow( raw ) ) { | ||
863 | return { | ||
864 | width: elem.width(), | ||
865 | height: elem.height(), | ||
866 | offset: { top: elem.scrollTop(), left: elem.scrollLeft() } | ||
867 | }; | ||
868 | } | ||
869 | if ( raw.preventDefault ) { | ||
870 | return { | ||
871 | width: 0, | ||
872 | height: 0, | ||
873 | offset: { top: raw.pageY, left: raw.pageX } | ||
874 | }; | ||
875 | } | ||
876 | return { | ||
877 | width: elem.outerWidth(), | ||
878 | height: elem.outerHeight(), | ||
879 | offset: elem.offset() | ||
880 | }; | ||
881 | } | ||
882 | |||
883 | $.position = { | ||
884 | scrollbarWidth: function() { | ||
885 | if ( cachedScrollbarWidth !== undefined ) { | ||
886 | return cachedScrollbarWidth; | ||
887 | } | ||
888 | var w1, w2, | ||
889 | div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ), | ||
890 | innerDiv = div.children()[0]; | ||
891 | |||
892 | $( "body" ).append( div ); | ||
893 | w1 = innerDiv.offsetWidth; | ||
894 | div.css( "overflow", "scroll" ); | ||
895 | |||
896 | w2 = innerDiv.offsetWidth; | ||
897 | |||
898 | if ( w1 === w2 ) { | ||
899 | w2 = div[0].clientWidth; | ||
900 | } | ||
901 | |||
902 | div.remove(); | ||
903 | |||
904 | return (cachedScrollbarWidth = w1 - w2); | ||
905 | }, | ||
906 | getScrollInfo: function( within ) { | ||
907 | var overflowX = within.isWindow || within.isDocument ? "" : | ||
908 | within.element.css( "overflow-x" ), | ||
909 | overflowY = within.isWindow || within.isDocument ? "" : | ||
910 | within.element.css( "overflow-y" ), | ||
911 | hasOverflowX = overflowX === "scroll" || | ||
912 | ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), | ||
913 | hasOverflowY = overflowY === "scroll" || | ||
914 | ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); | ||
915 | return { | ||
916 | width: hasOverflowY ? $.position.scrollbarWidth() : 0, | ||
917 | height: hasOverflowX ? $.position.scrollbarWidth() : 0 | ||
918 | }; | ||
919 | }, | ||
920 | getWithinInfo: function( element ) { | ||
921 | var withinElement = $( element || window ), | ||
922 | isWindow = $.isWindow( withinElement[0] ), | ||
923 | isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9; | ||
924 | return { | ||
925 | element: withinElement, | ||
926 | isWindow: isWindow, | ||
927 | isDocument: isDocument, | ||
928 | offset: withinElement.offset() || { left: 0, top: 0 }, | ||
929 | scrollLeft: withinElement.scrollLeft(), | ||
930 | scrollTop: withinElement.scrollTop(), | ||
931 | width: isWindow ? withinElement.width() : withinElement.outerWidth(), | ||
932 | height: isWindow ? withinElement.height() : withinElement.outerHeight() | ||
933 | }; | ||
934 | } | ||
935 | }; | ||
936 | |||
937 | $.fn.position = function( options ) { | ||
938 | if ( !options || !options.of ) { | ||
939 | return _position.apply( this, arguments ); | ||
940 | } | ||
941 | |||
942 | // make a copy, we don't want to modify arguments | ||
943 | options = $.extend( {}, options ); | ||
944 | |||
945 | var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, | ||
946 | target = $( options.of ), | ||
947 | within = $.position.getWithinInfo( options.within ), | ||
948 | scrollInfo = $.position.getScrollInfo( within ), | ||
949 | collision = ( options.collision || "flip" ).split( " " ), | ||
950 | offsets = {}; | ||
951 | |||
952 | dimensions = getDimensions( target ); | ||
953 | if ( target[0].preventDefault ) { | ||
954 | // force left top to allow flipping | ||
955 | options.at = "left top"; | ||
956 | } | ||
957 | targetWidth = dimensions.width; | ||
958 | targetHeight = dimensions.height; | ||
959 | targetOffset = dimensions.offset; | ||
960 | // clone to reuse original targetOffset later | ||
961 | basePosition = $.extend( {}, targetOffset ); | ||
962 | |||
963 | // force my and at to have valid horizontal and vertical positions | ||
964 | // if a value is missing or invalid, it will be converted to center | ||
965 | $.each( [ "my", "at" ], function() { | ||
966 | var pos = ( options[ this ] || "" ).split( " " ), | ||
967 | horizontalOffset, | ||
968 | verticalOffset; | ||
969 | |||
970 | if ( pos.length === 1) { | ||
971 | pos = rhorizontal.test( pos[ 0 ] ) ? | ||
972 | pos.concat( [ "center" ] ) : | ||
973 | rvertical.test( pos[ 0 ] ) ? | ||
974 | [ "center" ].concat( pos ) : | ||
975 | [ "center", "center" ]; | ||
976 | } | ||
977 | pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; | ||
978 | pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; | ||
979 | |||
980 | // calculate offsets | ||
981 | horizontalOffset = roffset.exec( pos[ 0 ] ); | ||
982 | verticalOffset = roffset.exec( pos[ 1 ] ); | ||
983 | offsets[ this ] = [ | ||
984 | horizontalOffset ? horizontalOffset[ 0 ] : 0, | ||
985 | verticalOffset ? verticalOffset[ 0 ] : 0 | ||
986 | ]; | ||
987 | |||
988 | // reduce to just the positions without the offsets | ||
989 | options[ this ] = [ | ||
990 | rposition.exec( pos[ 0 ] )[ 0 ], | ||
991 | rposition.exec( pos[ 1 ] )[ 0 ] | ||
992 | ]; | ||
993 | }); | ||
994 | |||
995 | // normalize collision option | ||
996 | if ( collision.length === 1 ) { | ||
997 | collision[ 1 ] = collision[ 0 ]; | ||
998 | } | ||
999 | |||
1000 | if ( options.at[ 0 ] === "right" ) { | ||
1001 | basePosition.left += targetWidth; | ||
1002 | } else if ( options.at[ 0 ] === "center" ) { | ||
1003 | basePosition.left += targetWidth / 2; | ||
1004 | } | ||
1005 | |||
1006 | if ( options.at[ 1 ] === "bottom" ) { | ||
1007 | basePosition.top += targetHeight; | ||
1008 | } else if ( options.at[ 1 ] === "center" ) { | ||
1009 | basePosition.top += targetHeight / 2; | ||
1010 | } | ||
1011 | |||
1012 | atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); | ||
1013 | basePosition.left += atOffset[ 0 ]; | ||
1014 | basePosition.top += atOffset[ 1 ]; | ||
1015 | |||
1016 | return this.each(function() { | ||
1017 | var collisionPosition, using, | ||
1018 | elem = $( this ), | ||
1019 | elemWidth = elem.outerWidth(), | ||
1020 | elemHeight = elem.outerHeight(), | ||
1021 | marginLeft = parseCss( this, "marginLeft" ), | ||
1022 | marginTop = parseCss( this, "marginTop" ), | ||
1023 | collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, | ||
1024 | collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, | ||
1025 | position = $.extend( {}, basePosition ), | ||
1026 | myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); | ||
1027 | |||
1028 | if ( options.my[ 0 ] === "right" ) { | ||
1029 | position.left -= elemWidth; | ||
1030 | } else if ( options.my[ 0 ] === "center" ) { | ||
1031 | position.left -= elemWidth / 2; | ||
1032 | } | ||
1033 | |||
1034 | if ( options.my[ 1 ] === "bottom" ) { | ||
1035 | position.top -= elemHeight; | ||
1036 | } else if ( options.my[ 1 ] === "center" ) { | ||
1037 | position.top -= elemHeight / 2; | ||
1038 | } | ||
1039 | |||
1040 | position.left += myOffset[ 0 ]; | ||
1041 | position.top += myOffset[ 1 ]; | ||
1042 | |||
1043 | // if the browser doesn't support fractions, then round for consistent results | ||
1044 | if ( !$.support.offsetFractions ) { | ||
1045 | position.left = round( position.left ); | ||
1046 | position.top = round( position.top ); | ||
1047 | } | ||
1048 | |||
1049 | collisionPosition = { | ||
1050 | marginLeft: marginLeft, | ||
1051 | marginTop: marginTop | ||
1052 | }; | ||
1053 | |||
1054 | $.each( [ "left", "top" ], function( i, dir ) { | ||
1055 | if ( $.ui.position[ collision[ i ] ] ) { | ||
1056 | $.ui.position[ collision[ i ] ][ dir ]( position, { | ||
1057 | targetWidth: targetWidth, | ||
1058 | targetHeight: targetHeight, | ||
1059 | elemWidth: elemWidth, | ||
1060 | elemHeight: elemHeight, | ||
1061 | collisionPosition: collisionPosition, | ||
1062 | collisionWidth: collisionWidth, | ||
1063 | collisionHeight: collisionHeight, | ||
1064 | offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], | ||
1065 | my: options.my, | ||
1066 | at: options.at, | ||
1067 | within: within, | ||
1068 | elem : elem | ||
1069 | }); | ||
1070 | } | ||
1071 | }); | ||
1072 | |||
1073 | if ( options.using ) { | ||
1074 | // adds feedback as second argument to using callback, if present | ||
1075 | using = function( props ) { | ||
1076 | var left = targetOffset.left - position.left, | ||
1077 | right = left + targetWidth - elemWidth, | ||
1078 | top = targetOffset.top - position.top, | ||
1079 | bottom = top + targetHeight - elemHeight, | ||
1080 | feedback = { | ||
1081 | target: { | ||
1082 | element: target, | ||
1083 | left: targetOffset.left, | ||
1084 | top: targetOffset.top, | ||
1085 | width: targetWidth, | ||
1086 | height: targetHeight | ||
1087 | }, | ||
1088 | element: { | ||
1089 | element: elem, | ||
1090 | left: position.left, | ||
1091 | top: position.top, | ||
1092 | width: elemWidth, | ||
1093 | height: elemHeight | ||
1094 | }, | ||
1095 | horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", | ||
1096 | vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" | ||
1097 | }; | ||
1098 | if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { | ||
1099 | feedback.horizontal = "center"; | ||
1100 | } | ||
1101 | if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { | ||
1102 | feedback.vertical = "middle"; | ||
1103 | } | ||
1104 | if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { | ||
1105 | feedback.important = "horizontal"; | ||
1106 | } else { | ||
1107 | feedback.important = "vertical"; | ||
1108 | } | ||
1109 | options.using.call( this, props, feedback ); | ||
1110 | }; | ||
1111 | } | ||
1112 | |||
1113 | elem.offset( $.extend( position, { using: using } ) ); | ||
1114 | }); | ||
1115 | }; | ||
1116 | |||
1117 | $.ui.position = { | ||
1118 | fit: { | ||
1119 | left: function( position, data ) { | ||
1120 | var within = data.within, | ||
1121 | withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, | ||
1122 | outerWidth = within.width, | ||
1123 | collisionPosLeft = position.left - data.collisionPosition.marginLeft, | ||
1124 | overLeft = withinOffset - collisionPosLeft, | ||
1125 | overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, | ||
1126 | newOverRight; | ||
1127 | |||
1128 | // element is wider than within | ||
1129 | if ( data.collisionWidth > outerWidth ) { | ||
1130 | // element is initially over the left side of within | ||
1131 | if ( overLeft > 0 && overRight <= 0 ) { | ||
1132 | newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; | ||
1133 | position.left += overLeft - newOverRight; | ||
1134 | // element is initially over right side of within | ||
1135 | } else if ( overRight > 0 && overLeft <= 0 ) { | ||
1136 | position.left = withinOffset; | ||
1137 | // element is initially over both left and right sides of within | ||
1138 | } else { | ||
1139 | if ( overLeft > overRight ) { | ||
1140 | position.left = withinOffset + outerWidth - data.collisionWidth; | ||
1141 | } else { | ||
1142 | position.left = withinOffset; | ||
1143 | } | ||
1144 | } | ||
1145 | // too far left -> align with left edge | ||
1146 | } else if ( overLeft > 0 ) { | ||
1147 | position.left += overLeft; | ||
1148 | // too far right -> align with right edge | ||
1149 | } else if ( overRight > 0 ) { | ||
1150 | position.left -= overRight; | ||
1151 | // adjust based on position and margin | ||
1152 | } else { | ||
1153 | position.left = max( position.left - collisionPosLeft, position.left ); | ||
1154 | } | ||
1155 | }, | ||
1156 | top: function( position, data ) { | ||
1157 | var within = data.within, | ||
1158 | withinOffset = within.isWindow ? within.scrollTop : within.offset.top, | ||
1159 | outerHeight = data.within.height, | ||
1160 | collisionPosTop = position.top - data.collisionPosition.marginTop, | ||
1161 | overTop = withinOffset - collisionPosTop, | ||
1162 | overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, | ||
1163 | newOverBottom; | ||
1164 | |||
1165 | // element is taller than within | ||
1166 | if ( data.collisionHeight > outerHeight ) { | ||
1167 | // element is initially over the top of within | ||
1168 | if ( overTop > 0 && overBottom <= 0 ) { | ||
1169 | newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; | ||
1170 | position.top += overTop - newOverBottom; | ||
1171 | // element is initially over bottom of within | ||
1172 | } else if ( overBottom > 0 && overTop <= 0 ) { | ||
1173 | position.top = withinOffset; | ||
1174 | // element is initially over both top and bottom of within | ||
1175 | } else { | ||
1176 | if ( overTop > overBottom ) { | ||
1177 | position.top = withinOffset + outerHeight - data.collisionHeight; | ||
1178 | } else { | ||
1179 | position.top = withinOffset; | ||
1180 | } | ||
1181 | } | ||
1182 | // too far up -> align with top | ||
1183 | } else if ( overTop > 0 ) { | ||
1184 | position.top += overTop; | ||
1185 | // too far down -> align with bottom edge | ||
1186 | } else if ( overBottom > 0 ) { | ||
1187 | position.top -= overBottom; | ||
1188 | // adjust based on position and margin | ||
1189 | } else { | ||
1190 | position.top = max( position.top - collisionPosTop, position.top ); | ||
1191 | } | ||
1192 | } | ||
1193 | }, | ||
1194 | flip: { | ||
1195 | left: function( position, data ) { | ||
1196 | var within = data.within, | ||
1197 | withinOffset = within.offset.left + within.scrollLeft, | ||
1198 | outerWidth = within.width, | ||
1199 | offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, | ||
1200 | collisionPosLeft = position.left - data.collisionPosition.marginLeft, | ||
1201 | overLeft = collisionPosLeft - offsetLeft, | ||
1202 | overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, | ||
1203 | myOffset = data.my[ 0 ] === "left" ? | ||
1204 | -data.elemWidth : | ||
1205 | data.my[ 0 ] === "right" ? | ||
1206 | data.elemWidth : | ||
1207 | 0, | ||
1208 | atOffset = data.at[ 0 ] === "left" ? | ||
1209 | data.targetWidth : | ||
1210 | data.at[ 0 ] === "right" ? | ||
1211 | -data.targetWidth : | ||
1212 | 0, | ||
1213 | offset = -2 * data.offset[ 0 ], | ||
1214 | newOverRight, | ||
1215 | newOverLeft; | ||
1216 | |||
1217 | if ( overLeft < 0 ) { | ||
1218 | newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; | ||
1219 | if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { | ||
1220 | position.left += myOffset + atOffset + offset; | ||
1221 | } | ||
1222 | } | ||
1223 | else if ( overRight > 0 ) { | ||
1224 | newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; | ||
1225 | if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { | ||
1226 | position.left += myOffset + atOffset + offset; | ||
1227 | } | ||
1228 | } | ||
1229 | }, | ||
1230 | top: function( position, data ) { | ||
1231 | var within = data.within, | ||
1232 | withinOffset = within.offset.top + within.scrollTop, | ||
1233 | outerHeight = within.height, | ||
1234 | offsetTop = within.isWindow ? within.scrollTop : within.offset.top, | ||
1235 | collisionPosTop = position.top - data.collisionPosition.marginTop, | ||
1236 | overTop = collisionPosTop - offsetTop, | ||
1237 | overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, | ||
1238 | top = data.my[ 1 ] === "top", | ||
1239 | myOffset = top ? | ||
1240 | -data.elemHeight : | ||
1241 | data.my[ 1 ] === "bottom" ? | ||
1242 | data.elemHeight : | ||
1243 | 0, | ||
1244 | atOffset = data.at[ 1 ] === "top" ? | ||
1245 | data.targetHeight : | ||
1246 | data.at[ 1 ] === "bottom" ? | ||
1247 | -data.targetHeight : | ||
1248 | 0, | ||
1249 | offset = -2 * data.offset[ 1 ], | ||
1250 | newOverTop, | ||
1251 | newOverBottom; | ||
1252 | if ( overTop < 0 ) { | ||
1253 | newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; | ||
1254 | if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { | ||
1255 | position.top += myOffset + atOffset + offset; | ||
1256 | } | ||
1257 | } | ||
1258 | else if ( overBottom > 0 ) { | ||
1259 | newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; | ||
1260 | if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { | ||
1261 | position.top += myOffset + atOffset + offset; | ||
1262 | } | ||
1263 | } | ||
1264 | } | ||
1265 | }, | ||
1266 | flipfit: { | ||
1267 | left: function() { | ||
1268 | $.ui.position.flip.left.apply( this, arguments ); | ||
1269 | $.ui.position.fit.left.apply( this, arguments ); | ||
1270 | }, | ||
1271 | top: function() { | ||
1272 | $.ui.position.flip.top.apply( this, arguments ); | ||
1273 | $.ui.position.fit.top.apply( this, arguments ); | ||
1274 | } | ||
1275 | } | ||
1276 | }; | ||
1277 | |||
1278 | // fraction support test | ||
1279 | (function () { | ||
1280 | var testElement, testElementParent, testElementStyle, offsetLeft, i, | ||
1281 | body = document.getElementsByTagName( "body" )[ 0 ], | ||
1282 | div = document.createElement( "div" ); | ||
1283 | |||
1284 | //Create a "fake body" for testing based on method used in jQuery.support | ||
1285 | testElement = document.createElement( body ? "div" : "body" ); | ||
1286 | testElementStyle = { | ||
1287 | visibility: "hidden", | ||
1288 | width: 0, | ||
1289 | height: 0, | ||
1290 | border: 0, | ||
1291 | margin: 0, | ||
1292 | background: "none" | ||
1293 | }; | ||
1294 | if ( body ) { | ||
1295 | $.extend( testElementStyle, { | ||
1296 | position: "absolute", | ||
1297 | left: "-1000px", | ||
1298 | top: "-1000px" | ||
1299 | }); | ||
1300 | } | ||
1301 | for ( i in testElementStyle ) { | ||
1302 | testElement.style[ i ] = testElementStyle[ i ]; | ||
1303 | } | ||
1304 | testElement.appendChild( div ); | ||
1305 | testElementParent = body || document.documentElement; | ||
1306 | testElementParent.insertBefore( testElement, testElementParent.firstChild ); | ||
1307 | |||
1308 | div.style.cssText = "position: absolute; left: 10.7432222px;"; | ||
1309 | |||
1310 | offsetLeft = $( div ).offset().left; | ||
1311 | $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; | ||
1312 | |||
1313 | testElement.innerHTML = ""; | ||
1314 | testElementParent.removeChild( testElement ); | ||
1315 | })(); | ||
1316 | |||
1317 | }( jQuery ) ); | ||
1318 | (function( $, undefined ) { | ||
1319 | |||
1320 | $.widget( "ui.autocomplete", { | ||
1321 | version: "1.10.4", | ||
1322 | defaultElement: "<input>", | ||
1323 | options: { | ||
1324 | appendTo: null, | ||
1325 | autoFocus: false, | ||
1326 | delay: 300, | ||
1327 | minLength: 1, | ||
1328 | position: { | ||
1329 | my: "left top", | ||
1330 | at: "left bottom", | ||
1331 | collision: "none" | ||
1332 | }, | ||
1333 | source: null, | ||
1334 | |||
1335 | // callbacks | ||
1336 | change: null, | ||
1337 | close: null, | ||
1338 | focus: null, | ||
1339 | open: null, | ||
1340 | response: null, | ||
1341 | search: null, | ||
1342 | select: null | ||
1343 | }, | ||
1344 | |||
1345 | requestIndex: 0, | ||
1346 | pending: 0, | ||
1347 | |||
1348 | _create: function() { | ||
1349 | // Some browsers only repeat keydown events, not keypress events, | ||
1350 | // so we use the suppressKeyPress flag to determine if we've already | ||
1351 | // handled the keydown event. #7269 | ||
1352 | // Unfortunately the code for & in keypress is the same as the up arrow, | ||
1353 | // so we use the suppressKeyPressRepeat flag to avoid handling keypress | ||
1354 | // events when we know the keydown event was used to modify the | ||
1355 | // search term. #7799 | ||
1356 | var suppressKeyPress, suppressKeyPressRepeat, suppressInput, | ||
1357 | nodeName = this.element[0].nodeName.toLowerCase(), | ||
1358 | isTextarea = nodeName === "textarea", | ||
1359 | isInput = nodeName === "input"; | ||
1360 | |||
1361 | this.isMultiLine = | ||
1362 | // Textareas are always multi-line | ||
1363 | isTextarea ? true : | ||
1364 | // Inputs are always single-line, even if inside a contentEditable element | ||
1365 | // IE also treats inputs as contentEditable | ||
1366 | isInput ? false : | ||
1367 | // All other element types are determined by whether or not they're contentEditable | ||
1368 | this.element.prop( "isContentEditable" ); | ||
1369 | |||
1370 | this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; | ||
1371 | this.isNewMenu = true; | ||
1372 | |||
1373 | this.element | ||
1374 | .addClass( "ui-autocomplete-input" ) | ||
1375 | .attr( "autocomplete", "off" ); | ||
1376 | |||
1377 | this._on( this.element, { | ||
1378 | keydown: function( event ) { | ||
1379 | if ( this.element.prop( "readOnly" ) ) { | ||
1380 | suppressKeyPress = true; | ||
1381 | suppressInput = true; | ||
1382 | suppressKeyPressRepeat = true; | ||
1383 | return; | ||
1384 | } | ||
1385 | |||
1386 | suppressKeyPress = false; | ||
1387 | suppressInput = false; | ||
1388 | suppressKeyPressRepeat = false; | ||
1389 | var keyCode = $.ui.keyCode; | ||
1390 | switch( event.keyCode ) { | ||
1391 | case keyCode.PAGE_UP: | ||
1392 | suppressKeyPress = true; | ||
1393 | this._move( "previousPage", event ); | ||
1394 | break; | ||
1395 | case keyCode.PAGE_DOWN: | ||
1396 | suppressKeyPress = true; | ||
1397 | this._move( "nextPage", event ); | ||
1398 | break; | ||
1399 | case keyCode.UP: | ||
1400 | suppressKeyPress = true; | ||
1401 | this._keyEvent( "previous", event ); | ||
1402 | break; | ||
1403 | case keyCode.DOWN: | ||
1404 | suppressKeyPress = true; | ||
1405 | this._keyEvent( "next", event ); | ||
1406 | break; | ||
1407 | case keyCode.ENTER: | ||
1408 | case keyCode.NUMPAD_ENTER: | ||
1409 | // when menu is open and has focus | ||
1410 | if ( this.menu.active ) { | ||
1411 | // #6055 - Opera still allows the keypress to occur | ||
1412 | // which causes forms to submit | ||
1413 | suppressKeyPress = true; | ||
1414 | event.preventDefault(); | ||
1415 | this.menu.select( event ); | ||
1416 | } | ||
1417 | break; | ||
1418 | case keyCode.TAB: | ||
1419 | if ( this.menu.active ) { | ||
1420 | this.menu.select( event ); | ||
1421 | } | ||
1422 | break; | ||
1423 | case keyCode.ESCAPE: | ||
1424 | if ( this.menu.element.is( ":visible" ) ) { | ||
1425 | this._value( this.term ); | ||
1426 | this.close( event ); | ||
1427 | // Different browsers have different default behavior for escape | ||
1428 | // Single press can mean undo or clear | ||
1429 | // Double press in IE means clear the whole form | ||
1430 | event.preventDefault(); | ||
1431 | } | ||
1432 | break; | ||
1433 | default: | ||
1434 | suppressKeyPressRepeat = true; | ||
1435 | // search timeout should be triggered before the input value is changed | ||
1436 | this._searchTimeout( event ); | ||
1437 | break; | ||
1438 | } | ||
1439 | }, | ||
1440 | keypress: function( event ) { | ||
1441 | if ( suppressKeyPress ) { | ||
1442 | suppressKeyPress = false; | ||
1443 | if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { | ||
1444 | event.preventDefault(); | ||
1445 | } | ||
1446 | return; | ||
1447 | } | ||
1448 | if ( suppressKeyPressRepeat ) { | ||
1449 | return; | ||
1450 | } | ||
1451 | |||
1452 | // replicate some key handlers to allow them to repeat in Firefox and Opera | ||
1453 | var keyCode = $.ui.keyCode; | ||
1454 | switch( event.keyCode ) { | ||
1455 | case keyCode.PAGE_UP: | ||
1456 | this._move( "previousPage", event ); | ||
1457 | break; | ||
1458 | case keyCode.PAGE_DOWN: | ||
1459 | this._move( "nextPage", event ); | ||
1460 | break; | ||
1461 | case keyCode.UP: | ||
1462 | this._keyEvent( "previous", event ); | ||
1463 | break; | ||
1464 | case keyCode.DOWN: | ||
1465 | this._keyEvent( "next", event ); | ||
1466 | break; | ||
1467 | } | ||
1468 | }, | ||
1469 | input: function( event ) { | ||
1470 | if ( suppressInput ) { | ||
1471 | suppressInput = false; | ||
1472 | event.preventDefault(); | ||
1473 | return; | ||
1474 | } | ||
1475 | this._searchTimeout( event ); | ||
1476 | }, | ||
1477 | focus: function() { | ||
1478 | this.selectedItem = null; | ||
1479 | this.previous = this._value(); | ||
1480 | }, | ||
1481 | blur: function( event ) { | ||
1482 | if ( this.cancelBlur ) { | ||
1483 | delete this.cancelBlur; | ||
1484 | return; | ||
1485 | } | ||
1486 | |||
1487 | clearTimeout( this.searching ); | ||
1488 | this.close( event ); | ||
1489 | this._change( event ); | ||
1490 | } | ||
1491 | }); | ||
1492 | |||
1493 | this._initSource(); | ||
1494 | this.menu = $( "<ul>" ) | ||
1495 | .addClass( "ui-autocomplete ui-front" ) | ||
1496 | .appendTo( this._appendTo() ) | ||
1497 | .menu({ | ||
1498 | // disable ARIA support, the live region takes care of that | ||
1499 | role: null | ||
1500 | }) | ||
1501 | .hide() | ||
1502 | .data( "ui-menu" ); | ||
1503 | |||
1504 | this._on( this.menu.element, { | ||
1505 | mousedown: function( event ) { | ||
1506 | // prevent moving focus out of the text field | ||
1507 | event.preventDefault(); | ||
1508 | |||
1509 | // IE doesn't prevent moving focus even with event.preventDefault() | ||
1510 | // so we set a flag to know when we should ignore the blur event | ||
1511 | this.cancelBlur = true; | ||
1512 | this._delay(function() { | ||
1513 | delete this.cancelBlur; | ||
1514 | }); | ||
1515 | |||
1516 | // clicking on the scrollbar causes focus to shift to the body | ||
1517 | // but we can't detect a mouseup or a click immediately afterward | ||
1518 | // so we have to track the next mousedown and close the menu if | ||
1519 | // the user clicks somewhere outside of the autocomplete | ||
1520 | var menuElement = this.menu.element[ 0 ]; | ||
1521 | if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { | ||
1522 | this._delay(function() { | ||
1523 | var that = this; | ||
1524 | this.document.one( "mousedown", function( event ) { | ||
1525 | if ( event.target !== that.element[ 0 ] && | ||
1526 | event.target !== menuElement && | ||
1527 | !$.contains( menuElement, event.target ) ) { | ||
1528 | that.close(); | ||
1529 | } | ||
1530 | }); | ||
1531 | }); | ||
1532 | } | ||
1533 | }, | ||
1534 | menufocus: function( event, ui ) { | ||
1535 | // support: Firefox | ||
1536 | // Prevent accidental activation of menu items in Firefox (#7024 #9118) | ||
1537 | if ( this.isNewMenu ) { | ||
1538 | this.isNewMenu = false; | ||
1539 | if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { | ||
1540 | this.menu.blur(); | ||
1541 | |||
1542 | this.document.one( "mousemove", function() { | ||
1543 | $( event.target ).trigger( event.originalEvent ); | ||
1544 | }); | ||
1545 | |||
1546 | return; | ||
1547 | } | ||
1548 | } | ||
1549 | |||
1550 | var item = ui.item.data( "ui-autocomplete-item" ); | ||
1551 | if ( false !== this._trigger( "focus", event, { item: item } ) ) { | ||
1552 | // use value to match what will end up in the input, if it was a key event | ||
1553 | if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { | ||
1554 | this._value( item.value ); | ||
1555 | } | ||
1556 | } else { | ||
1557 | // Normally the input is populated with the item's value as the | ||
1558 | // menu is navigated, causing screen readers to notice a change and | ||
1559 | // announce the item. Since the focus event was canceled, this doesn't | ||
1560 | // happen, so we update the live region so that screen readers can | ||
1561 | // still notice the change and announce it. | ||
1562 | this.liveRegion.text( item.value ); | ||
1563 | } | ||
1564 | }, | ||
1565 | menuselect: function( event, ui ) { | ||
1566 | var item = ui.item.data( "ui-autocomplete-item" ), | ||
1567 | previous = this.previous; | ||
1568 | |||
1569 | // only trigger when focus was lost (click on menu) | ||
1570 | if ( this.element[0] !== this.document[0].activeElement ) { | ||
1571 | this.element.focus(); | ||
1572 | this.previous = previous; | ||
1573 | // #6109 - IE triggers two focus events and the second | ||
1574 | // is asynchronous, so we need to reset the previous | ||
1575 | // term synchronously and asynchronously :-( | ||
1576 | this._delay(function() { | ||
1577 | this.previous = previous; | ||
1578 | this.selectedItem = item; | ||
1579 | }); | ||
1580 | } | ||
1581 | |||
1582 | if ( false !== this._trigger( "select", event, { item: item } ) ) { | ||
1583 | this._value( item.value ); | ||
1584 | } | ||
1585 | // reset the term after the select event | ||
1586 | // this allows custom select handling to work properly | ||
1587 | this.term = this._value(); | ||
1588 | |||
1589 | this.close( event ); | ||
1590 | this.selectedItem = item; | ||
1591 | } | ||
1592 | }); | ||
1593 | |||
1594 | this.liveRegion = $( "<span>", { | ||
1595 | role: "status", | ||
1596 | "aria-live": "polite" | ||
1597 | }) | ||
1598 | .addClass( "ui-helper-hidden-accessible" ) | ||
1599 | .insertBefore( this.element ); | ||
1600 | |||
1601 | // turning off autocomplete prevents the browser from remembering the | ||
1602 | // value when navigating through history, so we re-enable autocomplete | ||
1603 | // if the page is unloaded before the widget is destroyed. #7790 | ||
1604 | this._on( this.window, { | ||
1605 | beforeunload: function() { | ||
1606 | this.element.removeAttr( "autocomplete" ); | ||
1607 | } | ||
1608 | }); | ||
1609 | }, | ||
1610 | |||
1611 | _destroy: function() { | ||
1612 | clearTimeout( this.searching ); | ||
1613 | this.element | ||
1614 | .removeClass( "ui-autocomplete-input" ) | ||
1615 | .removeAttr( "autocomplete" ); | ||
1616 | this.menu.element.remove(); | ||
1617 | this.liveRegion.remove(); | ||
1618 | }, | ||
1619 | |||
1620 | _setOption: function( key, value ) { | ||
1621 | this._super( key, value ); | ||
1622 | if ( key === "source" ) { | ||
1623 | this._initSource(); | ||
1624 | } | ||
1625 | if ( key === "appendTo" ) { | ||
1626 | this.menu.element.appendTo( this._appendTo() ); | ||
1627 | } | ||
1628 | if ( key === "disabled" && value && this.xhr ) { | ||
1629 | this.xhr.abort(); | ||
1630 | } | ||
1631 | }, | ||
1632 | |||
1633 | _appendTo: function() { | ||
1634 | var element = this.options.appendTo; | ||
1635 | |||
1636 | if ( element ) { | ||
1637 | element = element.jquery || element.nodeType ? | ||
1638 | $( element ) : | ||
1639 | this.document.find( element ).eq( 0 ); | ||
1640 | } | ||
1641 | |||
1642 | if ( !element ) { | ||
1643 | element = this.element.closest( ".ui-front" ); | ||
1644 | } | ||
1645 | |||
1646 | if ( !element.length ) { | ||
1647 | element = this.document[0].body; | ||
1648 | } | ||
1649 | |||
1650 | return element; | ||
1651 | }, | ||
1652 | |||
1653 | _initSource: function() { | ||
1654 | var array, url, | ||
1655 | that = this; | ||
1656 | if ( $.isArray(this.options.source) ) { | ||
1657 | array = this.options.source; | ||
1658 | this.source = function( request, response ) { | ||
1659 | response( $.ui.autocomplete.filter( array, request.term ) ); | ||
1660 | }; | ||
1661 | } else if ( typeof this.options.source === "string" ) { | ||
1662 | url = this.options.source; | ||
1663 | this.source = function( request, response ) { | ||
1664 | if ( that.xhr ) { | ||
1665 | that.xhr.abort(); | ||
1666 | } | ||
1667 | that.xhr = $.ajax({ | ||
1668 | url: url, | ||
1669 | data: request, | ||
1670 | dataType: "json", | ||
1671 | success: function( data ) { | ||
1672 | response( data ); | ||
1673 | }, | ||
1674 | error: function() { | ||
1675 | response( [] ); | ||
1676 | } | ||
1677 | }); | ||
1678 | }; | ||
1679 | } else { | ||
1680 | this.source = this.options.source; | ||
1681 | } | ||
1682 | }, | ||
1683 | |||
1684 | _searchTimeout: function( event ) { | ||
1685 | clearTimeout( this.searching ); | ||
1686 | this.searching = this._delay(function() { | ||
1687 | // only search if the value has changed | ||
1688 | if ( this.term !== this._value() ) { | ||
1689 | this.selectedItem = null; | ||
1690 | this.search( null, event ); | ||
1691 | } | ||
1692 | }, this.options.delay ); | ||
1693 | }, | ||
1694 | |||
1695 | search: function( value, event ) { | ||
1696 | value = value != null ? value : this._value(); | ||
1697 | |||
1698 | // always save the actual value, not the one passed as an argument | ||
1699 | this.term = this._value(); | ||
1700 | |||
1701 | if ( value.length < this.options.minLength ) { | ||
1702 | return this.close( event ); | ||
1703 | } | ||
1704 | |||
1705 | if ( this._trigger( "search", event ) === false ) { | ||
1706 | return; | ||
1707 | } | ||
1708 | |||
1709 | return this._search( value ); | ||
1710 | }, | ||
1711 | |||
1712 | _search: function( value ) { | ||
1713 | this.pending++; | ||
1714 | this.element.addClass( "ui-autocomplete-loading" ); | ||
1715 | this.cancelSearch = false; | ||
1716 | |||
1717 | this.source( { term: value }, this._response() ); | ||
1718 | }, | ||
1719 | |||
1720 | _response: function() { | ||
1721 | var index = ++this.requestIndex; | ||
1722 | |||
1723 | return $.proxy(function( content ) { | ||
1724 | if ( index === this.requestIndex ) { | ||
1725 | this.__response( content ); | ||
1726 | } | ||
1727 | |||
1728 | this.pending--; | ||
1729 | if ( !this.pending ) { | ||
1730 | this.element.removeClass( "ui-autocomplete-loading" ); | ||
1731 | } | ||
1732 | }, this ); | ||
1733 | }, | ||
1734 | |||
1735 | __response: function( content ) { | ||
1736 | if ( content ) { | ||
1737 | content = this._normalize( content ); | ||
1738 | } | ||
1739 | this._trigger( "response", null, { content: content } ); | ||
1740 | if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { | ||
1741 | this._suggest( content ); | ||
1742 | this._trigger( "open" ); | ||
1743 | } else { | ||
1744 | // use ._close() instead of .close() so we don't cancel future searches | ||
1745 | this._close(); | ||
1746 | } | ||
1747 | }, | ||
1748 | |||
1749 | close: function( event ) { | ||
1750 | this.cancelSearch = true; | ||
1751 | this._close( event ); | ||
1752 | }, | ||
1753 | |||
1754 | _close: function( event ) { | ||
1755 | if ( this.menu.element.is( ":visible" ) ) { | ||
1756 | this.menu.element.hide(); | ||
1757 | this.menu.blur(); | ||
1758 | this.isNewMenu = true; | ||
1759 | this._trigger( "close", event ); | ||
1760 | } | ||
1761 | }, | ||
1762 | |||
1763 | _change: function( event ) { | ||
1764 | if ( this.previous !== this._value() ) { | ||
1765 | this._trigger( "change", event, { item: this.selectedItem } ); | ||
1766 | } | ||
1767 | }, | ||
1768 | |||
1769 | _normalize: function( items ) { | ||
1770 | // assume all items have the right format when the first item is complete | ||
1771 | if ( items.length && items[0].label && items[0].value ) { | ||
1772 | return items; | ||
1773 | } | ||
1774 | return $.map( items, function( item ) { | ||
1775 | if ( typeof item === "string" ) { | ||
1776 | return { | ||
1777 | label: item, | ||
1778 | value: item | ||
1779 | }; | ||
1780 | } | ||
1781 | return $.extend({ | ||
1782 | label: item.label || item.value, | ||
1783 | value: item.value || item.label | ||
1784 | }, item ); | ||
1785 | }); | ||
1786 | }, | ||
1787 | |||
1788 | _suggest: function( items ) { | ||
1789 | var ul = this.menu.element.empty(); | ||
1790 | this._renderMenu( ul, items ); | ||
1791 | this.isNewMenu = true; | ||
1792 | this.menu.refresh(); | ||
1793 | |||
1794 | // size and position menu | ||
1795 | ul.show(); | ||
1796 | this._resizeMenu(); | ||
1797 | ul.position( $.extend({ | ||
1798 | of: this.element | ||
1799 | }, this.options.position )); | ||
1800 | |||
1801 | if ( this.options.autoFocus ) { | ||
1802 | this.menu.next(); | ||
1803 | } | ||
1804 | }, | ||
1805 | |||
1806 | _resizeMenu: function() { | ||
1807 | var ul = this.menu.element; | ||
1808 | ul.outerWidth( Math.max( | ||
1809 | // Firefox wraps long text (possibly a rounding bug) | ||
1810 | // so we add 1px to avoid the wrapping (#7513) | ||
1811 | ul.width( "" ).outerWidth() + 1, | ||
1812 | this.element.outerWidth() | ||
1813 | ) ); | ||
1814 | }, | ||
1815 | |||
1816 | _renderMenu: function( ul, items ) { | ||
1817 | var that = this; | ||
1818 | $.each( items, function( index, item ) { | ||
1819 | that._renderItemData( ul, item ); | ||
1820 | }); | ||
1821 | }, | ||
1822 | |||
1823 | _renderItemData: function( ul, item ) { | ||
1824 | return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); | ||
1825 | }, | ||
1826 | |||
1827 | _renderItem: function( ul, item ) { | ||
1828 | return $( "<li>" ) | ||
1829 | .append( $( "<a>" ).text( item.label ) ) | ||
1830 | .appendTo( ul ); | ||
1831 | }, | ||
1832 | |||
1833 | _move: function( direction, event ) { | ||
1834 | if ( !this.menu.element.is( ":visible" ) ) { | ||
1835 | this.search( null, event ); | ||
1836 | return; | ||
1837 | } | ||
1838 | if ( this.menu.isFirstItem() && /^previous/.test( direction ) || | ||
1839 | this.menu.isLastItem() && /^next/.test( direction ) ) { | ||
1840 | this._value( this.term ); | ||
1841 | this.menu.blur(); | ||
1842 | return; | ||
1843 | } | ||
1844 | this.menu[ direction ]( event ); | ||
1845 | }, | ||
1846 | |||
1847 | widget: function() { | ||
1848 | return this.menu.element; | ||
1849 | }, | ||
1850 | |||
1851 | _value: function() { | ||
1852 | return this.valueMethod.apply( this.element, arguments ); | ||
1853 | }, | ||
1854 | |||
1855 | _keyEvent: function( keyEvent, event ) { | ||
1856 | if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { | ||
1857 | this._move( keyEvent, event ); | ||
1858 | |||
1859 | // prevents moving cursor to beginning/end of the text field in some browsers | ||
1860 | event.preventDefault(); | ||
1861 | } | ||
1862 | } | ||
1863 | }); | ||
1864 | |||
1865 | $.extend( $.ui.autocomplete, { | ||
1866 | escapeRegex: function( value ) { | ||
1867 | return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); | ||
1868 | }, | ||
1869 | filter: function(array, term) { | ||
1870 | var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); | ||
1871 | return $.grep( array, function(value) { | ||
1872 | return matcher.test( value.label || value.value || value ); | ||
1873 | }); | ||
1874 | } | ||
1875 | }); | ||
1876 | |||
1877 | |||
1878 | // live region extension, adding a `messages` option | ||
1879 | // NOTE: This is an experimental API. We are still investigating | ||
1880 | // a full solution for string manipulation and internationalization. | ||
1881 | $.widget( "ui.autocomplete", $.ui.autocomplete, { | ||
1882 | options: { | ||
1883 | messages: { | ||
1884 | noResults: "No search results.", | ||
1885 | results: function( amount ) { | ||
1886 | return amount + ( amount > 1 ? " results are" : " result is" ) + | ||
1887 | " available, use up and down arrow keys to navigate."; | ||
1888 | } | ||
1889 | } | ||
1890 | }, | ||
1891 | |||
1892 | __response: function( content ) { | ||
1893 | var message; | ||
1894 | this._superApply( arguments ); | ||
1895 | if ( this.options.disabled || this.cancelSearch ) { | ||
1896 | return; | ||
1897 | } | ||
1898 | if ( content && content.length ) { | ||
1899 | message = this.options.messages.results( content.length ); | ||
1900 | } else { | ||
1901 | message = this.options.messages.noResults; | ||
1902 | } | ||
1903 | this.liveRegion.text( message ); | ||
1904 | } | ||
1905 | }); | ||
1906 | |||
1907 | }( jQuery )); | ||
1908 | (function( $, undefined ) { | ||
1909 | |||
1910 | $.widget( "ui.menu", { | ||
1911 | version: "1.10.4", | ||
1912 | defaultElement: "<ul>", | ||
1913 | delay: 300, | ||
1914 | options: { | ||
1915 | icons: { | ||
1916 | submenu: "ui-icon-carat-1-e" | ||
1917 | }, | ||
1918 | menus: "ul", | ||
1919 | position: { | ||
1920 | my: "left top", | ||
1921 | at: "right top" | ||
1922 | }, | ||
1923 | role: "menu", | ||
1924 | |||
1925 | // callbacks | ||
1926 | blur: null, | ||
1927 | focus: null, | ||
1928 | select: null | ||
1929 | }, | ||
1930 | |||
1931 | _create: function() { | ||
1932 | this.activeMenu = this.element; | ||
1933 | // flag used to prevent firing of the click handler | ||
1934 | // as the event bubbles up through nested menus | ||
1935 | this.mouseHandled = false; | ||
1936 | this.element | ||
1937 | .uniqueId() | ||
1938 | .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) | ||
1939 | .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) | ||
1940 | .attr({ | ||
1941 | role: this.options.role, | ||
1942 | tabIndex: 0 | ||
1943 | }) | ||
1944 | // need to catch all clicks on disabled menu | ||
1945 | // not possible through _on | ||
1946 | .bind( "click" + this.eventNamespace, $.proxy(function( event ) { | ||
1947 | if ( this.options.disabled ) { | ||
1948 | event.preventDefault(); | ||
1949 | } | ||
1950 | }, this )); | ||
1951 | |||
1952 | if ( this.options.disabled ) { | ||
1953 | this.element | ||
1954 | .addClass( "ui-state-disabled" ) | ||
1955 | .attr( "aria-disabled", "true" ); | ||
1956 | } | ||
1957 | |||
1958 | this._on({ | ||
1959 | // Prevent focus from sticking to links inside menu after clicking | ||
1960 | // them (focus should always stay on UL during navigation). | ||
1961 | "mousedown .ui-menu-item > a": function( event ) { | ||
1962 | event.preventDefault(); | ||
1963 | }, | ||
1964 | "click .ui-state-disabled > a": function( event ) { | ||
1965 | event.preventDefault(); | ||
1966 | }, | ||
1967 | "click .ui-menu-item:has(a)": function( event ) { | ||
1968 | var target = $( event.target ).closest( ".ui-menu-item" ); | ||
1969 | if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { | ||
1970 | this.select( event ); | ||
1971 | |||
1972 | // Only set the mouseHandled flag if the event will bubble, see #9469. | ||
1973 | if ( !event.isPropagationStopped() ) { | ||
1974 | this.mouseHandled = true; | ||
1975 | } | ||
1976 | |||
1977 | // Open submenu on click | ||
1978 | if ( target.has( ".ui-menu" ).length ) { | ||
1979 | this.expand( event ); | ||
1980 | } else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) { | ||
1981 | |||
1982 | // Redirect focus to the menu | ||
1983 | this.element.trigger( "focus", [ true ] ); | ||
1984 | |||
1985 | // If the active item is on the top level, let it stay active. | ||
1986 | // Otherwise, blur the active item since it is no longer visible. | ||
1987 | if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { | ||
1988 | clearTimeout( this.timer ); | ||
1989 | } | ||
1990 | } | ||
1991 | } | ||
1992 | }, | ||
1993 | "mouseenter .ui-menu-item": function( event ) { | ||
1994 | var target = $( event.currentTarget ); | ||
1995 | // Remove ui-state-active class from siblings of the newly focused menu item | ||
1996 | // to avoid a jump caused by adjacent elements both having a class with a border | ||
1997 | target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); | ||
1998 | this.focus( event, target ); | ||
1999 | }, | ||
2000 | mouseleave: "collapseAll", | ||
2001 | "mouseleave .ui-menu": "collapseAll", | ||
2002 | focus: function( event, keepActiveItem ) { | ||
2003 | // If there's already an active item, keep it active | ||
2004 | // If not, activate the first item | ||
2005 | var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 ); | ||
2006 | |||
2007 | if ( !keepActiveItem ) { | ||
2008 | this.focus( event, item ); | ||
2009 | } | ||
2010 | }, | ||
2011 | blur: function( event ) { | ||
2012 | this._delay(function() { | ||
2013 | if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { | ||
2014 | this.collapseAll( event ); | ||
2015 | } | ||
2016 | }); | ||
2017 | }, | ||
2018 | keydown: "_keydown" | ||
2019 | }); | ||
2020 | |||
2021 | this.refresh(); | ||
2022 | |||
2023 | // Clicks outside of a menu collapse any open menus | ||
2024 | this._on( this.document, { | ||
2025 | click: function( event ) { | ||
2026 | if ( !$( event.target ).closest( ".ui-menu" ).length ) { | ||
2027 | this.collapseAll( event ); | ||
2028 | } | ||
2029 | |||
2030 | // Reset the mouseHandled flag | ||
2031 | this.mouseHandled = false; | ||
2032 | } | ||
2033 | }); | ||
2034 | }, | ||
2035 | |||
2036 | _destroy: function() { | ||
2037 | // Destroy (sub)menus | ||
2038 | this.element | ||
2039 | .removeAttr( "aria-activedescendant" ) | ||
2040 | .find( ".ui-menu" ).addBack() | ||
2041 | .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" ) | ||
2042 | .removeAttr( "role" ) | ||
2043 | .removeAttr( "tabIndex" ) | ||
2044 | .removeAttr( "aria-labelledby" ) | ||
2045 | .removeAttr( "aria-expanded" ) | ||
2046 | .removeAttr( "aria-hidden" ) | ||
2047 | .removeAttr( "aria-disabled" ) | ||
2048 | .removeUniqueId() | ||
2049 | .show(); | ||
2050 | |||
2051 | // Destroy menu items | ||
2052 | this.element.find( ".ui-menu-item" ) | ||
2053 | .removeClass( "ui-menu-item" ) | ||
2054 | .removeAttr( "role" ) | ||
2055 | .removeAttr( "aria-disabled" ) | ||
2056 | .children( "a" ) | ||
2057 | .removeUniqueId() | ||
2058 | .removeClass( "ui-corner-all ui-state-hover" ) | ||
2059 | .removeAttr( "tabIndex" ) | ||
2060 | .removeAttr( "role" ) | ||
2061 | .removeAttr( "aria-haspopup" ) | ||
2062 | .children().each( function() { | ||
2063 | var elem = $( this ); | ||
2064 | if ( elem.data( "ui-menu-submenu-carat" ) ) { | ||
2065 | elem.remove(); | ||
2066 | } | ||
2067 | }); | ||
2068 | |||
2069 | // Destroy menu dividers | ||
2070 | this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); | ||
2071 | }, | ||
2072 | |||
2073 | _keydown: function( event ) { | ||
2074 | var match, prev, character, skip, regex, | ||
2075 | preventDefault = true; | ||
2076 | |||
2077 | function escape( value ) { | ||
2078 | return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); | ||
2079 | } | ||
2080 | |||
2081 | switch ( event.keyCode ) { | ||
2082 | case $.ui.keyCode.PAGE_UP: | ||
2083 | this.previousPage( event ); | ||
2084 | break; | ||
2085 | case $.ui.keyCode.PAGE_DOWN: | ||
2086 | this.nextPage( event ); | ||
2087 | break; | ||
2088 | case $.ui.keyCode.HOME: | ||
2089 | this._move( "first", "first", event ); | ||
2090 | break; | ||
2091 | case $.ui.keyCode.END: | ||
2092 | this._move( "last", "last", event ); | ||
2093 | break; | ||
2094 | case $.ui.keyCode.UP: | ||
2095 | this.previous( event ); | ||
2096 | break; | ||
2097 | case $.ui.keyCode.DOWN: | ||
2098 | this.next( event ); | ||
2099 | break; | ||
2100 | case $.ui.keyCode.LEFT: | ||
2101 | this.collapse( event ); | ||
2102 | break; | ||
2103 | case $.ui.keyCode.RIGHT: | ||
2104 | if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { | ||
2105 | this.expand( event ); | ||
2106 | } | ||
2107 | break; | ||
2108 | case $.ui.keyCode.ENTER: | ||
2109 | case $.ui.keyCode.SPACE: | ||
2110 | this._activate( event ); | ||
2111 | break; | ||
2112 | case $.ui.keyCode.ESCAPE: | ||
2113 | this.collapse( event ); | ||
2114 | break; | ||
2115 | default: | ||
2116 | preventDefault = false; | ||
2117 | prev = this.previousFilter || ""; | ||
2118 | character = String.fromCharCode( event.keyCode ); | ||
2119 | skip = false; | ||
2120 | |||
2121 | clearTimeout( this.filterTimer ); | ||
2122 | |||
2123 | if ( character === prev ) { | ||
2124 | skip = true; | ||
2125 | } else { | ||
2126 | character = prev + character; | ||
2127 | } | ||
2128 | |||
2129 | regex = new RegExp( "^" + escape( character ), "i" ); | ||
2130 | match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { | ||
2131 | return regex.test( $( this ).children( "a" ).text() ); | ||
2132 | }); | ||
2133 | match = skip && match.index( this.active.next() ) !== -1 ? | ||
2134 | this.active.nextAll( ".ui-menu-item" ) : | ||
2135 | match; | ||
2136 | |||
2137 | // If no matches on the current filter, reset to the last character pressed | ||
2138 | // to move down the menu to the first item that starts with that character | ||
2139 | if ( !match.length ) { | ||
2140 | character = String.fromCharCode( event.keyCode ); | ||
2141 | regex = new RegExp( "^" + escape( character ), "i" ); | ||
2142 | match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { | ||
2143 | return regex.test( $( this ).children( "a" ).text() ); | ||
2144 | }); | ||
2145 | } | ||
2146 | |||
2147 | if ( match.length ) { | ||
2148 | this.focus( event, match ); | ||
2149 | if ( match.length > 1 ) { | ||
2150 | this.previousFilter = character; | ||
2151 | this.filterTimer = this._delay(function() { | ||
2152 | delete this.previousFilter; | ||
2153 | }, 1000 ); | ||
2154 | } else { | ||
2155 | delete this.previousFilter; | ||
2156 | } | ||
2157 | } else { | ||
2158 | delete this.previousFilter; | ||
2159 | } | ||
2160 | } | ||
2161 | |||
2162 | if ( preventDefault ) { | ||
2163 | event.preventDefault(); | ||
2164 | } | ||
2165 | }, | ||
2166 | |||
2167 | _activate: function( event ) { | ||
2168 | if ( !this.active.is( ".ui-state-disabled" ) ) { | ||
2169 | if ( this.active.children( "a[aria-haspopup='true']" ).length ) { | ||
2170 | this.expand( event ); | ||
2171 | } else { | ||
2172 | this.select( event ); | ||
2173 | } | ||
2174 | } | ||
2175 | }, | ||
2176 | |||
2177 | refresh: function() { | ||
2178 | var menus, | ||
2179 | icon = this.options.icons.submenu, | ||
2180 | submenus = this.element.find( this.options.menus ); | ||
2181 | |||
2182 | this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ); | ||
2183 | |||
2184 | // Initialize nested menus | ||
2185 | submenus.filter( ":not(.ui-menu)" ) | ||
2186 | .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) | ||
2187 | .hide() | ||
2188 | .attr({ | ||
2189 | role: this.options.role, | ||
2190 | "aria-hidden": "true", | ||
2191 | "aria-expanded": "false" | ||
2192 | }) | ||
2193 | .each(function() { | ||
2194 | var menu = $( this ), | ||
2195 | item = menu.prev( "a" ), | ||
2196 | submenuCarat = $( "<span>" ) | ||
2197 | .addClass( "ui-menu-icon ui-icon " + icon ) | ||
2198 | .data( "ui-menu-submenu-carat", true ); | ||
2199 | |||
2200 | item | ||
2201 | .attr( "aria-haspopup", "true" ) | ||
2202 | .prepend( submenuCarat ); | ||
2203 | menu.attr( "aria-labelledby", item.attr( "id" ) ); | ||
2204 | }); | ||
2205 | |||
2206 | menus = submenus.add( this.element ); | ||
2207 | |||
2208 | // Don't refresh list items that are already adapted | ||
2209 | menus.children( ":not(.ui-menu-item):has(a)" ) | ||
2210 | .addClass( "ui-menu-item" ) | ||
2211 | .attr( "role", "presentation" ) | ||
2212 | .children( "a" ) | ||
2213 | .uniqueId() | ||
2214 | .addClass( "ui-corner-all" ) | ||
2215 | .attr({ | ||
2216 | tabIndex: -1, | ||
2217 | role: this._itemRole() | ||
2218 | }); | ||
2219 | |||
2220 | // Initialize unlinked menu-items containing spaces and/or dashes only as dividers | ||
2221 | menus.children( ":not(.ui-menu-item)" ).each(function() { | ||
2222 | var item = $( this ); | ||
2223 | // hyphen, em dash, en dash | ||
2224 | if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) { | ||
2225 | item.addClass( "ui-widget-content ui-menu-divider" ); | ||
2226 | } | ||
2227 | }); | ||
2228 | |||
2229 | // Add aria-disabled attribute to any disabled menu item | ||
2230 | menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); | ||
2231 | |||
2232 | // If the active item has been removed, blur the menu | ||
2233 | if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { | ||
2234 | this.blur(); | ||
2235 | } | ||
2236 | }, | ||
2237 | |||
2238 | _itemRole: function() { | ||
2239 | return { | ||
2240 | menu: "menuitem", | ||
2241 | listbox: "option" | ||
2242 | }[ this.options.role ]; | ||
2243 | }, | ||
2244 | |||
2245 | _setOption: function( key, value ) { | ||
2246 | if ( key === "icons" ) { | ||
2247 | this.element.find( ".ui-menu-icon" ) | ||
2248 | .removeClass( this.options.icons.submenu ) | ||
2249 | .addClass( value.submenu ); | ||
2250 | } | ||
2251 | this._super( key, value ); | ||
2252 | }, | ||
2253 | |||
2254 | focus: function( event, item ) { | ||
2255 | var nested, focused; | ||
2256 | this.blur( event, event && event.type === "focus" ); | ||
2257 | |||
2258 | this._scrollIntoView( item ); | ||
2259 | |||
2260 | this.active = item.first(); | ||
2261 | focused = this.active.children( "a" ).addClass( "ui-state-focus" ); | ||
2262 | // Only update aria-activedescendant if there's a role | ||
2263 | // otherwise we assume focus is managed elsewhere | ||
2264 | if ( this.options.role ) { | ||
2265 | this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); | ||
2266 | } | ||
2267 | |||
2268 | // Highlight active parent menu item, if any | ||
2269 | this.active | ||
2270 | .parent() | ||
2271 | .closest( ".ui-menu-item" ) | ||
2272 | .children( "a:first" ) | ||
2273 | .addClass( "ui-state-active" ); | ||
2274 | |||
2275 | if ( event && event.type === "keydown" ) { | ||
2276 | this._close(); | ||
2277 | } else { | ||
2278 | this.timer = this._delay(function() { | ||
2279 | this._close(); | ||
2280 | }, this.delay ); | ||
2281 | } | ||
2282 | |||
2283 | nested = item.children( ".ui-menu" ); | ||
2284 | if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { | ||
2285 | this._startOpening(nested); | ||
2286 | } | ||
2287 | this.activeMenu = item.parent(); | ||
2288 | |||
2289 | this._trigger( "focus", event, { item: item } ); | ||
2290 | }, | ||
2291 | |||
2292 | _scrollIntoView: function( item ) { | ||
2293 | var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; | ||
2294 | if ( this._hasScroll() ) { | ||
2295 | borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; | ||
2296 | paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; | ||
2297 | offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; | ||
2298 | scroll = this.activeMenu.scrollTop(); | ||
2299 | elementHeight = this.activeMenu.height(); | ||
2300 | itemHeight = item.height(); | ||
2301 | |||
2302 | if ( offset < 0 ) { | ||
2303 | this.activeMenu.scrollTop( scroll + offset ); | ||
2304 | } else if ( offset + itemHeight > elementHeight ) { | ||
2305 | this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); | ||
2306 | } | ||
2307 | } | ||
2308 | }, | ||
2309 | |||
2310 | blur: function( event, fromFocus ) { | ||
2311 | if ( !fromFocus ) { | ||
2312 | clearTimeout( this.timer ); | ||
2313 | } | ||
2314 | |||
2315 | if ( !this.active ) { | ||
2316 | return; | ||
2317 | } | ||
2318 | |||
2319 | this.active.children( "a" ).removeClass( "ui-state-focus" ); | ||
2320 | this.active = null; | ||
2321 | |||
2322 | this._trigger( "blur", event, { item: this.active } ); | ||
2323 | }, | ||
2324 | |||
2325 | _startOpening: function( submenu ) { | ||
2326 | clearTimeout( this.timer ); | ||
2327 | |||
2328 | // Don't open if already open fixes a Firefox bug that caused a .5 pixel | ||
2329 | // shift in the submenu position when mousing over the carat icon | ||
2330 | if ( submenu.attr( "aria-hidden" ) !== "true" ) { | ||
2331 | return; | ||
2332 | } | ||
2333 | |||
2334 | this.timer = this._delay(function() { | ||
2335 | this._close(); | ||
2336 | this._open( submenu ); | ||
2337 | }, this.delay ); | ||
2338 | }, | ||
2339 | |||
2340 | _open: function( submenu ) { | ||
2341 | var position = $.extend({ | ||
2342 | of: this.active | ||
2343 | }, this.options.position ); | ||
2344 | |||
2345 | clearTimeout( this.timer ); | ||
2346 | this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) | ||
2347 | .hide() | ||
2348 | .attr( "aria-hidden", "true" ); | ||
2349 | |||
2350 | submenu | ||
2351 | .show() | ||
2352 | .removeAttr( "aria-hidden" ) | ||
2353 | .attr( "aria-expanded", "true" ) | ||
2354 | .position( position ); | ||
2355 | }, | ||
2356 | |||
2357 | collapseAll: function( event, all ) { | ||
2358 | clearTimeout( this.timer ); | ||
2359 | this.timer = this._delay(function() { | ||
2360 | // If we were passed an event, look for the submenu that contains the event | ||
2361 | var currentMenu = all ? this.element : | ||
2362 | $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); | ||
2363 | |||
2364 | // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway | ||
2365 | if ( !currentMenu.length ) { | ||
2366 | currentMenu = this.element; | ||
2367 | } | ||
2368 | |||
2369 | this._close( currentMenu ); | ||
2370 | |||
2371 | this.blur( event ); | ||
2372 | this.activeMenu = currentMenu; | ||
2373 | }, this.delay ); | ||
2374 | }, | ||
2375 | |||
2376 | // With no arguments, closes the currently active menu - if nothing is active | ||
2377 | // it closes all menus. If passed an argument, it will search for menus BELOW | ||
2378 | _close: function( startMenu ) { | ||
2379 | if ( !startMenu ) { | ||
2380 | startMenu = this.active ? this.active.parent() : this.element; | ||
2381 | } | ||
2382 | |||
2383 | startMenu | ||
2384 | .find( ".ui-menu" ) | ||
2385 | .hide() | ||
2386 | .attr( "aria-hidden", "true" ) | ||
2387 | .attr( "aria-expanded", "false" ) | ||
2388 | .end() | ||
2389 | .find( "a.ui-state-active" ) | ||
2390 | .removeClass( "ui-state-active" ); | ||
2391 | }, | ||
2392 | |||
2393 | collapse: function( event ) { | ||
2394 | var newItem = this.active && | ||
2395 | this.active.parent().closest( ".ui-menu-item", this.element ); | ||
2396 | if ( newItem && newItem.length ) { | ||
2397 | this._close(); | ||
2398 | this.focus( event, newItem ); | ||
2399 | } | ||
2400 | }, | ||
2401 | |||
2402 | expand: function( event ) { | ||
2403 | var newItem = this.active && | ||
2404 | this.active | ||
2405 | .children( ".ui-menu " ) | ||
2406 | .children( ".ui-menu-item" ) | ||
2407 | .first(); | ||
2408 | |||
2409 | if ( newItem && newItem.length ) { | ||
2410 | this._open( newItem.parent() ); | ||
2411 | |||
2412 | // Delay so Firefox will not hide activedescendant change in expanding submenu from AT | ||
2413 | this._delay(function() { | ||
2414 | this.focus( event, newItem ); | ||
2415 | }); | ||
2416 | } | ||
2417 | }, | ||
2418 | |||
2419 | next: function( event ) { | ||
2420 | this._move( "next", "first", event ); | ||
2421 | }, | ||
2422 | |||
2423 | previous: function( event ) { | ||
2424 | this._move( "prev", "last", event ); | ||
2425 | }, | ||
2426 | |||
2427 | isFirstItem: function() { | ||
2428 | return this.active && !this.active.prevAll( ".ui-menu-item" ).length; | ||
2429 | }, | ||
2430 | |||
2431 | isLastItem: function() { | ||
2432 | return this.active && !this.active.nextAll( ".ui-menu-item" ).length; | ||
2433 | }, | ||
2434 | |||
2435 | _move: function( direction, filter, event ) { | ||
2436 | var next; | ||
2437 | if ( this.active ) { | ||
2438 | if ( direction === "first" || direction === "last" ) { | ||
2439 | next = this.active | ||
2440 | [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) | ||
2441 | .eq( -1 ); | ||
2442 | } else { | ||
2443 | next = this.active | ||
2444 | [ direction + "All" ]( ".ui-menu-item" ) | ||
2445 | .eq( 0 ); | ||
2446 | } | ||
2447 | } | ||
2448 | if ( !next || !next.length || !this.active ) { | ||
2449 | next = this.activeMenu.children( ".ui-menu-item" )[ filter ](); | ||
2450 | } | ||
2451 | |||
2452 | this.focus( event, next ); | ||
2453 | }, | ||
2454 | |||
2455 | nextPage: function( event ) { | ||
2456 | var item, base, height; | ||
2457 | |||
2458 | if ( !this.active ) { | ||
2459 | this.next( event ); | ||
2460 | return; | ||
2461 | } | ||
2462 | if ( this.isLastItem() ) { | ||
2463 | return; | ||
2464 | } | ||
2465 | if ( this._hasScroll() ) { | ||
2466 | base = this.active.offset().top; | ||
2467 | height = this.element.height(); | ||
2468 | this.active.nextAll( ".ui-menu-item" ).each(function() { | ||
2469 | item = $( this ); | ||
2470 | return item.offset().top - base - height < 0; | ||
2471 | }); | ||
2472 | |||
2473 | this.focus( event, item ); | ||
2474 | } else { | ||
2475 | this.focus( event, this.activeMenu.children( ".ui-menu-item" ) | ||
2476 | [ !this.active ? "first" : "last" ]() ); | ||
2477 | } | ||
2478 | }, | ||
2479 | |||
2480 | previousPage: function( event ) { | ||
2481 | var item, base, height; | ||
2482 | if ( !this.active ) { | ||
2483 | this.next( event ); | ||
2484 | return; | ||
2485 | } | ||
2486 | if ( this.isFirstItem() ) { | ||
2487 | return; | ||
2488 | } | ||
2489 | if ( this._hasScroll() ) { | ||
2490 | base = this.active.offset().top; | ||
2491 | height = this.element.height(); | ||
2492 | this.active.prevAll( ".ui-menu-item" ).each(function() { | ||
2493 | item = $( this ); | ||
2494 | return item.offset().top - base + height > 0; | ||
2495 | }); | ||
2496 | |||
2497 | this.focus( event, item ); | ||
2498 | } else { | ||
2499 | this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() ); | ||
2500 | } | ||
2501 | }, | ||
2502 | |||
2503 | _hasScroll: function() { | ||
2504 | return this.element.outerHeight() < this.element.prop( "scrollHeight" ); | ||
2505 | }, | ||
2506 | |||
2507 | select: function( event ) { | ||
2508 | // TODO: It should never be possible to not have an active item at this | ||
2509 | // point, but the tests don't trigger mouseenter before click. | ||
2510 | this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); | ||
2511 | var ui = { item: this.active }; | ||
2512 | if ( !this.active.has( ".ui-menu" ).length ) { | ||
2513 | this.collapseAll( event, true ); | ||
2514 | } | ||
2515 | this._trigger( "select", event, ui ); | ||
2516 | } | ||
2517 | }); | ||
2518 | |||
2519 | }( jQuery )); | ||
diff --git a/themes/default/js/jquery-ui-1.10.4.custom.min.js b/themes/default/js/jquery-ui-1.10.4.custom.min.js new file mode 100644 index 00000000..31635026 --- /dev/null +++ b/themes/default/js/jquery-ui-1.10.4.custom.min.js | |||
@@ -0,0 +1,6 @@ | |||
1 | /*! jQuery UI - v1.10.4 - 2014-03-08 | ||
2 | * http://jqueryui.com | ||
3 | * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.menu.js | ||
4 | * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ | ||
5 | |||
6 | (function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,a="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:a?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType;return{element:i,isWindow:s,isDocument:n,offset:i.offset()||{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:s?i.width():i.outerWidth(),height:s?i.height():i.outerHeight()}}},t.fn.position=function(e){if(!e||!e.of)return f.apply(this,arguments);e=t.extend({},e);var a,p,g,m,v,_,b=t(e.of),y=t.position.getWithinInfo(e.within),k=t.position.getScrollInfo(y),w=(e.collision||"flip").split(" "),D={};return _=n(b),b[0].preventDefault&&(e.at="left top"),p=_.width,g=_.height,m=_.offset,v=t.extend({},m),t.each(["my","at"],function(){var t,i,s=(e[this]||"").split(" ");1===s.length&&(s=h.test(s[0])?s.concat(["center"]):c.test(s[0])?["center"].concat(s):["center","center"]),s[0]=h.test(s[0])?s[0]:"center",s[1]=c.test(s[1])?s[1]:"center",t=u.exec(s[0]),i=u.exec(s[1]),D[this]=[t?t[0]:0,i?i[0]:0],e[this]=[d.exec(s[0])[0],d.exec(s[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===e.at[0]?v.left+=p:"center"===e.at[0]&&(v.left+=p/2),"bottom"===e.at[1]?v.top+=g:"center"===e.at[1]&&(v.top+=g/2),a=i(D.at,p,g),v.left+=a[0],v.top+=a[1],this.each(function(){var n,h,c=t(this),u=c.outerWidth(),d=c.outerHeight(),f=s(this,"marginLeft"),_=s(this,"marginTop"),x=u+f+s(this,"marginRight")+k.width,C=d+_+s(this,"marginBottom")+k.height,M=t.extend({},v),T=i(D.my,c.outerWidth(),c.outerHeight());"right"===e.my[0]?M.left-=u:"center"===e.my[0]&&(M.left-=u/2),"bottom"===e.my[1]?M.top-=d:"center"===e.my[1]&&(M.top-=d/2),M.left+=T[0],M.top+=T[1],t.support.offsetFractions||(M.left=l(M.left),M.top=l(M.top)),n={marginLeft:f,marginTop:_},t.each(["left","top"],function(i,s){t.ui.position[w[i]]&&t.ui.position[w[i]][s](M,{targetWidth:p,targetHeight:g,elemWidth:u,elemHeight:d,collisionPosition:n,collisionWidth:x,collisionHeight:C,offset:[a[0]+T[0],a[1]+T[1]],my:e.my,at:e.at,within:y,elem:c})}),e.using&&(h=function(t){var i=m.left-M.left,s=i+p-u,n=m.top-M.top,a=n+g-d,l={target:{element:b,left:m.left,top:m.top,width:p,height:g},element:{element:c,left:M.left,top:M.top,width:u,height:d},horizontal:0>s?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(e){e.widget("ui.autocomplete",{version:"1.10.4",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("<ul>").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().data("ui-menu"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){if(this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type)))return this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),undefined;var s=i.item.data("ui-autocomplete-item");!1!==this._trigger("focus",t,{item:s})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(s.value):this.liveRegion.text(s.value)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("<span>",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertBefore(this.element),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length<this.options.minLength?this.close(t):this._trigger("search",t)!==!1?this._search(e):undefined},_search:function(e){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:e},this._response())},_response:function(){var t=++this.requestIndex;return e.proxy(function(e){t===this.requestIndex&&this.__response(e),this.pending--,this.pending||this.element.removeClass("ui-autocomplete-loading")},this)},__response:function(e){e&&(e=this._normalize(e)),this._trigger("response",null,{content:e}),!this.options.disabled&&e&&e.length&&!this.cancelSearch?(this._suggest(e),this._trigger("open")):this._close()},close:function(e){this.cancelSearch=!0,this._close(e)},_close:function(e){this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",e))},_change:function(e){this.previous!==this._value()&&this._trigger("change",e,{item:this.selectedItem})},_normalize:function(t){return t.length&&t[0].label&&t[0].value?t:e.map(t,function(t){return"string"==typeof t?{label:t,value:t}:e.extend({label:t.label||t.value,value:t.value||t.label},t)})},_suggest:function(t){var i=this.menu.element.empty();this._renderMenu(i,t),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(e.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next()},_resizeMenu:function(){var e=this.menu.element;e.outerWidth(Math.max(e.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(t,i){var s=this;e.each(i,function(e,i){s._renderItemData(t,i)})},_renderItemData:function(e,t){return this._renderItem(e,t).data("ui-autocomplete-item",t)},_renderItem:function(t,i){return e("<li>").append(e("<a>").text(i.label)).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this._value(this.term),this.menu.blur(),undefined):(this.menu[e](t),undefined):(this.search(null,t),undefined)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var t;this._superApply(arguments),this.options.disabled||this.cancelSearch||(t=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.text(t))}})})(jQuery);(function(t){t.widget("ui.menu",{version:"1.10.4",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,t.proxy(function(t){this.options.disabled&&t.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(t){t.preventDefault()},"click .ui-state-disabled > a":function(t){t.preventDefault()},"click .ui-menu-item:has(a)":function(e){var i=t(e.target).closest(".ui-menu-item");!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i=t(e.currentTarget);i.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(e,i)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.children(".ui-menu-item").eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){t(e.target).closest(".ui-menu").length||this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){function i(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var s,n,a,o,r,l=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:l=!1,n=this.previousFilter||"",a=String.fromCharCode(e.keyCode),o=!1,clearTimeout(this.filterTimer),a===n?o=!0:a=n+a,r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())}),s=o&&-1!==s.index(this.active.next())?this.active.nextAll(".ui-menu-item"):s,s.length||(a=String.fromCharCode(e.keyCode),r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())})),s.length?(this.focus(e,s),s.length>1?(this.previousFilter=a,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}l&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i=this.options.icons.submenu,s=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),s.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),s=e.prev("a"),n=t("<span>").addClass("ui-menu-icon ui-icon "+i).data("ui-menu-submenu-carat",!0);s.attr("aria-haspopup","true").prepend(n),e.attr("aria-labelledby",s.attr("id"))}),e=s.add(this.element),e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),e.children(":not(.ui-menu-item)").each(function(){var e=t(this);/[^\-\u2014\u2013\s]/.test(e.text())||e.addClass("ui-widget-content ui-menu-divider")}),e.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),this._super(t,e)},focus:function(t,e){var i,s;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=e.height(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.children(".ui-menu-item")[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())),undefined):(this.next(e),undefined)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item").first())),undefined):(this.next(e),undefined)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)}})})(jQuery); \ No newline at end of file | ||
diff --git a/themes/default/js/popupForm.js b/themes/default/js/popupForm.js new file mode 100644 index 00000000..06be3f0c --- /dev/null +++ b/themes/default/js/popupForm.js | |||
@@ -0,0 +1,20 @@ | |||
1 | $(document).ready(function() { | ||
2 | |||
3 | $("#search-form").hide(); | ||
4 | |||
5 | function closeSearch() { | ||
6 | $("#search-form").toggle(); | ||
7 | $("#search").toggleClass("current"); | ||
8 | $("#search-arrow").toggleClass("arrow-down"); | ||
9 | } | ||
10 | |||
11 | $("#search").click(function(){ | ||
12 | closeSearch(); | ||
13 | }); | ||
14 | |||
15 | $("#search-form-close").click(function(){ | ||
16 | closeSearch(); | ||
17 | }); | ||
18 | |||
19 | |||
20 | }); \ No newline at end of file | ||
diff --git a/themes/default/js/saveLink.js b/themes/default/js/saveLink.js new file mode 100755 index 00000000..6dbce97e --- /dev/null +++ b/themes/default/js/saveLink.js | |||
@@ -0,0 +1,101 @@ | |||
1 | $.fn.ready(function() { | ||
2 | |||
3 | var $bagit = $('#bagit'), | ||
4 | $bagitForm = $('#bagit-form'), | ||
5 | $bagitFormForm = $('#bagit-form-form'); | ||
6 | |||
7 | /* ========================================================================== | ||
8 | bag it link and close button | ||
9 | ========================================================================== */ | ||
10 | |||
11 | function toggleSaveLinkForm(url, event) { | ||
12 | $("#add-link-result").empty(); | ||
13 | |||
14 | $bagit.toggleClass("active-current"); | ||
15 | |||
16 | //only if bagiti link is not presented on page | ||
17 | if ( $bagit.length === 0 ) { | ||
18 | if ( event !== 'undefined' && event ) { | ||
19 | $bagitForm.css( {position:"absolute", top:event.pageY, left:event.pageX-200}); | ||
20 | } | ||
21 | else { | ||
22 | $bagitForm.css( {position:"relative", top:"auto", left:"auto"}); | ||
23 | } | ||
24 | } | ||
25 | |||
26 | $bagitForm.toggle(); | ||
27 | $('#content').toggleClass("opacity03"); | ||
28 | if (url !== 'undefined' && url) { | ||
29 | $('#plainurl').val(url); | ||
30 | } | ||
31 | $('#plainurl').focus(); | ||
32 | } | ||
33 | |||
34 | |||
35 | $bagit.click(function(){ | ||
36 | $bagit.toggleClass("current"); | ||
37 | $("#bagit-arrow").toggleClass("arrow-down"); | ||
38 | toggleSaveLinkForm(); | ||
39 | }); | ||
40 | |||
41 | $("#bagit-form-close").click(function(){ | ||
42 | $bagit.removeClass("current"); | ||
43 | $("#bagit-arrow").removeClass("arrow-down"); | ||
44 | toggleSaveLinkForm(); | ||
45 | }); | ||
46 | |||
47 | |||
48 | //send "bag it link" form request via ajax | ||
49 | $bagitFormForm.submit( function(event) { | ||
50 | $("body").css("cursor", "wait"); | ||
51 | $("#add-link-result").empty(); | ||
52 | |||
53 | $.ajax({ | ||
54 | type: $bagitFormForm.attr('method'), | ||
55 | url: $bagitFormForm.attr('action'), | ||
56 | data: $bagitFormForm.serialize(), | ||
57 | success: function(data) { | ||
58 | $('#add-link-result').html("Done!"); | ||
59 | $('#plainurl').val(''); | ||
60 | $('#plainurl').blur(''); | ||
61 | $("body").css("cursor", "auto"); | ||
62 | //setTimeout( function() { toggleSaveLinkForm(); }, 1000); //close form after 1000 delay | ||
63 | }, | ||
64 | error: function(data) { | ||
65 | $('#add-link-result').html("Failed!"); | ||
66 | $("body").css("cursor", "auto"); | ||
67 | } | ||
68 | }); | ||
69 | |||
70 | event.preventDefault(); | ||
71 | }); | ||
72 | |||
73 | /* ========================================================================== | ||
74 | Keyboard gestion | ||
75 | ========================================================================== */ | ||
76 | |||
77 | $(window).keydown(function(e){ | ||
78 | if ( ( e.target.tagName.toLowerCase() !== 'input' && e.keyCode == 83 ) || (e.keyCode == 27 && $bagitForm.is(':visible') ) ) { | ||
79 | $bagit.removeClass("current"); | ||
80 | $("#bagit-arrow").removeClass("arrow-down"); | ||
81 | toggleSaveLinkForm(); | ||
82 | return false; | ||
83 | } | ||
84 | }); | ||
85 | |||
86 | /* ========================================================================== | ||
87 | Process all links inside an article | ||
88 | ========================================================================== */ | ||
89 | |||
90 | $("article a[href^='http']").after(function() { | ||
91 | return " <a href=\"" + $(this).attr('href') + "\" class=\"add-to-wallabag-link-after\" alt=\"add to wallabag\" title=\"add to wallabag\"></a> "; | ||
92 | }); | ||
93 | |||
94 | $(".add-to-wallabag-link-after").click(function(event){ | ||
95 | toggleSaveLinkForm($(this).attr('href'), event); | ||
96 | event.preventDefault(); | ||
97 | }); | ||
98 | |||
99 | }); | ||
100 | |||
101 | |||
diff --git a/themes/default/tags.twig b/themes/default/tags.twig index cff6b1d7..e5be748e 100644..100755 --- a/themes/default/tags.twig +++ b/themes/default/tags.twig | |||
@@ -4,5 +4,15 @@ | |||
4 | {% include '_menu.twig' %} | 4 | {% include '_menu.twig' %} |
5 | {% endblock %} | 5 | {% endblock %} |
6 | {% block content %} | 6 | {% block content %} |
7 | {% for tag in tags %}<a href="./?view=tag&id={{ tag.id }}">{{ tag.value }}</a> {% if token != '' %}<a href="?feed&type=tag&user_id={{ user_id }}&tag_id={{ tag.id }}&token={{ token }}" target="_blank"><img src="{{ poche_url }}/themes/{{ theme }}/img/{{ theme }}/rss.png" /></a>{% endif %} {% endfor %} | 7 | <div class="two-column"> |
8 | {% for tag in tags %} | ||
9 | <a href="./?view=tag&id={{ tag.id }}">{{ tag.value }}</a> ({{ tag.entriescount }}) {% if token != '' %}<a href="?feed&type=tag&user_id={{ user_id }}&tag_id={{ tag.id }}&token={{ token }}" target="_blank"><img src="{{ poche_url }}themes/{{ theme }}/img/{{ theme }}/rss.png" /></a>{% endif %} | ||
10 | <br> | ||
11 | |||
12 | {% if loop.index == '%d'|format(loop.length/2 + 0.5) %} | ||
13 | </div><div class="two-column"> | ||
14 | {% endif %} | ||
15 | |||
16 | {% endfor %} | ||
17 | </div> | ||
8 | {% endblock %} \ No newline at end of file | 18 | {% endblock %} \ No newline at end of file |
diff --git a/themes/default/view.twig b/themes/default/view.twig index 916abe0d..b7d48c00 100644..100755 --- a/themes/default/view.twig +++ b/themes/default/view.twig | |||
@@ -1,19 +1,23 @@ | |||
1 | {% extends "layout.twig" %} | 1 | {% extends "layout.twig" %} |
2 | {% block title %}{{ entry.title|raw }} ({{ entry.url | e | getDomain }}){% endblock %} | 2 | {% block title %}{{ entry.title|raw }} ({{ entry.url | e | getDomain }}){% endblock %} |
3 | {% block content %} | 3 | {% block content %} |
4 | {% include '_pocheit-form.twig' %} | ||
4 | <div id="article_toolbar"> | 5 | <div id="article_toolbar"> |
5 | <ul> | 6 | <ul> |
6 | <li><a href="./" title="{% trans "Return home" %}" class="tool back"><span>{% trans "Return home" %}</span></a></li> | 7 | <li><a href="./" title="{% trans "Return home" %}" class="tool back"><span>{% trans "Return home" %}</span></a></li> |
7 | <li><a href="#top" title="{% trans "Back to top" %}" class="tool top"><span>{% trans "Back to top" %}</span></a></li> | 8 | <li><a href="#top" title="{% trans "Back to top" %}" class="tool top"><span>{% trans "Back to top" %}</span></a></li> |
8 | <li><a href="{{ entry.url|e }}" target="_blank" title="{% trans "original" %} : {{ entry.title|e }}" class="tool link"><span>{{ entry.url | e | getDomain }}</span></a></li> | 9 | <li><a href="{{ entry.url|e }}" target="_blank" title="{% trans "original" %} : {{ entry.title|e }}" class="tool link"><span>{{ entry.url | e | getDomain }}</span></a></li> |
9 | <li><a title="{% trans "Mark as read" %}" class="tool {% if entry.is_read == 0 %}archive-off{% else %}archive{% endif %}" href="./?action=toggle_archive&id={{ entry.id|e }}"><span>{% trans "Toggle mark as read" %}</span></a></li> | 10 | <li><a title="{% trans "Mark as read" %}" class="tool {% if entry.is_read == 0 %}archive-off{% else %}archive{% endif %}" href="javascript: void(null);" id="markAsRead"><span>{% trans "Toggle mark as read" %}</span></a></li> |
10 | <li><a title="{% trans "Favorite" %}" class="tool {% if entry.is_fav == 0 %}fav-off{% else %}fav{% endif %}" href="./?action=toggle_fav&id={{ entry.id|e }}"><span>{% trans "Toggle favorite" %}</span></a></li> | 11 | <li><a title="{% trans "Favorite" %}" class="tool {% if entry.is_fav == 0 %}fav-off{% else %}fav{% endif %}" href="javascript: void(null);" id="setFav"><span>{% trans "Toggle favorite" %}</span></a></li> |
11 | <li><a title="{% trans "Delete" %}" class="tool delete" href="./?action=delete&id={{ entry.id|e }}"><span>{% trans "Delete" %}</span></a></li> | 12 | <li><a title="{% trans "Delete" %}" class="tool delete" href="./?action=delete&id={{ entry.id|e }}"><span>{% trans "Delete" %}</span></a></li> |
12 | {% if constant('SHARE_TWITTER') == 1 %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter" title="{% trans "Tweet" %}"><span>{% trans "Tweet" %}</span></a></li>{% endif %} | 13 | {% if constant('SHARE_TWITTER') == 1 %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter" title="{% trans "Tweet" %}"><span>{% trans "Tweet" %}</span></a></li>{% endif %} |
13 | {% if constant('SHARE_MAIL') == 1 %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email" title="{% trans "Email" %}"><span>{% trans "Email" %}</span></a></li>{% endif %} | 14 | {% if constant('SHARE_MAIL') == 1 %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email" title="{% trans "Email" %}"><span>{% trans "Email" %}</span></a></li>{% endif %} |
14 | {% if constant('SHARE_SHAARLI') == 1 %}<li><a href="{{ constant('SHAARLI_URL') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" target="_blank" class="tool shaarli" title="{% trans "shaarli" %}"><span>{% trans "shaarli" %}</span></a></li>{% endif %} | 15 | {% if constant('SHARE_SHAARLI') == 1 %}<li><a href="{{ constant('SHAARLI_URL') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" target="_blank" class="tool shaarli" title="{% trans "shaarli" %}"><span>{% trans "shaarli" %}</span></a></li>{% endif %} |
15 | {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}<li><a href="http://flattr.com/submit/auto?url={{ entry.url }}" class="tool 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" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span>{{ flattr.numflattrs }}</a></li>{% endif %}{% endif %} | 16 | {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}<li><a href="http://flattr.com/submit/auto?url={{ entry.url }}" class="tool 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" target="_blank" title="{% trans "flattr" %}"><span>{% trans "flattr" %}</span>{{ flattr.numflattrs }}</a></li>{% endif %}{% endif %} |
17 | {% if constant('SHOW_PRINTLINK') == 1 %}<li><a title="{% trans "Print" %}" class="tool print" href="javascript: window.print();"><span>{% trans "Print" %}</span></a></li>{% endif %} | ||
18 | <li><a href="./?epub&method=id&id={{ entry.id|e }}" title="Generate epub file">EPUB</a></li> | ||
16 | <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&body={{ entry.url|url_encode }}" title="{% trans "Does this article appear wrong?" %}" class="tool bad-display"><span>{% trans "Does this article appear wrong?" %}</span></a></li> | 19 | <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&body={{ entry.url|url_encode }}" title="{% trans "Does this article appear wrong?" %}" class="tool bad-display"><span>{% trans "Does this article appear wrong?" %}</span></a></li> |
20 | {% if constant('SHOW_READPERCENT') == 1 %}<li><div id="readLeftPercent">0%</div></li>{% endif %} | ||
17 | </ul> | 21 | </ul> |
18 | </div> | 22 | </div> |
19 | <div id="article"> | 23 | <div id="article"> |
@@ -30,9 +34,63 @@ | |||
30 | </article> | 34 | </article> |
31 | {{ block('tags') }} | 35 | {{ block('tags') }} |
32 | </div> | 36 | </div> |
33 | <script src="{{ poche_url }}/themes/{{ constant('DEFAULT_THEME') }}/js/restoreScroll.js"></script> | 37 | <script src="{{ poche_url }}themes/{{theme}}/js/restoreScroll.js"></script> |
34 | <script type="text/javascript"> | 38 | <script type="text/javascript"> |
35 | $(document).ready(function() { | 39 | $(document).ready(function() { |
40 | |||
41 | // toggle read property of current article | ||
42 | $('#markAsRead').click(function(){ | ||
43 | $("body").css("cursor", "wait"); | ||
44 | $.ajax( { url: './?action=toggle_archive&id={{ entry.id|e }}' }).done( | ||
45 | function( data ) { | ||
46 | if ( data == '1' ) { | ||
47 | if ( $('#markAsRead').hasClass("archive-off") ) { | ||
48 | $('#markAsRead').removeClass("archive-off"); | ||
49 | $('#markAsRead').addClass("archive"); | ||
50 | } | ||
51 | else { | ||
52 | $('#markAsRead').removeClass("archive"); | ||
53 | $('#markAsRead').addClass("archive-off"); | ||
54 | } | ||
55 | } | ||
56 | else { | ||
57 | alert('Error! Pls check if you are logged in.'); | ||
58 | } | ||
59 | }); | ||
60 | $("body").css("cursor", "auto"); | ||
61 | }); | ||
62 | |||
63 | // toggle favorite property of current article | ||
64 | $('#setFav').click(function(){ | ||
65 | $("body").css("cursor", "wait"); | ||
66 | $.ajax( { url: './?action=toggle_fav&id={{ entry.id|e }}' }).done( | ||
67 | function( data ) { | ||
68 | if ( data == '1' ) { | ||
69 | if ( $('#setFav').hasClass("fav-off") ) { | ||
70 | $('#setFav').removeClass("fav-off"); | ||
71 | $('#setFav').addClass("fav"); | ||
72 | } | ||
73 | else { | ||
74 | $('#setFav').removeClass("fav"); | ||
75 | $('#setFav').addClass("fav-off"); | ||
76 | } | ||
77 | } | ||
78 | else { | ||
79 | alert('Error! Pls check if you are logged in.'); | ||
80 | } | ||
81 | }); | ||
82 | $("body").css("cursor", "auto"); | ||
83 | }); | ||
84 | |||
85 | // set percent of read on startup | ||
86 | if ( $(document).height() <= $(window).innerHeight() ) { | ||
87 | pp = 100; | ||
88 | } | ||
89 | else { | ||
90 | pp = 0; | ||
91 | } | ||
92 | $('#readLeftPercent').text( pp + '%' ); | ||
93 | |||
36 | 94 | ||
37 | $(window).scroll(function(e){ | 95 | $(window).scroll(function(e){ |
38 | var scrollTop = $(window).scrollTop(); | 96 | var scrollTop = $(window).scrollTop(); |
@@ -40,6 +98,10 @@ | |||
40 | var scrollPercent = (scrollTop) / (docHeight); | 98 | var scrollPercent = (scrollTop) / (docHeight); |
41 | var scrollPercentRounded = Math.round(scrollPercent*100)/100; | 99 | var scrollPercentRounded = Math.round(scrollPercent*100)/100; |
42 | savePercent({{ entry.id|e }}, scrollPercentRounded); | 100 | savePercent({{ entry.id|e }}, scrollPercentRounded); |
101 | |||
102 | // change percent of read on scroll | ||
103 | pp = Math.round(scrollTop * 100 / ( docHeight - $(window).innerHeight() )); | ||
104 | $('#readLeftPercent').text( pp + '%' ); | ||
43 | }); | 105 | }); |
44 | 106 | ||
45 | retrievePercent({{ entry.id|e }}); | 107 | retrievePercent({{ entry.id|e }}); |
@@ -55,3 +117,4 @@ | |||
55 | }); | 117 | }); |
56 | </script> | 118 | </script> |
57 | {% endblock %} | 119 | {% endblock %} |
120 | |||
diff --git a/themes/dmagenta/img/dmagenta/rss.png b/themes/dmagenta/img/dmagenta/rss.png new file mode 100644 index 00000000..21bad1a1 --- /dev/null +++ b/themes/dmagenta/img/dmagenta/rss.png | |||
Binary files differ | |||
diff --git a/themes/solarized-dark/img/solarized-dark/rss.png b/themes/solarized-dark/img/solarized-dark/rss.png new file mode 100644 index 00000000..21bad1a1 --- /dev/null +++ b/themes/solarized-dark/img/solarized-dark/rss.png | |||
Binary files differ | |||
diff --git a/themes/solarized/img/solarized/rss.png b/themes/solarized/img/solarized/rss.png new file mode 100644 index 00000000..21bad1a1 --- /dev/null +++ b/themes/solarized/img/solarized/rss.png | |||
Binary files differ | |||
diff --git a/wallabag_compatibility_test.php b/wallabag_compatibility_test.php index 26dce018..d6f22156 100644 --- a/wallabag_compatibility_test.php +++ b/wallabag_compatibility_test.php | |||
@@ -1,5 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | $app_name = 'wallabag 1'; | 2 | $app_name = 'wallabag'; |
3 | 3 | ||
4 | $php_ok = (function_exists('version_compare') && version_compare(phpversion(), '5.3.3', '>=')); | 4 | $php_ok = (function_exists('version_compare') && version_compare(phpversion(), '5.3.3', '>=')); |
5 | $pcre_ok = extension_loaded('pcre'); | 5 | $pcre_ok = extension_loaded('pcre'); |
@@ -8,9 +8,11 @@ $mbstring_ok = extension_loaded('mbstring'); | |||
8 | $iconv_ok = extension_loaded('iconv'); | 8 | $iconv_ok = extension_loaded('iconv'); |
9 | $tidy_ok = function_exists('tidy_parse_string'); | 9 | $tidy_ok = function_exists('tidy_parse_string'); |
10 | $curl_ok = function_exists('curl_exec'); | 10 | $curl_ok = function_exists('curl_exec'); |
11 | $parse_ini_ok = function_exists('parse_ini_file'); | ||
11 | $parallel_ok = ((extension_loaded('http') && class_exists('HttpRequestPool')) || ($curl_ok && function_exists('curl_multi_init'))); | 12 | $parallel_ok = ((extension_loaded('http') && class_exists('HttpRequestPool')) || ($curl_ok && function_exists('curl_multi_init'))); |
12 | $allow_url_fopen_ok = (bool)ini_get('allow_url_fopen'); | 13 | $allow_url_fopen_ok = (bool)ini_get('allow_url_fopen'); |
13 | $filter_ok = extension_loaded('filter'); | 14 | $filter_ok = extension_loaded('filter'); |
15 | $gettext_ok = function_exists("gettext"); | ||
14 | 16 | ||
15 | if (extension_loaded('xmlreader')) { | 17 | if (extension_loaded('xmlreader')) { |
16 | $xml_ok = true; | 18 | $xml_ok = true; |
@@ -130,8 +132,6 @@ table#chart tr.enabled td { | |||
130 | 132 | ||
131 | table#chart tr.disabled td, | 133 | table#chart tr.disabled td, |
132 | table#chart tr.disabled td a { | 134 | table#chart tr.disabled td a { |
133 | color:#999; | ||
134 | font-style:italic; | ||
135 | } | 135 | } |
136 | 136 | ||
137 | table#chart tr.disabled td a { | 137 | table#chart tr.disabled td a { |
@@ -154,12 +154,31 @@ div.chunk { | |||
154 | background-color:transparent; | 154 | background-color:transparent; |
155 | font-style:italic; | 155 | font-style:italic; |
156 | } | 156 | } |
157 | |||
158 | .good{ | ||
159 | background-color:#52CC5B; | ||
160 | } | ||
161 | .bad{ | ||
162 | background-color:#F74343; | ||
163 | font-style:italic; | ||
164 | font-weight: bold; | ||
165 | } | ||
166 | .pass{ | ||
167 | background-color:#FF9500; | ||
168 | } | ||
169 | |||
157 | </style> | 170 | </style> |
158 | 171 | ||
159 | </head> | 172 | </head> |
160 | 173 | ||
161 | <body> | 174 | <body> |
162 | 175 | <?php | |
176 | $frominstall = false; | ||
177 | if (isset($_GET['from'])){ | ||
178 | if ($_GET['from'] == 'install'){ | ||
179 | $frominstall = true; | ||
180 | }} | ||
181 | ?> | ||
163 | <div id="site"> | 182 | <div id="site"> |
164 | <div id="content"> | 183 | <div id="content"> |
165 | 184 | ||
@@ -177,58 +196,68 @@ div.chunk { | |||
177 | <tr class="<?php echo ($php_ok) ? 'enabled' : 'disabled'; ?>"> | 196 | <tr class="<?php echo ($php_ok) ? 'enabled' : 'disabled'; ?>"> |
178 | <td>PHP</td> | 197 | <td>PHP</td> |
179 | <td>5.3.3 or higher</td> | 198 | <td>5.3.3 or higher</td> |
180 | <td><?php echo phpversion(); ?></td> | 199 | <td class="<?php echo ($php_ok) ? 'good' : 'disabled'; ?>"><?php echo phpversion(); ?></td> |
181 | </tr> | 200 | </tr> |
182 | <tr class="<?php echo ($xml_ok) ? 'enabled, and sane' : 'disabled, or broken'; ?>"> | 201 | <tr class="<?php echo ($xml_ok) ? 'enabled' : 'disabled'; ?>"> |
183 | <td><a href="http://php.net/xml">XML</a></td> | 202 | <td><a href="http://php.net/xml">XML</a></td> |
184 | <td>Enabled</td> | 203 | <td>Enabled</td> |
185 | <td><?php echo ($xml_ok) ? 'Enabled, and sane' : 'Disabled, or broken'; ?></td> | 204 | <?php echo ($xml_ok) ? '<td class="good">Enabled, and sane</span>' : '<td class="bad">Disabled, or broken'; ?></td> |
186 | </tr> | 205 | </tr> |
187 | <tr class="<?php echo ($pcre_ok) ? 'enabled' : 'disabled'; ?>"> | 206 | <tr class="<?php echo ($pcre_ok) ? 'enabled' : 'disabled'; ?>"> |
188 | <td><a href="http://php.net/pcre">PCRE</a></td> | 207 | <td><a href="http://php.net/pcre">PCRE</a></td> |
189 | <td>Enabled</td> | 208 | <td>Enabled</td> |
190 | <td><?php echo ($pcre_ok) ? 'Enabled' : 'Disabled'; ?></td> | 209 | <?php echo ($pcre_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> |
191 | </tr> | 210 | </tr> |
192 | <!-- <tr class="<?php echo ($zlib_ok) ? 'enabled' : 'disabled'; ?>"> | 211 | <!-- <tr class="<?php echo ($zlib_ok) ? 'enabled' : 'disabled'; ?>"> |
193 | <td><a href="http://php.net/zlib">Zlib</a></td> | 212 | <td><a href="http://php.net/zlib">Zlib</a></td> |
194 | <td>Enabled</td> | 213 | <td>Enabled</td> |
195 | <td><?php echo ($zlib_ok) ? 'Enabled' : 'Disabled'; ?></td> | 214 | <?php echo ($zlib_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> |
196 | </tr> --> | 215 | </tr> --> |
197 | <!-- <tr class="<?php echo ($mbstring_ok) ? 'enabled' : 'disabled'; ?>"> | 216 | <!-- <tr class="<?php echo ($mbstring_ok) ? 'enabled' : 'disabled'; ?>"> |
198 | <td><a href="http://php.net/mbstring">mbstring</a></td> | 217 | <td><a href="http://php.net/mbstring">mbstring</a></td> |
199 | <td>Enabled</td> | 218 | <td>Enabled</td> |
200 | <td><?php echo ($mbstring_ok) ? 'Enabled' : 'Disabled'; ?></td> | 219 | <?php echo ($mbstring_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> |
201 | </tr> --> | 220 | </tr> --> |
202 | <!-- <tr class="<?php echo ($iconv_ok) ? 'enabled' : 'disabled'; ?>"> | 221 | <!-- <tr class="<?php echo ($iconv_ok) ? 'enabled' : 'disabled'; ?>"> |
203 | <td><a href="http://php.net/iconv">iconv</a></td> | 222 | <td><a href="http://php.net/iconv">iconv</a></td> |
204 | <td>Enabled</td> | 223 | <td>Enabled</td> |
205 | <td><?php echo ($iconv_ok) ? 'Enabled' : 'Disabled'; ?></td> | 224 | <?php echo ($iconv_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> |
206 | </tr> --> | 225 | </tr> --> |
207 | <tr class="<?php echo ($filter_ok) ? 'enabled' : 'disabled'; ?>"> | 226 | <tr class="<?php echo ($filter_ok) ? 'enabled' : 'disabled'; ?>"> |
208 | <td><a href="http://uk.php.net/manual/en/book.filter.php">Data filtering</a></td> | 227 | <td><a href="http://uk.php.net/manual/en/book.filter.php">Data filtering</a></td> |
209 | <td>Enabled</td> | 228 | <td>Enabled</td> |
210 | <td><?php echo ($filter_ok) ? 'Enabled' : 'Disabled'; ?></td> | 229 | <?php echo ($filter_ok) ? '<td class="good">Enabled' : '<td class="pass">Disabled'; ?></td> |
211 | </tr> | 230 | </tr> |
212 | <tr class="<?php echo ($tidy_ok) ? 'enabled' : 'disabled'; ?>"> | 231 | <tr class="<?php echo ($tidy_ok) ? 'enabled' : 'disabled'; ?>"> |
213 | <td><a href="http://php.net/tidy">Tidy</a></td> | 232 | <td><a href="http://php.net/tidy">Tidy</a></td> |
214 | <td>Enabled</td> | 233 | <td>Enabled</td> |
215 | <td><?php echo ($tidy_ok) ? 'Enabled' : 'Disabled'; ?></td> | 234 | <?php echo ($tidy_ok) ? '<td class="good">Enabled' : '<td class="pass">Disabled'; ?></td> |
216 | </tr> | 235 | </tr> |
217 | <tr class="<?php echo ($curl_ok) ? 'enabled' : 'disabled'; ?>"> | 236 | <tr class="<?php echo ($curl_ok) ? 'enabled' : 'disabled'; ?>"> |
218 | <td><a href="http://php.net/curl">cURL</a></td> | 237 | <td><a href="http://php.net/curl">cURL</a></td> |
219 | <td>Enabled</td> | 238 | <td>Enabled</td> |
220 | <td><?php echo (extension_loaded('curl')) ? 'Enabled' : 'Disabled'; ?></td> | 239 | <?php echo (extension_loaded('curl')) ? '<td class="good">Enabled' : '<td class="pass">Disabled'; ?></td> |
221 | </tr> | 240 | </tr> |
241 | <tr class="<?php echo ($parse_ini_ok) ? 'enabled' : 'disabled'; ?>"> | ||
242 | <td><a href="http://uk.php.net/manual/en/function.parse-ini-file.php">Parse ini file</td> | ||
243 | <td>Enabled</td> | ||
244 | <?php echo ($parse_ini_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> | ||
245 | </tr> | ||
222 | <tr class="<?php echo ($parallel_ok) ? 'enabled' : 'disabled'; ?>"> | 246 | <tr class="<?php echo ($parallel_ok) ? 'enabled' : 'disabled'; ?>"> |
223 | <td>Parallel URL fetching</td> | 247 | <td>Parallel URL fetching</td> |
224 | <td>Enabled</td> | 248 | <td>Enabled</td> |
225 | <td><?php echo ($parallel_ok) ? 'Enabled' : 'Disabled'; ?></td> | 249 | <?php echo ($parallel_ok) ? '<td class="good">Enabled' : '<td class="pass">Disabled'; ?></td> |
226 | </tr> | 250 | </tr> |
227 | <tr class="<?php echo ($allow_url_fopen_ok) ? 'enabled' : 'disabled'; ?>"> | 251 | <tr class="<?php echo ($allow_url_fopen_ok) ? 'enabled' : 'disabled'; ?>"> |
228 | <td><a href="http://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen">allow_url_fopen</a></td> | 252 | <td><a href="http://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen">allow_url_fopen</a></td> |
229 | <td>Enabled</td> | 253 | <td>Enabled</td> |
230 | <td><?php echo ($allow_url_fopen_ok) ? 'Enabled' : 'Disabled'; ?></td> | 254 | <?php echo ($allow_url_fopen_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> |
231 | </tr> | 255 | </tr> |
256 | <tr class="<?php echo ($gettext_ok) ? 'enabled' : 'disabled'; ?>"> | ||
257 | <td><a href="http://php.net/manual/en/book.gettext.php">gettext</a></td> | ||
258 | <td>Enabled</td> | ||
259 | <?php echo ($gettext_ok) ? '<td class="good">Enabled' : '<td class="bad">Disabled'; ?></td> | ||
260 | </tr> | ||
232 | </tbody> | 261 | </tbody> |
233 | </table> | 262 | </table> |
234 | </div> | 263 | </div> |
@@ -237,7 +266,7 @@ div.chunk { | |||
237 | <h3>What does this mean?</h3> | 266 | <h3>What does this mean?</h3> |
238 | <ol> | 267 | <ol> |
239 | <?php //if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $iconv_ok && $filter_ok && $zlib_ok && $tidy_ok && $curl_ok && $parallel_ok && $allow_url_fopen_ok): ?> | 268 | <?php //if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $iconv_ok && $filter_ok && $zlib_ok && $tidy_ok && $curl_ok && $parallel_ok && $allow_url_fopen_ok): ?> |
240 | <?php if ($php_ok && $xml_ok && $pcre_ok && $filter_ok && $tidy_ok && $curl_ok && $parallel_ok && $allow_url_fopen_ok): ?> | 269 | <?php if ($php_ok && $xml_ok && $pcre_ok && $filter_ok && $tidy_ok && $curl_ok && $parallel_ok && $allow_url_fopen_ok && $gettext_ok && $parse_ini_ok): ?> |
241 | <li><em>You have everything you need to run <?php echo $app_name; ?> properly! Congratulations!</em></li> | 270 | <li><em>You have everything you need to run <?php echo $app_name; ?> properly! Congratulations!</em></li> |
242 | <?php else: ?> | 271 | <?php else: ?> |
243 | <?php if ($php_ok): ?> | 272 | <?php if ($php_ok): ?> |
@@ -250,59 +279,72 @@ div.chunk { | |||
250 | <?php if ($allow_url_fopen_ok): ?> | 279 | <?php if ($allow_url_fopen_ok): ?> |
251 | <li><strong>allow_url_fopen:</strong> You have allow_url_fopen enabled. <em>No problems here.</em></li> | 280 | <li><strong>allow_url_fopen:</strong> You have allow_url_fopen enabled. <em>No problems here.</em></li> |
252 | 281 | ||
253 | <?php if ($filter_ok): ?> | 282 | <?php if ($gettext_ok): ?> |
254 | <li><strong>Data filtering:</strong> You have the PHP filter extension enabled. <em>No problems here.</em></li> | 283 | <li><strong>Gettext:</strong> You have <code>gettext</code> enabled. <em>No problems here.</em></li> |
255 | 284 | ||
256 | <?php if ($zlib_ok): ?> | 285 | <?php if ($parse_ini_ok): ?> |
257 | <li><strong>Zlib:</strong> You have <code>Zlib</code> enabled. This allows SimplePie to support GZIP-encoded feeds. <em>No problems here.</em></li> | 286 | |
258 | <?php else: ?> | 287 | <?php if ($filter_ok): ?> |
259 | <li><strong>Zlib:</strong> The <code>Zlib</code> extension is not available. SimplePie will ignore any GZIP-encoding, and instead handle feeds as uncompressed text.</li> | 288 | <li><strong>Data filtering:</strong> You have the PHP filter extension enabled. <em>No problems here.</em></li> |
260 | <?php endif; ?> | 289 | |
261 | 290 | <?php if ($zlib_ok): ?> | |
262 | <?php if ($mbstring_ok && $iconv_ok): ?> | 291 | <li><strong>Zlib:</strong> You have <code>Zlib</code> enabled. This allows SimplePie to support GZIP-encoded feeds. <em>No problems here.</em></li> |
263 | <li><strong>mbstring and iconv:</strong> You have both <code>mbstring</code> and <code>iconv</code> installed! This will allow <?php echo $app_name; ?> to handle the greatest number of languages. <em>No problems here.</em></li> | 292 | <?php else: ?> |
264 | <?php elseif ($mbstring_ok): ?> | 293 | <li><strong>Zlib:</strong> The <code>Zlib</code> extension is not available. SimplePie will ignore any GZIP-encoding, and instead handle feeds as uncompressed text.</li> |
265 | <li><strong>mbstring:</strong> <code>mbstring</code> is installed, but <code>iconv</code> is not.</li> | 294 | <?php endif; ?> |
266 | <?php elseif ($iconv_ok): ?> | 295 | |
267 | <li><strong>iconv:</strong> <code>iconv</code> is installed, but <code>mbstring</code> is not.</li> | 296 | <?php if ($mbstring_ok && $iconv_ok): ?> |
268 | <?php else: ?> | 297 | <li><strong>mbstring and iconv:</strong> You have both <code>mbstring</code> and <code>iconv</code> installed! This will allow <?php echo $app_name; ?> to handle the greatest number of languages. <em>No problems here.</em></li> |
269 | <li><strong>mbstring and iconv:</strong> <em>You do not have either of the extensions installed.</em> This will significantly impair your ability to read non-English feeds, as well as even some English ones.</li> | 298 | <?php elseif ($mbstring_ok): ?> |
270 | <?php endif; ?> | 299 | <li><strong>mbstring:</strong> <code>mbstring</code> is installed, but <code>iconv</code> is not.</li> |
271 | 300 | <?php elseif ($iconv_ok): ?> | |
272 | <?php if ($tidy_ok): ?> | 301 | <li><strong>iconv:</strong> <code>iconv</code> is installed, but <code>mbstring</code> is not.</li> |
273 | <li><strong>Tidy:</strong> You have <code>Tidy</code> support installed. <em>No problems here.</em></li> | 302 | <?php else: ?> |
274 | <?php else: ?> | 303 | <li><strong>mbstring and iconv:</strong> <em>You do not have either of the extensions installed.</em> This will significantly impair your ability to read non-English feeds, as well as even some English ones.</li> |
275 | <li><strong>Tidy:</strong> The <code>Tidy</code> extension is not available. <?php echo $app_name; ?> should still work with most feeds, but you may experience problems with some.</li> | 304 | <?php endif; ?> |
276 | <?php endif; ?> | 305 | |
306 | <?php if ($tidy_ok): ?> | ||
307 | <li><strong>Tidy:</strong> You have <code>Tidy</code> support installed. <em>No problems here.</em></li> | ||
308 | <?php else: ?> | ||
309 | <li><strong>Tidy:</strong> The <code>Tidy</code> extension is not available. <?php echo $app_name; ?> should still work with most feeds, but you may experience problems with some.</li> | ||
310 | <?php endif; ?> | ||
311 | |||
312 | <?php if ($curl_ok): ?> | ||
313 | <li><strong>cURL:</strong> You have <code>cURL</code> support installed. <em>No problems here.</em></li> | ||
314 | <?php else: ?> | ||
315 | <li><strong>cURL:</strong> The <code>cURL</code> extension is not available. SimplePie will use <code>fsockopen()</code> instead.</li> | ||
316 | <?php endif; ?> | ||
317 | |||
318 | <?php if ($parallel_ok): ?> | ||
319 | <li><strong>Parallel URL fetching:</strong> You have <code>HttpRequestPool</code> or <code>curl_multi</code> support installed. <em>No problems here.</em></li> | ||
320 | <?php else: ?> | ||
321 | <li><strong>Parallel URL fetching:</strong> <code>HttpRequestPool</code> or <code>curl_multi</code> support is not available. <?php echo $app_name; ?> will use <code>file_get_contents()</code> instead to fetch URLs sequentially rather than in parallel.</li> | ||
322 | <?php endif; ?> | ||
323 | |||
324 | <?php else: ?> | ||
325 | <li><strong>Data filtering:</strong> Your PHP configuration has the filter extension disabled. <strong><?php echo $app_name; ?> will not work here.</strong></li> | ||
326 | <?php endif; ?> | ||
327 | |||
328 | <?php else : ?> | ||
329 | <li><strong>Parse ini files function :</strong> Bad luck : your webhost has decided to block the use of the <em>parse_ini_file</em> function. <strong><?php echo $app_name; ?> will not work here.</strong> | ||
330 | <?php endif; ?> | ||
277 | 331 | ||
278 | <?php if ($curl_ok): ?> | ||
279 | <li><strong>cURL:</strong> You have <code>cURL</code> support installed. <em>No problems here.</em></li> | ||
280 | <?php else: ?> | ||
281 | <li><strong>cURL:</strong> The <code>cURL</code> extension is not available. SimplePie will use <code>fsockopen()</code> instead.</li> | ||
282 | <?php endif; ?> | ||
283 | |||
284 | <?php if ($parallel_ok): ?> | ||
285 | <li><strong>Parallel URL fetching:</strong> You have <code>HttpRequestPool</code> or <code>curl_multi</code> support installed. <em>No problems here.</em></li> | ||
286 | <?php else: ?> | ||
287 | <li><strong>Parallel URL fetching:</strong> <code>HttpRequestPool</code> or <code>curl_multi</code> support is not available. <?php echo $app_name; ?> will use <code>file_get_contents()</code> instead to fetch URLs sequentially rather than in parallel.</li> | ||
288 | <?php endif; ?> | ||
289 | |||
290 | <?php else: ?> | 332 | <?php else: ?> |
291 | <li><strong>Data filtering:</strong> Your PHP configuration has the filter extension disabled. <em><?php echo $app_name; ?> will not work here.</em></li> | 333 | <li><strong>GetText:</strong> The <code>gettext</code> extension is not available. The system we use to display wallabag in various languages is not available. <strong><?php echo $app_name; ?> will not work here.</strong></li> |
292 | <?php endif; ?> | 334 | <?php endif; ?> |
293 | 335 | ||
294 | <?php else: ?> | 336 | <?php else: ?> |
295 | <li><strong>allow_url_fopen:</strong> Your PHP configuration has allow_url_fopen disabled. <em><?php echo $app_name; ?> will not work here.</em></li> | 337 | <li><strong>allow_url_fopen:</strong> Your PHP configuration has allow_url_fopen disabled. <strong><?php echo $app_name; ?> will not work here.</strong></li> |
296 | <?php endif; ?> | 338 | <?php endif; ?> |
297 | 339 | ||
298 | <?php else: ?> | 340 | <?php else: ?> |
299 | <li><strong>PCRE:</strong> Your PHP installation doesn't support Perl-Compatible Regular Expressions. <em><?php echo $app_name; ?> will not work here.</em></li> | 341 | <li><strong>PCRE:</strong> Your PHP installation doesn't support Perl-Compatible Regular Expressions. <strong><?php echo $app_name; ?> will not work here.</strong></li> |
300 | <?php endif; ?> | 342 | <?php endif; ?> |
301 | <?php else: ?> | 343 | <?php else: ?> |
302 | <li><strong>XML:</strong> Your PHP installation doesn't support XML parsing. <em><?php echo $app_name; ?> will not work here.</em></li> | 344 | <li><strong>XML:</strong> Your PHP installation doesn't support XML parsing. <strong><?php echo $app_name; ?> will not work here.</strong></li> |
303 | <?php endif; ?> | 345 | <?php endif; ?> |
304 | <?php else: ?> | 346 | <?php else: ?> |
305 | <li><strong>PHP:</strong> You are running an unsupported version of PHP. <em><?php echo $app_name; ?> will not work here.</em></li> | 347 | <li><strong>PHP:</strong> You are running an unsupported version of PHP. <strong><?php echo $app_name; ?> will not work here.</strong></li> |
306 | <?php endif; ?> | 348 | <?php endif; ?> |
307 | <?php endif; ?> | 349 | <?php endif; ?> |
308 | </ol> | 350 | </ol> |
@@ -310,16 +352,26 @@ div.chunk { | |||
310 | 352 | ||
311 | <div class="chunk"> | 353 | <div class="chunk"> |
312 | <?php //if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $iconv_ok && $filter_ok && $allow_url_fopen_ok) { ?> | 354 | <?php //if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $iconv_ok && $filter_ok && $allow_url_fopen_ok) { ?> |
313 | <?php if ($php_ok && $xml_ok && $pcre_ok && $filter_ok && $allow_url_fopen_ok) { ?> | 355 | <?php if ($php_ok && $xml_ok && $pcre_ok && $filter_ok && $allow_url_fopen_ok && $gettext_ok && $parse_ini_ok) { ?> |
314 | <h3>Bottom Line: Yes, you can!</h3> | 356 | <h3>Bottom Line: Yes, you can!</h3> |
315 | <p><em>Your webhost has its act together!</em></p> | 357 | <p><em>Your webhost has its act together!</em></p> |
358 | <?php if (!$frominstall) { ?> | ||
316 | <p>You can download the latest version of <?php echo $app_name; ?> from <a href="http://wallabag.org/download">wallabag.org</a>.</p> | 359 | <p>You can download the latest version of <?php echo $app_name; ?> from <a href="http://wallabag.org/download">wallabag.org</a>.</p> |
360 | <p>If you already have done that, you should access <a href="index.php">the index.php file</a> of your installation to configure and/or start using wallabag</p> | ||
361 | <?php } else { ?> | ||
362 | <p>You can now <a href="index.php">return to the installation section</a>.</p> | ||
363 | <?php } ?> | ||
317 | <p><strong>Note</strong>: Passing this test does not guarantee that <?php echo $app_name; ?> will run on your webhost — it only ensures that the basic requirements have been addressed. If you experience any problems, please let us know.</p> | 364 | <p><strong>Note</strong>: Passing this test does not guarantee that <?php echo $app_name; ?> will run on your webhost — it only ensures that the basic requirements have been addressed. If you experience any problems, please let us know.</p> |
318 | <?php //} else if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $allow_url_fopen_ok && $filter_ok) { ?> | 365 | <?php //} else if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $allow_url_fopen_ok && $filter_ok) { ?> |
319 | <?php } else if ($php_ok && $xml_ok && $pcre_ok && $allow_url_fopen_ok && $filter_ok) { ?> | 366 | <?php } else if ($php_ok && $xml_ok && $pcre_ok && $allow_url_fopen_ok && $filter_ok && $gettext_ok && $parse_ini_ok) { ?> |
320 | <h3>Bottom Line: Yes, you can!</h3> | 367 | <h3>Bottom Line: Yes, you can!</h3> |
321 | <p><em>For most feeds, it'll run with no problems.</em> There are certain languages that you might have a hard time with though.</p> | 368 | <p><em>For most feeds, it'll run with no problems.</em> There are certain languages that you might have a hard time with though.</p> |
369 | <?php if (!$frominstall) { ?> | ||
322 | <p>You can download the latest version of <?php echo $app_name; ?> from <a href="http://wallabag.org/download">wallabag.org</a>.</p> | 370 | <p>You can download the latest version of <?php echo $app_name; ?> from <a href="http://wallabag.org/download">wallabag.org</a>.</p> |
371 | <p>If you already have done that, you should access <a href="index.php">the index.php file</a> of your installation to configure and/or start using wallabag</p> | ||
372 | <?php } else { ?> | ||
373 | <p>You can now <a href="index.php">return to the installation section</a>.</p> | ||
374 | <?php } ?> | ||
323 | <p><strong>Note</strong>: Passing this test does not guarantee that <?php echo $app_name; ?> will run on your webhost — it only ensures that the basic requirements have been addressed. If you experience any problems, please let us know.</p> | 375 | <p><strong>Note</strong>: Passing this test does not guarantee that <?php echo $app_name; ?> will run on your webhost — it only ensures that the basic requirements have been addressed. If you experience any problems, please let us know.</p> |
324 | <?php } else { ?> | 376 | <?php } else { ?> |
325 | <h3>Bottom Line: We're sorry…</h3> | 377 | <h3>Bottom Line: We're sorry…</h3> |