/tools/populate
http://github.com/openmelody/melody · Perl · 797 lines · 706 code · 56 blank · 35 comment · 33 complexity · c745eb7ad7dfe516e4a9896375e18b19 MD5 · raw file
- #!/usr/bin/perl
- # FIXME: authenticated commenters aren't created
- use strict;
- use warnings;
- use lib 'extlib', 'lib';
- use Data::Dumper;
- use Getopt::Long;
- use MT;
- use MT::Author;
- use MT::Permission;
- my $mt = MT->new() or die "No MT object " . MT->errstr();
- my $blogs = 1;
- my $destroy = 0;
- my $authors = 3;
- my $entries = 500;
- my $cats = 5;
- # this can be used to scale the default amount of data generated
- my $weight_factor = 1;
- my $bypass_confirm = 0;
- my $help = 0;
- my $silent = 0;
- my $years = 1;
- my $result = GetOptions(
- "authors=i" => \$authors,
- "blogs=i" => \$blogs,
- "categories=i" => \$cats,
- "entries=i" => \$entries,
- "weight=i" => \$weight_factor,
- "destroy" => \$destroy,
- "yes-really" => \$bypass_confirm,
- "help|?" => \$help,
- "silent" => \$silent,
- "years=i" => \$years,
- );
- if ($help) {
- eval { require Pod::Usage };
- die "No Pod::Usage available. Use the Source, Luke." if $@;
- Pod::Usage::pod2usage( -exitstatus => 1 );
- }
- use vars qw($this_blog $this_iter $this_obj $this_parent $tmpl_list);
- # define objects and such...
- use constant RAND_BOOL => [ 0, 1 ];
- my @word_pool = qw(
- cool tech blogging politics interesting video
- perl python ruby web2.0 sixapart reference link audio
- php javascript voip news
- );
- # Randomize the random number generator to make things nice and consistent.
- srand(1);
- # these are all the numbers that control how many of what are generated
- sub weighted { +shift *$weight_factor }
- sub randint { int( rand( +shift ) ) }
- my $notifications_per_blog = sub { 3 + randint(20) };
- my $blog_count = sub { weighted($blogs) };
- my $authors_per_blog = sub { weighted($authors) };
- my $categories_per_blog = sub { weighted( $cats + randint(10) ) };
- my $entries_per_blog = sub { weighted( $entries + randint(30) ) };
- my $comments_per_entry = sub { weighted( randint(10) ) };
- my $tags_per_entry = sub { weighted( randint(5) ) };
- my $pings_per_trackback = sub { weighted( randint(10) ) };
- my %defs = (
- 'MT::Blog' => {
- table => 'blog',
- as_of => 1.0,
- needs => [
- 'MT::Author' => $authors_per_blog,
- 'MT::Category' => $categories_per_blog,
- 'MT::Entry' => $entries_per_blog,
- 'MT::Template' => \&template_loader,
- 'MT::Notification' => $notifications_per_blog
- ],
- fields => [
- field( 'name', \&random_blog_name ),
- field(
- 'description',
- sub {
- random_paragraph( int( rand(1) ) );
- }
- ),
- field(
- 'archive_type',
- [
- 'Individual,Category,Monthly', 'Individual,Monthly',
- 'Daily,Category,Monthly'
- ]
- ),
- field(
- 'archive_type_preferred',
- sub {
- my @at = split /,/, $this_obj->archive_type;
- $at[ int( rand( scalar @at ) ) ];
- }
- ),
- field( 'site_path', sub {"/www/blog_$this_iter"} ),
- field(
- 'archive_path',
- sub { $this_obj->site_path . '/archives' }
- ),
- field( 'site_url', sub {"http://www.blog${this_iter}.com/"} ),
- field( 'archive_url', sub { $this_obj->site_url . 'archives/' } ),
- field( 'days_on_index', [ 7, 10, 14 ] ),
- field( 'entries_on_index', [ 10, 15, 20 ], 3.2 ),
- field(
- 'file_extension',
- [ '', '', 'html', 'asp', 'jsp', 'php' ]
- ),
- field( 'email_new_comments', RAND_BOOL ),
- field( 'allow_comment_html', RAND_BOOL ),
- field( 'autolink_urls', RAND_BOOL ),
- field(
- 'server_offset',
- [ -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 ]
- ),
- field( 'ping_blogs', RAND_BOOL, 2.5 ),
- field( 'ping_others', RAND_BOOL, 2.5 ),
- field( 'autodiscover_links', RAND_BOOL, 2.5 ),
- field( 'convert_paras', RAND_BOOL ),
- field( 'convert_paras_comments', RAND_BOOL ),
- field( 'sanitize_spec', [ 'a href,br/,p', '' ], 2.6 ),
- field(
- 'cc_license',
- sub {
- if ( MT->version_number < 3.16 ) {
- [
- '', 'by-nc-sa', 'nd', 'by',
- 'pd', 'sa', 'by-nd-nc', 'by-nc'
- ];
- }
- else {
- [ '', '' ], # FIXME: URLs for new cc licenses
- }
- },
- 2.6
- ),
- field( 'is_dynamic', 0, 2.6 ),
- field( 'require_comment_emails', RAND_BOOL, 3.0 ),
- field( 'allow_unreg_comments', RAND_BOOL, 3.0 ),
- field( 'allow_reg_comments', RAND_BOOL, 3.0 ),
- field( 'manual_approve_commenters', RAND_BOOL, 3.0 ),
- field( 'old_style_archive_links', RAND_BOOL, 3.0 ),
- field( 'moderate_unreg_comments', RAND_BOOL, 3.0 ),
- field( 'remote_auth_token', [ '', 'some_remote_token' ], 3.0 ),
- field( 'children_modified_on', undef, 3.1 ),
- field(
- 'custom_dynamic_templates',
- [ 'none', 'archives', 'custom' ],
- 3.1
- ),
- ],
- },
- 'MT::Entry' => {
- table => 'entry',
- as_of => 1.0,
- needs => [
- 'MT::Comment' => sub {
- $this_obj->allow_comments ? $comments_per_entry->() : 0;
- },
- 'MT::Trackback' => sub { $this_obj->allow_pings ? 1 : 0 },
- #'MT::Placement' => [ 0, 1, 1, 1, 2 ],
- ],
- fields => [
- field( 'blog_id', sub { $this_blog->id } ),
- field( 'author_id', \&random_author ),
- field( 'title', \&random_title ),
- field( 'text', sub { random_paragraph( 2 + int( rand(3) ) ) } ),
- field( 'text_more', sub { random_paragraph( int( rand(10) ) ) } ),
- field( 'excerpt', '' ),
- field( 'status', [ 1, 2, 2, 2, 2, 4 ] ),
- field( 'keywords', '', 2.5 ),
- field( 'tangent_cache', '', 2.5 ),
- field( 'allow_pings', [ 0, 1 ] ),
- field( 'allow_comments', [ 0, 1, 1, 1 ] ),
- field(
- 'convert_breaks',
- sub {
- MT->version_number < 2.6
- ? [ 0, 1 ]
- : [ '__default__', '0' ];
- }
- ),
- field( 'created_on', \&random_date, 1.0 ),
- field( 'basename', undef, 3.0 ), # auto-set upon save
- field( 'tags', \&assign_random_tags, 3.3 ),
- field( 'categories', \&assign_random_categories, 1.0 ),
- ],
- },
- 'MT::Author' => {
- table => 'author',
- as_of => 1.0,
- needs => [ 'MT::Permission' => \&permission_check, ],
- fields => [
- field( 'name', \&random_username ),
- field( 'nickname', \&random_name ),
- field( 'email', \&random_email ),
- field( 'url', \&random_url ),
- field(
- 'type',
- sub {
- ref $this_parent eq 'MT::Comment' ? 2 : 1;
- },
- 3.0
- ),
- field(
- 'password',
- sub { $this_obj->type == 1 ? crypt( 'Nelson', 'xx' ) : '' }
- ),
- field(
- 'preferred_language',
- sub {
- $this_obj->type == 1
- ? ( $this_obj->name eq 'Melody'
- ? 'en_US'
- : [ 'en_US', 'en_US', 'en-us', 'fr', 'de' ] )
- : undef;
- },
- 2.5
- ),
- field(
- 'can_create_blog',
- sub { $this_obj->type == 1 ? RAND_BOOL : undef }
- ),
- field(
- 'is_superuser',
- sub { $this_obj->type == 1 && $this_iter == 1 ? 1 : undef }
- ,
- 3.2
- ),
- field(
- 'can_view_log',
- sub { $this_obj->type == 1 ? RAND_BOOL : undef }
- ),
- field( 'hint', sub { $this_obj->type == 1 ? 'hint' : undef } ),
- field( 'public_key', undef ),
- field(
- 'api_password',
- sub { $this_obj->type == 1 ? 'Nelson' : undef },
- 3.2
- ),
- field( 'remote_auth_username', undef, 3.0 ),
- field( 'remote_auth_token', undef, 3.0 ),
- ],
- },
- 'MT::Category' => {
- table => 'category',
- as_of => 1.0,
- needs => [ 'MT::Trackback' => [ 0, 0, 0, 0, 0, 1 ], ],
- fields => [
- field( 'blog_id', sub { $this_blog->id } ),
- field( 'label', \&random_category ),
- field( 'author_id', ),
- field(
- 'description',
- sub {
- random_paragraph( int( rand(1) ) );
- }
- ),
- field( 'allow_pings', [ 0, 0, 0, 0, 1 ], 1.0 ), # FIXME: version #
- field( 'parent', [ 0, 0, 0, \&random_category_id ], 3.1 ),
- ],
- },
- 'MT::Permission' => {
- table => 'permission',
- as_of => 1.0,
- fields => [
- field( 'author_id', sub { $this_parent->id } ),
- field( 'blog_id', sub { $this_blog->id } ),
- field(
- 'permissions',
- sub {
- $this_parent->type == 1
- ? ( $this_iter == 1 ? 14334 : &random_role )
- : [ 0, 1 ];
- }
- ),
- field( 'entry_prefs', undef ),
- ],
- },
- 'MT::Comment' => {
- table => 'comment',
- as_of => 1.0,
- needs => [ 'MT::Author' => [ 0, 0, 0, 0, 1 ], ],
- fields => [
- field( 'blog_id', sub { $this_parent->blog_id } ),
- field( 'entry_id', sub { $this_parent->id } ),
- field( 'author', \&random_name ),
- field( 'commenter_id', \&random_commenter, 3.0 ),
- field( 'email', \&random_email ),
- field( 'url', \&random_url ),
- field(
- 'text',
- sub {
- random_paragraph( 1 + int( rand(2) ) );
- }
- ),
- field( 'ip', \&random_ip ),
- field( 'visible', [ 0, 1, 1, 1, 1 ], 3.0 ),
- field( 'junk_status', [ -1, 1, 1, 1, 1 ], 3.2 ),
- field(
- 'junk_score',
- sub { $this_obj->junk_status == -1 ? rand(2) * -1 : 0 },
- 3.2
- ),
- field(
- 'junk_log',
- sub {
- $this_obj->junk_status == -1
- ? 'Random ('
- . $this_obj->junk_score
- . "): Randomly scored as junk\n"
- : '';
- },
- 3.2
- ),
- ],
- },
- 'MT::Trackback' => {
- table => 'trackback',
- as_of => 1.2, # ??
- needs => [ 'MT::TBPing' => $pings_per_trackback, ],
- fields => [
- field( 'blog_id', sub { $this_parent->blog_id } ),
- field( 'title', \&random_title ),
- field( 'description', undef ),
- field( 'rss_file', undef ),
- field( 'url', undef ),
- field(
- 'entry_id',
- sub {
- ref $this_parent eq 'MT::Entry' ? $this_parent->id : 0;
- }
- ),
- field(
- 'category_id',
- sub {
- ref $this_parent eq 'MT::Category' ? $this_parent->id : 0;
- }
- ),
- field( 'is_disabled', 0 ),
- field( 'passphrase', undef ),
- ],
- },
- 'MT::TBPing' => {
- table => 'tbping',
- as_of => 1.2, # ??
- fields => [
- field( 'blog_id', sub { $this_parent->blog_id } ),
- field( 'tb_id', sub { $this_parent->id } ),
- field( 'title', \&random_title ),
- field( 'excerpt', \&random_paragraph ),
- field( 'source_url', \&random_url ),
- field( 'ip', \&random_ip ),
- field( 'blog_name', \&random_blog_name ),
- field( 'visible', [ 0, 1, 1, 1, 1 ], 3.0 ),
- field( 'junk_status', [ -1, 1, 1, 1, 1 ], 3.2 ),
- field(
- 'junk_score',
- sub {
- $this_obj->junk_status == -1 ? rand(2) * -1 : 0;
- },
- 3.2
- ),
- field(
- 'junk_log',
- sub {
- $this_obj->junk_status == -1
- ? 'Random ('
- . $this_obj->junk_score
- . "): Randomly scored as junk\n"
- : '';
- },
- 3.2
- ),
- ],
- },
- 'MT::Template' => {
- table => 'template',
- as_of => 1.0,
- # fields => [
- # field('build_dynamic', [], 3.1),
- # ],
- },
- 'MT::TemplateMap' => { table => 'templatemap', as_of => 2.0, },
- 'MT::Log' => {
- table => 'log',
- as_of => 1.0,
- # fields => [
- # field('blog_id', [], 3.2),
- # ],
- },
- 'MT::Session' => { table => 'session', as_of => 3.0, },
- 'MT::Notification' => {
- table => 'notification',
- as_of => 1.0,
- fields => [
- field( 'blog_id', sub { $this_parent->id } ),
- field( 'name', \&random_name ),
- field( 'email', \&random_email ),
- field( 'url', \&random_url ),
- ],
- },
- 'MT::Placement' => { table => 'placement', as_of => 2.0, },
- 'MT::FileInfo' => { table => 'fileinfo', as_of => 3.1, },
- 'MT::PluginData' => { table => 'plugindata', as_of => 2.6, },
- 'MT::Tag' => { table => 'tag', as_of => 3.3, },
- 'MT::ObjectTag' => { table => 'objecttag', as_of => 3.3, },
- # 'MT::Config' => {
- # table => 'config',
- # as_of => 3.2,
- # },
- );
- if ($destroy) {
- unless ($bypass_confirm) {
- print <<'WARNING';
- ** WARNING **
- You've chosen to clear all existing data in your database and populate
- it with random, generated data. The system administrator login will be
- Melody/Nelson. This tool is mainly used for testing MT performance and
- usability with varying sizes of data.
- To continue, type OK and press enter.
- WARNING
- my $confirm = <STDIN>;
- chomp $confirm;
- exit unless $confirm eq 'OK';
- }
- clean();
- }
- # kick off the build process
- build( 'MT::Blog', $blog_count->(), 0 );
- sub field {
- my ( $name, $inputs, $as_of ) = @_;
- my $def = { inputs => $inputs };
- $def->{as_of} = $as_of if defined $as_of;
- ( $name, $def );
- }
- sub clean {
- # CLEAR ANY EXISTING DATA
- print "Clearing existing data...\n" unless $silent;
- my $driver = MT::Object->driver();
- if ( my $dbh = $driver->rw_handle ) {
- my @tables = map { $defs{$_}{table} } keys %defs;
- foreach (@tables) {
- print "\tWiping $_\n" unless $silent;
- $dbh->do("delete from mt_$_");
- }
- if ( $driver->isa('MT::ObjectDriver::Driver::DBD::Pg') ) {
- # reset sequences
- $dbh->do("drop sequence mt_${_}_id") foreach @tables;
- $dbh->do("create sequence mt_${_}_id") foreach @tables;
- }
- }
- } ## end sub clean
- sub build {
- my ( $pkg, $num, $depth ) = @_;
- my $def = $defs{$pkg};
- return unless $def->{as_of} <= MT->version_number();
- print( ( "\t" x $depth ) . "Creating $num $pkg objects...\n" )
- unless $silent;
- local $this_obj = $this_obj;
- local $this_parent = $this_obj;
- local $this_iter;
- for ( my $i = 0; $i < $num; $i++ ) {
- $this_iter = $i + 1; # always indexed from 1...
- my $obj;
- # special case for MT::Author...
- if ( $pkg eq 'MT::Author' ) {
- my $name = random_username();
- my $user = MT::Author->load( { name => $name, type => 1 } );
- if ($user) {
- $obj = $user;
- }
- }
- unless ($obj) {
- no strict 'refs';
- no warnings;
- eval("require $pkg") unless defined *{"$pkg::"};
- $obj = $pkg->new();
- }
- $this_obj = $obj;
- if ( !$obj->id ) {
- my @fields = @{ $def->{fields} };
- while ( my ( $fld, $fld_def ) = ( shift @fields, shift @fields ) )
- {
- if (
- ( !exists $fld_def->{as_of} )
- || ( $fld_def->{as_of}
- && ( $fld_def->{as_of} <= $MT::VERSION ) )
- )
- {
- my $input = $fld_def->{inputs};
- while ( ref $input ) {
- if ( ref $input eq 'ARRAY' ) {
- $input
- = $input->[ int( rand( scalar @$input ) ) ];
- }
- if ( ref $input eq 'CODE' ) {
- $input = $input->();
- }
- }
- print "\tsetting $fld to [$input]\n"
- if !$silent && defined $input;
- $obj->$fld($input) if defined $input;
- } ## end if ( ( !exists $fld_def...))
- last unless @fields;
- } ## end while ( my ( $fld, $fld_def...))
- $obj->save or die $obj->errstr;
- } ## end if ( !$obj->id )
- if ( $pkg eq 'MT::Blog' ) {
- $this_blog = $obj; # assign for use in other objects...
- }
- # process needs...
- if ( my $needs = $def->{needs} ) {
- my @needs = @$needs;
- while ( my ( $pkg, $num ) = ( shift @needs, shift @needs ) ) {
- while ( ref $num ) {
- if ( ref $num eq 'ARRAY' ) {
- $num = $num->[ int( rand( scalar @$num ) ) ];
- }
- if ( ref $num eq 'CODE' ) {
- $num = $num->();
- }
- }
- if ($num) {
- build( $pkg, $num, $depth + 1 );
- }
- last unless @needs;
- }
- }
- } ## end for ( my $i = 0; $i < $num...)
- } ## end sub build
- sub permission_check {
- my $author = $this_obj;
- my $blog = $this_blog;
- require MT::Permission;
- my $num = MT::Permission->count(
- { author_id => $author->id, blog_id => $blog->id } );
- $num ? 0 : 1;
- }
- sub template_loader {
- my $blog = $this_blog;
- print "\tCreating templates...\n" unless $silent;
- $blog->create_default_templates();
- 0;
- }
- ## some random routines
- sub random_name {
- $this_iter == 1 ? 'Melody Nelson' : 'Joe ' . $this_iter;
- }
- sub random_email {
- "user" . $this_iter . '@gmail.com';
- }
- sub random_paragraph {
- my $num = shift;
- $num ||= 1 unless defined $num;
- ( "Lorem ipsum blah blah blah.\n\n" x $num ) || '';
- }
- sub random_username {
- $this_iter == 1 ? 'Melody' : 'user' . $this_iter;
- }
- sub random_blog_name {
- 'Weblog ' . $this_iter;
- }
- sub random_category_id {
- }
- sub random_category {
- 'Category ' . $this_iter;
- }
- sub random_title {
- 'Title for ' . ( ref $this_obj ) . ' #' . $this_iter;
- }
- sub random_commenter {
- my @c = MT::Author->load( { type => 2 } );
- return unless @c;
- my $c = $c[ int( rand( scalar @c ) ) ];
- $c->id;
- }
- sub random_author {
- my @a = MT::Author->load( { type => 1 } );
- my $a = $a[ int( rand( scalar @a ) ) ];
- $a->id;
- }
- sub random_role {
- # [ 1, 'comment', 'Post Comments', ],
- # [ 4096, 'administer_blog', 'Blog Administrator'],
- # [ 2, 'post', 'Post', ],
- # [ 4, 'upload', 'Upload File', ],
- # [ 8, 'edit_all_posts', 'Edit All Posts', ],
- # [ 16, 'edit_templates', 'Manage Templates', ],
- ## [ 32, 'edit_authors', 'Edit Authors & Permissions', ],
- # [ 64, 'edit_config', 'Configure Weblog', ],
- # [ 128, 'rebuild', 'Rebuild Files', ],
- # [ 256, 'send_notifications', 'Send Notifications', ],
- # [ 512, 'edit_categories', 'Manage Categories', ],
- # [ 1024, 'edit_notifications', 'Manage Notification List' ],
- # [ 2048, 'not_comment', ''], # not a real permission but a denial thereeof
- # [ 8192, 'view_blog_log', 'View Activity Log For This Weblog']
- my @roles;
- if ( $this_parent->type == 2 ) {
- @roles = (
- "'comment'", "'comment'",
- "'comment'", "'comment'",
- "'not_comment'"
- );
- }
- else {
- if ( MT->version_number >= 3.2 ) {
- push @roles, ( "'post'", "'post'", "'post'", "'post'" );
- push @roles, ("'post','upload'");
- push @roles, ("'post','upload','edit_all_posts'");
- push @roles,
- ("'edit_templates','edit_all_posts','post','upload'");
- push @roles,
- (
- "'edit_config','edit_templates','edit_all_posts','post','upload'"
- );
- push @roles,
- (
- "'edit_categories','edit_config','edit_templates','edit_all_posts','post','upload'"
- );
- push @roles, ("'view_blog_log','edit_config'");
- }
- else {
- push @roles, ( "'post'", "'post'", "'post'", "'post'" );
- push @roles, ("'post','upload'");
- push @roles, ("'edit_all_posts','post','upload'");
- push @roles,
- ("'edit_templates','edit_all_posts','post','upload'");
- push @roles,
- (
- "'edit_config','edit_templates','edit_all_posts','post','upload'"
- );
- push @roles,
- (
- "'edit_categories','edit_templates','edit_all_posts','post','upload'"
- );
- push @roles, ("'edit_config','edit_authors','edit_all_posts'");
- }
- } ## end else [ if ( $this_parent->type...)]
- $roles[ int( rand( scalar @roles ) ) ];
- } ## end sub random_role
- sub random_url {
- 'http://www.example.com/user' . $this_iter . '/';
- }
- sub random_ip {
- "192.168.0." . $this_iter;
- }
- sub assign_random_tags {
- my @tags;
- my $count = $tags_per_entry->();
- for ( my $i = 0; $i < $count; $i++ ) {
- push @tags, $word_pool[ rand( scalar @word_pool ) ];
- }
- $this_obj->tags(@tags);
- undef;
- }
- sub assign_random_categories {
- my @count = ( 0, 1, 1, 1, 1, 2 );
- # this is the last field; go ahead and save this object so
- # we have an entry id.
- $this_obj->save;
- my $count = $count[ int( rand( scalar @count ) ) ];
- require MT::Category;
- require MT::Placement;
- my @cats = MT::Category->load( { blog_id => $this_obj->blog_id } );
- my %used;
- $count = scalar @cats if scalar @cats < $count;
- for ( my $i = 0; $i < $count; $i++ ) {
- my $cat = $cats[ int( rand( scalar @cats ) ) ];
- next if $used{ $cat->id };
- my $place = MT::Placement->new;
- $place->category_id( $cat->id );
- $place->blog_id( $this_obj->blog_id );
- $place->entry_id( $this_obj->id );
- $place->is_primary( %used ? 0 : 1 );
- $place->save or die $place->errstr;
- $used{ $cat->id } = 1;
- }
- undef;
- } ## end sub assign_random_categories
- sub random_date {
- my @date = localtime( time - int( rand( $years * 365 * 24 * 60 * 60 ) ) );
- return
- sprintf( "%04d%02d%02d%02d%02d%02d",
- $date[5] + 1900,
- $date[4] + 1,
- $date[3], $date[2], $date[1], $date[0] );
- }
- 1;
- __END__
- =head1 NAME
- populate - A utility for creating pseudorandom weblog data.
- =head1 SYNOPSIS
- # Create 3 'random' weblogs with ~500 entries each
- tools/populate --blogs 3 --destroy --yes-really
- # Adds an addtional weblog
- tools/populate --blogs 1
- =head1 OPTIONS
-
- =over 4
- =item B<--destroy>
- Wipes your existing database before populating it. This switch is confirmed
- before execution. If this is done, the system admin becomes Melody Nelson.
- =item --yes-really
- Bypasses the confirmation for the '--destroy' switch.
- =item --blogs E<lt>nE<gt>
- Lets you specify the number of weblogs to create (default is 1).
- =item --authors E<lt>nE<gt>
- Specifies the number of user records to create (default is 3).
- =item --entries E<lt>nE<gt>
- Specifies the number of entries (approximately) to create (default is 500).
- =item --categories E<lt>nE<gt>
- Specifies the number of categories (approximately) to create (default is 5).
- =item --years E<lt>nE<gt>
- Specifies the number of years to post-date entry creation (default is 1).
- =back