#!/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 my $blog_count = sub { $blogs * $weight_factor }; my $authors_per_blog = sub { $authors * $weight_factor }; my $categories_per_blog = sub { $cats + int(rand(10)) * $weight_factor }; my $entries_per_blog = sub { $entries + int(rand(30)) * $weight_factor }; my $notifications_per_blog = sub { 3 + int(rand(20)) }; my $bans_per_blog = sub { 2 + int(rand(10)) }; my $comments_per_entry = sub { int(rand(10)) * $weight_factor }; my $tags_per_entry = sub { int(rand(5)) * $weight_factor }; my $pings_per_trackback = sub { int(rand(10)) * $weight_factor }; 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, 'MT::IPBanList' => $bans_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('ping_technorati', RAND_BOOL, 3.1), 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::IPBanList' => { table => 'ipbanlist', as_of => 1.4, # ?? fields => [ field('blog_id', sub { $this_parent->id } ), field('ip', \&random_ip), ], }, '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 = ; 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; } } } 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; } last unless @fields; } $obj->save or die $obj->errstr; } 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; } } } } 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'"); } } $roles[int(rand(scalar @roles))]; } 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; } 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 EnE Lets you specify the number of weblogs to create (default is 1). =item --authors EnE Specifies the number of user records to create (default is 3). =item --entries EnE Specifies the number of entries (approximately) to create (default is 500). =item --categories EnE Specifies the number of categories (approximately) to create (default is 5). =item --years EnE Specifies the number of years to post-date entry creation (default is 1). =back