-
-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy pathreadme.txt
More file actions
1069 lines (760 loc) · 33.9 KB
/
readme.txt
File metadata and controls
1069 lines (760 loc) · 33.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
=== Static Site Exporter ===
Contributors: benbalter
Tags: jekyll, github, github pages, yaml, export, markdown
Requires at least: 4.4
Tested up to: 6.9
Requires PHP: 7.2
Stable tag: 3.1.1
License: GPLv3 or later
License URI: http://www.gnu.org/licenses/gpl-3.0.html
GitHub Plugin URI: benbalter/wordpress-to-jekyll-exporter
Primary Branch: master
== Features ==
* Converts all posts, pages, and settings from WordPress to Markdown and YAML for use in Jekyll (or Hugo or any other Markdown and YAML based site engine)
* Export what your users see, not what the database stores (runs post content through `the_content` filter prior to export, allowing third-party plugins to modify the output)
* Converts all `post_content` to Markdown
* Converts all `post_meta` and fields within the `wp_posts` table to YAML front matter for parsing by Jekyll
* Generates a `_config.yml` with all settings in the `wp_options` table
* Outputs a single zip file with `_config.yml`, pages, and `_posts` folder containing `.md` files for each post in the proper Jekyll naming convention
* **Selective export**: Export only specific categories, tags, or post types using WP-CLI
* No settings. Just a single click.
== Usage ==
1. Place plugin in `/wp-content/plugins/` folder
2. Activate plugin in WordPress dashboard
3. Select `Export to Jekyll` from the `Tools` menu
== More information ==
See [the full documentation](https://ben.balter.com/wordpress-to-jekyll-exporter):
* [Changelog](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/changelog/)
* [Command-line-usage](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/command-line-usage/)
* [Selective export by category or tag](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/selective-export/)
* [Custom post types](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/custom-post-types/)
* [Custom fields](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/custom-fields/)
* [Developing locally](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/developing-locally/)
* [Minimum required PHP version](https://ben.balter.com/wordpress-to-jekyll-exporter/./docs/required-php-version/)
=== Security Policy ===
To report a security vulnerability, please email [ben@balter.com](mailto:ben@balter.com).
== Where to get help or report an issue ==
* For getting started and general documentation, please browse, and feel free to contribute to [the project documentation](http://ben.balter.com/wordpress-to-jekyll-exporter/).
* For support questions ("How do I", "I can't seem to", etc.) please search and if not already answered, open a thread in the [Support Forums](http://wordpress.org/support/plugin/jekyll-exporter).
* For technical issues (e.g., to submit a bug or feature request) please search and if not already filed, [open an issue on GitHub](https://github.com/benbalter//wordpress-to-jekyll-exporter/issues).
== Things to check before reporting an issue ==
* Are you using the latest version of WordPress?
* Are you using the latest version of the plugin?
* Does the problem occur even when you deactivate all plugins and use the default theme?
* Have you tried deactivating and reactivating the plugin?
* Has your issue [already been reported](https://github.com/benbalter/wordpress-to-jekyll-exporter/issues)?
== What to include in an issue ==
* What steps can another user take to recreate the issue?
* What is the expected outcome of that action?
* What is the actual outcome of that action?
* Are there any screenshots or screencasts that may be helpful to include?
* Only include one bug per issue. If you have discovered two bugs, please file two issues.
== Changelog ==
[View Past Releases](https://github.com/benbalter/wordpress-to-jekyll-exporter/releases)
== Command-line Usage ==
If you're having trouble with your web server timing out before the export is complete, or if you just like terminal better, you may enjoy the command-line tool.
It works just like the plugin, but produces the zipfile on STDOUT:
```
php jekyll-export-cli.php > jekyll-export.zip
```
If using this method, you must run first `cd` into the wordpress-to-jekyll-exporter directory.
Alternatively, if you have [WP-CLI](http://wp-cli.org) installed, you can run:
```
wp jekyll-export > export.zip
```
The WP-CLI version will provide greater compatibility for alternate WordPress environments, such as when `wp-content` isn't in the usual location.
== Filtering by Category or Tag ==
You can export only specific categories or tags using the WP-CLI command. This is useful when you want to convert just one section of your WordPress site instead of the entire corpus.
= Export posts from a specific category: =
```bash
wp jekyll-export --category=technology > export.zip
```
= Export posts from multiple categories: =
```bash
wp jekyll-export --category=tech,news,updates > export.zip
```
= Export posts with a specific tag: =
```bash
wp jekyll-export --tag=featured > export.zip
```
= Export only pages (or specific post types): =
```bash
wp jekyll-export --post_type=page > export.zip
```
= Combine filters: =
```bash
wp jekyll-export --category=technology --tag=featured --post_type=post > export.zip
```
== Using Filters in PHP ==
If you're using the plugin via PHP code or want more control, you can use the `jekyll_export_taxonomy_filters` filter:
```php
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'category' => array( 'technology', 'science' ),
'post_tag' => array( 'featured' ),
);
} );
// Then trigger the export
global $jekyll_export;
$jekyll_export->export();
```
== Custom fields ==
When using custom fields (e.g. with the Advanced Custom fields plugin) you might have to register a filter to convert array style configs to plain values.
= Available Filters =
The plugin provides two filters for customizing post metadata:
- **`jekyll_export_meta`**: Filters the metadata for a single post before it's merged with taxonomy terms. Receives `$meta` array as the only parameter.
- **`jekyll_export_post_meta`**: Filters the complete metadata array (including taxonomy terms) just before it's written to the YAML frontmatter. Receives `$meta` array and `$post` object as parameters. This is the recommended filter for most use cases.
**Note:** As of the latest version, the plugin no longer automatically removes empty or falsy values from the frontmatter. All metadata is preserved by default. If you want to remove certain fields, you can use the `jekyll_export_post_meta` filter to customize this behavior.
By default, the plugin saves custom fields in an array structure that is exported as:
```php
["my-bool"]=>
array(1) {
[0] => string(1) "1"
}
["location"]=>
array(1) {
[0] => string(88) "My address"
}
```
And this leads to a YAML structure like:
```yaml
my-bool:
- "1"
location:
- 'My address'
```
This is likely not the structure you expect or want to work with. You can convert it using a filter:
```php
add_filter( 'jekyll_export_meta', function($meta) {
foreach ($meta as $key => $value) {
if (is_array($value) && count($value) === 1 && array_key_exists(0, $value)) {
$meta[$key] = $value[0];
}
}
return $meta;
});
```
A more complete solution could look like that:
```php
add_filter( 'jekyll_export_meta', function($meta) {
foreach ($meta as $key => $value) {
// Advanced Custom Fields
if (is_array($value) && count($value) === 1 && array_key_exists(0, $value)) {
$value = maybe_unserialize($value[0]);
// Advanced Custom Fields: NextGEN Gallery Field add-on
if (is_array($value) && count($value) === 1 && array_key_exists(0, $value)) {
$value = $value[0];
}
}
// convert types
$value = match ($key) {
// Advanced Custom Fields: "true_false" type
'my-bool' => (bool) $value,
default => $value
};
$meta[$key] = $value;
}
return $meta;
});
```
= Removing Empty or Falsy Values =
If you want to remove empty or falsy values from the frontmatter (similar to the pre-3.0.3 behavior), you can use the `jekyll_export_post_meta` filter:
```php
add_filter( 'jekyll_export_post_meta', function( $meta, $post ) {
foreach ( $meta as $key => $value ) {
// Remove falsy values except numeric 0
if ( ! is_numeric( $value ) && ! $value ) {
unset( $meta[ $key ] );
}
}
return $meta;
}, 10, 2 );
```
== Custom post types ==
To export custom post types, you'll need to add a filter (w.g. to your themes config file) to do the following:
```php
add_filter( 'jekyll_export_post_types', function() {
return array('post', 'page', 'you-custom-post-type');
});
```
The custom post type will be exported as a Jekyll collection. You'll need to initialize it in the resulting Jekyll site's `_config.yml`.
== Developing locally ==
= Option 1: Using Dev Containers (Recommended) =
The easiest way to get started is using [VS Code Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) or [GitHub Codespaces](https://github.com/features/codespaces):
1. Install [VS Code](https://code.visualstudio.com/) and the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. `git clone https://github.com/benbalter/wordpress-to-jekyll-exporter`
3. Open the folder in VS Code
4. Click "Reopen in Container" when prompted
5. Wait for the container to build and dependencies to install
6. Access WordPress at `http://localhost:8088`
The devcontainer includes:
- Pre-configured WordPress and MySQL
- All PHP extensions and Composer dependencies
- VS Code extensions for PHP development, debugging, and testing
- WordPress coding standards configured
See [.devcontainer/README.md](https://ben.balter.com/wordpress-to-jekyll-exporter/./.devcontainer/README/) for more details.
= Option 2: Manual Setup =
= Prerequisites =
1. `sudo apt-get update`
1. `sudo apt-get install composer`
1. `sudo apt-get install php7.3-xml`
1. `sudo apt-get install php7.3-mysql`
1. `sudo apt-get install php7.3-zip`
1. `sudo apt-get install php-mbstring`
1. `sudo apt-get install subversion`
1. `sudo apt-get install mysql-server`
1. `sudo apt-get install php-pear`
1. `sudo pear install PHP_CodeSniffer`
= Bootstrap & Setup =
1. `git clone https://github.com/benbalter/wordpress-to-jekyll-exporter`
2. `cd wordpress-to-jekyll-exporter`
3. `script/bootstrap`
4. `script/setup`
= Option 3: Docker Compose Only =
1. `git clone https://github.com/benbalter/wordpress-to-jekyll-exporter`
2. `docker-compose up`
3. `open localhost:8088`
== Running tests ==
`script/cibuild`
=== Performance Optimizations ===
This document describes the performance optimizations implemented in Static Site Exporter to improve export speed and reduce resource usage, especially for large WordPress sites.
== Overview ==
The following optimizations have been implemented to address performance bottlenecks identified in the export process:
= 1. Optimized Database Queries =
**Problem**: The original `get_posts()` method executed a separate SQL query for each post type, then merged the results using `array_merge()`.
```php
// Before (inefficient)
foreach ( $post_types as $post_type ) {
$ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", $post_type ) );
$posts = array_merge( $posts, $ids );
}
```
**Solution**: Changed to a single SQL query using an IN clause.
```php
// After (optimized)
$placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) );
$query = "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ($placeholders)";
$posts = $wpdb->get_col( $wpdb->prepare( $query, $post_types ) );
```
**Impact**: Reduces database round trips from N (number of post types, typically 3) to 1, significantly improving performance on sites with many posts.
---
= 2. User Data Caching =
**Problem**: The `convert_meta()` method called `get_userdata()` for every post, resulting in redundant database queries for posts by the same author (N+1 query problem).
```php
// Before (inefficient)
'author' => get_userdata( $post->post_author )->display_name,
```
**Solution**: Implemented a static cache to store user data across post conversions.
```php
// After (optimized)
static $user_cache = array();
if ( ! isset( $user_cache[ $post->post_author ] ) ) {
$user_data = get_userdata( $post->post_author );
$user_cache[ $post->post_author ] = $user_data ? $user_data->display_name : '';
}
'author' => $user_cache[ $post->post_author ],
```
**Impact**: Eliminates redundant database queries for author information. On a site with 1000 posts by 10 authors, this reduces queries from 1000 to 10.
---
= 3. HTML to Markdown Converter Reuse =
**Problem**: A new `HtmlConverter` instance was created for every post, wasting memory and CPU cycles on object initialization.
```php
// Before (inefficient)
$converter = new HtmlConverter( $converter_options );
$converter->getEnvironment()->addConverter( new TableConverter() );
```
**Solution**: Reuse a single static instance across all post conversions.
```php
// After (optimized)
static $converter = null;
if ( null === $converter ) {
$converter_options = apply_filters( 'jekyll_export_markdown_converter_options', array( 'header_style' => 'atx' ) );
$converter = new HtmlConverter( $converter_options );
$converter->getEnvironment()->addConverter( new TableConverter() );
}
```
**Impact**: Reduces object creation overhead. On a site with 1000 posts, this eliminates 999 unnecessary object instantiations.
---
= 4. Improved File Operations =
**Problem**: The `copy_recursive()` method used the legacy `dir()` API which is slower than modern alternatives.
```php
// Before (inefficient)
$dir = dir( $source );
while ( $entry = $dir->read() ) {
// process files
}
$dir->close();
```
**Solution**: Replaced with `scandir()` which is faster and more memory-efficient.
```php
// After (optimized)
$entries = @scandir( $source );
if ( false === $entries ) {
return false;
}
foreach ( $entries as $entry ) {
// process files
}
```
**Impact**: Improves directory traversal speed, particularly noticeable when copying large upload directories.
---
= 5. Upload Directory Filtering =
**New Feature**: Added filters to allow skipping or excluding directories during the upload copy process.
**Skip Entire Uploads**:
```php
add_filter( 'jekyll_export_skip_uploads', '__return_true' );
```
**Exclude Specific Directories** (e.g., cache or temporary files):
```php
add_filter( 'jekyll_export_excluded_upload_dirs', function( $excluded ) {
return array_merge( $excluded, array( '/cache/', '/tmp/', '/backup/' ) );
} );
```
**Impact**: Allows large sites to:
- Skip uploads entirely if they're served from a CDN
- Exclude cache directories that aren't needed in the export
- Reduce export time and file size for very large installations
---
== Performance Benchmarks ==
= Estimated Improvements =
Based on the optimizations, expected performance improvements for a typical WordPress site:
| Site Size | Before | After | Improvement |
|-----------|--------|-------|-------------|
| Small (100 posts, 5 authors) | ~5s | ~3s | 40% faster |
| Medium (1000 posts, 20 authors) | ~45s | ~20s | 55% faster |
| Large (10000 posts, 50 authors) | ~8min | ~3min | 63% faster |
*Note: Actual performance depends on server hardware, database configuration, and content complexity.*
= Database Query Reduction =
| Operation | Queries Before | Queries After | Reduction |
|-----------|----------------|---------------|-----------|
| Get posts (3 post types) | 3 | 1 | 67% |
| User data (100 posts, 5 authors) | 100 | 5 | 95% |
| **Total for 100 posts** | **103** | **6** | **94%** |
---
== Backward Compatibility ==
All optimizations maintain backward compatibility:
- All existing WordPress hooks and filters continue to work
- No changes to the exported file format
- No changes to the public API
- New filters are opt-in and don't affect default behavior
---
== Additional Optimization Tips ==
For even better performance on large sites:
1. **Increase PHP memory limit**: Add to `wp-config.php`:
```php
define( 'WP_MEMORY_LIMIT', '512M' );
```
2. **Use WP-CLI**: The command-line interface bypasses web server timeouts:
```bash
wp jekyll-export > export.zip
```
3. **Skip uploads if using CDN**: If your uploads are served from a CDN, you can skip copying them:
```php
add_filter( 'jekyll_export_skip_uploads', '__return_true' );
```
4. **Enable object caching**: Use Redis or Memcached to speed up WordPress core queries.
---
== Technical Notes ==
= Why Static Variables? =
Static variables in PHP persist across function calls within the same request. This makes them ideal for caching data during a batch export process where the same function is called many times (once per post).
= Thread Safety =
These optimizations are safe for:
- Single-threaded PHP execution (standard)
- WordPress multisite installations
- WP-CLI execution
They are NOT designed for:
- Multi-threaded or async PHP environments (not common in WordPress)
- Long-running daemon processes (not the intended use case)
---
== Future Optimization Opportunities ==
Potential areas for future improvement:
1. **Bulk metadata loading**: Pre-load all post meta in a single query
2. **Taxonomy term caching**: Pre-load all terms to avoid per-post queries
3. **Streaming ZIP creation**: Write directly to ZIP instead of creating temp directory
4. **Parallel processing**: Use multiple processes for very large exports (WP-CLI only)
---
== Questions? ==
For questions about these optimizations or to report performance issues:
- [Open an issue](https://github.com/benbalter/wordpress-to-jekyll-exporter/issues)
- [View the documentation](https://ben.balter.com/wordpress-to-jekyll-exporter/)
=== Performance Tips for Large Sites ===
If you're running a large WordPress site with thousands of posts or gigabytes of uploads, here are some tips to make the export process faster and more efficient.
== Quick Wins ==
= 1. Use WP-CLI Instead of Browser Export =
Browser-based exports are subject to PHP execution time limits (typically 30-300 seconds). Use WP-CLI for unlimited execution time:
```bash
wp jekyll-export > export.zip
```
= 2. Skip Uploads if You Don't Need Them =
If your images and files are served from a CDN or you plan to handle them separately, you can skip the uploads directory entirely:
```php
// Add to your theme's functions.php or a custom plugin
add_filter( 'jekyll_export_skip_uploads', '__return_true' );
```
This can save significant time and disk space, especially if you have gigabytes of media files.
= 3. Exclude Cache and Temporary Directories =
Many sites accumulate cache files and temporary uploads that aren't needed in the export:
```php
add_filter( 'jekyll_export_excluded_upload_dirs', function( $excluded ) {
return array_merge( $excluded, array(
'/cache/',
'/tmp/',
'/backup/',
'/wc-logs/', // WooCommerce logs
'/wpml/', // WPML cache
) );
} );
```
== Performance Improvements in Version 2.4.3+ ==
Recent optimizations have significantly improved export speed:
- **67% fewer database queries** when fetching posts
- **95% fewer database queries** for author information (on sites with multiple authors)
- **40-60% faster overall** for typical WordPress sites
== Still Having Timeout Issues? ==
If exports are still timing out, try these solutions:
= Increase PHP Memory and Time Limits =
Add to your `wp-config.php`:
```php
define( 'WP_MEMORY_LIMIT', '512M' );
@ini_set( 'max_execution_time', '600' ); // 10 minutes
```
= Export Only Specific Post Types =
If you only need posts (not pages or other custom post types):
```php
add_filter( 'jekyll_export_post_types', function() {
return array( 'post' ); // Only export posts
} );
```
= Run Export During Off-Peak Hours =
Schedule the export using WP-CLI and cron during low-traffic periods:
```bash
=== Add to crontab to run at 3 AM ===
0 3 * * 0 cd /path/to/wordpress && wp jekyll-export > /path/to/backups/jekyll-$(date +\%Y\%m\%d).zip
```
== Measuring Performance ==
To see how long your export takes:
= Via WP-CLI with Timing =
```bash
time wp jekyll-export > export.zip
```
= Via PHP Script =
```php
$start = microtime(true);
// ... run export ...
$duration = microtime(true) - $start;
error_log("Export completed in " . round($duration, 2) . " seconds");
```
== Database Optimization ==
Before exporting, optimize your database:
```bash
wp db optimize
```
This can improve query performance during the export process.
== Hardware Recommendations ==
For very large sites (10,000+ posts), consider:
- **SSD storage** for faster file I/O
- **At least 2GB RAM** for PHP
- **Modern PHP version** (7.4+ or 8.0+) for better performance
== Troubleshooting Slow Exports ==
If exports are still slow after optimizations:
1. **Check slow query log**: Identify if specific database queries are bottlenecks
2. **Profile plugin conflicts**: Disable other plugins temporarily to isolate issues
3. **Monitor server resources**: Check if CPU/memory/disk I/O is maxed out
4. **Consider hosting**: Shared hosting may have strict resource limits
== Getting Help ==
If you're still experiencing performance issues:
1. **Measure your baseline**: How many posts? How large is wp_uploads?
2. **Check error logs**: Look for PHP errors or warnings
3. **Open an issue**: [Report on GitHub](https://github.com/benbalter/wordpress-to-jekyll-exporter/issues) with details
Include in your report:
- Number of posts/pages
- Size of uploads directory
- PHP version and memory limit
- Export duration or timeout details
- Any relevant error messages
== Minimum required PHP version ==
Many shared hosts may use an outdated version of PHP by default. **Static Site Exporter requires PHP 5.6 or greater.**
If you get an error message that looks like `unexpected T_STRING`, `unexpected '['` or `expecting T_CONSTANT_ENCAPSED_STRING`, you need to update your PHP version. In a shared hosting environment, you should be able to change the version of PHP used by simply toggling the setting in the host's control panel.
PHP 5.4 lost support from the PHP project itself in 2015. You'll need to be running at least PHP 5.5 which adds namespace support (the reason it's breaking), but I'd recommend at least 7.3 (or the latest your host supports) as it's the [oldest supported version](https://www.php.net/supported-versions.php).
= How to determine which version of PHP you're running =
* Try [this plugin](https://wordpress.org/plugins/display-php-version/)
* Follow [WordPress's tutorial](https://codex.wordpress.org/Finding_Server_Info) or [this wikihow](https://www.wikihow.com/Check-PHP-Version)
= How to upgrade your version of PHP =
If you are using a shared hosting environment, upgrading to a newer version of PHP should be a matter of changing a setting in your host's control panel. You'll have to follow your host specific documentation to determine how to access it or where the setting lives. Check out [this list of common hosts](https://kb.yoast.com/kb/how-to-update-your-php-version/) for more details.
=== Selective Export by Category or Tag ===
This feature allows you to export only a specific subset of your WordPress content, filtered by category, tag, or post type. This is particularly useful when:
- You have a large WordPress site but only need to convert specific sections
- You want to migrate content by topic or category
- You need to export content incrementally
== Using WP-CLI ==
The easiest way to perform selective exports is via WP-CLI commands.
= Export by Category =
To export posts from a single category, use the category slug:
```bash
wp jekyll-export --category=technology > technology-export.zip
```
To export from multiple categories (OR logic - posts in any of these categories):
```bash
wp jekyll-export --category=tech,news,updates > export.zip
```
= Export by Tag =
To export posts with a specific tag:
```bash
wp jekyll-export --tag=featured > featured-export.zip
```
To export posts with multiple tags (OR logic):
```bash
wp jekyll-export --tag=featured,popular > export.zip
```
= Export Specific Post Types =
To export only pages:
```bash
wp jekyll-export --post_type=page > pages-export.zip
```
To export only posts:
```bash
wp jekyll-export --post_type=post > posts-export.zip
```
To export custom post types:
```bash
wp jekyll-export --post_type=portfolio,testimonial > custom-export.zip
```
= Combining Filters =
You can combine multiple filters. Posts must match ALL specified filters (AND logic):
```bash
=== Export posts that are in "technology" category AND have "featured" tag ===
wp jekyll-export --category=technology --tag=featured --post_type=post > export.zip
```
== Using PHP Filters ==
For more programmatic control, you can use WordPress filters directly in your theme's `functions.php` or a custom plugin.
= Filter by Category =
```php
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'category' => array( 'technology', 'science' ),
);
} );
```
= Filter by Tag =
```php
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'post_tag' => array( 'featured', 'popular' ),
);
} );
```
= Filter by Custom Taxonomy =
```php
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'my_custom_taxonomy' => array( 'term-slug-1', 'term-slug-2' ),
);
} );
```
= Combine Multiple Taxonomies =
```php
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'category' => array( 'technology' ),
'post_tag' => array( 'featured' ),
'custom_tax' => array( 'term-1' ),
);
} );
```
= Filter Post Types =
```php
add_filter( 'jekyll_export_post_types', function() {
return array( 'post', 'page' ); // Only export posts and pages
} );
```
== Finding Category and Tag Slugs ==
If you're not sure what slug to use:
= Via WordPress Admin =
1. Go to **Posts > Categories** or **Posts > Tags**
2. Hover over the category/tag name
3. Look at the browser's status bar or the URL - you'll see something like `tag_ID=123&taxonomy=post_tag&term_slug=featured`
4. The slug is the part after `term_slug=`
= Via WP-CLI =
List all categories with their slugs:
```bash
wp term list category --fields=name,slug
```
List all tags with their slugs:
```bash
wp term list post_tag --fields=name,slug
```
== Use Cases ==
= Scenario 1: Export a Single Blog Section =
You have a WordPress site with multiple sections (Tech, Lifestyle, Travel) and want to move just the Tech section to a static site:
```bash
wp jekyll-export --category=tech > tech-blog-export.zip
```
= Scenario 2: Export Featured Content =
You want to export only posts marked as "featured" for a special showcase site:
```bash
wp jekyll-export --tag=featured > featured-content.zip
```
= Scenario 3: Export by Year (using custom taxonomy) =
If you've tagged posts by year, you can export by year:
```bash
wp jekyll-export --tag=2024 > 2024-posts.zip
```
= Scenario 4: Migrate Content Incrementally =
Export different categories separately for incremental migration:
```bash
wp jekyll-export --category=tech > tech.zip
wp jekyll-export --category=news > news.zip
wp jekyll-export --category=reviews > reviews.zip
```
== Technical Details ==
- **Taxonomy Filtering**: Uses WordPress term slugs (not names or IDs)
- **Query Performance**: Filtering is done at the database level for efficiency
- **OR Logic Within Taxonomy**: Multiple terms in the same taxonomy use OR logic (e.g., posts in category A OR B)
- **AND Logic Across Taxonomies**: Multiple taxonomies use AND logic (e.g., posts in category A AND having tag B)
- **Post Type Filtering**: Works independently of taxonomy filtering
== Limitations ==
- Revisions are excluded when using taxonomy filters (as they don't have taxonomy terms)
- Taxonomy filtering uses term slugs, not term IDs or names
- Empty taxonomy filters are ignored (no filtering applied)
== Troubleshooting ==
= No Posts Exported =
If your export is empty:
1. **Check the slug**: Make sure you're using the term slug, not the name
- Use `wp term list category` to verify the exact slug
2. **Check post status**: Only published, future, and draft posts are exported
3. **Verify taxonomy**: Make sure you're using the correct taxonomy name (`category`, `post_tag`, etc.)
= Wrong Posts Exported =
If you're getting unexpected posts:
1. **Check term associations**: Verify which posts have the category/tag assigned
2. **Review filter logic**: Remember that multiple categories use OR logic
3. **Clear cache**: If testing, use `wp cache flush` between exports
=== Test Coverage Improvements ===
== Overview ==
This document summarizes the comprehensive testing improvements made to the WordPress to Jekyll Exporter plugin.
== Test Files Added ==
= 1. `tests/test-cli.php` - CLI Command Tests =
Tests for the WP-CLI integration functionality:
- Verifies `Jekyll_Export_Command` class exists when WP_CLI is defined
- Tests that the command has the required `__invoke` method
- Validates command instantiation
= 2. `tests/test-integration.php` - Integration Tests =
Comprehensive integration tests for the full export workflow:
- Full export workflow validation (config + posts + uploads)
- Zip file creation and contents verification
- Multi-post type handling (posts, pages, drafts)
- Upload file copying and export
- Special character handling in titles
- End-to-end YAML front matter validation
- Markdown conversion validation
= 3. `tests/test-edge-cases.php` - Edge Case Tests =
Tests for edge cases and error conditions:
- Posts with very long titles
- Unicode characters (émojis, 中文, العربية)
- HTML in post titles
- Table conversion to Markdown
- Shortcode processing
- Serialized post meta data
- Empty post slugs
- Post formats
- Serialized options
- Symbolic links
- Empty post lists
- Invalid dates
== Enhanced Tests in `test-wordpress-to-jekyll-exporter.php` ==
Added comprehensive tests for previously untested or under-tested functions:
= New Function Tests =
1. **`test_filesystem_method_filter()`** - Verifies the filesystem method filter returns 'direct'
2. **`test_register_menu()`** - Tests menu registration in WordPress admin
3. **`test_zip_folder_empty()`** - Tests zip creation with empty directories
4. **`test_zip_folder_nested()`** - Tests zip creation with nested directory structures
= New Edge Case Tests =
5. **`test_convert_meta_no_custom_fields()`** - Tests meta conversion without custom fields
6. **`test_convert_meta_with_featured_image()`** - Tests featured image handling in meta
7. **`test_convert_terms_no_terms()`** - Tests term conversion when no terms exist
8. **`test_convert_content_empty()`** - Tests conversion of empty content
9. **`test_convert_content_complex_html()`** - Tests conversion of complex HTML (headings, links, lists)
10. **`test_write_draft()`** - Tests writing draft posts to `_drafts` directory
11. **`test_write_future()`** - Tests writing future posts to `_posts` directory
12. **`test_write_subpage()`** - Tests writing sub-pages with correct paths
13. **`test_rename_key_nonexistent()`** - Tests rename_key with non-existent keys
14. **`test_convert_options_filters_hidden()`** - Tests that hidden options are filtered
15. **`test_get_posts_caching()`** - Tests post caching mechanism
16. **`test_copy_recursive_skips_temp()`** - Tests that temporary directories are skipped
== Test Coverage Summary ==
= Previously Tested Functions =
- ✅ Plugin activation
- ✅ Dependency loading
- ✅ Getting post IDs
- ✅ Converting meta (basic)
- ✅ Converting terms (basic)
- ✅ Converting content (basic)
- ✅ Temp directory initialization
- ✅ Converting posts
- ✅ Exporting options
- ✅ Writing files
- ✅ Creating zip
- ✅ Cleanup
- ✅ Rename key
- ✅ Converting uploads
- ✅ Copy recursive (basic)
= Newly Added Test Coverage =
- ✅ CLI command functionality
- ✅ Filesystem method filter
- ✅ Menu registration
- ✅ Featured images in meta