diff --git a/docs/create.sql b/docs/create.sql index f6ebca1c7..1d999d6b8 100644 --- a/docs/create.sql +++ b/docs/create.sql @@ -1758,6 +1758,8 @@ CREATE TABLE SyndicatedContent ( maxHeadlines int(11) NOT NULL default '0', assetId varchar(22) NOT NULL default '', templateId varchar(22) NOT NULL default '', + displayMode varchar(20) NOT NULL default 'interleaved', + hasTerms varchar(255) NOT NULL default '', PRIMARY KEY (assetId) ) TYPE=MyISAM; diff --git a/docs/upgrades/upgrade_6.6.1-6.6.2.sql b/docs/upgrades/upgrade_6.6.1-6.6.2.sql index 3d6068b35..3bee65d9e 100644 --- a/docs/upgrades/upgrade_6.6.1-6.6.2.sql +++ b/docs/upgrades/upgrade_6.6.1-6.6.2.sql @@ -1,4 +1,6 @@ insert into webguiVersion values ('6.6.2','upgrade',unix_timestamp()); alter table Shortcut add disableContentLock int(11) NOT NULL default '0'; +alter table SyndicatedContent add column displayMode varchar(20) not null default 'interleaved'; +alter table SyndicatedContent add column hasTerms varchar(255) not null; update template set template='

Price:
Product Number:




Features


Benefits


Specifications

:

Accessories


Related Products


' where assetId='PBtmpl0000000000000056'; update template set template='^StyleSheet(^Extras;/adminConsole/adminConsole.css);\r\n^JavaScript(^Extras;/adminConsole/adminConsole.js);\r\n\r\n
\r\n \r\n \" target=\"_blank\">\"?\"\r\n \r\n
\r\n
\r\n \" border=\"0\" title=\"\" alt=\"\" />\r\n
\r\n
\r\n\"*\"\r\n
\r\n
\r\n \" border=\"0\" title=\"\" alt=\"\" />\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n  \r\n
\r\n \r\n \r\n \r\n \r\n \r\n
\r\n  \r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n \" >
\r\n
\r\n
\r\n
\r\n \">
\r\n ^AdminToggle;
\r\n ^LoginToggle;
\r\n
\r\n
\r\n\r\n' where assetId='PBtmpl0000000000000001'; diff --git a/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm b/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm index db347fc5c..627cedb1f 100644 --- a/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm +++ b/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm @@ -25,11 +25,43 @@ use WebGUI::Asset::Wobject; use XML::RSSLite; use LWP::UserAgent; use WebGUI::ErrorHandler; +use POSIX qw/floor/; my $hasEncode=1; eval " use Encode qw(from_to); "; $hasEncode=0 if $@; our @ISA = qw(WebGUI::Asset::Wobject); +=head1 NAME + +Package WebGUI::Asset::Wobject::SyndicatedContent + +=head1 DESCRIPTION + +Displays items and channels from RSS feeds. + +=head1 SYNOPSIS + +use WebGUI::Asset::Wobject::SyndicatedWobject; + + +=head1 METHODS + +These methods are available from this class: + +=cut + + +#------------------------------------------------------------------- + +=head2 definition ( definition ) + +Defines the properties of this asset. + +=head3 definition + +A hash reference passed in from a subclass definition. + +=cut #------------------------------------------------------------------- @@ -46,19 +78,31 @@ sub definition { }, rssUrl=>{ defaultValue=>undef, - fieldType=>"url" + fieldType=>"textarea" }, maxHeadlines=>{ fieldType=>"integer", defaultValue=>10 }, + displayMode=>{ + fieldType=>"text", + defaultValue=>"interleaved" + }, + hasTerms=>{ + fieldType=>"text", + defaultValue=>"" + } } }); return $class->SUPER::definition($definition); } +=head2 getName () + +Returns the icons associated with this asset. + +=cut -#------------------------------------------------------------------- sub getIcon { my $self = shift; my $small = shift; @@ -66,43 +110,81 @@ sub getIcon { return $session{config}{extrasURL}.'/assets/syndicatedContent.gif'; } -#------------------------------------------------------------------- + +=head2 getName () + +Returns the displayable name of this asset. + +=cut + sub getName { return WebGUI::International::get(2,"Asset_SyndicatedContent"); } -#------------------------------------------------------------------- +=head2 getUiLevel () + +Returns the displayable name of this asset. + +=cut + sub getUiLevel { return 6; } - #------------------------------------------------------------------- + +=head2 getEditForm () + +Returns the TabForm object that will be used in generating the edit page for this asset. + +=cut + sub getEditForm { my $self = shift; my $tabform = $self->SUPER::getEditForm(); $tabform->getTab("display")->template( -value=>$self->getValue('templateId'), - -namespace=>"SyndicatedContent" + -namespace=>"SyndicatedContent", -label=>WebGUI::International::get(72,"Asset_SyndicatedContent"), ); - $tabform->getTab("properties")->url( + $tabform->getTab("display")->selectList( + -name=>"displayMode", + -options=>{ + 'interleaved'=>WebGUI::International::get("interleaved","Asset_SyndicatedContent"), + 'grouped'=>WebGUI::International::get("grouped","Asset_SyndicatedContent"), + }, + -sortByValue=>1, + -label=>WebGUI::International::get("displayModeLabel","Asset_SyndicatedContent"), + -value=>[$self->getValue('displayMode')], + -subtext=>WebGUI::International::get("displayModeSubtext","Asset_SyndicatedContent"), + ); + $tabform->getTab("display")->text( + -name=>"hasTerms", + -label=>WebGUI::International::get("hasTermsLabel","Asset_SyndicatedContent"), + -maxlength=>255, + -value=>$self->getValue("hasTerms"), + ); + $tabform->getTab("properties")->textarea( -name=>"rssUrl", -label=>WebGUI::International::get(1,"Asset_SyndicatedContent"), -value=>$self->getValue("rssUrl") ); + $tabform->getTab("display")->integer( -name=>"maxHeadlines", -label=>WebGUI::International::get(3,"Asset_SyndicatedContent"), -value=>$self->getValue("maxHeadlines") ); + #$tabform->addTab("rss",WebGUI::International::get("rssTabName","Asset_SyndicatedContent")); + return $tabform; } #------------------------------------------------------------------- # strip all html tags from the given data structure. This is important to # prevent cross site scripting attacks -my $_stripped_html = {}; +#my $_stripped_html = {}; + sub _strip_html { #my ($data) = @_; @@ -129,6 +211,7 @@ sub _strip_html { #------------------------------------------------------------------- # horrible kludge to find the channel or item record # in the varying kinds of rss structures returned by RSSLite + sub _find_record { my ($data, $regex) = @_; @@ -166,6 +249,7 @@ sub _find_record { # the guid, if it is a link, always means the former. # Also copy the first few words of the description into the title if # there is no title + sub _normalize_items { #my ($items) = @_; @@ -195,7 +279,7 @@ sub _normalize_items { #------------------------------------------------------------------- sub _get_rss_data { - my ($url) = @_; + my $url = shift; my $cache = WebGUI::Cache->new("url:" . $url, "RSS"); my $rss_serial = $cache->get; @@ -223,19 +307,16 @@ sub _get_rss_data { } } - - - # there is no encode_entities_numeric that I can find, so I am - # commenting this out. -hal - # $xml =~ s#()(.*?)()#$1.encode_entities_numeric(decode_entities($2)).$3#ges; - # $xml =~ s#()(.*?)()#$1.encode_entities_numeric(decode_entities($2)).$3#ges; my $rss_lite = {}; eval { XML::RSSLite::parseXML($rss_lite, \$xml); }; if ($@) { - WebGUI::ErrorHandler::warn("error parsing rss for url $url"); + WebGUI::ErrorHandler::warn("error parsing rss for url $url :".$@); + #Returning undef on a parse failure is a change from previous behaviour, + #but it SHOULDN'T have a major effect. + return undef; } # make sure that the {channel} points to the channel @@ -243,6 +324,7 @@ sub _get_rss_data { # of items. without this voodoo, different versions of # rss return the data in different places in the data # structure. + $rss_lite = {channel => $rss_lite}; if (!($rss->{channel} = _find_record($rss_lite, qr/^channel$/))) { @@ -251,13 +333,19 @@ sub _get_rss_data { if (!($rss->{items} = _find_record($rss_lite, qr/^items?$/))) { WebGUI::ErrorHandler::warn("unable to find item info for url $url"); $rss->{items} = []; - } + } _strip_html($rss); $rss->{items} = [ $rss->{items} ] unless (ref $rss->{items} eq 'ARRAY'); _normalize_items($rss->{items}); - + + #Assign dates "globally" rather than when seen in a viewed feed. + #This is important because we can "filter" now and want to ensure we keep order + #correctly as new items appear. + _assign_rss_dates($rss->{items}); + + #Default to an hour timeout $cache->set(Storable::freeze($rss), 3600); } @@ -268,6 +356,7 @@ sub _get_rss_data { # rss items don't have a standard date, so timestamp them the first time # we see them and use that timestamp as the date. Periodically nuke the # whole database to keep the thing from growing too large + sub _assign_rss_dates { my ($items) = @_; @@ -285,113 +374,189 @@ sub _assign_rss_dates { } #------------------------------------------------------------------- -sub _get_aggregate_items { +# $items is the hashref to put items into. +# $rss_feeds is an arrayref of all the feeds in this wobject +# The only difference between an "interleaved" feed and a grouped feed +# is the order the items are output. + +sub _create_grouped_items{ + my($items,$rss_feeds,$maxHeadlines,$hasTermsRegex)=@_; + + _create_interleaved_items($items,$rss_feeds,$maxHeadlines,$hasTermsRegex); + + @$items=sort{$a->{'site_title'} cmp $b->{'site_title'}} @$items; + + #Loop through the items and output the "site_ + my $siteTitleTracker; + foreach(@$items){ + if($siteTitleTracker ne $_->{site_title}){ + $_->{new_rss_site}=1; + } + $siteTitleTracker=$_->{site_title}; + } +} + + +#------------------------------------------------------------------- +# Loop through the feeds for this wobject +# and push in the items in "interleaved mode" +# No need to return because we're doing everything by reference. + +sub _create_interleaved_items{ + my($items,$rss_feeds,$maxHeadlines,$hasTermsRegex)=@_; + my $items_remain = 1; + while((@$items < $maxHeadlines) && $items_remain){ + foreach my $rss(@$rss_feeds){ + $items_remain=0; + if(defined $rss->{items} + && @$items < $maxHeadlines + && (my $item = shift @{$rss->{items}}) + ){ + $item->{site_title}=$rss->{channel}->{title}; + $item->{site_link}=$rss->{channel}->{link}; + if(! $hasTermsRegex || _check_hasTerms($item,$hasTermsRegex)){ + push(@{$items},$item); + } + if (@{$rss->{items}}) { + $items_remain = 1; + } + } + } + } +} + +#------------------------------------------------------------------- +# Uses the regex constructed in _get_items (with the terms defaulting to OR) +# to see if the title or description associated with this item match the kinds +# of items we're looking for. +# + +sub _check_hasTerms{ + my($item,$hasTermsRegex)=@_; + my $to_check=$item->{title}.$item->{description}; + if( $to_check =~ /$hasTermsRegex/gism){ + return 1; + } else { + return 0; + } +} + + +################################################################################ +sub _make_regex{ + my $terms=shift; + my @terms=split(/,/,$terms); + return join("|",@terms); +} +############################# + + +#------------------------------------------------------------------- +# So- We're going to manage an "aggregate cache" that represents +# the rendering of the cumulative feeds in a Syndicated Wobject, +# but let each feed "fend for itself" based on URL in the cache. +# +# This means we can set up the hourly task to get and cache each +# individual feed WITHOUT having to re-request (undoubtedly the slowest +# part of every RSS parsing action is the network traffic) each feed +# when we re-render each aggregrate representation. +# +# If, however, a feed expires between hourly tasks, it will be re-requested and +# parsed per the usual. BUT, if a feed ever goes un-requested for more than an hour, +# then it's retrieval schedule will be taken over by the hourly task, and we'll +# be pre-seeding the RSS object cache automatically. +# +# Having the caching set up this way means we can re-use the same raw feed all over the site without +# having each wobject request it separately, ASSUMING the URL is the same. +# +# All the values that may have an effect on the composition of items +# are included in the cache key for the aggregate representation. + +sub _get_items { my $self = shift; my $urls = shift; my $maxHeadlines = shift; - my $cache = WebGUI::Cache->new("aggregate:" . - $self->get("rssUrl"), "RSS"); + my $displayMode=$self->getValue('displayMode'); + + my $hasTermsRegex=_make_regex($self->getValue('hasTerms')); + my $maxHeadlines=$self->getValue('maxHeadlines'); + + my $key=join(":",("aggregate", $displayMode,$hasTermsRegex,$maxHeadlines,$self->get("rssUrl"))); + + my $cache = WebGUI::Cache->new($key, "RSS"); my $items = Storable::thaw($cache->get()); + my @rss_feeds; if (!$items) { $items = []; - my $items_remain = 1; - my @rsss; for my $url (@{$urls}) { - push(@rsss, _get_rss_data($url)); + my $rss_info=_get_rss_data($url); + push(@rss_feeds, $rss_info) if($rss_info); } - - while ((@{$items} < $maxHeadlines) && $items_remain) { - $items_remain = 0; - for my $rss (@rsss) { - if ($rss->{items} && - (my $item = shift(@{$rss->{items}}))) { - push(@{$items}, - {site_title => $rss->{channel}->{title}, - site_link => $rss->{channel}->{link}, - link => $item->{link}, - title => $item->{title}, - description => $item->{description}, - }); - if (@{$rss->{items}}) { - $items_remain = 1; - } - } - } - } - - _assign_rss_dates($items); + + #Sort feeds in order by channel title. + #@rss_feeds=sort{$a->{channel}->{title} cmp $b->{channel}->{title}} @rss_feeds; + + if ($displayMode eq 'grouped') { + _create_grouped_items($items,\@rss_feeds,$maxHeadlines,$hasTermsRegex); + } else { + _create_interleaved_items($items,\@rss_feeds,$maxHeadlines,$hasTermsRegex); + } @{$items} = sort { $b->{date} <=> $a->{date} } @{$items}; - #if (@{$items} > $_aggregate_size) { - # @{$items} = @{$items}[0..($_aggregate_size-1)]; - #} - $cache->set(Storable::freeze($items), 3600); - } + } - return $items; -} - -#------------------------------------------------------------------- -# interleave stories from each feed, up to a total of $_aggregate_size -sub _view_aggregate_feed { - my $self = shift; - my $urls = shift; - my $maxHeadlines = shift; - my %var; - $var{'channel.title'} = $self->get("title"); - $var{'channel.description'} = $self->get("description"); - $var{item_loop} = $self->_get_aggregate_items($urls, $maxHeadlines); - - return $self->processTemplate(\%var,$self->get("templateId")); + #So return the item loop and the first RSS feed, because + #when we're parsing a single feed we can use that feed's title and + #description for channel.title, channel.link, and channel.description + return ($items,\@rss_feeds); } +=head2 view() -#------------------------------------------------------------------- -sub _view_single_feed { - my $self = shift; - my $maxHeadlines = shift; - my $rss = _get_rss_data($self->get("rssUrl")); - my %var; - $var{"channel.title"} = $rss->{channel}->{title}; - $var{"channel.link"} = $rss->{channel}->{link}; - $var{"channel.description"} = $rss->{channel}->{description}; - my @items; - $rss->{items} ||= []; - for (my $i = 0; ($i < @{$rss->{items}}) && ($i < $maxHeadlines);$i++) { - my $item = $rss->{items}->[$i]; - push (@items,{ - link=>$item->{link}, - title=>$item->{title}, - description=>$item->{description} - }); - } - $var{item_loop} = \@items; - return $self->processTemplate(\%var,$self->get("templateId")); -} +Returns the rendered output of the wobject. + +=cut -#------------------------------------------------------------------- sub view { my $self = shift; $self->logView() if ($session{setting}{passiveProfilingEnabled}); + my $maxHeadlines = $self->get("maxHeadlines") || 1000000; - my @urls = split(/\s+/,$self->get("rssUrl")); - if (@urls == 1) { - return $self->_view_single_feed($maxHeadlines); - } else { - return $self->_view_aggregate_feed(\@urls, $maxHeadlines); - } + my @urls = split(/\s+/,$self->get("rssUrl")); + + my %var; + + my($item_loop,$rss_feeds)=$self->_get_items(\@urls, $maxHeadlines); + if(@$rss_feeds > 1){ + #If there is more than one (valid) feed in this wobject, put in the wobject description info. + $var{'channel.title'} = $self->get("title"); + $var{'channel.description'} = $self->get("description"); + } else { + #One feed. Put in the info from the feed. + $var{"channel.title"} = $rss_feeds->[0]->{channel}->{title}; + $var{"channel.link"} = $rss_feeds->[0]->{channel}->{link}; + $var{"channel.description"} = $rss_feeds->[0]->{channel}->{description}; + } + $var{item_loop} = $item_loop; + + return $self->processTemplate(\%var,$self->get("templateId")); } -#------------------------------------------------------------------- +=head2 www_edit() + +Sets parameters and returns a form to edit this wobject. + +=cut + sub www_edit { my $self = shift; return WebGUI::Privilege::insufficient() unless $self->canEdit; - $self->getAdminConsole->setHelp("syndicated content add/edit","SyndicatedContent"); + $self->getAdminConsole->setHelp("syndicated content add/edit","Asset_SyndicatedContent"); return $self->getAdminConsole->render($self->getEditForm->print,WebGUI::International::get("4","Asset_SyndicatedContent")); } diff --git a/lib/WebGUI/i18n/English/Asset_SyndicatedContent.pm b/lib/WebGUI/i18n/English/Asset_SyndicatedContent.pm index 8887d58e8..a8a62d73b 100644 --- a/lib/WebGUI/i18n/English/Asset_SyndicatedContent.pm +++ b/lib/WebGUI/i18n/English/Asset_SyndicatedContent.pm @@ -1,20 +1,38 @@ package WebGUI::i18n::English::Asset_SyndicatedContent; our $I18N = { - '3' => { - message => q|Maximum Number of Headlines|, - lastUpdated => 1057208065 - }, - - '71' => { - message => q|Syndicated content is content that is pulled from another site using the RDF/RSS specification. This technology is often used to pull headlines from various news sites like CNN and Slashdot. It can, of course, be used for other things like sports scores, stock market info, etc. + '1' => { + 'lastUpdated' => 1031514049, + 'message' => 'URL to RSS File' + }, + '2' => { + 'lastUpdated' => 1031514049, + 'message' => 'Syndicated Content' + }, + '3' => { + 'lastUpdated' => 1057208065, + 'message' => 'Maximum Number of Headlines' + }, + '4' => { + 'lastUpdated' => 1031514049, + 'message' => 'Edit Syndicated Content' + }, + '61' => { + 'lastUpdated' => 1047855741, + 'message' => 'Syndicated Content, Add/Edit' + }, + '71' => { + 'lastUpdated' => 1110070203, + 'message' => 'Syndicated content is content that is pulled from another site using the RDF/RSS specification. This technology is often used to pull headlines from various news sites like CNN and Slashdot. It can, of course, be used for other things like sports scores, stock market info, etc. +

+Some terminology: RSS "feeds" (or "channels") are published by websites as collections of "items."

This Syndicated Content client is a Wobject and an Asset, so it has the properties of both. It also has these unique properties:

URL to RSS file
-Provide the exact URL (starting with http://) to the syndicated content's RDF or RSS file. The syndicated content will be downloaded from this URL hourly. +Provide the exact URL (starting with http://) to the syndicated content\'s RDF or RSS file. The syndicated content will be downloaded from this URL hourly.

You can find syndicated content at the following locations:

- +Currently, we can handle RSS versions .90, .91, 1.0, and 2.0. Atom feeds aren\'t supported for now. Probably other RSS-ish files would work too.

-To create an aggregate RSS feed, include a list of space separated URLs instead of a single URL. For an aggregate feed, the system will display an equal number of headlines from each source, sorted by the date the system first received the story.

+To create an aggregate RSS feed (one that pulls information from multiple RSS feeds), include a list of URLs, one on each line, instead of a single URL. Items will be sorted by the date WebGUI first received the story.

+ +Display Mode
+If you\'re aggregating feeds, you can change the mode in which the items are displayed. "Grouped by Feed" means the items will be grouped together by the feeds they come from. "Interleaved" means the items will be mixed together in a "round-robin" fashion from all the feeds. If you\'re grouping your feeds, please look at new_rss_site "item_loop" template variables, it gives you a hook allowing you to output the feed title +

+ +With any of these terms
+Enter terms (separated by commas) that you\'d like to filter the feeds on. For instance, if you enter:
+

linux, windows development, blogs
+The Syndicated Content web object will display items containing "linux", "windows development" or "blogs" (in the title or description of the item) from all the feeds you\'re aggregating together. +

Template
Select a template for this content.

Maximum Headlines
-Enter the maximum number of headlines that should be displayed. For an aggregate feed, the system will display an equal number of headlines from each source, even if doing so requires displaying more than the requested maximum number of headlines. Set to zero to allow any number of headlines. -

|, - lastUpdated => 1110070203, - }, - - '61' => { - message => q|Syndicated Content, Add/Edit|, - lastUpdated => 1047855741 - }, - - '2' => { - message => q|Syndicated Content|, - lastUpdated => 1031514049 - }, - - '1' => { - message => q|URL to RSS File|, - lastUpdated => 1031514049 - }, - - '4' => { - message => q|Edit Syndicated Content|, - lastUpdated => 1031514049 - }, - - '72' => { - message => q|Syndicated Content Template|, - lastUpdated => 1047855526 - }, - - '73' => { - message => q|The following are the template variables available to the Syndicated Content template. +Enter the maximum number of headlines that should be displayed. Set to zero to allow any number of headlines. +

' + }, + '72' => { + 'lastUpdated' => 1047855526, + 'message' => 'Syndicated Content Template' + }, + '73' => { + 'lastUpdated' => 1047855526, + 'message' => 'The following are the template variables available to the Syndicated Content template.

channel.title
-The title of this piece of syndicated content. +The title of this piece of syndicated content. This will be the same as the title of the Syndicated Content object when you\'re creating an aggregate feed.

channel.description
-A description of the content available through this channel. +A description of the content available through this channel. This will be the same as the description of the Syndicated Content object when you\'re creating an aggregate feed.

channel.link
-A URL back to the originating site of this channel. +A URL back to the originating site of this channel. This variable *will not* exist when you\'re creating an aggregate feed, because there\'s no single channel to link to.

item_loop
@@ -85,21 +91,55 @@ A loop containing the data from this channel.

+site_title
+The title of the RSS feed this item comes from +

+ +site_link
+Link to the source RSS feed. +

+ +new_rss_site
+A "boolean" variable (suitable for using in a <tmpl_if> tag) that indicates we\'ve started outputting items from a source RSS feed different than the previous item. This is most useful when you\'re viewing feeds in "grouped" mode- it gives you a hook to output site_title and site_link at the right time. +

+ + title
-The title of a piece of content. +The title of a piece of content. If you\'re filtering on terms, this field will be inspected.

description
-The description of the content. +The description of the content. If you\'re filtering on terms, this field will be inspected as well.

link -A URL directly to the original content. - -

|, - lastUpdated => 1047855526 - }, - -}; +A URL directly to the content of the item. +' + }, + 'displayModeLabel' => { + 'lastUpdated' => 1047855526, + 'message' => 'Display Mode' + }, + 'displayModeSubtext' => { + 'lastUpdated' => 1047855526, + 'message' => '

"Interleaved" means items from all feeds are lumped together, "Grouped by Feed" means items are grouped by the feed they came from. Either setting is fine if you\'re only bringing in a single feed.' + }, + 'grouped' => { + 'lastUpdated' => 1047855526, + 'message' => 'Grouped by Feed' + }, + 'hasTermsLabel' => { + 'lastUpdated' => 1047855526, + 'message' => 'With any of these terms' + }, + 'interleaved' => { + 'lastUpdated' => 1047855526, + 'message' => 'Interleaved' + }, + 'rssTabName' => { + 'lastUpdated' => 1118417024, + 'message' => 'RSS' + } + }; 1; diff --git a/sbin/Hourly/GetSyndicatedContent.pm b/sbin/Hourly/GetSyndicatedContent.pm new file mode 100644 index 000000000..8a39d0bf1 --- /dev/null +++ b/sbin/Hourly/GetSyndicatedContent.pm @@ -0,0 +1,34 @@ +package Hourly::GetSyndicatedContent; + +use strict; +use warnings; +use WebGUI::SQL; +use WebGUI::Asset::Wobject::SyndicatedContent; + +=head2 Hourly::GetSyndicatedContent + +Loops through all the URLs in the SyndicatedWobjects and puts them into WebGUI::Cache if they haven't been spidered or if they have expired from the cache. This should reduce HTTP traffic a little, and allow for more granular scheduling of feed downloads in the future. + +=cut + + +#------------------------------------------------------------------- +sub process{ + + #In the new Wobject, "rssURL" actually can refer to more than one URL. + my @syndicatedWobjectURLs = WebGUI::SQL->buildArray("select rssUrl from SyndicatedContent"); + foreach my $url(@syndicatedWobjectURLs) { + + #Loop through the SyndicatedWobjects and split all the URLs they are syndicating off into + #a separate array. + + my @urlsToSyndicate = split(/\s+/,$url); + foreach ((@urlsToSyndicate)) { + WebGUI::Asset::Wobject::SyndicatedContent::_get_rss_data($_); + } + } +} + + + +1; diff --git a/sbin/updateTranslations.pl b/sbin/updateTranslations.pl new file mode 100755 index 000000000..c319f0b33 --- /dev/null +++ b/sbin/updateTranslations.pl @@ -0,0 +1,126 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Data::Dumper; +use Getopt::Long; + +our $webguiRoot; + +BEGIN { + $webguiRoot = ".."; + unshift (@INC, $webguiRoot."/lib"); +} + +my $language='English'; +my $namespace; +my $help; +my $test; + +GetOptions( + 'language=s'=>\$language, + 'namespace=s'=>\$namespace, + 'help'=>\$help, + 'test'=>\$test + ); + +if ((!$help && !$namespace) or $help){ + print </ translation files. It sorts the entries +by key value and takes care of escaping automatically. + +Options: + + --language The language you're adding an entry for. + Defaults to English + + --help Display this help message and exit. + + --namespace The name of the file you want to manipulate + in "lib/WebGUI/i18n/" WITHOUT the .pm + extension. So, if you're editing the "Macro_GroupAdd.pm" + file, it's "Macro_GroupAdd". + + --test Don't edit the actual file, but create a copy with ".test" + appended to the name. + +STOP + exit; +} + +die('You need to give us a namespace to edit') if (!$namespace); + +my $tranmodule=join('::',('WebGUI','i18n',$language,$namespace)); +eval "use $tranmodule;"; + +if (($@)) { + die('Either that namespace does not exist or you spelled it incorrectly. Please try again'); +} + +my $variable='$'.$tranmodule.'::I18N'; +my $i18n=eval "$variable"; + +print "\nEnter a new key value to create a new entry.\nCurrent Keys:\n\n"; +foreach ((keys %$i18n)) { + print "$_\n"; +} + +my $key=''; +while (lc($key) ne 'quit') { + print "\n\nNew Key, or quit to stop:\n"; + $key=; + chomp($key); + next if(!$key); + last if(lc($key) eq 'quit'); + if (! defined $i18n->{$key}) { + print "\nNew key. Ok?\n"; + my $input=; + chomp($input); + if (lc(substr($input,0,1)) eq 'y') { + get_info($key,$i18n); + } + } else { + print "\nErm. . . That key's already in use. Please try again.\n"; + } +} + + +################################################################################ +sub save_file{ + open(OUTPUT,">","../lib/WebGUI/i18n/$language/$namespace.pm".(($test) ? '.test' : '')) or die($!); + $Data::Dumper::Varname='I18N'; + $Data::Dumper::Sortkeys=1; + + print OUTPUT "package $tranmodule;\n\n"; + my $output=Dumper($i18n); + $output =~ s/^\$I18N1/\$I18N/i; + print OUTPUT "our ".$output; + print OUTPUT "1;"; + close OUTPUT; + print "Saved!!\n"; +} +######################################## + + +################################################################################ +sub get_info{ + my $key=shift; + my $i18n=shift; + print "\nEnter the new information for this key. Press Ctrl-D to save\n"; + my @info=; + print "\n\nOk? \n"; + my $input=; + chomp($input); + if (lc(substr($input,0,1)) eq 'y') { + my $string=join("",@info); + chomp($string); + $i18n->{$key}->{message}=$string; + $i18n->{$key}->{lastUpdated}=time; + save_file(); + } +} +########################################