Version Description
- expose API::export function for easier reuse by 3rd-party code
- set last modify time for each file in zip to match the timestamp on disk
Download this release
Release Info
Developer | andrej.pavlovic |
Plugin | Export Media Library |
Version | 1.1.0 |
Comparing to | |
See all releases |
Code changes from version 1.0.1 to 1.1.0
- composer.json +2 -1
- composer.lock +5 -3
- index.php +1 -1
- lib/MassEdge/WordPress/Plugin/ExportMediaLibrary/API.php +148 -0
- lib/MassEdge/WordPress/Plugin/ExportMediaLibrary/Module/AdminPageExport.php +20 -87
- readme.txt +5 -1
composer.json
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
{
|
2 |
"name": "massedge/wordpress-plugin-export-media-library",
|
3 |
"description": "Wordpress plugin that allows admins to export media library files as a compressed zip archive.",
|
4 |
-
"version": "1.0
|
5 |
"type": "wordpress-plugin",
|
6 |
"license": "GPL-3.0",
|
7 |
"require": {
|
|
|
8 |
"maennchen/zipstream-php": "0.4.1"
|
9 |
},
|
10 |
"autoload": {
|
1 |
{
|
2 |
"name": "massedge/wordpress-plugin-export-media-library",
|
3 |
"description": "Wordpress plugin that allows admins to export media library files as a compressed zip archive.",
|
4 |
+
"version": "1.1.0",
|
5 |
"type": "wordpress-plugin",
|
6 |
"license": "GPL-3.0",
|
7 |
"require": {
|
8 |
+
"php": ">=5.6",
|
9 |
"maennchen/zipstream-php": "0.4.1"
|
10 |
},
|
11 |
"autoload": {
|
composer.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
{
|
2 |
"_readme": [
|
3 |
"This file locks the dependencies of your project to a known state",
|
4 |
-
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#
|
5 |
"This file is @generated automatically"
|
6 |
],
|
7 |
-
"content-hash": "
|
8 |
"packages": [
|
9 |
{
|
10 |
"name": "maennchen/zipstream-php",
|
@@ -65,6 +65,8 @@
|
|
65 |
"stability-flags": [],
|
66 |
"prefer-stable": false,
|
67 |
"prefer-lowest": false,
|
68 |
-
"platform":
|
|
|
|
|
69 |
"platform-dev": []
|
70 |
}
|
1 |
{
|
2 |
"_readme": [
|
3 |
"This file locks the dependencies of your project to a known state",
|
4 |
+
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
5 |
"This file is @generated automatically"
|
6 |
],
|
7 |
+
"content-hash": "a853daad251f8773c406fcc9f1e7a7a0",
|
8 |
"packages": [
|
9 |
{
|
10 |
"name": "maennchen/zipstream-php",
|
65 |
"stability-flags": [],
|
66 |
"prefer-stable": false,
|
67 |
"prefer-lowest": false,
|
68 |
+
"platform": {
|
69 |
+
"php": ">=5.6"
|
70 |
+
},
|
71 |
"platform-dev": []
|
72 |
}
|
index.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
Plugin Name: Export Media Library
|
4 |
Plugin URI: https://github.com/massedge/wordpress-plugin-export-media-library
|
5 |
Description: Allows admins to export media library files as a compressed zip archive.
|
6 |
-
Version: 1.0
|
7 |
Author: Mass Edge Inc.
|
8 |
Author URI: https://www.massedge.com/
|
9 |
License: GPL3
|
3 |
Plugin Name: Export Media Library
|
4 |
Plugin URI: https://github.com/massedge/wordpress-plugin-export-media-library
|
5 |
Description: Allows admins to export media library files as a compressed zip archive.
|
6 |
+
Version: 1.1.0
|
7 |
Author: Mass Edge Inc.
|
8 |
Author URI: https://www.massedge.com/
|
9 |
License: GPL3
|
lib/MassEdge/WordPress/Plugin/ExportMediaLibrary/API.php
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace MassEdge\WordPress\Plugin\ExportMediaLibrary;
|
4 |
+
|
5 |
+
use ZipStream\ZipStream;
|
6 |
+
|
7 |
+
class API {
|
8 |
+
const FOLDER_STRUCTURE_NESTED = 'nested';
|
9 |
+
const FOLDER_STRUCTURE_FLAT = 'flat';
|
10 |
+
|
11 |
+
static function defaultExportOptions() {
|
12 |
+
return [
|
13 |
+
'filename' => 'export.zip',
|
14 |
+
'root_path' => null, // defaults to `filename` without extension
|
15 |
+
'folder_structure' => self::FOLDER_STRUCTURE_NESTED,
|
16 |
+
'compress' => false,
|
17 |
+
'upload_basedir' => self::getUploadBasedir(),
|
18 |
+
'query_args' => [
|
19 |
+
'post_type' => 'attachment',
|
20 |
+
'post_status' => 'inherit',
|
21 |
+
'fields' => 'ids',
|
22 |
+
'posts_per_page' => -1,
|
23 |
+
],
|
24 |
+
'add_attachment_callback' => function($value, $params) { return $value; },
|
25 |
+
'add_attachment_failed_callback' => function($params) {},
|
26 |
+
'add_extra_files_callback' => function($params) {},
|
27 |
+
];
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Stream zip file comprised of all attachments directly to output stream.
|
32 |
+
* @param array $options
|
33 |
+
* @return void
|
34 |
+
*/
|
35 |
+
static function export(array $options = array()) {
|
36 |
+
$options = array_merge(self::defaultExportOptions(), $options);
|
37 |
+
|
38 |
+
if ($options['root_path'] === null) {
|
39 |
+
// default to `filename` without extension
|
40 |
+
$options['root_path'] = pathinfo($options['filename'], PATHINFO_FILENAME);
|
41 |
+
}
|
42 |
+
// ensure path doesn't end in slash
|
43 |
+
if ($options['root_path']) $options['root_path'] = rtrim($options['root_path'], '/\\');
|
44 |
+
|
45 |
+
// create a new zipstream object
|
46 |
+
$zip = new ZipStream($options['filename'], [
|
47 |
+
// WORKAROUND: treat each file as large in order to use STORE method, thereby avoiding compression
|
48 |
+
ZipStream::OPTION_LARGE_FILE_SIZE => ($options['compress']) ? 20 * 1024 * 1024 : 1,
|
49 |
+
]);
|
50 |
+
|
51 |
+
$query = new \WP_Query();
|
52 |
+
$attachmentIds = $query->query($options['query_args']);
|
53 |
+
|
54 |
+
$flatFilenames = [];
|
55 |
+
|
56 |
+
foreach($attachmentIds as $attachmentId) {
|
57 |
+
$attachmentPath = get_attached_file($attachmentId);
|
58 |
+
|
59 |
+
if ($attachmentPath) {
|
60 |
+
switch($options['folder_structure']) {
|
61 |
+
case self::FOLDER_STRUCTURE_NESTED:
|
62 |
+
// check if attachment in upload folder
|
63 |
+
if (substr($attachmentPath, 0, strlen($options['upload_basedir'])) === $options['upload_basedir']) {
|
64 |
+
$file = substr($attachmentPath, strlen($options['upload_basedir']) + 1);
|
65 |
+
} else {
|
66 |
+
if (0 == strpos( $attachmentPath, '/' )) {
|
67 |
+
$file = substr($attachmentPath, 1);
|
68 |
+
} else if (preg_match( '|^.:\\\|', $attachmentPath )) {
|
69 |
+
$file = substr($attachmentPath, 3);
|
70 |
+
} else {
|
71 |
+
$file = $attachmentPath;
|
72 |
+
}
|
73 |
+
}
|
74 |
+
break;
|
75 |
+
|
76 |
+
case self::FOLDER_STRUCTURE_FLAT:
|
77 |
+
default:
|
78 |
+
$file = basename($attachmentPath, PATHINFO_BASENAME);
|
79 |
+
$filename = pathinfo($file, PATHINFO_FILENAME);
|
80 |
+
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
81 |
+
|
82 |
+
// append a number to file name, of another with same name is already present
|
83 |
+
for($i = 0; in_array($file, $flatFilenames); $i++) {
|
84 |
+
$file = $filename . $i . (($ext !== null) ? '.' . $ext : '');
|
85 |
+
}
|
86 |
+
|
87 |
+
// keep track of file name, so another file doesn't over write it
|
88 |
+
$flatFilenames[] = $file;
|
89 |
+
}
|
90 |
+
|
91 |
+
$file = ($options['root_path'])
|
92 |
+
? "{$options['root_path']}/{$file}"
|
93 |
+
: "{$file}";
|
94 |
+
|
95 |
+
$time = @filectime($attachmentPath);
|
96 |
+
} else {
|
97 |
+
$file = null;
|
98 |
+
$time = false;
|
99 |
+
}
|
100 |
+
|
101 |
+
// opportunity to manipulate adding of attachment to zip
|
102 |
+
$result = $options['add_attachment_callback']([
|
103 |
+
'name' => $file,
|
104 |
+
'path' => $attachmentPath,
|
105 |
+
'options' => [
|
106 |
+
'time' => $time,
|
107 |
+
],
|
108 |
+
], [
|
109 |
+
'attachment_id' => $attachmentId,
|
110 |
+
]);
|
111 |
+
|
112 |
+
// skip attachment if result not specified
|
113 |
+
if (!$result || empty($result['name']) || empty($result['path'])) continue;
|
114 |
+
|
115 |
+
try {
|
116 |
+
$zip->addFileFromPath($result['name'], $result['path'], $result['options']);
|
117 |
+
} catch (\Exception $ex) {
|
118 |
+
$options['add_attachment_failed_callback']([
|
119 |
+
'name' => $result['name'],
|
120 |
+
'path' => $result['path'],
|
121 |
+
'options' => $result['options'],
|
122 |
+
'exception' => $ex,
|
123 |
+
]);
|
124 |
+
|
125 |
+
// skip files that fail to be added to zip
|
126 |
+
continue;
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
// give opportunity to add extra files before finishing the stream
|
131 |
+
$options['add_extra_files_callback']([
|
132 |
+
'add_file_callback' => function($name, $path, array $options = []) use ($zip) {
|
133 |
+
return $zip->addFileFromPath($name, $path, $options);
|
134 |
+
},
|
135 |
+
]);
|
136 |
+
|
137 |
+
// finish the zip stream
|
138 |
+
$zip->finish();
|
139 |
+
}
|
140 |
+
|
141 |
+
private static function getUploadBasedir() {
|
142 |
+
$uploads = wp_get_upload_dir();
|
143 |
+
if ($uploads['error'] !== false) {
|
144 |
+
throw new \Exception($uploads['error']);
|
145 |
+
}
|
146 |
+
return $uploads['basedir'];
|
147 |
+
}
|
148 |
+
}
|
lib/MassEdge/WordPress/Plugin/ExportMediaLibrary/Module/AdminPageExport.php
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
|
3 |
namespace MassEdge\WordPress\Plugin\ExportMediaLibrary\Module;
|
4 |
|
5 |
-
use
|
6 |
|
7 |
class AdminPageExport extends Base {
|
8 |
const FIELD_SUBMIT_DOWNLOAD = 'massedge-wp-plugin-eml-ape-submit-download';
|
@@ -10,9 +10,6 @@ class AdminPageExport extends Base {
|
|
10 |
const FIELD_FOLDER_STRUCTURE = 'folder_structure';
|
11 |
const FIELD_COMPRESS = 'compress';
|
12 |
|
13 |
-
const FOLDER_STRUCTURE_NESTED = 'nested';
|
14 |
-
const FOLDER_STRUCTURE_FLAT = 'flat';
|
15 |
-
|
16 |
const REQUIRED_CAPABILITY = 'upload_files';
|
17 |
|
18 |
function registerHooks() {
|
@@ -38,22 +35,34 @@ class AdminPageExport extends Base {
|
|
38 |
if (!check_admin_referer(self::FIELD_NONCE_DOWNLOAD_ACTION)) return;
|
39 |
|
40 |
// create name for download
|
41 |
-
$filename = sprintf('%s_media_library_export', current_time('Y-m-d_H-i-s'));
|
42 |
|
43 |
// set folder structure
|
44 |
$folderStructure = (
|
45 |
empty($_POST[self::FIELD_FOLDER_STRUCTURE]) ||
|
46 |
-
!in_array($_POST[self::FIELD_FOLDER_STRUCTURE], [
|
47 |
)
|
48 |
-
?
|
49 |
: $_POST['folder_structure'];
|
50 |
|
51 |
// set compress option
|
52 |
$compress = !empty($_POST[self::FIELD_COMPRESS]);
|
53 |
|
54 |
try {
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
} catch (\Exception $ex) {
|
58 |
add_action('admin_notices', function() use ($ex) {
|
59 |
echo sprintf('<div class="error"><p>%s</p></div>', esc_html($ex->getMessage()));
|
@@ -79,8 +88,8 @@ class AdminPageExport extends Base {
|
|
79 |
<th>Folder Structure</th>
|
80 |
<td>
|
81 |
<select name="<?php echo esc_attr(self::FIELD_FOLDER_STRUCTURE) ?>">
|
82 |
-
<option value="<?php echo esc_attr(
|
83 |
-
<option value="<?php echo esc_attr(
|
84 |
</select>
|
85 |
</td>
|
86 |
</tr>
|
@@ -104,80 +113,4 @@ class AdminPageExport extends Base {
|
|
104 |
<?php
|
105 |
echo ob_get_clean();
|
106 |
}
|
107 |
-
|
108 |
-
function export($exportName, $folderStructure, $compress) {
|
109 |
-
$exportFilename = "{$exportName}.zip";
|
110 |
-
$basedir = self::getUploadBasedir();
|
111 |
-
|
112 |
-
# create a new zipstream object
|
113 |
-
$zip = new ZipStream($exportFilename, [
|
114 |
-
// WORKAROUND: treat each file as large in order to use STORE method, thereby avoiding compression
|
115 |
-
ZipStream::OPTION_LARGE_FILE_SIZE => ($compress) ? 20 * 1024 * 1024 : 1,
|
116 |
-
]);
|
117 |
-
|
118 |
-
$query = new \WP_Query();
|
119 |
-
$attachmentIds = $query->query([
|
120 |
-
'post_type' => 'attachment',
|
121 |
-
'post_status' => 'inherit',
|
122 |
-
'fields' => 'ids',
|
123 |
-
'posts_per_page' => -1,
|
124 |
-
]);
|
125 |
-
|
126 |
-
$flatFilenames = [];
|
127 |
-
|
128 |
-
foreach($attachmentIds as $attachmentId) {
|
129 |
-
$attachmentPath = get_attached_file($attachmentId);
|
130 |
-
|
131 |
-
if (!$attachmentPath) continue;
|
132 |
-
|
133 |
-
switch($folderStructure) {
|
134 |
-
case self::FOLDER_STRUCTURE_NESTED:
|
135 |
-
// check if attachment in upload folder
|
136 |
-
if (substr($attachmentPath, 0, strlen($basedir)) === $basedir) {
|
137 |
-
$file = substr($attachmentPath, strlen($basedir) + 1);
|
138 |
-
} else {
|
139 |
-
if (0 == strpos( $attachmentPath, '/' )) {
|
140 |
-
$file = substr($attachmentPath, 1);
|
141 |
-
} else if (preg_match( '|^.:\\\|', $attachmentPath )) {
|
142 |
-
$file = substr($attachmentPath, 3);
|
143 |
-
} else {
|
144 |
-
$file = $attachmentPath;
|
145 |
-
}
|
146 |
-
}
|
147 |
-
break;
|
148 |
-
|
149 |
-
case self::FOLDER_STRUCTURE_FLAT:
|
150 |
-
default:
|
151 |
-
$file = basename($attachmentPath, PATHINFO_BASENAME);
|
152 |
-
$filename = pathinfo($file, PATHINFO_FILENAME);
|
153 |
-
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
154 |
-
|
155 |
-
// append a number to file name, of another with same name is already present
|
156 |
-
for($i = 0; in_array($file, $flatFilenames); $i++) {
|
157 |
-
$file = $filename . $i . (($ext !== null) ? '.' . $ext : '');
|
158 |
-
}
|
159 |
-
|
160 |
-
// keep track of file name, so another file doesn't over write it
|
161 |
-
$flatFilenames[] = $file;
|
162 |
-
}
|
163 |
-
|
164 |
-
try {
|
165 |
-
$zip->addFileFromPath("{$exportName}/{$file}", $attachmentPath);
|
166 |
-
} catch (\Exception $ex) {
|
167 |
-
// skip files that fail to be added to zip
|
168 |
-
continue;
|
169 |
-
}
|
170 |
-
}
|
171 |
-
|
172 |
-
# finish the zip stream
|
173 |
-
$zip->finish();
|
174 |
-
}
|
175 |
-
|
176 |
-
private static function getUploadBasedir() {
|
177 |
-
$uploads = wp_get_upload_dir();
|
178 |
-
if ($uploads['error'] !== false) {
|
179 |
-
throw new \Exception($uploads['error']);
|
180 |
-
}
|
181 |
-
return $uploads['basedir'];
|
182 |
-
}
|
183 |
}
|
2 |
|
3 |
namespace MassEdge\WordPress\Plugin\ExportMediaLibrary\Module;
|
4 |
|
5 |
+
use MassEdge\WordPress\Plugin\ExportMediaLibrary\API;
|
6 |
|
7 |
class AdminPageExport extends Base {
|
8 |
const FIELD_SUBMIT_DOWNLOAD = 'massedge-wp-plugin-eml-ape-submit-download';
|
10 |
const FIELD_FOLDER_STRUCTURE = 'folder_structure';
|
11 |
const FIELD_COMPRESS = 'compress';
|
12 |
|
|
|
|
|
|
|
13 |
const REQUIRED_CAPABILITY = 'upload_files';
|
14 |
|
15 |
function registerHooks() {
|
35 |
if (!check_admin_referer(self::FIELD_NONCE_DOWNLOAD_ACTION)) return;
|
36 |
|
37 |
// create name for download
|
38 |
+
$filename = sprintf('%s_media_library_export.zip', current_time('Y-m-d_H-i-s'));
|
39 |
|
40 |
// set folder structure
|
41 |
$folderStructure = (
|
42 |
empty($_POST[self::FIELD_FOLDER_STRUCTURE]) ||
|
43 |
+
!in_array($_POST[self::FIELD_FOLDER_STRUCTURE], [API::FOLDER_STRUCTURE_NESTED, API::FOLDER_STRUCTURE_FLAT])
|
44 |
)
|
45 |
+
? API::FOLDER_STRUCTURE_NESTED
|
46 |
: $_POST['folder_structure'];
|
47 |
|
48 |
// set compress option
|
49 |
$compress = !empty($_POST[self::FIELD_COMPRESS]);
|
50 |
|
51 |
try {
|
52 |
+
API::export([
|
53 |
+
'filename' => $filename,
|
54 |
+
'folder_structure' => $folderStructure,
|
55 |
+
'compress' => $compress,
|
56 |
+
'add_attachment_callback' => function($value, $params) {
|
57 |
+
return apply_filters('massedge-wp-eml/export/add_attachment', $value, $params);
|
58 |
+
},
|
59 |
+
'add_attachment_failed_callback' => function($params) {
|
60 |
+
do_action('massedge-wp-eml/export/add_attachment_failed', $params);
|
61 |
+
},
|
62 |
+
'add_extra_files_callback' => function($params) {
|
63 |
+
do_action('massedge-wp-eml/export/add_extra_files', $params);
|
64 |
+
}
|
65 |
+
]);
|
66 |
} catch (\Exception $ex) {
|
67 |
add_action('admin_notices', function() use ($ex) {
|
68 |
echo sprintf('<div class="error"><p>%s</p></div>', esc_html($ex->getMessage()));
|
88 |
<th>Folder Structure</th>
|
89 |
<td>
|
90 |
<select name="<?php echo esc_attr(self::FIELD_FOLDER_STRUCTURE) ?>">
|
91 |
+
<option value="<?php echo esc_attr(API::FOLDER_STRUCTURE_FLAT) ?>">Single folder with all files</option>
|
92 |
+
<option value="<?php echo esc_attr(API::FOLDER_STRUCTURE_NESTED) ?>">Nested folders</option>
|
93 |
</select>
|
94 |
</td>
|
95 |
</tr>
|
113 |
<?php
|
114 |
echo ob_get_clean();
|
115 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
}
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Tags: export media library, download media library, media library, export, downl
|
|
4 |
Requires at least: 4.7.10
|
5 |
Tested up to: 4.9.6
|
6 |
Requires PHP: 5.6
|
7 |
-
Stable tag: 1.0
|
8 |
License: GPLv3
|
9 |
License URI: http://www.gnu.org/licenses/gpl-3.0.html
|
10 |
|
@@ -26,6 +26,10 @@ Allows users to export media library files as a compressed zip archive.
|
|
26 |
|
27 |
== Changelog ==
|
28 |
|
|
|
|
|
|
|
|
|
29 |
= 1.0.1 =
|
30 |
Fixed title of plugin in readme.
|
31 |
|
4 |
Requires at least: 4.7.10
|
5 |
Tested up to: 4.9.6
|
6 |
Requires PHP: 5.6
|
7 |
+
Stable tag: 1.1.0
|
8 |
License: GPLv3
|
9 |
License URI: http://www.gnu.org/licenses/gpl-3.0.html
|
10 |
|
26 |
|
27 |
== Changelog ==
|
28 |
|
29 |
+
= 1.1.0 =
|
30 |
+
* expose API::export function for easier reuse by 3rd-party code
|
31 |
+
* set last modify time for each file in zip to match the timestamp on disk
|
32 |
+
|
33 |
= 1.0.1 =
|
34 |
Fixed title of plugin in readme.
|
35 |
|