Document Gallery - Version 2.0

Version Description

  • Enhancement: This release is a BIG deal! We are introducing true document thumbnails (rather than the boring static images that were the same for every document), meaning that you will be able to generate and display thumbnails for most of your documents so your users can see a preview of the document before downloading. This has been months in development and I really hope that you all enjoy it!
  • Enhancement: Document Gallery now has a settings page where you can configure the default options for your galleries and chose how thumbnails are generated.
  • Enhancement: Customizing CSS for your document gallery is now much easier. If you want to add additional styling, just navigate to Settings -> Document Gallery in your dashboard and enter valid CSS in the "Custom CSS" textbox. See the changes instantly in your galleries!
  • Enhancement: Entire plugin is now Internalization-enabled. This means that we can now support users speaking all languages. If you are interested in translating Document Gallery into a language that you speak, please let me know!
  • Enhancement: This release saw much of the backend refactored to better support future development. Nothing you will notice unless you're digging into the code, but it will keep me sane long-term ;)
  • Note: The thumbnail generation implementation works very hard to support all hosting servers (including Unix and Windows systems). That said, I cannot test on all hosts out there, so there is the potential for bugs to appear. If you notice something that doesn't look right, please don't hesitate to report the issue so that I can resolve it. Thanks!
Download this release

Release Info

Developer dan.rossiter
Plugin Icon 128x128 Document Gallery
Version 2.0
Comparing to
See all releases

Code changes from version 1.4.1 to 2.0

Files changed (64) hide show
  1. license.txt → LICENSE.txt +0 -0
  2. readme.txt → README.txt +81 -19
  3. admin/class-admin.php +419 -0
  4. admin/css/style.css +17 -0
  5. style.css → assets/css/style.css +5 -3
  6. assets/icons/asc.png +0 -0
  7. assets/icons/audio.png +0 -0
  8. assets/icons/c.png +0 -0
  9. {icons → assets/icons}/compressed.png +0 -0
  10. assets/icons/cpp.png +0 -0
  11. {icons → assets/icons}/css.png +0 -0
  12. {icons → assets/icons}/exec.png +0 -0
  13. assets/icons/h.png +0 -0
  14. {icons → assets/icons}/html.png +0 -0
  15. {icons → assets/icons}/ics.png +0 -0
  16. assets/icons/image.png +0 -0
  17. {icons → assets/icons}/java.png +0 -0
  18. {icons → assets/icons}/javascript.png +0 -0
  19. assets/icons/key.png +0 -0
  20. {icons → assets/icons}/midi.png +0 -0
  21. {icons → assets/icons}/missing.png +0 -0
  22. {icons → assets/icons}/msaccess.png +0 -0
  23. {icons → assets/icons}/msdoc.png +0 -0
  24. {icons → assets/icons}/msppt.png +0 -0
  25. {icons → assets/icons}/msxls.png +0 -0
  26. assets/icons/numbers.png +0 -0
  27. {icons → assets/icons}/opendocument-database.png +0 -0
  28. {icons → assets/icons}/opendocument-formula.png +0 -0
  29. {icons → assets/icons}/opendocument-graphics.png +0 -0
  30. {icons → assets/icons}/opendocument-presentation.png +0 -0
  31. {icons → assets/icons}/opendocument-spreadsheet.png +0 -0
  32. {icons → assets/icons}/opendocument-text.png +0 -0
  33. assets/icons/pages.png +0 -0
  34. {icons → assets/icons}/pdf.png +0 -0
  35. {icons → assets/icons}/rtf.png +0 -0
  36. {icons → assets/icons}/rtx.png +0 -0
  37. {icons → assets/icons}/shockwave.png +0 -0
  38. assets/icons/text.png +0 -0
  39. assets/icons/video.png +0 -0
  40. assets/icons/wordperfect.png +0 -0
  41. document-gallery.php +131 -22
  42. icons/7zip.png +0 -0
  43. icons/avi.png +0 -0
  44. icons/csv.png +0 -0
  45. icons/divx.png +0 -0
  46. icons/flv.png +0 -0
  47. icons/mkv.png +0 -0
  48. icons/mov.png +0 -0
  49. icons/mp3.png +0 -0
  50. icons/ogg.png +0 -0
  51. icons/rar.png +0 -0
  52. icons/wav.png +0 -0
  53. icons/wma.png +0 -0
  54. icons/wmv.png +0 -0
  55. icons/zip.png +0 -0
  56. inc/class-document.php +79 -0
  57. inc/class-gallery.php +595 -0
  58. inc/class-setup.php +90 -0
  59. inc/class-thumber.php +788 -0
  60. languages/document-gallery.pot +235 -0
  61. models/class-document.php +0 -196
  62. models/class-gallery.php +0 -400
  63. screenshot-1.png +0 -0
  64. screenshot-2.png +0 -0
license.txt → LICENSE.txt RENAMED
File without changes
readme.txt → README.txt RENAMED
@@ -1,14 +1,14 @@
1
  === Document Gallery ===
2
  Contributors: dan.rossiter
3
- Tags: attachments, icons, documents, gallery, MS office, pdf
4
- Requires at least: 2.8
5
- Tested up to: 3.6
6
- Stable tag: 1.4.1
7
  License: GPLv2
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
10
- This plugin allows the user to easily create a "gallery" of all non-image
11
- attachments on a given post/page, making them easy to share.
12
 
13
  == Description ==
14
 
@@ -46,7 +46,7 @@ must include the following shortcode in the post: `[dg]`.
46
  In addition to the default behavior, the plugin provides many options to
47
  customize behavior with various attributes, seen below:
48
 
49
- `[dg [attachment_pg=<true/false>]
50
  [category/custom_taxon_name=<**comma-separated list of taxon values**> [relation=<AND/OR>]]
51
  [descriptions=<true/false>] [ids=<**comma-separated list of ID #s**>]
52
  [images=<true/false>] [localpost=<true/false>] [order=<ASC/DEC>] [orderby=<**see below**>]]`
@@ -59,7 +59,8 @@ without any added attributes.
59
 
60
  By default, document gallery will use `no descriptions`, `orderby menu_order`
61
  , `ASC order`, `no attachment_pg links`, and `no images` from the `local post`
62
- if you do not specify otherwise.
 
63
 
64
  **Attachment Page Option** *(New in Version 1.1)*
65
 
@@ -86,6 +87,19 @@ alongside it.
86
  *Note: this will use the `description` field, **not** the `caption`. Be
87
  careful when entering your document data.*
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  **Order Option**
90
 
91
  This option works alongside the `orderby` option to determine whether the
@@ -110,8 +124,7 @@ documents are displayed in ascending or descending order.
110
  (Only useful in conjunction with `localpost=false` option.)
111
  * `comment_count` - Order by number of comments (available with WP >= 2.9).
112
  * `none` - No order (available with Version 2.8).
113
- * `post__in` - Preserve post ID order given in the post__in array (available
114
- with WP >= 3.5).
115
 
116
  **Images Option** *(New in Version 1.2)*
117
 
@@ -154,13 +167,13 @@ match (OR).
154
  By default, the document gallery will use the styles within your active theme
155
  to handle most of the appearance, but, with a little CSS knowledge, you can
156
  customize pretty much anything about how it looks. See
157
- [`style.css`](http://plugins.svn.wordpress.org/document-gallery/trunk/style.css)
158
  for an idea of what will select different elements within the gallery display.
159
 
160
  **Example**
161
 
162
  Say I would like to include a border for the right and bottom of the document
163
- icon, but only when descriptions are shown (to deliniate the icon from the
164
  description text). To do this, I would need to add the following CSS to my
165
  theme stylesheet:
166
 
@@ -223,13 +236,12 @@ is perfectly alright.
223
 
224
  == Screenshots ==
225
 
226
- 1. This is an example of multiple Document Galleries on a single page (using
 
 
227
  the `ids` attribute). It also shows how images will appear in a Document
228
  Gallery. Note that the description field supports HTML markup, so the
229
  possibilities are endless!
230
- 2. This is how the Document Gallery looks with `descriptions=true`. The
231
- descriptions are auto-populated using the description field from when you
232
- upload the document.
233
  3. This is how the Document Gallery looks with `descriptions=false` (default).
234
  Note that the display inherits styling from your active theme.
235
 
@@ -247,6 +259,56 @@ Note that the display inherits styling from your active theme.
247
  forum](http://wordpress.org/support/plugin/document-gallery) if you have
248
  ideas)!
249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  = 1.4.1 =
251
  * **Bug Fix:** This resolves a bug introduced in `1.4`, which caused a warning
252
  to be thrown when no attributes were used (i.e.: `[dg]`). (Thanks to
@@ -324,8 +386,7 @@ Note that the display inherits styling from your active theme.
324
 
325
  = 1.0.1 =
326
 
327
- * **Bug Fix:** Resolved issue with long document titles being cut off in some
328
- * themes.
329
 
330
  = 1.0 =
331
 
@@ -346,5 +407,6 @@ Note that the display inherits styling from your active theme.
346
 
347
  * **Release:** First public release of Document Gallery.
348
  * **Feature:** Displays PDF, Word, PowerPoint, Excel, and ZIP documents from a
349
- given page or post. **Feature:** Documents can be ordered by a number of
 
350
  different factors.
1
  === Document Gallery ===
2
  Contributors: dan.rossiter
3
+ Tags: attachments, thumbnail, documents, gallery, MS office, pdf
4
+ Requires at least: 3.6
5
+ Tested up to: 3.8.1
6
+ Stable tag: 2.0
7
  License: GPLv2
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
10
+ This plugin generates thumbnails for documents and displays them in a
11
+ gallery-like format for easy sharing.
12
 
13
  == Description ==
14
 
46
  In addition to the default behavior, the plugin provides many options to
47
  customize behavior with various attributes, seen below:
48
 
49
+ `[dg [fancy=true] [attachment_pg=<true/false>]
50
  [category/custom_taxon_name=<**comma-separated list of taxon values**> [relation=<AND/OR>]]
51
  [descriptions=<true/false>] [ids=<**comma-separated list of ID #s**>]
52
  [images=<true/false>] [localpost=<true/false>] [order=<ASC/DEC>] [orderby=<**see below**>]]`
59
 
60
  By default, document gallery will use `no descriptions`, `orderby menu_order`
61
  , `ASC order`, `no attachment_pg links`, and `no images` from the `local post`
62
+ if you do not specify otherwise. These defaults can be configured in your dashboard
63
+ under `Settings -> Document Gallery`.
64
 
65
  **Attachment Page Option** *(New in Version 1.1)*
66
 
87
  *Note: this will use the `description` field, **not** the `caption`. Be
88
  careful when entering your document data.*
89
 
90
+ **Fancy** *(New in Version 2.0)*
91
+
92
+ If `true`, we will try to generate a thumbnail for each document in the gallery.
93
+ The success in generating thumbs will depend mostly on what your server supports.
94
+ To fine-tune how thumbnails are generated, visit `Settings -> Document Gallery`
95
+ in your site's dashboard.
96
+
97
+ *NOTE: By default, the most universally-supported option for generating thumbnails,
98
+ [Google Drive Viewer](https://docs.google.com/viewer) is disabled by default
99
+ in order to protect your privacy, since using it requires sending your documents
100
+ to Google's servers. If you're not working with confidential documents, you are
101
+ encouraged to enable this for optimum performance.*
102
+
103
  **Order Option**
104
 
105
  This option works alongside the `orderby` option to determine whether the
124
  (Only useful in conjunction with `localpost=false` option.)
125
  * `comment_count` - Order by number of comments (available with WP >= 2.9).
126
  * `none` - No order (available with Version 2.8).
127
+ * `post__in` - Preserve post ID order given in the post__in array.
 
128
 
129
  **Images Option** *(New in Version 1.2)*
130
 
167
  By default, the document gallery will use the styles within your active theme
168
  to handle most of the appearance, but, with a little CSS knowledge, you can
169
  customize pretty much anything about how it looks. See
170
+ [`style.css`](http://plugins.svn.wordpress.org/document-gallery/trunk/assets/css/style.css)
171
  for an idea of what will select different elements within the gallery display.
172
 
173
  **Example**
174
 
175
  Say I would like to include a border for the right and bottom of the document
176
+ icon, but only when descriptions are shown (to delineate the icon from the
177
  description text). To do this, I would need to add the following CSS to my
178
  theme stylesheet:
179
 
236
 
237
  == Screenshots ==
238
 
239
+ 1. This is an example of "fancy" thumbnails. The images are a copy of the front
240
+ page for each document.
241
+ 2. This is an example of multiple Document Galleries on a single page (using
242
  the `ids` attribute). It also shows how images will appear in a Document
243
  Gallery. Note that the description field supports HTML markup, so the
244
  possibilities are endless!
 
 
 
245
  3. This is how the Document Gallery looks with `descriptions=false` (default).
246
  Note that the display inherits styling from your active theme.
247
 
259
  forum](http://wordpress.org/support/plugin/document-gallery) if you have
260
  ideas)!
261
 
262
+ = 2.0 =
263
+ * **Enhancement:** This release is a **BIG** deal! We are introducing true
264
+ document thumbnails (rather than the boring static images that were the same
265
+ for every document), meaning that you will be able to generate and display
266
+ thumbnails for most of your documents so your users can see a preview of the
267
+ document before downloading. This has been
268
+ [months in development](http://wordpress.org/support/topic/pdf-thumbnails-instead-of-generic-icon)
269
+ and I really hope that you all enjoy it!
270
+ * **Enhancement:** Document Gallery now has a settings page where you can
271
+ configure the default options for your galleries and chose how thumbnails are
272
+ generated.
273
+ * **Enhancement:** Customizing CSS for your document gallery is now *much easier*.
274
+ If you want to add additional styling, just navigate to `Settings -> Document Gallery`
275
+ in your dashboard and enter valid CSS in the "Custom CSS" textbox. See the changes
276
+ instantly in your galleries!
277
+ * **Enhancement:** Entire plugin is now
278
+ [Internalization-enabled](https://codex.wordpress.org/I18n_for_WordPress_Developers).
279
+ This means that we can now support users speaking all languages. If you are
280
+ interested in translating Document Gallery into a language that you speak,
281
+ please [let me know](http://wordpress.org/support/topic/seeking-translators)!
282
+ * **Enhancement:** This release saw much of the backend refactored to better
283
+ support future development. Nothing you will notice unless you're digging into
284
+ the code, but it will keep me sane long-term ;)
285
+ * **Note:** The thumbnail generation implementation works very hard to support
286
+ all hosting servers (including Unix and Windows systems). That said, I cannot
287
+ test on all hosts out there, so there is the potential for bugs to appear.
288
+ If you notice something that doesn't look right, please don't hesitate to
289
+ [report the issue](http://wordpress.org/support/plugin/document-gallery)
290
+ so that I can resolve it. Thanks!
291
+
292
+ = 1.4.3 =
293
+ * **Bug Fix:** Resolves minor bug introduced in version 1.4.2. Thanks, tkokholm!
294
+
295
+ = 1.4.2 =
296
+ * **Note:** This release includes an increase in the minimum WP version to 3.5.
297
+ If you have not yet upgraded to at least this version, you should consider doing
298
+ so as future releases include a number of *fantastic* new features as well as
299
+ many security improvements. If you chose not to upgrade, you must stay with
300
+ Document Gallery 1.4.1 or lower until you do. Sorry for the inconvenience!
301
+ * **Bug Fix:** Resolved icons being displayed differently depending on which
302
+ user was currently logged in. (Thanks to
303
+ [Sean](http://wordpress.org/support/topic/error-after-update-19?replies=12#post-5041251)
304
+ for reporting the issue.)
305
+ * **Enhancement:** A number of new icons were added (mainly for the iWork suite
306
+ and source code filetypes) and a number of pre-existing icons were removed if
307
+ they were very similar to another icon.
308
+ * **Under The Hood:** Many, many cool things. Stay tuned for a big reveal in the
309
+ coming weeks!
310
+ PS: If you're really curious, there are some clues in the source code ;)
311
+
312
  = 1.4.1 =
313
  * **Bug Fix:** This resolves a bug introduced in `1.4`, which caused a warning
314
  to be thrown when no attributes were used (i.e.: `[dg]`). (Thanks to
386
 
387
  = 1.0.1 =
388
 
389
+ * **Bug Fix:** Resolved issue with long document titles being cut off in some themes.
 
390
 
391
  = 1.0 =
392
 
407
 
408
  * **Release:** First public release of Document Gallery.
409
  * **Feature:** Displays PDF, Word, PowerPoint, Excel, and ZIP documents from a
410
+ given page or post.
411
+ * **Feature:** Documents can be ordered by a number of
412
  different factors.
admin/class-admin.php ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined('WPINC') OR exit;
3
+
4
+ class DG_Admin {
5
+
6
+ /**
7
+ * Renders Document Gallery options page.
8
+ */
9
+ public static function renderOptions() { ?>
10
+ <div class="wrap">
11
+ <h2>Document Gallery Settings</h2>
12
+
13
+ <form method="post" action="options.php">
14
+ <?php settings_fields(DG_OPTION_NAME); ?>
15
+ <?php do_settings_sections('document_gallery'); ?>
16
+ <?php submit_button(); ?>
17
+ </form>
18
+
19
+ </div>
20
+ <?php }
21
+
22
+ /**
23
+ * Adds settings link to main plugin view.
24
+ */
25
+ public static function addSettingsLink($links) {
26
+ $settings = '<a href="options-general.php?page=document_gallery">' .
27
+ __('Settings', 'document-gallery') . '</a>';
28
+ array_unshift($links, $settings);
29
+ return $links;
30
+ }
31
+
32
+ /**
33
+ * Adds Document Gallery settings page to admin navigation.
34
+ */
35
+ public static function addAdminPage() {
36
+ $page = add_options_page(
37
+ __('Document Gallery Settings', 'document-gallery'),
38
+ __('Document Gallery', 'document-gallery'),
39
+ 'manage_options', 'document_gallery', array(__CLASS__, 'renderOptions'));
40
+
41
+ add_action('admin_print_styles-' . $page, array(__CLASS__, 'enqueueAdminStyle'));
42
+ }
43
+
44
+ /**
45
+ * Registers stylesheet for admin options page.
46
+ */
47
+ public static function registerAdminStyle() {
48
+ wp_register_style('dg-admin', DG_URL . 'admin/css/style.css', null, DocumentGallery::version());
49
+ }
50
+
51
+ /**
52
+ * Registers settings for the Document Gallery options page.
53
+ */
54
+ public static function registerSettings() {
55
+ global $dg_options;
56
+
57
+ include_once DG_PATH . 'inc/class-gallery.php';
58
+ include_once DG_PATH . 'inc/class-thumber.php';
59
+
60
+ $defaults = $dg_options['gallery']['defaults'];
61
+ $thumber_active = $dg_options['thumber']['active'];
62
+ $thumber_gs = $dg_options['thumber']['gs'];
63
+
64
+ register_setting(DG_OPTION_NAME, DG_OPTION_NAME, array(__CLASS__, 'validateSettings'));
65
+
66
+ add_settings_section(
67
+ 'gallery_defaults', __('Default Settings', 'document-gallery'),
68
+ array(__CLASS__, 'renderDefaultSettingsSection'), 'document_gallery');
69
+
70
+ add_settings_section(
71
+ 'thumber_active', __('Thumbnail Generation', 'document-gallery'),
72
+ array(__CLASS__, 'renderThumberSection'), 'document_gallery');
73
+
74
+ add_settings_section(
75
+ 'css', __('Custon CSS', 'document-gallery'),
76
+ array(__CLASS__, 'renderCssSection'), 'document_gallery');
77
+
78
+ add_settings_section(
79
+ 'thumber_advanced', __('Advanced Thumbnail Generation', 'document-gallery'),
80
+ array(__CLASS__, 'renderThumberAdvancedSection'), 'document_gallery');
81
+
82
+ add_settings_field(
83
+ 'gallery_defaults_attachment_pg', 'attachment_pg',
84
+ array(__CLASS__, 'renderCheckboxField'),
85
+ 'document_gallery', 'gallery_defaults',
86
+ array (
87
+ 'label_for' => 'label_gallery_defaults_attachment_pg',
88
+ 'name' => 'gallery_defaults][attachment_pg',
89
+ 'value' => esc_attr($defaults['attachment_pg']),
90
+ 'option_name' => DG_OPTION_NAME,
91
+ 'description' => __('Link to attachment page rather than to file', 'document-gallery')
92
+ ));
93
+
94
+ add_settings_field(
95
+ 'gallery_defaults_descriptions', 'descriptions',
96
+ array(__CLASS__, 'renderCheckboxField'),
97
+ 'document_gallery', 'gallery_defaults',
98
+ array (
99
+ 'label_for' => 'label_gallery_defaults_descriptions',
100
+ 'name' => 'gallery_defaults][descriptions',
101
+ 'value' => esc_attr($defaults['descriptions']),
102
+ 'option_name' => DG_OPTION_NAME,
103
+ 'description' => __('Include document descriptions', 'document-gallery')
104
+ ));
105
+
106
+ add_settings_field(
107
+ 'gallery_defaults_fancy', 'fancy',
108
+ array(__CLASS__, 'renderCheckboxField'),
109
+ 'document_gallery', 'gallery_defaults',
110
+ array (
111
+ 'label_for' => 'label_gallery_defaults_fancy',
112
+ 'name' => 'gallery_defaults][fancy',
113
+ 'value' => esc_attr($defaults['fancy']),
114
+ 'option_name' => DG_OPTION_NAME,
115
+ 'description' => __('Use auto-generated document thumbnails', 'document-gallery')
116
+ ));
117
+
118
+ add_settings_field(
119
+ 'gallery_defaults_images', 'images',
120
+ array(__CLASS__, 'renderCheckboxField'),
121
+ 'document_gallery', 'gallery_defaults',
122
+ array (
123
+ 'label_for' => 'label_gallery_defaults_images',
124
+ 'name' => 'gallery_defaults][images',
125
+ 'value' => esc_attr($defaults['images']),
126
+ 'option_name' => DG_OPTION_NAME,
127
+ 'description' => __('Include image attachments in gallery', 'document-gallery')
128
+ ));
129
+
130
+ add_settings_field(
131
+ 'gallery_defaults_localpost', 'localpost',
132
+ array(__CLASS__, 'renderCheckboxField'),
133
+ 'document_gallery', 'gallery_defaults',
134
+ array (
135
+ 'label_for' => 'label_gallery_defaults_localpost',
136
+ 'name' => 'gallery_defaults][localpost',
137
+ 'value' => esc_attr($defaults['localpost']),
138
+ 'option_name' => DG_OPTION_NAME,
139
+ 'description' => __('Only look for attachments in post where [dg] is used', 'document-gallery')
140
+ ));
141
+
142
+ add_settings_field(
143
+ 'gallery_defaults_order', 'order',
144
+ array(__CLASS__, 'renderSelectField'),
145
+ 'document_gallery', 'gallery_defaults',
146
+ array (
147
+ 'label_for' => 'label_gallery_defaults_order',
148
+ 'name' => 'gallery_defaults][order',
149
+ 'value' => esc_attr($defaults['order']),
150
+ 'options' => DG_Gallery::getOrderOptions(),
151
+ 'option_name' => DG_OPTION_NAME,
152
+ 'description' => __('Ascending or decending sorting of documents', 'document-gallery')
153
+ ));
154
+
155
+ add_settings_field(
156
+ 'gallery_defaults_orderby', 'orderby',
157
+ array(__CLASS__, 'renderSelectField'),
158
+ 'document_gallery', 'gallery_defaults',
159
+ array (
160
+ 'label_for' => 'label_gallery_defaults_orderby',
161
+ 'name' => 'gallery_defaults][orderby',
162
+ 'value' => esc_attr($defaults['orderby']),
163
+ 'options' => DG_Gallery::getOrderbyOptions(),
164
+ 'option_name' => DG_OPTION_NAME,
165
+ 'description' => __('Which field to order documents by', 'document-gallery')
166
+ ));
167
+
168
+ add_settings_field(
169
+ 'gallery_defaults_relation', 'relation',
170
+ array(__CLASS__, 'renderSelectField'),
171
+ 'document_gallery', 'gallery_defaults',
172
+ array (
173
+ 'label_for' => 'label_gallery_defaults_relation',
174
+ 'name' => 'gallery_defaults][relation',
175
+ 'value' => esc_attr($defaults['relation']),
176
+ 'options' => DG_Gallery::getRelationOptions(),
177
+ 'option_name' => DG_OPTION_NAME,
178
+ 'description' => __('Whether matched documents must have all taxa_names (AND) or at least one (OR)', 'document-gallery')
179
+ ));
180
+
181
+ add_settings_field(
182
+ 'thumber_active_av', 'Audio/Video',
183
+ array(__CLASS__, 'renderCheckboxField'),
184
+ 'document_gallery', 'thumber_active',
185
+ array (
186
+ 'label_for' => 'label_thumber_active_av',
187
+ 'name' => 'thumber_active][av',
188
+ 'value' => esc_attr($thumber_active['av']),
189
+ 'option_name' => DG_OPTION_NAME,
190
+ 'description' => esc_html__('Locally generate thumbnails for audio & video files.', 'document-gallery')
191
+ ));
192
+
193
+ add_settings_field(
194
+ 'thumber_active_gs', 'Ghostscript',
195
+ array(__CLASS__, 'renderCheckboxField'),
196
+ 'document_gallery', 'thumber_active',
197
+ array (
198
+ 'label_for' => 'label_thumber_active_gs',
199
+ 'name' => 'thumber_active][gs',
200
+ 'value' => esc_attr($thumber_active['gs']),
201
+ 'option_name' => DG_OPTION_NAME,
202
+ 'description' => DG_Thumber::getGhostscriptExecutable()
203
+ ? __('Use <a href="http://www.ghostscript.com/" target="_blank">Ghostscript</a> for faster local PDF processing (compared to Imagick).', 'document-gallery')
204
+ : __('Your server is not configured to run <a href="http://www.ghostscript.com/" target="_blank">Ghostscript</a>.', 'document-gallery'),
205
+ 'disabled' => !DG_Thumber::getGhostscriptExecutable()
206
+ ));
207
+
208
+ add_settings_field(
209
+ 'thumber_active_imagick', 'Imagick',
210
+ array(__CLASS__, 'renderCheckboxField'),
211
+ 'document_gallery', 'thumber_active',
212
+ array (
213
+ 'label_for' => 'label_thumber_active_imagick',
214
+ 'name' => 'thumber_active][imagick',
215
+ 'value' => esc_attr($thumber_active['imagick']),
216
+ 'option_name' => DG_OPTION_NAME,
217
+ 'description' => DG_Thumber::isImagickAvailable()
218
+ ? __('Use <a href="http://www.php.net/manual/en/book.imagick.php" target="_blank">Imagick</a> to handle lots of filetypes locally.', 'document-gallery')
219
+ : __('Your server is not configured to run <a href="http://www.php.net/manual/en/book.imagick.php" target="_blank">Imagick</a>.', 'document-gallery'),
220
+ 'disabled' => !DG_Thumber::isImagickAvailable()
221
+ ));
222
+
223
+ add_settings_field(
224
+ 'thumber_active_google', 'Google Drive Viewer',
225
+ array(__CLASS__, 'renderCheckboxField'),
226
+ 'document_gallery', 'thumber_active',
227
+ array (
228
+ 'label_for' => 'label_thumber_active_google',
229
+ 'name' => 'thumber_active][google',
230
+ 'value' => esc_attr($thumber_active['google']),
231
+ 'option_name' => DG_OPTION_NAME,
232
+ 'description' => DG_Thumber::isGoogleDriveAvailable()
233
+ ? __('Use <a href="https://drive.google.com/viewer" target="_blank">Google Drive Viewer</a> to generate thumbnails for MS Office files and many other file types remotely.', 'document-gallery')
234
+ : __('Your server does not allow remote HTTP access.', 'document-gallery'),
235
+ 'disabled' => !DG_Thumber::isGoogleDriveAvailable()
236
+ ));
237
+
238
+ add_settings_field(
239
+ 'thumber_advanced_gs', 'Ghostscript Absolute Path',
240
+ array(__CLASS__, 'renderTextField'),
241
+ 'document_gallery', 'thumber_advanced',
242
+ array (
243
+ 'label_for' => 'label_thumber_advanced_gs',
244
+ 'name' => 'thumber_advanced][gs',
245
+ 'value' => esc_attr($thumber_gs),
246
+ 'option_name' => DG_OPTION_NAME,
247
+ 'description' => $thumber_gs
248
+ ? __('Successfully auto-detected the location of Ghostscript.', 'document-gallery')
249
+ : __('Failed to auto-detect the location of Ghostscript.', 'document-gallery')
250
+ ));
251
+ }
252
+
253
+ /**
254
+ * Render the Default Settings section.
255
+ */
256
+ public static function renderDefaultSettingsSection() { ?>
257
+ <p><?php _e('The following values will be used by default in the shortcode. You can still manually set each of these values in each individual shortcode.', 'document-gallery'); ?></p>
258
+ <?php }
259
+
260
+ /**
261
+ * Render the Thumber section.
262
+ */
263
+ public static function renderThumberSection() { ?>
264
+ <p><?php _e('Select which tools to use when generating thumbnails.', 'document-gallery'); ?></p>
265
+ <?php }
266
+
267
+ public static function renderCssSection() {
268
+ global $dg_options; ?>
269
+ <p><?php printf(
270
+ __('Enter custom CSS styling for use with document galleries. To see which ids and classes you can style, take a look at <a href="%s" target="_blank">style.css</a>.'),
271
+ DG_URL . 'assets/css/style.css'); ?></p>
272
+ <table class="form-table">
273
+ <tbody>
274
+ <tr valign="top">
275
+ <td>
276
+ <textarea name="document_gallery[css]" rows="10" cols="50" class="large-text code"><?php echo $dg_options['css']['text']; ?></textarea>
277
+ </td>
278
+ </tr>
279
+ </tbody>
280
+ </table>
281
+ <?php }
282
+
283
+ /**
284
+ * Render the Thumber Advanced section.
285
+ */
286
+ public static function renderThumberAdvancedSection() {
287
+ include_once DG_PATH . 'inc/class-thumber.php';?>
288
+ <p><?php _e('Unless you <em>really</em> know what you\'re doing, you should not touch these values.', 'document-gallery'); ?></p>
289
+ <?php if (!DG_Thumber::isExecAvailable()) : ?>
290
+ <p><em><?php _e('NOTE: <code>exec()</code> is not accessible. Ghostscript will not function.', 'document-gallery'); ?></em></p>
291
+ <?php endif; ?>
292
+ <?php }
293
+
294
+ /**
295
+ * Render a checkbox field.
296
+ * @param array $args
297
+ */
298
+ public static function renderCheckboxField($args) {
299
+ $args['disabled'] = isset($args['disabled']) ? $args['disabled'] : false;
300
+ printf('<input type="checkbox" value="1" name="%1$s[%2$s]" id="%3$s" %4$s %5$s/> %6$s',
301
+ $args['option_name'],
302
+ $args['name'],
303
+ $args['label_for'],
304
+ checked($args['value'], 1, false),
305
+ $args['disabled'] ? 'disabled="disabled"' : '',
306
+ $args['description']);
307
+ }
308
+
309
+ /**
310
+ * Render a text field.
311
+ * @param array $args
312
+ */
313
+ public static function renderTextField($args) {
314
+ printf('<input type="text" value="%1$s" name="%2$s[%3$s]" id="%4$s" /> %5$s',
315
+ $args['value'],
316
+ $args['option_name'],
317
+ $args['name'],
318
+ $args['label_for'],
319
+ $args['description']);
320
+ }
321
+
322
+ /**
323
+ * Render a select field.
324
+ * @param array $args
325
+ */
326
+ public static function renderSelectField($args) {
327
+ printf('<select name="%1$s[%2$s]" id="%3$s">',
328
+ $args['option_name'],
329
+ $args['name'],
330
+ $args['label_for']);
331
+
332
+ foreach ($args['options'] as $val) {
333
+ printf('<option value="%1$s" %2$s>%3$s</option>',
334
+ $val,
335
+ selected($val, $args['value'], false),
336
+ $val,
337
+ $args['description']);
338
+ }
339
+
340
+ print '</select> ' . $args['description'];
341
+ }
342
+
343
+ /**
344
+ * Validates submitted options, sanitizing any invalid options.
345
+ * @param array $values User-submitted new options.
346
+ * @return array Sanitized new options.
347
+ */
348
+ public static function validateSettings($values) {
349
+ include_once DG_PATH . 'inc/class-gallery.php';
350
+
351
+ global $dg_options;
352
+ $ret = $dg_options;
353
+
354
+ // handle gallery shortcode defaults
355
+ $errs = array();
356
+ $ret['gallery']['defaults'] =
357
+ DG_Gallery::sanitizeDefaults($values['gallery_defaults'], $errs);
358
+
359
+ foreach ($errs as $k => $v) {
360
+ add_settings_error(DG_OPTION_NAME, str_replace('_', '-', $k), $v);
361
+ }
362
+
363
+ // handle setting the active thumbers
364
+ foreach ($ret['thumber']['active'] as $k => $v) {
365
+ $ret['thumber']['active'][$k] = isset($values['thumber_active'][$k]);
366
+ }
367
+
368
+ // if new thumbers available, clear failed thumbnails for retry
369
+ foreach ($dg_options['thumber']['active'] as $k => $v) {
370
+ if (!$v && $ret['thumber']['active'][$k]) {
371
+ foreach ($dg_options['thumber']['thumbs'] as $k => $v) {
372
+ if (false === $v) {
373
+ unset($ret['thumber']['thumbs'][$k]);
374
+ }
375
+ }
376
+ break;
377
+ }
378
+ }
379
+
380
+ // handle changed CSS
381
+ if (trim($values['css']) != trim($ret['css']['text'])) {
382
+ if (DocumentGallery::updateUserGalleryStyle($values['css'])) {
383
+ $ret['css']['text'] = $values['css'];
384
+ $ret['css']['version']++;
385
+ } else {
386
+ add_settings_error(DG_OPTION_NAME, 'css',
387
+ __('Failed to update CSS file.', 'document-gallery'));
388
+ }
389
+ }
390
+
391
+ // handle setting the Ghostscript path
392
+ if (isset($values['thumber_advanced']['gs']) &&
393
+ 0 != strcmp($values['thumber_advanced']['gs'], $ret['thumber']['gs'])) {
394
+ if (false === strpos($values['thumber_advanced']['gs'], ';')) {
395
+ $ret['thumber']['gs'] = $values['thumber_advanced']['gs'];
396
+ } else {
397
+ add_settings_error(DG_OPTION_NAME, 'thumber-gs',
398
+ __('Invalid Ghostscript path given: ', 'document-gallery')
399
+ . $values['thumber_advanced']['gs']);
400
+ }
401
+ }
402
+
403
+ return $ret;
404
+ }
405
+
406
+ /**
407
+ * Enqueues stylesheet for admin options page.
408
+ */
409
+ public static function enqueueAdminStyle() {
410
+ wp_enqueue_style('dg-admin');
411
+ }
412
+
413
+ /**
414
+ * Blocks instantiation. All functions are static.
415
+ */
416
+ private function __construct() {
417
+
418
+ }
419
+ }
admin/css/style.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #cached-thumbs {
2
+ }
3
+
4
+ #cached-thumbs tr {
5
+ height: 100px;
6
+ }
7
+
8
+ table#cached-thumbs td.thumb-img {
9
+ width: 75px;
10
+ text-align: center;
11
+ padding-right: 5px;
12
+ }
13
+
14
+ #cached-thumbs td.thumb-img > img {
15
+ max-width: 75px;
16
+ max-height: 75px;
17
+ }
style.css → assets/css/style.css RENAMED
@@ -18,7 +18,7 @@ div.document-icon{
18
  display: inline-block;
19
  vertical-align: top;
20
  overflow: hidden;
21
- /* percents round up in some browsers, making
22
  only 3 icons fit per line so can't use 25% */
23
  width: 24.5%;
24
  }
@@ -30,7 +30,7 @@ div.document-icon-wrapper{
30
  /* END WITHOUT DESCRIPTION */
31
 
32
  /* WITH DESCRIPTION */
33
- div.descriptions.document-icon-wrapper div.document-icon{
34
  max-width: 115px;
35
  padding: 0;
36
  padding-right: 3px;
@@ -55,7 +55,7 @@ div.descriptions.document-icon-wrapper:after{
55
  content: "";
56
  display: table;
57
  }
58
-
59
  div.descriptions.document-icon-wrapper:after{
60
  clear: both;
61
  }
@@ -63,3 +63,5 @@ div.descriptions.document-icon-wrapper{
63
  zoom: 1; /* For IE 6/7 (trigger hasLayout) */
64
  }
65
  /* END WITH DESCRIPTION */
 
 
18
  display: inline-block;
19
  vertical-align: top;
20
  overflow: hidden;
21
+ /* percents round up in some browsers, making
22
  only 3 icons fit per line so can't use 25% */
23
  width: 24.5%;
24
  }
30
  /* END WITHOUT DESCRIPTION */
31
 
32
  /* WITH DESCRIPTION */
33
+ div.descriptions.document-icon-wrapper div.document-icon{
34
  max-width: 115px;
35
  padding: 0;
36
  padding-right: 3px;
55
  content: "";
56
  display: table;
57
  }
58
+
59
  div.descriptions.document-icon-wrapper:after{
60
  clear: both;
61
  }
63
  zoom: 1; /* For IE 6/7 (trigger hasLayout) */
64
  }
65
  /* END WITH DESCRIPTION */
66
+
67
+ /* CUSTOM USER CSS */
assets/icons/asc.png ADDED
Binary file
assets/icons/audio.png ADDED
Binary file
assets/icons/c.png ADDED
Binary file
{icons → assets/icons}/compressed.png RENAMED
File without changes
assets/icons/cpp.png ADDED
Binary file
{icons → assets/icons}/css.png RENAMED
File without changes
{icons → assets/icons}/exec.png RENAMED
File without changes
assets/icons/h.png ADDED
Binary file
{icons → assets/icons}/html.png RENAMED
File without changes
{icons → assets/icons}/ics.png RENAMED
File without changes
assets/icons/image.png ADDED
Binary file
{icons → assets/icons}/java.png RENAMED
File without changes
{icons → assets/icons}/javascript.png RENAMED
File without changes
assets/icons/key.png ADDED
Binary file
{icons → assets/icons}/midi.png RENAMED
File without changes
{icons → assets/icons}/missing.png RENAMED
File without changes
{icons → assets/icons}/msaccess.png RENAMED
File without changes
{icons → assets/icons}/msdoc.png RENAMED
File without changes
{icons → assets/icons}/msppt.png RENAMED
File without changes
{icons → assets/icons}/msxls.png RENAMED
File without changes
assets/icons/numbers.png ADDED
Binary file
{icons → assets/icons}/opendocument-database.png RENAMED
File without changes
{icons → assets/icons}/opendocument-formula.png RENAMED
File without changes
{icons → assets/icons}/opendocument-graphics.png RENAMED
File without changes
{icons → assets/icons}/opendocument-presentation.png RENAMED
File without changes
{icons → assets/icons}/opendocument-spreadsheet.png RENAMED
File without changes
{icons → assets/icons}/opendocument-text.png RENAMED
File without changes
assets/icons/pages.png ADDED
Binary file
{icons → assets/icons}/pdf.png RENAMED
File without changes
{icons → assets/icons}/rtf.png RENAMED
File without changes
{icons → assets/icons}/rtx.png RENAMED
File without changes
{icons → assets/icons}/shockwave.png RENAMED
File without changes
assets/icons/text.png ADDED
Binary file
assets/icons/video.png ADDED
Binary file
assets/icons/wordperfect.png ADDED
Binary file
document-gallery.php CHANGED
@@ -1,41 +1,150 @@
1
  <?php
 
2
 
3
  /*
4
  Plugin Name: Document Gallery
5
  Description: Display non-images (and images) in gallery format on a page or post with the [dg] shortcode.
6
- Version: 1.4.1
7
  Author: Dan Rossiter
8
  Author URI: http://danrossiter.org/
9
  License: GPLv2
 
10
  */
11
 
 
12
  define('DG_URL', plugin_dir_url(__FILE__));
13
- define('DG_PATH', dirname(__FILE__).'/');
 
 
 
 
 
 
14
 
15
- /**
16
- * Takes values passed from attributes and returns sutable HTML to represent
17
- * all valid attachments requested.
18
- *
19
- * @param array $atts Arguments from the user.
20
- * @return string HTML for the Document Gallery.
21
- */
22
- function dg_get_attachment_icons($atts) {
23
- include_once(DG_PATH . 'models/class-gallery.php');
 
 
 
24
 
25
- // empty string is passed when no arguments are given, but constructor expects an array
26
- return (string)(new DG_Gallery(empty($atts) ? array() : $atts));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
- /**
30
- * 'document gallery' shortcode depreciated as of v1.0. left for backward compatibility
31
- */
32
- add_shortcode('document gallery', 'dg_get_attachment_icons');
33
- add_shortcode('dg', 'dg_get_attachment_icons');
34
 
35
  /**
36
- * Include stylesheet for some basic design.
 
 
37
  */
38
- function dg_add_header_css() {
39
- wp_enqueue_style('document-gallery-css', DG_URL . 'style.css');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
- add_action('wp_print_styles', 'dg_add_header_css');
 
1
  <?php
2
+ defined('WPINC') OR exit;
3
 
4
  /*
5
  Plugin Name: Document Gallery
6
  Description: Display non-images (and images) in gallery format on a page or post with the [dg] shortcode.
7
+ Version: 2.0
8
  Author: Dan Rossiter
9
  Author URI: http://danrossiter.org/
10
  License: GPLv2
11
+ Text Domain: document-gallery
12
  */
13
 
14
+ // define helper paths & URLs
15
  define('DG_URL', plugin_dir_url(__FILE__));
16
+ define('DG_PATH', plugin_dir_path(__FILE__));
17
+ if(!defined('WP_INCLUDE_DIR')) {
18
+ define('WP_INCLUDE_DIR', preg_replace('/wp-content$/', 'wp-includes', WP_CONTENT_DIR));
19
+ }
20
+ if(!defined('WP_ADMIN_DIR')) {
21
+ define('WP_ADMIN_DIR', preg_replace('/wp-content$/', 'wp-admin', WP_CONTENT_DIR));
22
+ }
23
 
24
+ // init DG options for use throughout plugin
25
+ global $dg_options;
26
+ define('DG_OPTION_NAME', 'document_gallery');
27
+ $dg_options = get_option(DG_OPTION_NAME);
28
+
29
+ // handle activation and uninstallation
30
+ include_once DG_PATH . 'inc/class-setup.php';
31
+ register_activation_hook(__FILE__, array('DG_Setup', 'activation'));
32
+ register_uninstall_hook(__FILE__, array('DG_Setup', 'uninstall'));
33
+
34
+ // I18n
35
+ add_action('plugins_loaded', array('DocumentGallery', 'loadTextDomain'));
36
 
37
+ // cleanup cached data when thumbed attachment deleted
38
+ include_once DG_PATH . 'inc/class-thumber.php';
39
+ add_action('delete_attachment', array('DG_Thumber', 'deleteThumbMeta'));
40
+
41
+ if (is_admin()) {
42
+ // admin house keeping
43
+ include_once DG_PATH . 'admin/class-admin.php';
44
+
45
+ // add settings link
46
+ add_filter('plugin_action_links_' . plugin_basename(__FILE__),
47
+ array('DG_Admin', 'addSettingsLink'));
48
+
49
+ // build options page
50
+ add_action('admin_menu', array('DG_Admin', 'addAdminPage'));
51
+ if (!empty($GLOBALS['pagenow'])
52
+ && ('options-general.php' === $GLOBALS['pagenow'] // output
53
+ || 'options.php' === $GLOBALS['pagenow'])) { // validation
54
+ add_action('admin_init', array('DG_Admin', 'registerAdminStyle'));
55
+ add_action('admin_init', array('DG_Admin', 'registerSettings'));
56
+ }
57
+ } else {
58
+ // styling for gallery
59
+ add_action('wp_enqueue_scripts', array('DocumentGallery', 'enqueueGalleryStyle'));
60
  }
61
 
62
+ // adds 'dg' shortcode
63
+ add_shortcode('dg', array('DocumentGallery', 'doShortcode'));
 
 
 
64
 
65
  /**
66
+ * DocumentGallery wraps basic functionality to setup the plugin.
67
+ *
68
+ * @author drossiter
69
  */
70
+ class DocumentGallery {
71
+
72
+ /*==========================================================================
73
+ * THE SHORTCODE
74
+ *=========================================================================*/
75
+
76
+ /**
77
+ * Takes values passed from attributes and returns sutable HTML to represent
78
+ * all valid attachments requested.
79
+ *
80
+ * @param array $atts Arguments from the user.
81
+ * @return string HTML for the Document Gallery.
82
+ */
83
+ public static function doShortcode($atts) {
84
+ include_once 'inc/class-gallery.php';
85
+ return new DG_Gallery($atts);
86
+ }
87
+
88
+ /**
89
+ * Enqueue standard DG CSS.
90
+ */
91
+ public static function enqueueGalleryStyle() {
92
+ global $dg_options;
93
+ wp_register_style('dg-main', DG_URL . 'assets/css/style.css', null,
94
+ self::version() . ':' . $dg_options['css']['version']);
95
+ wp_enqueue_style('dg-main');
96
+ }
97
+
98
+ public static function updateUserGalleryStyle($css) {
99
+ $ret = false;
100
+
101
+ if ($css_file = file_get_contents(DG_PATH . 'assets/css/style.css')) {
102
+ $css_file = preg_replace('#/\* CUSTOM USER CSS \*/.*#s',
103
+ "/* CUSTOM USER CSS */\n" . $css, $css_file);
104
+ $ret = (bool)file_put_contents(DG_PATH . 'assets/css/style.css', $css_file, LOCK_EX);
105
+ }
106
+
107
+ return $ret;
108
+ }
109
+
110
+ /*==========================================================================
111
+ * I18n
112
+ *=========================================================================*/
113
+
114
+ public static function loadTextDomain() {
115
+ load_plugin_textdomain('document-gallery', false, DG_PATH . 'languages');
116
+ }
117
+
118
+ /*==========================================================================
119
+ * HELPER FUNCTIONS
120
+ *=========================================================================*/
121
+
122
+ /**
123
+ * Gets the current version of the plugin.
124
+ * @param bool recalculate Whether to re-read the file for version number.
125
+ * @return str DG version installed.
126
+ */
127
+ public static function version($recalculate = false) {
128
+ static $version = null;
129
+
130
+ if ($recalculate) {
131
+ include_once WP_ADMIN_DIR . '/includes/plugin.php';
132
+ $data = get_plugin_data(__FILE__, false);
133
+ $version = $data['Version'];
134
+ } elseif(is_null($version)) {
135
+ global $dg_options;
136
+ $version = $dg_options['version'];
137
+ }
138
+
139
+ return $version;
140
+ }
141
+
142
+ /**
143
+ * Blocks instantiation. All functions are static.
144
+ */
145
+ private function __construct() {
146
+
147
+ }
148
  }
149
+
150
+ ?>
icons/7zip.png DELETED
Binary file
icons/avi.png DELETED
Binary file
icons/csv.png DELETED
Binary file
icons/divx.png DELETED
Binary file
icons/flv.png DELETED
Binary file
icons/mkv.png DELETED
Binary file
icons/mov.png DELETED
Binary file
icons/mp3.png DELETED
Binary file
icons/ogg.png DELETED
Binary file
icons/rar.png DELETED
Binary file
icons/wav.png DELETED
Binary file
icons/wma.png DELETED
Binary file
icons/wmv.png DELETED
Binary file
icons/zip.png DELETED
Binary file
inc/class-document.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined('WPINC') OR exit;
3
+
4
+ /**
5
+ * Holds data specific to a given document.
6
+ *
7
+ * @author drossiter
8
+ */
9
+ class DG_Document {
10
+
11
+ /*==========================================================================
12
+ * PRIVATE FIELDS
13
+ *=========================================================================*/
14
+
15
+ // templates for HTML output
16
+ private static $doc_icon = false;
17
+ private static $img_string = '<img src="%s" title="%s" alt="%s" />';
18
+
19
+ // general document data
20
+ private $description, $gallery, $ID, $link, $title, $title_attribute;
21
+
22
+ /*==========================================================================
23
+ * INIT GALLERY
24
+ *=========================================================================*/
25
+
26
+ /**
27
+ * Constructs instance of Document.
28
+ * @param type $attachment Attachment object used to initalize fields.
29
+ * @param type $gallery Instance of Gallery class.
30
+ */
31
+ public function __construct($attachment, $gallery) {
32
+ include_once DG_PATH . 'inc/class-thumber.php';
33
+
34
+ // init template for HTML output
35
+ if(false === self::$doc_icon)
36
+ {
37
+ self::$doc_icon =
38
+ ' <div class="document-icon">' . PHP_EOL .
39
+ ' <a href="%s">%s<br>%s</a>' . PHP_EOL .
40
+ ' </div>' . PHP_EOL;
41
+ }
42
+
43
+ // init general document data
44
+ $this->gallery = $gallery;
45
+ $this->description = $attachment->post_content;
46
+ $this->ID = $attachment->ID;
47
+ $this->link = $gallery->linkToAttachmentPg()
48
+ ? get_attachment_link($attachment->ID)
49
+ : wp_get_attachment_url($attachment->ID);
50
+ $this->title = get_the_title($attachment->ID);
51
+ $this->title_attribute = esc_attr(strip_tags($this->title));
52
+ }
53
+
54
+ /*==========================================================================
55
+ * OUTPUT HTML STRING
56
+ *=========================================================================*/
57
+
58
+ /**
59
+ * Returns HTML representing this Document.
60
+ * @return string
61
+ */
62
+ public function __toString() {
63
+ $thumb = $this->gallery->useFancyThumbs()
64
+ ? DG_Thumber::getThumbnail($this->ID)
65
+ : DG_Thumber::getDefaultThumbnail($this->ID);
66
+ $icon = sprintf(self::$img_string, $thumb,
67
+ $this->title_attribute, $this->title_attribute);
68
+ $core = sprintf(self::$doc_icon, $this->link, $icon, $this->title);
69
+
70
+ if($this->gallery->useDescriptions()) {
71
+ $core .= " <p>$this->description</p>" . PHP_EOL;
72
+ }
73
+
74
+ // users may filter icon here
75
+ return apply_filters('dg_doc_icon', $core, $this->ID);
76
+ }
77
+ }
78
+
79
+ ?>
inc/class-gallery.php ADDED
@@ -0,0 +1,595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined('WPINC') OR exit;
3
+
4
+ DG_Gallery::init();
5
+
6
+ /**
7
+ * Holds data specific to a given document gallery.
8
+ *
9
+ * @author drossiter
10
+ */
11
+ class DG_Gallery {
12
+
13
+ /*==========================================================================
14
+ * PRIVATE FIELDS
15
+ *=========================================================================*/
16
+
17
+ private $atts, $taxa;
18
+ private $docs = array();
19
+ private $errs = array();
20
+
21
+ // templates for HTML output
22
+ private static $no_docs = false;
23
+ private static $icon_wrapper = false;
24
+ private static $comment = false;
25
+
26
+ private static $binary_err = false;
27
+
28
+ /*==========================================================================
29
+ * PUBLIC FUNCTIONS
30
+ *=========================================================================*/
31
+
32
+ /**
33
+ * Returns whether to link to attachment pg.
34
+ * @return bool
35
+ */
36
+ public function linkToAttachmentPg() {
37
+ return $this->atts['attachment_pg'];
38
+ }
39
+
40
+ public function useFancyThumbs() {
41
+ return $this->atts['fancy'];
42
+ }
43
+
44
+ /**
45
+ * Returns whether descriptions should be included in output.
46
+ * @return bool
47
+ */
48
+ public function useDescriptions() {
49
+ return $this->atts['descriptions'];
50
+ }
51
+
52
+ /*==========================================================================
53
+ * GET AND SET OPTIONS
54
+ *=========================================================================*/
55
+
56
+ /**
57
+ * Gets the DG options specific to Gallery.
58
+ * @return array
59
+ */
60
+ public static function getOptions() {
61
+ global $dg_options;
62
+ return $dg_options['gallery'];
63
+ }
64
+
65
+ /**
66
+ * Sets the DG options specific to Gallery.
67
+ * @param array $options
68
+ */
69
+ public static function setOptions($options) {
70
+ global $dg_options;
71
+ $dg_options['gallery'] = $options;
72
+ update_option(DG_OPTION_NAME, $dg_options);
73
+ }
74
+
75
+ public static function getDefaults() {
76
+ $options = self::getOptions();
77
+ return $options['defaults'];
78
+ }
79
+
80
+ public static function setDefaults($defaults) {
81
+ $options = self::getOptions();
82
+ $options['defaults'] = $defaults;
83
+ self::setOptions($options);
84
+ }
85
+
86
+ /*==========================================================================
87
+ * INIT GALLERY
88
+ *=========================================================================*/
89
+
90
+ public static function init() {
91
+ self::$comment =
92
+ PHP_EOL . '<!-- ' . __('Generated using Document Gallery. Get yours here: ', 'document-gallery') .
93
+ 'http://wordpress.org/extend/plugins/document-gallery -->' . PHP_EOL;
94
+ self::$icon_wrapper = '<div class="%s">'. PHP_EOL . '%s</div>' . PHP_EOL;
95
+ self::$no_docs = '<!-- ' . __('No attachments to display. How boring! :(', 'document-gallery') . ' -->';
96
+ self::$binary_err = __('The %s parameter may only be "%s" or "%s." You entered "%s."', 'document-gallery');
97
+ }
98
+
99
+ /**
100
+ * Builds a gallery object with attributes passed.
101
+ * @param array $atts Array of attributes used in shortcode.
102
+ */
103
+ public function __construct($atts) {
104
+ // empty string is passed when no arguments are given, but constructor expects an array
105
+ $atts = empty($atts) ? array() : $atts;
106
+ $defaults = self::getDefaults();
107
+
108
+ // values used to construct tax query (may be empty)
109
+ $this->taxa = array_diff_key($atts, $defaults);
110
+
111
+ // all recognized attributes go here
112
+ $this->atts = shortcode_atts($defaults, $atts);
113
+
114
+ // goes through all values in $this->atts, setting $this->errs as needed
115
+ $this->atts = self::sanitizeDefaults($this->atts, $this->errs);
116
+
117
+ // query DB for all documents requested
118
+ include_once DG_PATH . 'inc/class-document.php';
119
+ try {
120
+ $docs = $this->getDocuments();
121
+
122
+ foreach($docs as $doc) {
123
+ $this->docs[] = new DG_Document($doc, $this);
124
+ }
125
+ } catch(InvalidArgumentException $e) {
126
+ // errors will be printed in __toString()
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Cleans up user input, making sure we don't pass crap on to WP core.
132
+ * @global string $wp_version
133
+ */
134
+ public static function sanitizeDefaults($defaults, &$errs) {
135
+ $old_defaults = self::getDefaults();
136
+
137
+ // remove invalid keys
138
+ $defaults = array_intersect_key($defaults, $old_defaults);
139
+
140
+ // add any missing keys
141
+ foreach ($old_defaults as $k => $v) {
142
+ if (!isset($defaults[$k])) {
143
+ if (is_bool($v)) {
144
+ // checkbox
145
+ $defaults[$k] = false;
146
+ } else {
147
+ // missing value
148
+ $defaults[$k] = $v;
149
+ }
150
+ }
151
+ }
152
+
153
+ $defaults['attachment_pg'] = self::sanitizeAttachmentPg($defaults['attachment_pg'], $err);
154
+ if (isset($err)) {
155
+ $errs['attachment_pg'] = $err;
156
+ unset($err);
157
+ }
158
+
159
+ $defaults['descriptions'] = self::sanitizeDescriptions($defaults['descriptions'], $err);
160
+ if (isset($err)) {
161
+ $errs['descriptions'] = $err;
162
+ unset($err);
163
+ }
164
+
165
+ $defaults['fancy'] = self::sanitizeFancy($defaults['fancy'], $err);
166
+ if (isset($err)) {
167
+ $errs['fancy'] = $err;
168
+ unset($err);
169
+ }
170
+
171
+ $defaults['ids'] = self::sanitizeIds($defaults['ids'], $err);
172
+ if (isset($err)) {
173
+ $errs['ids'] = $err;
174
+ unset($err);
175
+ }
176
+
177
+ $defaults['images'] = self::sanitizeImages($defaults['images'], $err);
178
+ if (isset($err)) {
179
+ $errs['images'] = $err;
180
+ unset($err);
181
+ }
182
+
183
+ $defaults['localpost'] = self::sanitizeLocalpost($defaults['localpost'], $err);
184
+ if (isset($err)) {
185
+ $errs['localpost'] = $err;
186
+ unset($err);
187
+ }
188
+
189
+ $defaults['order'] = self::sanitizeOrder($defaults['order'], $err);
190
+ if (isset($err)) {
191
+ $errs['order'] = $err;
192
+ unset($err);
193
+ }
194
+
195
+ $defaults['orderby'] = self::sanitizeOrderby($defaults['orderby'], $err);
196
+ if (isset($err)) {
197
+ $errs['orderby'] = $err;
198
+ unset($err);
199
+ }
200
+
201
+ $defaults['relation'] = self::sanitizeRelation($defaults['relation'], $err);
202
+ if (isset($err)) {
203
+ $errs['relation'] = $err;
204
+ unset($err);
205
+ }
206
+
207
+ return $defaults;
208
+ }
209
+
210
+ private static function sanitizeAttachmentPg($value, &$err) {
211
+ $defaults = self::getDefaults();
212
+ $ret = $defaults['attachment_pg'];
213
+
214
+ $attachment_pg = self::toBool($value);
215
+
216
+ if(is_null($attachment_pg)) {
217
+ $err = sprintf(self::$binary_err, 'attachment_pg', 'true', 'false', $value);
218
+ } else {
219
+ $ret = $attachment_pg;
220
+ }
221
+
222
+ return $ret;
223
+ }
224
+
225
+ private static function sanitizeDescriptions($value, &$err) {
226
+ $defaults = self::getDefaults();
227
+ $ret = $defaults['descriptions'];
228
+
229
+ $descriptions = self::toBool($value);
230
+
231
+ if(is_null($descriptions)) {
232
+ $err = sprintf(self::$binary_err, 'descriptions', 'true', 'false', $value);
233
+ } else {
234
+ $ret = $descriptions;
235
+ }
236
+
237
+ return $ret;
238
+ }
239
+
240
+ private static function sanitizeFancy($value, &$err) {
241
+ $defaults = self::getDefaults();
242
+ $ret = $defaults['fancy'];
243
+
244
+ $fancy = self::toBool($value);
245
+
246
+ if(is_null($fancy)) {
247
+ $err = sprintf(self::$binary_err, 'fancy', 'true', 'false', $value);
248
+ } else {
249
+ $ret = $fancy;
250
+ }
251
+
252
+ return $ret;
253
+ }
254
+
255
+ private static function sanitizeIds($value, &$err) {
256
+ $defaults = self::getDefaults();
257
+ $ret = $defaults['ids'];
258
+
259
+ if(false === self::toBool($value)) {
260
+ $ret = false;
261
+ } else {
262
+ $value = trim($value);
263
+ $ids = $value ? explode(',', $value) : array();
264
+ $bad = array_filter($ids, array(__CLASS__, 'negativeInt'));
265
+
266
+ if(!empty($bad)) {
267
+ $err = _n('The following ID is invalid: ',
268
+ 'The following IDs are invalid: ',
269
+ count($bad), 'document-gallery') . implode(', ', $bad);
270
+ } else {
271
+ $ret = $ids;
272
+ }
273
+ }
274
+
275
+ return $ret;
276
+ }
277
+
278
+ private static function sanitizeImages($value, &$err) {
279
+ $defaults = self::getDefaults();
280
+ $ret = $defaults['images'];
281
+
282
+ $images = self::toBool($value);
283
+
284
+ if(is_null($images)) {
285
+ $err = sprintf(self::$binary_err, 'images', 'true', 'false', $value);
286
+ } else {
287
+ $ret = $images;
288
+ }
289
+
290
+ return $ret;
291
+ }
292
+
293
+ private static function sanitizeLocalpost($value, &$err) {
294
+ $defaults = self::getDefaults();
295
+ $ret = $defaults['localpost'];
296
+
297
+ $localpost = self::toBool($value);
298
+
299
+ if(is_null($localpost)) {
300
+ $err = sprintf(self::$binary_err, 'localpost', 'true', 'false', $value);
301
+ } else {
302
+ $ret = $localpost;
303
+ }
304
+
305
+ return $ret;
306
+ }
307
+
308
+ private static function sanitizeOrder($value, &$err) {
309
+ $defaults = self::getDefaults();
310
+ $ret = $defaults['order'];
311
+
312
+ $order = strtoupper($value);
313
+ if(!in_array($order, self::getOrderOptions())) {
314
+ $err = sprintf(self::$binary_err, 'order', 'ASC', 'DEC', $value);
315
+ } else {
316
+ $ret = $order;
317
+ }
318
+
319
+ return $ret;
320
+ }
321
+
322
+ public static function getOrderOptions() {
323
+ return array('ASC', 'DEC');
324
+ }
325
+
326
+ private static function sanitizeOrderby($value, &$err) {
327
+ $defaults = self::getDefaults();
328
+ $ret = $defaults['orderby'];
329
+
330
+ $orderby = 'ID' === strtoupper($value) ? 'ID' : strtolower($value);
331
+ if (!in_array($orderby, self::getOrderbyOptions())) {
332
+ $err = sprintf(
333
+ __('The orderby value entered, "%s," is not valid.', 'document-gallery'),
334
+ $value);
335
+ } else {
336
+ $ret = $orderby;
337
+ }
338
+
339
+ return $ret;
340
+ }
341
+
342
+ public static function getOrderbyOptions() {
343
+ return array('author', 'comment_count', 'date', 'ID',
344
+ 'menu_order', 'modified', 'name', 'none',
345
+ 'parent', 'post__in', 'rand', 'title');
346
+ }
347
+
348
+ private static function sanitizeRelation($value, &$err) {
349
+ $defaults = self::getDefaults();
350
+ $ret = $defaults['relation'];
351
+
352
+ $relation = strtoupper($value);
353
+ if(!in_array($relation, self::getRelationOptions())) {
354
+ $err = sprintf(self::$binary_err, 'relation', 'AND', 'OR', $value);
355
+ } else {
356
+ $ret = $relation;
357
+ }
358
+
359
+ return $ret;
360
+ }
361
+
362
+ public static function getRelationOptions() {
363
+ return array('AND', 'OR');
364
+ }
365
+
366
+ /**
367
+ * Gets all valid Documents based on the attributes passed by the user.
368
+ * @return array Contains all documents matching the query.
369
+ * @throws InvalidArgumentException Thrown when $this->errs is not empty.
370
+ */
371
+ private function getDocuments() {
372
+ $mime_types = array('application', 'video', 'text', 'audio');
373
+ if ($this->atts['images']) {
374
+ $mime_types[] = 'image';
375
+ }
376
+
377
+ $query = array(
378
+ 'numberposts' => -1,
379
+ 'orderby' => $this->atts['orderby'],
380
+ 'order' => $this->atts['order'],
381
+ 'post_status' => 'any',
382
+ 'post_type' => 'attachment',
383
+ 'post_mime_type' => implode(',', $mime_types));
384
+
385
+ $query['post_parent'] =
386
+ $this->atts['localpost']
387
+ && ($post = get_post()) ? $post->ID : '';
388
+
389
+ $this->setTaxa($query);
390
+
391
+ if(!empty($this->errs)) {
392
+ throw new InvalidArgumentException();
393
+ }
394
+
395
+ return (false !== $this->atts['ids'])
396
+ ? $this->getAttachmentsByIds()
397
+ : get_posts($query);
398
+ }
399
+
400
+ /**
401
+ * Function loops through all attributes passed that did not match
402
+ * self::$defaults. If they are the name of a taxonomy, they are plugged
403
+ * into the query, otherwise $this->errs is appended with an error string.
404
+ * @global string $wp_version Determines which tax query to use.
405
+ * @param array $query Query to insert tax query into.
406
+ */
407
+ private function setTaxa(&$query) {
408
+ if(!empty($this->taxa)) {
409
+ global $wp_version;
410
+ $taxa = array();
411
+
412
+ // use preferred tax_query if supported
413
+ if (version_compare($wp_version, '3.1', '>=')) {
414
+ // only include relation if we have multiple taxa
415
+ if(count($this->taxa) > 1) {
416
+ $taxa['relation'] = $this->atts['relation'];
417
+ }
418
+
419
+ foreach ($this->taxa as $taxon => $terms) {
420
+ $terms = $this->getTermIdsByNames($taxon, explode(',', $terms));
421
+
422
+ $taxa[] = array(
423
+ 'taxonomy' => $taxon,
424
+ 'field' => 'id',
425
+ 'terms' => $terms
426
+ );
427
+ }
428
+
429
+ // create nested structure
430
+ $query['tax_query'] = $taxa;
431
+ } elseif (version_compare($wp_version, '2.3', '>=')) {
432
+ // fallback to deprecated {tax_name} => {term_slug} construct
433
+ foreach ($this->taxa as $taxon => $terms) {
434
+ $taxa[$taxon] = ($taxon == 'category')
435
+ ? implode(',', $this->getTermIdsByNames($taxon, explode(',', $terms)))
436
+ : implode(',', $this->getTermSlugsByNames($taxon, explode(',', $terms)));
437
+ }
438
+
439
+ $query = array_merge($taxa, $query);
440
+ } else {
441
+ // WP < 2.3 not supported for category/custom taxa
442
+ $this->errs[] = __('The following attributes are invalid: ', 'document-gallery') .
443
+ implode(', ', array_keys($this->taxa));
444
+ }
445
+ }
446
+ }
447
+
448
+ /*==========================================================================
449
+ * HELPER FUNCTIONS
450
+ *=========================================================================*/
451
+
452
+ /**
453
+ * Returns an array of term ids when provided with a list of term names.
454
+ * Also appends an entry onto $errs if any invalid names are found.
455
+ * @param string $taxon
456
+ * @param array $term_names
457
+ * @return array
458
+ */
459
+ private function getTermIdsByNames($taxon, $term_names) {
460
+ return $this->getTermXByNames('term_id', $taxon, $term_names);
461
+ }
462
+
463
+ /**
464
+ * Returns an array of term slugs when provided with a list of term names.
465
+ * Also appends an entry onto $errs if any invalid names are found.
466
+ * @param string $taxon
467
+ * @param array $term_names
468
+ * @return array
469
+ */
470
+ private function getTermSlugsByNames($taxon, $term_names) {
471
+ return $this->getTermXByNames('slug', $taxon, $term_names);
472
+ }
473
+
474
+ /**
475
+ * (WP >= 2.3) Returns a list of x, where x may be any of the fields within a
476
+ * term object, when provided with a list of term names (not slugs).
477
+ * (http://codex.wordpress.org/Function_Reference/get_term_by#Return_Values)
478
+ *
479
+ * Also appends an entry onto $errs if any invalid names are found.
480
+ * @param string $x
481
+ * @param string $taxon
482
+ * @param array $term_names
483
+ * @return array
484
+ */
485
+ private function getTermXByNames($x, $taxon, $term_names) {
486
+ $ret = array();
487
+
488
+ foreach ($term_names as $name) {
489
+ if (($term = get_term_by('name', $name, $taxon))) {
490
+ $ret[] = $term->{$x};
491
+ } else {
492
+ $this->errs[] = sprintf(__('%s is not a valid term name in %s.',
493
+ 'document-gallery'), $name, $taxon);
494
+ }
495
+ }
496
+
497
+ return $ret;
498
+ }
499
+
500
+ /**
501
+ * Given a list of IDs, all attachments represented by these IDs are returned.
502
+ * @return array post objects
503
+ */
504
+ private function getAttachmentsByIds() {
505
+ $args = array(
506
+ 'post_type' => 'attachment',
507
+ 'post_status' => 'inherit',
508
+ 'post_per_page' => -1,
509
+ 'post__in' => $this->atts['ids']
510
+ );
511
+
512
+ return count($args['post__in']) ? get_posts($args) : array();
513
+ }
514
+
515
+ /**
516
+ * Function returns false for positive ints, true otherwise.
517
+ * @param string $var could be anything.
518
+ * @return boolean indicating whether $var is not a positive int.
519
+ */
520
+ private static function negativeInt($var) {
521
+ return !is_numeric($var) // isn't numeric
522
+ || (int)$var != $var // isn't int
523
+ || (int)$var < 0; // isn't positive
524
+ }
525
+
526
+ private static function toBool($val) {
527
+ if (is_bool($val)) {
528
+ return $val;
529
+ }
530
+
531
+ if (is_string($val)) {
532
+ $val = strtolower($val);
533
+ if ('true' === $val || '1' === $val) {
534
+ return true;
535
+ }
536
+
537
+ if ('false' === $val || '0' === $val) {
538
+ return false;
539
+ }
540
+ }
541
+
542
+ if (is_null($val)) {
543
+ return false;
544
+ }
545
+
546
+ return null;
547
+ }
548
+
549
+ /*==========================================================================
550
+ * OUTPUT HTML STRING
551
+ *=========================================================================*/
552
+
553
+ /**
554
+ * Returns HTML representing this Gallery.
555
+ * @return string
556
+ */
557
+ public function __toString() {
558
+ if(!empty($this->errs)) {
559
+ return '<p>' . implode('</p><p>', $this->errs) . '</p>';
560
+ }
561
+
562
+ if(empty($this->docs)) {
563
+ return self::$no_docs;
564
+ }
565
+
566
+ $core = '';
567
+ $classes = array('document-icon-wrapper');
568
+ if($this->useDescriptions()) {
569
+ $classes[] = 'descriptions';
570
+ }
571
+
572
+ $icon_wrapper = sprintf(self::$icon_wrapper, implode(' ', $classes), '%s');
573
+
574
+ if($this->useDescriptions()) {
575
+ foreach($this->docs as $doc) {
576
+ $core .= sprintf($icon_wrapper, $doc);
577
+ }
578
+ } else {
579
+ for($i = 0; $i < count($this->docs); $i+=4) {
580
+ $row = '';
581
+
582
+ $min = min($i+4, count($this->docs));
583
+ for($x = $i; $x < $min; $x++) {
584
+ $row .= $this->docs[$x];
585
+ }
586
+
587
+ $core .= sprintf($icon_wrapper, $row);
588
+ }
589
+ }
590
+
591
+ return self::$comment . $core;
592
+ }
593
+ }
594
+
595
+ ?>
inc/class-setup.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined('WPINC') OR exit;
3
+
4
+ /**
5
+ * Holds functions that handle DG setup / uninstallation.
6
+ *
7
+ * @author drossiter
8
+ */
9
+ class DG_Setup {
10
+
11
+ /**
12
+ * @return array Contains default options for DG.
13
+ */
14
+ public static function getDefaultOptions() {
15
+ return array(
16
+ 'thumber' => array(
17
+ 'thumbs' => array(),
18
+ 'gs' => DG_Thumber::getGhostscriptExecutable(),
19
+ 'active' => DG_Thumber::getDefaultThumbers(),
20
+ 'width' => 200,
21
+ 'height' => 200
22
+ ),
23
+ 'gallery' => array(
24
+ 'defaults' => array(
25
+ // default: link directly to file (true to link to attachment pg)
26
+ 'attachment_pg' => false,
27
+ 'descriptions' => false,
28
+ // include thumbnail of actual document in gallery display
29
+ 'fancy' => true,
30
+ // comma-separated list of attachment ids
31
+ 'ids' => false,
32
+ // if true, all images attached to current page will be included also
33
+ 'images' => false,
34
+ 'localpost' => true,
35
+ 'order' => 'ASC',
36
+ 'orderby' => 'menu_order',
37
+ // only relevant if tax_query used (WP >= 3.1)
38
+ 'relation' => 'AND'
39
+ )
40
+ ),
41
+ 'css' => array('version' => 0, 'text' => ''),
42
+ 'version' => DocumentGallery::version(true)
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Runs when DG is activated.
48
+ */
49
+ public static function activation() {
50
+ $options = get_option(DG_OPTION_NAME, null);
51
+ if (is_null($options)) {
52
+ // first installation
53
+ add_option(DG_OPTION_NAME, self::getDefaultOptions());
54
+ } else if (DocumentGallery::version(true) !== $options['version']) {
55
+ // update version number
56
+ $options['version'] = DocumentGallery::version(true);
57
+ update_option(DG_OPTION_NAME, $options);
58
+ if ('' !== $options['css']['text']) {
59
+ DocumentGallery::updateUserGalleryStyle($options['css']['text']);
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Runs when DG is uninstalled.
66
+ */
67
+ public static function uninstall() {
68
+ if (!current_user_can('activate_plugins')) return;
69
+ check_admin_referer('bulk-plugins');
70
+
71
+ $options = DG_Thumber::getOptions();
72
+
73
+ foreach ($options['thumbs'] as $val) {
74
+ if (false !== $val) {
75
+ @unlink($val['thumb_path']);
76
+ }
77
+ }
78
+
79
+ delete_option(DG_OPTION_NAME);
80
+ }
81
+
82
+ /**
83
+ * Blocks instantiation. All functions are static.
84
+ */
85
+ private function __construct() {
86
+
87
+ }
88
+ }
89
+
90
+ ?>
inc/class-thumber.php ADDED
@@ -0,0 +1,788 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined('WPINC') OR exit;
3
+
4
+ /**
5
+ * Thumber wraps the functionality required to
6
+ * generate thumbnails for arbitrary documents.
7
+ *
8
+ * @author drossiter
9
+ */
10
+ class DG_Thumber {
11
+
12
+ /**
13
+ * Returns the default mapping of thumber slug to whether it is active or not.
14
+ * @return array
15
+ */
16
+ public static function getDefaultThumbers() {
17
+ $gs_active = (bool)self::getGhostscriptExecutable();
18
+ $imagick_active = self::isImagickAvailable();
19
+
20
+ return array('av' => true, 'gs' => $gs_active,
21
+ 'imagick' => $imagick_active, 'google' => false);
22
+ }
23
+
24
+ /**
25
+ * Wraps generation of thumbnails for various attachment filetypes.
26
+ *
27
+ * @param int $ID Document ID
28
+ * @param int $pg Page number to get thumb from.
29
+ * @return str URL to the thumbnail.
30
+ */
31
+ public static function getThumbnail($ID, $pg = 1) {
32
+ static $timeout = false;
33
+ if (false === $timeout) {
34
+ $timeout = time();
35
+ }
36
+
37
+ $options = self::getOptions();
38
+
39
+ // if we haven't saved a thumb, generate one
40
+ if (!isset($options['thumbs'][$ID])) {
41
+ // prevent page timing out -- generate for no more than 30 sec
42
+ if ((time() - $timeout) > 30) {
43
+ return self::getDefaultThumbnail($ID, $pg);
44
+ }
45
+
46
+ // do the processing
47
+ $file = get_attached_file($ID);
48
+
49
+ foreach (self::getThumbers() as $ext_preg => $thumber) {
50
+ $ext_preg = '!\.(' . $ext_preg . ')$!i';
51
+
52
+ if (preg_match($ext_preg, $file)
53
+ && ($thumb = self::getThumbnailTemplate($thumber, $ID, $pg))) {
54
+ $options['thumbs'][$ID] = array(
55
+ 'created_timestamp' => time(),
56
+ 'thumb_url' => $thumb['url'],
57
+ 'thumb_path' => $thumb['path'],
58
+ 'thumber' => $thumber
59
+ );
60
+ self::setOptions($options);
61
+ break;
62
+ }
63
+ }
64
+ }
65
+
66
+ if (!isset($options['thumbs'][$ID]) || false === $options['thumbs'][$ID]) {
67
+ if (!isset($options['thumbs'][$ID])) {
68
+ $options['thumbs'][$ID] = false;
69
+ self::setOptions($options);
70
+ }
71
+
72
+ // fallback to default thumb for attachment type
73
+ $url = self::getDefaultThumbnail($ID, $pg);
74
+ } else {
75
+ // use generated thumbnail
76
+ $url = $options['thumbs'][$ID]['thumb_url'];
77
+ }
78
+
79
+ return $url;
80
+ }
81
+
82
+ /*==========================================================================
83
+ * AUDIO VIDEO THUMBNAILS
84
+ *=========================================================================*/
85
+
86
+ /**
87
+ * Uses wp_read_video_metadata() and wp_read_audio_metadata() to retrieve
88
+ * an embedded image to use as a thumbnail.
89
+ *
90
+ * NOTE: Caller must verify that WP version >= 3.6.
91
+ *
92
+ * @param str $ID The attachment ID to retrieve thumbnail from.
93
+ * @param int $pg Unused.
94
+ * @return bool|str False on failure, URL to thumb on success.
95
+ */
96
+ public static function getAudioVideoThumbnail($ID, $pg = 1) {
97
+ if(!file_exists(WP_ADMIN_DIR . '/includes/media.php')) {
98
+ return false;
99
+ }
100
+
101
+ include_once WP_ADMIN_DIR . '/includes/media.php';
102
+
103
+ $attachment = get_post($ID);
104
+ $doc_path = get_attached_file($ID);
105
+
106
+ if (preg_match('#^video/#', get_post_mime_type($attachment))) {
107
+ $metadata = wp_read_video_metadata($doc_path);
108
+ }
109
+ elseif (preg_match('#^audio/#', get_post_mime_type($attachment))) {
110
+ $metadata = wp_read_audio_metadata($doc_path);
111
+ }
112
+
113
+ // unsupported mime type || no embedded image present
114
+ if(!isset($metadata) || empty($metadata['image']['data'])) {
115
+ return false;
116
+ }
117
+
118
+ $ext = 'jpg';
119
+ switch ($metadata['image']['mime']) {
120
+ case 'image/gif':
121
+ $ext = 'gif';
122
+ break;
123
+ case 'image/png':
124
+ $ext = 'png';
125
+ break;
126
+ }
127
+
128
+ $temp_file = self::getTempFile($ext);
129
+
130
+ if (!$fp = @fopen($temp_file, 'wb')) {
131
+ self::writeLog(__('Could not open file: ', 'document-gallery') . $temp_file);
132
+ return false;
133
+ }
134
+
135
+ if (!@fwrite($fp, $metadata['image']['data'])) {
136
+ self::writeLog(__('Could not write file: ', 'document-gallery') . $temp_file);
137
+ fclose($fp);
138
+ return false;
139
+ }
140
+
141
+ fclose($fp);
142
+
143
+ return $temp_file;
144
+ }
145
+
146
+ /**
147
+ * @return array All extensions supported by WP Audio Video Media metadata.
148
+ */
149
+ private static function getAudioVideoExts() {
150
+ return array_merge(wp_get_audio_extensions(), wp_get_video_extensions());
151
+ }
152
+
153
+ /*==========================================================================
154
+ * IMAGICK THUMBNAILS
155
+ *=========================================================================*/
156
+
157
+ /**
158
+ * Uses WP_Image_Editor_Imagick to generate thumbnails.
159
+ *
160
+ * @param int $ID The attachment ID to retrieve thumbnail from.
161
+ * @param int $pg The page to get the thumbnail of.
162
+ * @return bool|str False on failure, URL to thumb on success.
163
+ */
164
+ public static function getImagickThumbnail($ID, $pg = 1) {
165
+ include_once WP_INCLUDE_DIR . '/class-wp-image-editor.php';
166
+ include_once WP_INCLUDE_DIR . '/class-wp-image-editor-imagick.php';
167
+
168
+ $doc_path = get_attached_file($ID) . '[' . $pg - 1 . ']';
169
+
170
+ $img = new WP_Image_Editor_Imagick($doc_path);
171
+ $err = $img->load();
172
+ if(is_wp_error($err)) {
173
+ self::writeLog(
174
+ __('Failed to open file in Imagick: ', 'document-gallery') .
175
+ $err->get_error_message());
176
+ return false;
177
+ }
178
+
179
+ $temp_file = self::getTempFile();
180
+
181
+ $err = $img->save($temp_file, 'image/png');
182
+ if (is_wp_error($err)) {
183
+ self::writeLog(
184
+ __('Failed to save image in Imagick: ', 'document-gallery') .
185
+ $err->get_error_message());
186
+ return false;
187
+ }
188
+
189
+ return $temp_file;
190
+ }
191
+
192
+ /**
193
+ * @return bool Whether WP_Image_Editor_Imagick can be used on this system.
194
+ */
195
+ public static function isImagickAvailable() {
196
+ static $ret = null;
197
+
198
+ if (is_null($ret)) {
199
+ $ret = false;
200
+ if (file_exists(WP_INCLUDE_DIR . '/class-wp-image-editor-imagick.php')) {
201
+ include_once WP_INCLUDE_DIR . '/class-wp-image-editor.php';
202
+ include_once WP_INCLUDE_DIR . '/class-wp-image-editor-imagick.php';
203
+ $ret = WP_Image_Editor_Imagick::test();
204
+ }
205
+ }
206
+
207
+ return $ret;
208
+ }
209
+
210
+ /*==========================================================================
211
+ * GHOSTSCRIPT THUMBNAILS
212
+ *=========================================================================*/
213
+
214
+ /**
215
+ * Get thumbnail for document with given ID using Ghostscript. Imagick could
216
+ * also handle this, but is *much* slower.
217
+ *
218
+ * @param int $ID The attachment ID to retrieve thumbnail from.
219
+ * @param int $pg The page number to make thumbnail of -- index starts at 1.
220
+ * @return bool|str False on failure, URL to thumb on success.
221
+ */
222
+ public static function getGhostscriptThumbnail($ID, $pg = 1) {
223
+ static $gs = null;
224
+
225
+ if (is_null($gs)) {
226
+ $options = self::getOptions();
227
+ $gs = $options['gs'];
228
+ if (false !== $gs) {
229
+ $gs = "\"$gs\" -sDEVICE=png16m -dFirstPage=%d -dLastPage=%d"
230
+ . ' -dBATCH -dNOPAUSE -dPDFFitPage -sOutputFile=%s %s';
231
+ }
232
+ }
233
+
234
+ if (false === $gs) {
235
+ return false;
236
+ }
237
+
238
+ $doc_path = get_attached_file($ID);
239
+ $temp_path = self::getTempFile();
240
+
241
+ exec(sprintf($gs, $pg, $pg, $temp_path, $doc_path), $out, $ret);
242
+
243
+ if ($ret != 0) {
244
+ self::writeLog(__('Ghostscript failed: ', 'document-gallery') . print_r($out));
245
+ @unlink($temp_path);
246
+ return false;
247
+ }
248
+
249
+ return $temp_path;
250
+ }
251
+
252
+ /**
253
+ * @return array All extensions supported by Ghostscript.
254
+ */
255
+ private static function getGhostscriptExts() {
256
+ return array('pdf');
257
+ }
258
+
259
+ /**
260
+ * Checks whether we may call gs through exec().
261
+ *
262
+ * @return bool|str If available, returns exe path. False otherwise.
263
+ */
264
+ public static function getGhostscriptExecutable() {
265
+ static $executable = null;
266
+
267
+ if (is_null($executable)) {
268
+ // we must be able to exec()
269
+ $executable = self::isExecAvailable();
270
+ if (!$executable) return $executable;
271
+
272
+ // find on Windows system
273
+ if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
274
+ // look for environment variable
275
+ $executable = getenv('GSC');
276
+ if($executable) return $executable;
277
+
278
+ // hope GS in the path
279
+ $executable = exec('where gswin*c.exe');
280
+ if(!empty($executable)) return $executable;
281
+
282
+ // look directly in filesystem
283
+ // 64- or 32-bit binary
284
+ $executable = exec('dir /o:n/s/b "C:\Program Files\gs\*gswin*c.exe"');
285
+ if (!empty($executable)) {
286
+ return $executable;
287
+ }
288
+
289
+ // 32-bit binary on 64-bit OS
290
+ $executable = exec('dir /o:n/s/b "C:\Program Files (x86)\gs\*gswin32c.exe"');
291
+ $executable = empty($executable) ? false : $executable;
292
+ return $executable;
293
+ }
294
+
295
+ // this is why I use Linux...
296
+ $executable = exec('which gs');
297
+ $executable = empty($executable) ? false : $executable;
298
+ return $executable;
299
+ }
300
+
301
+ return $executable;
302
+ }
303
+
304
+ /*==========================================================================
305
+ * GOOGLE DRIVE VIEWER THUMBNAILS
306
+ *=========================================================================*/
307
+
308
+ /**
309
+ * Get thumbnail for document with given ID from Google Drive Viewer.
310
+ *
311
+ * NOTE: Caller must verify that extension is supported.
312
+ *
313
+ * @param str $ID The attachment ID to retrieve thumbnail for.
314
+ * @param int $pg The page number to make thumbnail of -- index starts at 1.
315
+ * @return bool|str False on failure, URL to thumb on success.
316
+ */
317
+ public static function getGoogleDriveThumbnail($ID_URL, $pg = 1) {
318
+ // User agent for Lynx 2.8.7rel.2 -- Why? Because I can.
319
+ static $user_agent = 'Lynx/2.8.7rel.2 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.0.0a';
320
+ static $timeout = 60;
321
+
322
+ $google_viewer = 'https://docs.google.com/viewer?url=%s&a=bi&pagenumber=%d&w=%d';
323
+ $doc_url = wp_get_attachment_url($ID_URL);
324
+ if (!$doc_url) {
325
+ return false;
326
+ }
327
+
328
+ $temp_file = self::getTempFile();
329
+
330
+ // args for use in HTTP request
331
+ $args = array(
332
+ 'timeout' => $timeout, // these requests can take a LONG time
333
+ 'redirection' => 5,
334
+ 'httpversion' => '1.0',
335
+ 'user-agent' => $user_agent,
336
+ 'blocking' => true,
337
+ 'headers' => array(),
338
+ 'cookies' => array(),
339
+ 'body' => null,
340
+ 'compress' => false,
341
+ 'decompress' => true,
342
+ 'sslverify' => true,
343
+ 'stream' => true,
344
+ 'filename' => $temp_file
345
+ );
346
+
347
+ // prevent PHP timeout before HTTP completes
348
+ set_time_limit($timeout);
349
+
350
+ $options = self::getOptions();
351
+ $google_viewer = sprintf($google_viewer, urlencode($doc_url), (int)$pg, $options['width']);
352
+
353
+ // get thumbnail from Google Drive Viewer & check for error on return
354
+ $response = wp_remote_get($google_viewer, $args);
355
+
356
+ if (is_wp_error($response) || !preg_match('/[23][0-9]{2}/', $response['response']['code'])) {
357
+ self::writeLog(__('Failed to retrieve thumbnail from Google: ', 'document-gallery') .
358
+ (is_wp_error($response)
359
+ ? $response->get_error_message()
360
+ : $response['response']['message']));
361
+
362
+ @unlink($temp_file);
363
+ return false;
364
+ }
365
+
366
+ return $temp_file;
367
+ }
368
+
369
+ /**
370
+ * @return array All extensions supported by Google Drive Viewer.
371
+ */
372
+ private static function getGoogleDriveExts() {
373
+ return array(
374
+ 'tiff', 'bmp', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
375
+ 'pdf', 'pages', 'ai', 'psd', 'dxf', 'svg', 'eps', 'ps', 'ttf'
376
+ );
377
+ }
378
+
379
+ /**
380
+ * TODO: Currently always returns true.
381
+ * @return bool Whether Google Drive can access files on this system.
382
+ */
383
+ public static function isGoogleDriveAvailable() {
384
+ return true;
385
+ }
386
+
387
+ /*==========================================================================
388
+ * DEFAULT THUMBNAILS
389
+ *=========================================================================*/
390
+
391
+ /**
392
+ * Get thumbnail for document with given ID from default images.
393
+ *
394
+ * @param str $ID The attachment ID to retrieve thumbnail from.
395
+ * @param int $pg Unused.
396
+ * @return str URL to thumbnail.
397
+ */
398
+ public static function getDefaultThumbnail($ID, $pg = 1) {
399
+ $icon_url = DG_URL . 'assets/icons/';
400
+
401
+ $url = wp_get_attachment_url($ID);
402
+ $ext = self::getExt($url);
403
+
404
+ // handle images
405
+ if (wp_attachment_is_image($ID) &&
406
+ ($icon = wp_get_attachment_image_src($ID, 'thumbnail', false))) {
407
+ $icon = $icon[0];
408
+ }
409
+ // default extension icon
410
+ elseif ($name = self::getDefaultIcon($ext)) {
411
+ $icon = $icon_url . $name;
412
+ }
413
+ // fallback to standard WP icons
414
+ elseif ($icon = wp_get_attachment_image_src($ID, null, true)) {
415
+ $icon = $icon[0];
416
+ }
417
+ // everything failed. This is bad...
418
+ else {
419
+ $icon = $icon_url . 'missing.png';
420
+ }
421
+
422
+ return $icon;
423
+ }
424
+
425
+ /**
426
+ * Returns the name of the image to represent the filetype given.
427
+ *
428
+ * @param str $ext
429
+ * @return str
430
+ */
431
+ private static function getDefaultIcon($ext) {
432
+ // Maps file ext to default image name.
433
+ static $exts = array(
434
+ // Most Common First
435
+ 'pdf' => 'pdf.png',
436
+
437
+ // MS Office
438
+ 'doc|docx|docm|dotx|dotm' => 'msdoc.png',
439
+ 'ppt|pot|pps|pptx|pptm|ppsx|ppsm|potx|potm|ppam|sldx|sldm' => 'msppt.png',
440
+ 'xla|xls|xlt|xlw|xlsx|xlsm|xlsb|xltx|xltm|xlam' => 'msxls.png',
441
+ 'mdb' => 'msaccess.png',
442
+
443
+ // iWork
444
+ 'key' => 'key.png',
445
+ 'numbers' => 'numbers.png',
446
+ 'pages' => 'pages.png',
447
+
448
+ // Images
449
+ 'jpg|jpeg|jpe|gif|png|bmp|tif|tiff|ico' => 'image.png',
450
+
451
+ // Video formats
452
+ 'asf|asx|wmv|wmx|wm|avi|divx|flv|mov' => 'video.png',
453
+ 'qt|mpeg|mpg|mpe|mp4|m4v|ogv|webm|mkv' => 'video.png',
454
+
455
+ // Audio formats
456
+ 'mp3|m4a|m4b|ra|ram|wav|ogg|oga|wma|wax|mka' => 'audio.png',
457
+ 'midi|mid' => 'midi.png',
458
+
459
+ // Text formats
460
+ 'txt|tsv|csv' => 'text.png',
461
+ 'rtx' => 'rtx.png',
462
+ 'rtf' => 'rtf.png',
463
+ 'ics' => 'ics.png',
464
+ 'wp|wpd' => 'wordperfect.png',
465
+
466
+ // Programming
467
+ 'html|htm' => 'html.png',
468
+ 'css' => 'css.png',
469
+ 'js' => 'javascript.png',
470
+ 'class' => 'java.png',
471
+ 'asc' => 'asc.png',
472
+ 'c' => 'c.png',
473
+ 'cc|cpp' => 'cpp.png',
474
+ 'h' => 'h.png',
475
+
476
+ // Msc application formats
477
+ 'zip|tar|gzip|gz|bz2|tgz|7z|rar' => 'compressed.png',
478
+ 'exe' => 'exec.png',
479
+ 'swf' => 'shockwave.png',
480
+
481
+ // OpenDocument formats
482
+ 'odt' => 'opendocument-text.png',
483
+ 'odp' => 'opendocument-presentation.png',
484
+ 'ods' => 'opendocument-spreadsheet.png',
485
+ 'odg' => 'opendocument-graphics.png',
486
+ 'odb' => 'opendocument-database.png',
487
+ 'odf' => 'opendocument-formula.png'
488
+ );
489
+
490
+ foreach ($exts as $ext_preg => $icon) {
491
+ $ext_preg = '!(' . $ext_preg . ')$!i';
492
+ if (preg_match($ext_preg, $ext)) {
493
+ return $icon;
494
+ }
495
+ }
496
+
497
+ return false;
498
+ }
499
+
500
+ /*==========================================================================
501
+ * GENERAL THUMBNAIL HELPER FUNCTIONS
502
+ *=========================================================================*/
503
+
504
+ /**
505
+ * @return array WP_Post objects for each attachment that has been processed.
506
+ */
507
+ public static function getThumbed() {
508
+ $options = self::getOptions();
509
+ $args = array(
510
+ 'post_type' => 'attachment',
511
+ 'post_status' => 'inherit',
512
+ 'post_per_page' => -1,
513
+ 'post__in' => array_keys($options['thumbs'])
514
+ );
515
+
516
+ return count($args['post__in']) ? get_posts($args) : array();
517
+ }
518
+
519
+ /**
520
+ * Key: Attachment ID
521
+ * Val: array
522
+ * + created_timestamp - When the thumbnail was generated.
523
+ * + thumb_path - System path to thumbnail image.
524
+ * + thumb_url - URL pointing to the thumbnail for this document.
525
+ * + thumber - Generator used to create thumb OR false if failed to gen.
526
+ * @return array Thumber options from DB.
527
+ */
528
+ public static function getOptions() {
529
+ global $dg_options;
530
+ return $dg_options['thumber'];
531
+ }
532
+
533
+ /**
534
+ * Key: Attachment ID
535
+ * Val: array
536
+ * + created_timestamp - When the thumbnail was generated.
537
+ * + thumb_path - System path to thumbnail image.
538
+ * + thumb_url - URL pointing to the thumbnail for this document.
539
+ * + thumber - Generator used to create thumb OR false if failed to gen.
540
+ * @param array $options Thumber options to store in DB
541
+ */
542
+ private static function setOptions($options) {
543
+ global $dg_options;
544
+ $dg_options['thumber'] = $options;
545
+ update_option(DG_OPTION_NAME, $dg_options);
546
+ }
547
+
548
+ /**
549
+ * @filter dg_thumbers Allows developers to filter the Thumbers used
550
+ * for specific filetypes. Index is the regex to match file extensions
551
+ * supported and the value is anything that can be accepted by call_user_func().
552
+ * The function must take two parameters, 1st is the int ID of the attachment
553
+ * to get a thumbnail for, 2nd is the page to take a thumbnail of
554
+ * (may not be relevant for some filetypes).
555
+ *
556
+ * @return array
557
+ */
558
+ private static function getThumbers() {
559
+ static $thumbers = false;
560
+
561
+ if (false === $thumbers) {
562
+ global $wp_version;
563
+ $options = self::getOptions();
564
+ $active = $options['active'];
565
+ $thumbers = array();
566
+
567
+ // Audio/Video embedded images
568
+ if ($active['av'] && version_compare($wp_version, '3.6', '>=')) {
569
+ $exts = implode('|', self::getAudioVideoExts());
570
+ $thumbers[$exts] = array(__CLASS__, 'getAudioVideoThumbnail');
571
+ }
572
+
573
+ // Ghostscript
574
+ if ($active['gs'] && false !== self::getGhostscriptExecutable()) {
575
+ $exts = implode('|', self::getGhostscriptExts());
576
+ $thumbers[$exts] = array(__CLASS__, 'getGhostscriptThumbnail');
577
+ }
578
+
579
+ // Imagick
580
+ if ($active['imagick'] && self::isImagickAvailable()) {
581
+ include_once WP_INCLUDE_DIR . '/class-wp-image-editor.php';
582
+ include_once WP_INCLUDE_DIR . '/class-wp-image-editor-imagick.php';
583
+ try {
584
+ $exts = @Imagick::queryFormats();
585
+ if($exts) {
586
+ $exts = implode('|', $exts);
587
+ $thumbers[$exts] = array(__CLASS__, 'getImagickThumbnail');
588
+ }
589
+ }
590
+ catch (Exception $e) {
591
+
592
+ }
593
+ }
594
+
595
+ // Google Drive Viewer
596
+ if ($active['google']) {
597
+ $exts = implode('|', self::getGoogleDriveExts());
598
+ $thumbers[$exts] = array(__CLASS__, 'getGoogleDriveThumbnail');
599
+ }
600
+
601
+ // allow users to filter thumbers used
602
+ $thumbers = apply_filters('dg_thumbers', $thumbers);
603
+
604
+ // strip out anything that can't be called
605
+ $thumbers = array_filter($thumbers, 'is_callable');
606
+ }
607
+
608
+ return $thumbers;
609
+ }
610
+
611
+ /**
612
+ * Template that handles generating a thumbnail.
613
+ *
614
+ * @param callable $generator Takes ID and pg and returns path to temp file or false.
615
+ * @param int $ID ID for the attachment that we need a thumbnail for.
616
+ * @param int $pg Page number of the attachment to get a thumbnail for.
617
+ * @return bool|array Array containing 'url' and 'path' values or false.
618
+ */
619
+ public static function getThumbnailTemplate($generator, $ID, $pg = 1) {
620
+ // delegate thumbnail generation to $generator
621
+ if (false === ($temp_path = call_user_func($generator, $ID, $pg))) {
622
+ return false;
623
+ }
624
+
625
+ // get some useful stuff
626
+ $doc_path = get_attached_file($ID);
627
+ $doc_url = wp_get_attachment_url($ID);
628
+ $dirname = dirname($doc_path);
629
+ $basename = basename($doc_path);
630
+ if (false === ($len = strrpos($basename, '.'))) {
631
+ $len = strlen($basename);
632
+ }
633
+ $extless = substr($basename, 0, $len);
634
+ $ext = self::getExt($temp_path);
635
+
636
+ $thumb_name = self::getUniqueThumbName($dirname, $extless, $ext);
637
+ $thumb_path = $dirname . DIRECTORY_SEPARATOR . $thumb_name;
638
+
639
+ // scale generated image down
640
+ $img = wp_get_image_editor($temp_path);
641
+
642
+ if (is_wp_error($img)) {
643
+ self::writeLog(
644
+ __('Failed to get image editor: ', 'document-gallery') .
645
+ $img->get_error_message());
646
+ return false;
647
+ }
648
+
649
+ $options = self::getOptions();
650
+ $img->resize($options['width'], $options['height'], false);
651
+ $err = $img->save($thumb_path);
652
+
653
+ if (is_wp_error($err)) {
654
+ self::writeLog(
655
+ __('Failed to save image: ', 'document-gallery') .
656
+ $err->get_error_message());
657
+ return false;
658
+ }
659
+
660
+ // do some cleanup
661
+ @unlink($temp_path);
662
+ self::deleteThumbMeta($ID);
663
+
664
+ return array(
665
+ 'path' => $thumb_path,
666
+ 'url' => preg_replace('#'.preg_quote($basename).'$#', $thumb_name, $doc_url));
667
+ }
668
+
669
+ /**
670
+ * Caller should handle removal of the temp file when finished.
671
+ *
672
+ * @staticvar int $count
673
+ * @param str $ext
674
+ */
675
+ private static function getTempFile($ext = 'png') {
676
+ static $base = null;
677
+ static $tmp;
678
+
679
+ if (is_null($base)) {
680
+ $base = md5(time());
681
+ $tmp = untrailingslashit(get_temp_dir());
682
+ }
683
+
684
+ return $tmp . DIRECTORY_SEPARATOR . wp_unique_filename($tmp, "$base.$ext");
685
+ }
686
+
687
+ /**
688
+ * Constructs name for file's thumbnail, ensuring that it does not conflict
689
+ * with any existing file.
690
+ *
691
+ * @param str $dirname Directory where the document is located.
692
+ * @param str $extless Base name, less the extension.
693
+ * @param str $ext The extension of the image to be created.
694
+ * @return str Name unique within the directory given, derived from the basename given.
695
+ */
696
+ private static function getUniqueThumbName($dirname, $extless, $ext = 'png') {
697
+ return wp_unique_filename($dirname, str_replace('.', '-', $extless) . '-thumb.' . $ext);
698
+ }
699
+
700
+ /**
701
+ * Removes the existing thumbnail/document meta for the attachment
702
+ * with $ID, if such a thumbnail exists.
703
+ *
704
+ * @param int $ID
705
+ */
706
+ public static function deleteThumbMeta($ID) {
707
+ $options = self::getOptions();
708
+
709
+ if (isset($options['thumbs'][$ID])) {
710
+ if (false !== $options['thumbs'][$ID]) {
711
+ @unlink($options['thumbs'][$ID]['thumb_path']);
712
+ }
713
+
714
+ unset($options['thumbs'][$ID]);
715
+ self::setOptions($options);
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Checks whether exec() may be used.
721
+ * Source: http://stackoverflow.com/a/12980534/866618
722
+ *
723
+ * @return bool Whether exec() is available.
724
+ */
725
+ public static function isExecAvailable() {
726
+ static $available = null;
727
+
728
+ if (is_null($available)) {
729
+ $available = true;
730
+
731
+ if (ini_get('safe_mode')) {
732
+ $available = false;
733
+ } else {
734
+ $d = ini_get('disable_functions');
735
+ $s = ini_get('suhosin.executor.func.blacklist');
736
+ if ("$d$s") {
737
+ $array = preg_split('/,\s*/', "$d,$s");
738
+ $available = !in_array('exec', $array);
739
+ }
740
+ }
741
+ }
742
+
743
+ return $available;
744
+ }
745
+
746
+ /**
747
+ * Formerly achieved with wp_check_filetype(), but it was only returning
748
+ * valid results if the active user had permission to upload the given filetype.
749
+ *
750
+ * @param str $filename Name of the file to get extension from.
751
+ * @return str|bool Returns the file extension on success, false on failure.
752
+ */
753
+ private static function getExt($filename) {
754
+ foreach (wp_get_mime_types() as $ext_preg => $unused) {
755
+ $ext_preg = '!\.(' . $ext_preg . ')$!i';
756
+ if (preg_match($ext_preg, $filename, $ext_matches)) {
757
+ return $ext_matches[1];
758
+ }
759
+ }
760
+
761
+ return false;
762
+ }
763
+
764
+ /**
765
+ * Appends error log with $entry if WordPress is in debug mode.
766
+ *
767
+ * @param str $entry
768
+ */
769
+ private static function writeLog($entry) {
770
+ if (defined('WP_DEBUG') && WP_DEBUG) {
771
+ $err = 'DG: ' . print_r($entry, true) . PHP_EOL;
772
+ if (defined('ERRORLOGFILE')) {
773
+ error_log($err, 3, ERRORLOGFILE);
774
+ } else {
775
+ error_log($err);
776
+ }
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Blocks instantiation. All functions are static.
782
+ */
783
+ private function __construct() {
784
+
785
+ }
786
+ }
787
+
788
+ ?>
languages/document-gallery.pot ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2014 Document Gallery
2
+ # This file is distributed under the same license as the Document Gallery package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Document Gallery 2.0\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/document-gallery\n"
7
+ "POT-Creation-Date: 2014-03-22 07:07:30+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2014-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: admin/class-admin.php:27
16
+ msgid "Settings"
17
+ msgstr ""
18
+
19
+ #: admin/class-admin.php:37
20
+ msgid "Document Gallery Settings"
21
+ msgstr ""
22
+
23
+ #. #-#-#-#-# plugin.pot (Document Gallery 2.0) #-#-#-#-#
24
+ #. Plugin Name of the plugin/theme
25
+ #: admin/class-admin.php:38
26
+ msgid "Document Gallery"
27
+ msgstr ""
28
+
29
+ #: admin/class-admin.php:67
30
+ msgid "Default Settings"
31
+ msgstr ""
32
+
33
+ #: admin/class-admin.php:71
34
+ msgid "Thumbnail Generation"
35
+ msgstr ""
36
+
37
+ #: admin/class-admin.php:75
38
+ msgid "Custon CSS"
39
+ msgstr ""
40
+
41
+ #: admin/class-admin.php:79
42
+ msgid "Advanced Thumbnail Generation"
43
+ msgstr ""
44
+
45
+ #: admin/class-admin.php:91
46
+ msgid "Link to attachment page rather than to file"
47
+ msgstr ""
48
+
49
+ #: admin/class-admin.php:103
50
+ msgid "Include document descriptions"
51
+ msgstr ""
52
+
53
+ #: admin/class-admin.php:115
54
+ msgid "Use auto-generated document thumbnails"
55
+ msgstr ""
56
+
57
+ #: admin/class-admin.php:127
58
+ msgid "Include image attachments in gallery"
59
+ msgstr ""
60
+
61
+ #: admin/class-admin.php:139
62
+ msgid "Only look for attachments in post where [dg] is used"
63
+ msgstr ""
64
+
65
+ #: admin/class-admin.php:152
66
+ msgid "Ascending or decending sorting of documents"
67
+ msgstr ""
68
+
69
+ #: admin/class-admin.php:165
70
+ msgid "Which field to order documents by"
71
+ msgstr ""
72
+
73
+ #: admin/class-admin.php:178
74
+ msgid ""
75
+ "Whether matched documents must have all taxa_names (AND) or at least one (OR)"
76
+ msgstr ""
77
+
78
+ #: admin/class-admin.php:190
79
+ msgid "Locally generate thumbnails for audio & video files."
80
+ msgstr ""
81
+
82
+ #: admin/class-admin.php:203
83
+ msgid ""
84
+ "Use <a href=\"http://www.ghostscript.com/\" target=\"_blank\">Ghostscript</"
85
+ "a> for faster local PDF processing (compared to Imagick)."
86
+ msgstr ""
87
+
88
+ #: admin/class-admin.php:204
89
+ msgid ""
90
+ "Your server is not configured to run <a href=\"http://www.ghostscript.com/\" "
91
+ "target=\"_blank\">Ghostscript</a>."
92
+ msgstr ""
93
+
94
+ #: admin/class-admin.php:218
95
+ msgid ""
96
+ "Use <a href=\"http://www.php.net/manual/en/book.imagick.php\" target=\"_blank"
97
+ "\">Imagick</a> to handle lots of filetypes locally."
98
+ msgstr ""
99
+
100
+ #: admin/class-admin.php:219
101
+ msgid ""
102
+ "Your server is not configured to run <a href=\"http://www.php.net/manual/en/"
103
+ "book.imagick.php\" target=\"_blank\">Imagick</a>."
104
+ msgstr ""
105
+
106
+ #: admin/class-admin.php:233
107
+ msgid ""
108
+ "Use <a href=\"https://drive.google.com/viewer\" target=\"_blank\">Google "
109
+ "Drive Viewer</a> to generate thumbnails for MS Office files and many other "
110
+ "file types remotely."
111
+ msgstr ""
112
+
113
+ #: admin/class-admin.php:234
114
+ msgid "Your server does not allow remote HTTP access."
115
+ msgstr ""
116
+
117
+ #: admin/class-admin.php:248
118
+ msgid "Successfully auto-detected the location of Ghostscript."
119
+ msgstr ""
120
+
121
+ #: admin/class-admin.php:249
122
+ msgid "Failed to auto-detect the location of Ghostscript."
123
+ msgstr ""
124
+
125
+ #: admin/class-admin.php:257
126
+ msgid ""
127
+ "The following values will be used by default in the shortcode. You can still "
128
+ "manually set each of these values in each individual shortcode."
129
+ msgstr ""
130
+
131
+ #: admin/class-admin.php:264
132
+ msgid "Select which tools to use when generating thumbnails."
133
+ msgstr ""
134
+
135
+ #: admin/class-admin.php:270
136
+ msgid ""
137
+ "Enter custom CSS styling for use with document galleries. To see which ids "
138
+ "and classes you can style, take a look at <a href=\"%s\" target=\"_blank"
139
+ "\">style.css</a>."
140
+ msgstr ""
141
+
142
+ #: admin/class-admin.php:288
143
+ msgid ""
144
+ "Unless you <em>really</em> know what you're doing, you should not touch "
145
+ "these values."
146
+ msgstr ""
147
+
148
+ #: admin/class-admin.php:290
149
+ msgid ""
150
+ "NOTE: <code>exec()</code> is not accessible. Ghostscript will not function."
151
+ msgstr ""
152
+
153
+ #: admin/class-admin.php:387
154
+ msgid "Failed to update CSS file."
155
+ msgstr ""
156
+
157
+ #: admin/class-admin.php:398
158
+ msgid "Invalid Ghostscript path given: "
159
+ msgstr ""
160
+
161
+ #: inc/class-gallery.php:88
162
+ msgid "Generated using Document Gallery. Get yours here: "
163
+ msgstr ""
164
+
165
+ #: inc/class-gallery.php:91
166
+ msgid "No attachments to display. How boring! :("
167
+ msgstr ""
168
+
169
+ #: inc/class-gallery.php:92
170
+ msgid "The %s parameter may only be \"%s\" or \"%s.\" You entered \"%s.\""
171
+ msgstr ""
172
+
173
+ #: inc/class-gallery.php:263
174
+ msgid "The following ID is invalid: "
175
+ msgid_plural "The following IDs are invalid: "
176
+ msgstr[0] ""
177
+ msgstr[1] ""
178
+
179
+ #: inc/class-gallery.php:329
180
+ msgid "The orderby value entered, \"%s,\" is not valid."
181
+ msgstr ""
182
+
183
+ #: inc/class-gallery.php:438
184
+ msgid "The following attributes are invalid: "
185
+ msgstr ""
186
+
187
+ #: inc/class-gallery.php:488
188
+ msgid "%s is not a valid term name in %s."
189
+ msgstr ""
190
+
191
+ #: inc/class-thumber.php:131
192
+ msgid "Could not open file: "
193
+ msgstr ""
194
+
195
+ #: inc/class-thumber.php:136
196
+ msgid "Could not write file: "
197
+ msgstr ""
198
+
199
+ #: inc/class-thumber.php:174
200
+ msgid "Failed to open file in Imagick: "
201
+ msgstr ""
202
+
203
+ #: inc/class-thumber.php:184
204
+ msgid "Failed to save image in Imagick: "
205
+ msgstr ""
206
+
207
+ #: inc/class-thumber.php:244
208
+ msgid "Ghostscript failed: "
209
+ msgstr ""
210
+
211
+ #: inc/class-thumber.php:357
212
+ msgid "Failed to retrieve thumbnail from Google: "
213
+ msgstr ""
214
+
215
+ #: inc/class-thumber.php:644
216
+ msgid "Failed to get image editor: "
217
+ msgstr ""
218
+
219
+ #: inc/class-thumber.php:655
220
+ msgid "Failed to save image: "
221
+ msgstr ""
222
+
223
+ #. Description of the plugin/theme
224
+ msgid ""
225
+ "Display non-images (and images) in gallery format on a page or post with the "
226
+ "[dg] shortcode."
227
+ msgstr ""
228
+
229
+ #. Author of the plugin/theme
230
+ msgid "Dan Rossiter"
231
+ msgstr ""
232
+
233
+ #. Author URI of the plugin/theme
234
+ msgid "http://danrossiter.org/"
235
+ msgstr ""
models/class-document.php DELETED
@@ -1,196 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Description of Document
5
- *
6
- * @author drossiter
7
- */
8
- class DG_Document {
9
- // templates for HTML output
10
- private $doc_icon, $icon_url;
11
- private $img_string = '<img src="%s" title="%s" alt="%s" />';
12
-
13
- // general document data
14
- private $description, $gallery, $ID, $link, $title, $title_attribute;
15
-
16
- // filetype => image mapping
17
- private static $exts = array(
18
- // Most Common First
19
- 'pdf' => 'pdf.png',
20
-
21
- // MS Office
22
- 'doc' => 'msdoc.png',
23
- 'docx' => 'msdoc.png',
24
- 'docm' => 'msdoc.png',
25
- 'dotx' => 'msdoc.png',
26
- 'dotm' => 'msdoc.png',
27
- 'ppt' => 'msppt.png',
28
- 'pot' => 'msppt.png',
29
- 'pps' => 'msppt.png',
30
- 'pptx' => 'msppt.png',
31
- 'pptm' => 'msppt.png',
32
- 'ppsx' => 'msppt.png',
33
- 'ppsm' => 'msppt.png',
34
- 'potx' => 'msppt.png',
35
- 'potm' => 'msppt.png',
36
- 'ppam' => 'msppt.png',
37
- 'sldx' => 'msppt.png',
38
- 'sldm' => 'msppt.png',
39
- 'xla' => 'msxls.png',
40
- 'xls' => 'msxls.png',
41
- 'xlt' => 'msxls.png',
42
- 'xlw' => 'msxls.png',
43
- 'xlsx' => 'msxls.png',
44
- 'xlsm' => 'msxls.png',
45
- 'xlsb' => 'msxls.png',
46
- 'xltx' => 'msxls.png',
47
- 'xltm' => 'msxls.png',
48
- 'xlam' => 'msxls.png',
49
- 'mdb' => 'msaccess.png',
50
-
51
- // Video formats
52
- 'avi' => 'avi.png',
53
- 'divx' => 'divx.png',
54
- 'flv' => 'flv.png',
55
- 'qt' => 'mov.png',
56
- 'mov' => 'mov.png',
57
- 'asf' => 'wmv.png',
58
- 'asx' => 'wmv.png',
59
- 'wax' => 'wmv.png',
60
- 'wmv' => 'wmv.png',
61
- 'wmx' => 'wmv.png',
62
- 'mkv' => 'mkv.png',
63
-
64
- // Audio formats
65
- 'mp3' => 'mp3.png',
66
- 'wav' => 'wav.png',
67
- 'ogg' => 'ogg.png',
68
- 'oga' => 'ogg.png',
69
- 'midi' => 'midi.png',
70
- 'mid' => 'midi.png',
71
- 'wma' => 'wma.png',
72
-
73
- // Text formats
74
- 'rtx' => 'rtx.png',
75
- 'ics' => 'ics.png',
76
- 'csv' => 'csv.png',
77
-
78
- // Msc application formats
79
- 'html' => 'html.png',
80
- 'htm' => 'html.png', // death to all who use this!
81
- 'css' => 'css.png',
82
- 'js' => 'javascript.png',
83
- 'class'=> 'java.png',
84
- 'zip' => 'zip.png',
85
- 'tar' => 'compressed.png',
86
- 'gzip' => 'compressed.png',
87
- 'gz' => 'compressed.png',
88
- 'bz2' => 'compressed.png', // not yet WP-supported
89
- 'tgz' => 'compressed.png', // not yet WP-supported
90
- 'rar' => 'rar.png', // RAWR!!!
91
- '7z' => '7zip.png',
92
- 'exec' => 'exec.png',
93
- 'rtf' => 'rtf.png',
94
- 'swf' => 'shockwave.png',
95
-
96
- // OpenDocument formats
97
- 'odt' => 'opendocument-text.png',
98
- 'odp' => 'opendocument-presentation.png',
99
- 'ods' => 'opendocument-spreadsheet.png',
100
- 'odg' => 'opendocument-graphics.png',
101
- 'odb' => 'opendocument-database.png',
102
- 'odf' => 'opendocument-formula.png'
103
- );
104
-
105
- /**
106
- * Constructs instance of Document.
107
- * @param type $attachment Attachment object used to initalize fields.
108
- * @param type $gallery Instance of Gallery class.
109
- */
110
- public function __construct($attachment, $gallery) {
111
- // init templates for HTML output
112
- $this->doc_icon =
113
- ' <div class="document-icon">' . PHP_EOL .
114
- ' <a href="%s">%s<br>%s</a>' . PHP_EOL .
115
- ' </div>' . PHP_EOL;
116
- $this->icon_url = DG_URL . 'icons/';
117
-
118
- // init general document data
119
- $this->gallery = $gallery;
120
- $this->description = $attachment->post_content;
121
- $this->ID = $attachment->ID;
122
- $this->link = $gallery->linkToAttachmentPg()
123
- ? get_attachment_link($attachment->ID)
124
- : wp_get_attachment_url($attachment->ID);
125
- $this->title = get_the_title($attachment->ID);
126
- $this->title_attribute = esc_attr(strip_tags($this->title));
127
- }
128
-
129
- /**
130
- * Determines the filetype of the attachment and returns an HTML value to
131
- * appropriately represent the attachment.
132
- * @param attachment object representing the attachment
133
- * @return string HTML for the specified attachment's display
134
- */
135
- private function getIcon() {
136
- $url = wp_get_attachment_url($this->ID);
137
- $filetype = wp_check_filetype(basename($url));
138
-
139
- // identify extension
140
- if(array_key_exists($filetype['ext'], self::$exts)) {
141
- $icon = $this->icon_url . self::$exts[$filetype['ext']];
142
- }
143
- // handle images
144
- elseif (wp_attachment_is_image($this->ID) &&
145
- ($icon = wp_get_attachment_image_src($this->ID, 'thumbnail', false))) {
146
- $icon = $icon[0];
147
- }
148
- // fallback to default icons if not recognized
149
- elseif (($icon = wp_get_attachment_image_src($this->ID, null, true))) {
150
- $icon = $icon[0];
151
- }
152
- // everything failed. This is bad...
153
- else {
154
- $icon = $this->icon_url . 'missing.png';
155
- }
156
-
157
- return sprintf($this->img_string, $icon,
158
- $this->title_attribute, $this->title_attribute);
159
- }
160
-
161
- /**
162
- * Returns associative array of filetype to icon mapping.
163
- * @return array
164
- */
165
- public static function getFiletypeMapping() {
166
- return self::$exts;
167
- }
168
-
169
- /**
170
- * Takes associative array of filetype to icon mapping.
171
- *
172
- * NOTE: This is foundation work for allowing users to add their own
173
- * filetypes in the future.
174
- * @param array $new
175
- */
176
- public static function setFiletypeMapping($new) {
177
- self::$exts = $new;
178
- }
179
-
180
- /**
181
- * Returns HTML representing this Document.
182
- * @return string
183
- */
184
- public function __toString() {
185
- $core = sprintf($this->doc_icon, $this->link, $this->getIcon(), $this->title);
186
-
187
- if($this->gallery->useDescriptions()) {
188
- $core .= " <p>$this->description</p>" . PHP_EOL;
189
- }
190
-
191
- // users may filter icon here
192
- return apply_filters('dg_doc_icon', $core, $this->ID);
193
- }
194
- }
195
-
196
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/class-gallery.php DELETED
@@ -1,400 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Description of Gallery
5
- *
6
- * @author drossiter
7
- */
8
- class DG_Gallery {
9
- private $atts, $taxa, $comment;
10
- private $docs = array();
11
- private $errs = array();
12
-
13
- private static $defaults = array(
14
- // default: link directly to file (true to link to attachment pg)
15
- 'attachment_pg' => FALSE,
16
- 'descriptions' => FALSE,
17
- // include thumbnail of actual document in gallery display
18
- //'fancy_thumbs' => FALSE,
19
- // comma-separated list of attachment ids
20
- 'ids' => FALSE,
21
- // if true, all images attached to current page will be included also
22
- 'images' => FALSE,
23
- 'localpost' => TRUE,
24
- 'order' => 'ASC',
25
- 'orderby' => 'menu_order',
26
- // only relevant if tax_query used (WP >= 3.1)
27
- 'relation' => 'AND'
28
- );
29
-
30
- // templates for HTML output
31
- private $no_docs = '<!-- No attachments to display. How boring! :( -->';
32
- private $icon_wrapper;
33
-
34
- /**
35
- * Builds a gallery object with attributes passed.
36
- * @param array $atts Array of attributes used in shortcode.
37
- */
38
- public function __construct($atts) {
39
- // init templates for HTML output
40
- $this->comment =
41
- PHP_EOL . '<!-- Generated using Document Gallery. Get yours here: ' .
42
- 'http://wordpress.org/extend/plugins/document-gallery -->' . PHP_EOL;
43
- $this->icon_wrapper = '<div class="%s">'. PHP_EOL . '%s</div>' . PHP_EOL;
44
-
45
- // values used to construct tax query (may be empty)
46
- $this->taxa = array_diff_key($atts, self::$defaults);
47
-
48
- // all recognized attributes go here
49
- $this->atts = shortcode_atts(self::$defaults, $atts);
50
-
51
- // goes through all values in $this->atts, setting $this->errs as needed
52
- $this->sanitizeDefaults();
53
-
54
- // query DB for all documents requested
55
- try {
56
- $docs = $this->getDocuments();
57
- include_once(DG_PATH . 'models/class-document.php');
58
- foreach($docs as $doc) {
59
- $this->docs[] = new DG_Document($doc, $this);
60
- }
61
- } catch(InvalidArgumentException $e) {
62
- // errors will be printed in __toString()
63
- }
64
- }
65
-
66
- /**
67
- * Gets all valid Documents based on the attributes passed by the user.
68
- * @return array Contains all documents matching the query.
69
- * @throws InvalidArgumentException Thrown when $this->errs is not empty.
70
- */
71
- private function getDocuments() {
72
- $mime_types = array('application', 'video', 'text', 'audio');
73
- if ($this->atts['images']) {
74
- $mime_types[] = 'image';
75
- }
76
-
77
- $query = array(
78
- 'numberposts' => -1,
79
- 'orderby' => $this->atts['orderby'],
80
- 'order' => $this->atts['order'],
81
- 'post_status' => 'any',
82
- 'post_type' => 'attachment',
83
- 'post_mime_type' => implode(',', $mime_types));
84
-
85
- $query['post_parent'] =
86
- $this->atts['localpost']
87
- && ($post = get_post()) ? $post->ID : '';
88
-
89
- $this->setTaxa($query);
90
-
91
- if(!empty($this->errs)) {
92
- throw new InvalidArgumentException();
93
- }
94
-
95
- return ($this->atts['ids'] !== FALSE)
96
- ? $this->getAttachmentsByIds()
97
- : get_posts($query);
98
- }
99
-
100
- /**
101
- * Cleans up user input, making sure we don't pass crap on to WP core.
102
- * @global string $wp_version
103
- */
104
- private function sanitizeDefaults() {
105
- global $wp_version;
106
-
107
- if($this->atts['attachment_pg'] !== self::$defaults['attachment_pg']) {
108
- $attachment_pg =
109
- filter_var($this->atts['attachment_pg'],
110
- FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
111
-
112
- if($attachment_pg === null) {
113
- $this->errs[] =
114
- 'The attachment_pg parameter may only be \'true\' or \'false.\' ' .
115
- "You entered {$this->atts['attachment_pg']}.";
116
- } else {
117
- $this->atts['attachment_pg'] = $attachment_pg;
118
- }
119
- }
120
-
121
- if($this->atts['descriptions'] !== self::$defaults['descriptions']) {
122
- $descriptions =
123
- filter_var($this->atts['descriptions'],
124
- FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
125
-
126
- if($descriptions === null) {
127
- $this->errs[] =
128
- 'The descriptions parameter may only be \'true\' or \'false.\' ' .
129
- "You entered {$this->atts['descriptions']}.";
130
- } else {
131
- $this->atts['descriptions'] = $descriptions;
132
- }
133
- }
134
-
135
- if($this->atts['ids'] !== self::$defaults['ids']) {
136
- if(strcasecmp('false', $this->atts['ids']) == 0) {
137
- $this->atts['ids'] = FALSE;
138
- } else {
139
- $ids = explode(',', $this->atts['ids']);
140
- $bad = array_filter($ids, array(__CLASS__, 'negativeInt'));
141
-
142
- if(!empty($bad)) {
143
- $this->errs[] =
144
- 'The following ID(s) are not valid: ' .
145
- implode(', ', $bad) . '.';
146
- } else {
147
- $this->atts['ids'] = $ids;
148
- }
149
- }
150
- }
151
-
152
- if($this->atts['images'] !== self::$defaults['images']) {
153
- $images =
154
- filter_var($this->atts['images'],
155
- FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
156
-
157
- if($images === null) {
158
- $this->errs[] =
159
- 'The images parameter may only be \'true\' or \'false.\' ' .
160
- "You entered {$this->atts['images']}.";
161
- } else {
162
- $this->atts['images'] = $images;
163
- }
164
- }
165
-
166
- if($this->atts['localpost'] !== self::$defaults['localpost']) {
167
- $localpost =
168
- filter_var($this->atts['localpost'],
169
- FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
170
- if($localpost === null) {
171
- $this->errs[] =
172
- 'The localpost parameter may only be \'true\' or \'false.\' ' .
173
- "You entered {$this->atts['localpost']}.";
174
- } else {
175
- $this->atts['localpost'] = $localpost;
176
- }
177
- }
178
-
179
- $order = strtoupper($this->atts['order']);
180
- if('ASC' !== $order && 'DEC' !== $order) {
181
- $this->errs[] =
182
- 'The order parameter must be either \'ASC\' or \'DEC.\' '.
183
- "You entered {$this->atts['order']}.";
184
- }
185
-
186
- $orderby = strtoupper($this->atts['orderby']) === 'ID'
187
- ? 'ID' : strtolower($this->atts['orderby']);
188
- if ($orderby !== 'ID' && $orderby !== 'menu_order'
189
- && $orderby !== 'author' && $orderby !== 'title'
190
- && $orderby !== 'name' && $orderby !== 'date'
191
- && $orderby !== 'modified' && $orderby !== 'parent'
192
- && $orderby !== 'rand' /* && $orderby !== 'meta_value' */
193
- // check version-specific parameters
194
- && version_compare($wp_version, '2.8', '>=') && $orderby !== 'none' /* && $orderby !== 'meta_value_num' */
195
- && version_compare($wp_version, '2.9', '>=') && $orderby !== 'comment_count'
196
- && version_compare($wp_version, '3.5', '>=') && $orderby !== 'post__in') {
197
- $this->errs[] =
198
- "The orderby parameter value entered, {$this->atts['orderby']}, " .
199
- "is not valid in WP $wp_version.";
200
- } else {
201
- $this->atts['orderby'] = $orderby;
202
- }
203
-
204
- $relation = strtoupper($this->atts['relation']);
205
- if ('AND' !== $relation && 'OR' !== $relation) {
206
- $this->errs[] =
207
- 'The relation parameter must be either \'AND\' or \'OR.\' ' .
208
- "You entered {$this->atts['relation']}.";
209
- } else {
210
- $this->atts['relation'] = $relation;
211
- }
212
- }
213
-
214
- /**
215
- * Returns whether to link to attachment pg.
216
- * @return bool
217
- */
218
- public function linkToAttachmentPg() {
219
- return $this->atts['attachment_pg'];
220
- }
221
-
222
- /**
223
- * Returns whether descriptions should be included in output.
224
- * @return bool
225
- */
226
- public function useDescriptions() {
227
- return $this->atts['descriptions'];
228
- }
229
-
230
- /**
231
- * Function loops through all attributes passed that did not match
232
- * self::$defaults. If they are the name of a taxonomy, they are plugged
233
- * into the query, otherwise $this->errs is appended with an error string.
234
- * @global string $wp_version Determines which tax query to use.
235
- * @param array $query Query to insert tax query into.
236
- */
237
- private function setTaxa(&$query) {
238
- if(!empty($this->taxa)) {
239
- global $wp_version;
240
- $taxa = array();
241
-
242
- // use preferred tax_query if supported
243
- if (version_compare($wp_version, '3.1', '>=')) {
244
- // only include relation if we have multiple taxa
245
- if(count($this->taxa) > 1) {
246
- $taxa['relation'] = $this->atts['relation'];
247
- }
248
-
249
- foreach ($this->taxa as $taxon => $terms) {
250
- $terms = $this->getTermIdsByNames($taxon, explode(',', $terms));
251
-
252
- $taxa[] = array(
253
- 'taxonomy' => $taxon,
254
- 'field' => 'id',
255
- 'terms' => $terms
256
- );
257
- }
258
-
259
- // create nested structure
260
- $query['tax_query'] = $taxa;
261
- } // fallback to deprecated {tax_name} => {term_slug} construct
262
- elseif (version_compare($wp_version, '2.3', '>=')) {
263
- foreach ($this->taxa as $taxon => $terms) {
264
- $taxa[$taxon] = ($taxon == 'category')
265
- ? implode(',', $this->getTermIdsByNames($taxon, explode(',', $terms)))
266
- : implode(',', $this->getTermSlugsByNames($taxon, explode(',', $terms)));
267
- }
268
-
269
- $query = array_merge($taxa, $query);
270
- } // WP < 2.3 not supported for category/custom taxa
271
- else {
272
- $this->errs[] = 'The following attributes are invalid: ' .
273
- implode(', ', array_keys($this->taxa));
274
- }
275
- }
276
- }
277
-
278
-
279
- /**
280
- * Returns an array of term ids when provided with a list of term names.
281
- * Also appends an entry onto $errs if any invalid names are found.
282
- * @param string $taxon
283
- * @param array $term_names
284
- * @param &array $errs
285
- * @return array
286
- */
287
- private function getTermIdsByNames($taxon, $term_names) {
288
- return $this->getTermXByNames('term_id', $taxon, $term_names);
289
- }
290
-
291
- /**
292
- * Returns an array of term slugs when provided with a list of term names.
293
- * Also appends an entry onto $errs if any invalid names are found.
294
- * @param string $taxon
295
- * @param array $term_names
296
- * @param &array $errs
297
- * @return array
298
- */
299
- private function getTermSlugsByNames($taxon, $term_names) {
300
- return $this->getTermXByNames('slug', $taxon, $term_names);
301
- }
302
-
303
- /**
304
- * (WP >= 2.3) Returns a list of x, where x may be any of the fields within a
305
- * term object, when provided with a list of term names (not slugs).
306
- * (http://codex.wordpress.org/Function_Reference/get_term_by#Return_Values)
307
- *
308
- * Also appends an entry onto $errs if any invalid names are found.
309
- * @param string $x
310
- * @param string $taxon
311
- * @param array $term_names
312
- * @param &array $errs
313
- * @return array
314
- */
315
- private function getTermXByNames($x, $taxon, $term_names) {
316
- $ret = array();
317
-
318
- foreach ($term_names as $name) {
319
- if (($term = get_term_by('name', $name, $taxon))) {
320
- $ret[] = $term->{$x};
321
- }
322
- else {
323
- $this->errs[] = "$name is not a valid term name in $taxon.";
324
- }
325
- }
326
-
327
- return $ret;
328
- }
329
-
330
- /**
331
- * Given a list of IDs, all attachments represented by these IDs are returned.
332
- * @return array post objects
333
- */
334
- private function getAttachmentsByIds() {
335
- $attachments = array();
336
- foreach ($this->atts['ids'] as $id) {
337
- $attachment = get_post($id);
338
- if ($attachment->post_type === 'attachment')
339
- $attachments[] = $attachment;
340
- // else: not an attachment so skip
341
- }
342
-
343
- return $attachments;
344
- }
345
-
346
- /**
347
- * Function returns false for positive ints, true otherwise.
348
- * @param var $var could be anything.
349
- * @return boolean indicating whether $var is not a positive int.
350
- */
351
- private static function negativeInt($var) {
352
- return !is_numeric($var) // isn't numeric
353
- || (int)$var != $var // isn't int
354
- || (int)$var < 0; // isn't positive
355
- }
356
-
357
-
358
- /**
359
- * Returns HTML representing this Gallery.
360
- * @return string
361
- */
362
- public function __toString() {
363
- if(!empty($this->errs)) {
364
- return '<p>' . implode('</p><p>', $this->errs) . '</p>';
365
- }
366
-
367
- if(empty($this->docs)) {
368
- return $this->no_docs;
369
- }
370
-
371
- $core = '';
372
- $classes = array('document-icon-wrapper');
373
- if($this->useDescriptions()) {
374
- $classes[] = 'descriptions';
375
- }
376
-
377
- $icon_wrapper = sprintf($this->icon_wrapper, implode(' ', $classes), '%s');
378
-
379
- if($this->useDescriptions()) {
380
- foreach($this->docs as $doc) {
381
- $core .= sprintf($icon_wrapper, (string)$doc);
382
- }
383
- } else {
384
- for($i = 0; $i < count($this->docs); $i+=4) {
385
- $row = '';
386
-
387
- $min = min($i+4, count($this->docs));
388
- for($x = $i; $x < $min; $x++) {
389
- $row .= (string)$this->docs[$x];
390
- }
391
-
392
- $core .= sprintf($icon_wrapper, $row);
393
- }
394
- }
395
-
396
- return $this->comment . $core;
397
- }
398
- }
399
-
400
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file