Merge branch 'master' into WebGUI8. Merged up to 7.10.4

This commit is contained in:
Colin Kuskie 2010-11-03 09:47:36 -07:00
commit 5f3014aaee
66 changed files with 3078 additions and 997 deletions

View file

@ -1,3 +1,32 @@
7.10.5
7.10.4
- Added WebGUI::Fork api
- Moved html export to Fork
- Moved clipboard functions to Fork
- Moved trash functions to Fork
- Moved version tag rollback to Fork
- fixed #11929: In/Out board breaks in Chrome sometimes
- fixed #11928: Story Archive breaks if url has extension
- fixed #11920: Defaul DataForm emails missing entries.
- fixed #11921: DataForm emails contain 1 table per field
- fixed #11922: Help tempalte is squatting on a good URL
- fixed #11923: Collaboration System Mail Cron Errors
- fixed #11925: Some problems in Thingy export (metaData values in CSV export)
- fixed #11925: Some problems in Thingy export (export times out on Things with many rows)
- refixed #11902: forums bug (works without mobile style being set)
- fixed #11924: Deleting version tags leaves pending inbox messages in a permanent state
- intalled YUI fix for flash files.
- fixed #11932: Bad URL in the newly templated recover password email
- fixed #11938: upgrade script always removes specialState
- fixed #11937: Duplicating events does not duplicate storage locations
- fixed #11935: shortcut uses wrong value for getContentLastModified
- fixed #11940: quickCSV chokes on newlines in data
- fixed #11933: EMS shows a dollar currency symbol on Badge Listing
- fixed #11941: cannot edit default templates in some browsers
- fixed #11926: Version Tag: Delete
- fixed #11939: WebGUI replacements are not anchored.
7.10.3
- fixed #11903: Unnecessary debug in Thingy
- fixed #11908: Inbox messages linger after deleting a user
@ -3978,674 +4007,3 @@
- Made the Include macro more secure.
- Added Len's patch to fix some caching problems.
7.3.3
- fix: Wiki Purge throws fatal
- fix: Calendar now reports proper product ID on iCal feed
- fix: Calendar now tries to use the feed ID when sending uid of event on iCal
feeds (to prevent over-propagation of events shared between calendars).
- fix: Bug in AssetLineage->getLineage documentation.
- rfe: Event now has a template var to toggle if an event only lasts one day
- rfe: WebGUI::DateTime->toMysql now automatically adjusts to UTC. NOTE:
toMysqlDate and toMysqlTime do NOT adjust for timezones. If you are
using them you must adjust manually.
- fix: Bug in WebGUI::DateTime documentation
- fix: Calendar default date of "first event" or "last event" now works.
- fix: Calendar now handles Events that have ' in titles appropriately.
- rfe: Added a "dateSpan" Event template variable that will show a properly
formatted date/time span depending on how the event's start and end are.
- fix: Disobedient Forum Rich Editor
- fix: SQLForm - fixed a bug where regexes would sometimes be ignored (Martin
Kamerbeek / Oqapi)
- fix: SQLForm - checkList/varchar (Martin Kamerbeek / Oqapi)
- fix: testEnvironment.pl
7.3.2
- fix: Calendar and Event now have printable templates and URL parameters.
- fix: Miscellaneous Calendar template fixes
- fix: Cannot manageAssets with a locked Thread -- NOTE: Kludgy, but any other
way would probably have to break API.
- t/lib/WebGUI/Test.pm now has a method for returning the path to the
test collateral directory. The method is called getTestCollateralPath.
Existing tests using that directory have been modified to use the new
method instead of finding the path manually.
- fix: Avatar in Thread & Posts of CS
- fix: CS Phishing Exploit.
- fix: Groups admin gui (1) Default should be contains
- fix: Groups admin gui (2,3) Make group form sticky
- fix: Wiki does not show history correctly
- fix: SQLForm - Field Constraint (Martin Kamerbeek / Oqapi)
- fix: SQLForm - Default search template uses downloadUrl in stead of
templateUrl for displaying thumbnails. (Martin Kamerbeek / Oqapi)
- fix: SQLForm - Required file fields could be left open (Martin Kamerbeek /
Oqapi)
- fix: SQLForm - Using radio buttons would error when re-edited (Martin Kamerbeek /
Oqapi)
- fix: SQLForm - DBD Error handling (Martin Kamerbeek / Oqapi)
- fix: Media folder permission check
7.3.1
- Fixed a problem with IE and resizable text areas that caused IE to crash
when loading edit screens.
- Fixed a problem with the new autocommit code that caused reply posts not to
work in the collaboration system.
- Storage deletes were throwing fatals when they should throw warnings.
- Fixed a bug in WebGUI::ProfileField->getCategory which caused it to always
return undef. (Martin Kamerbeek / Procolix)
- Fixed a bug in WebGUI::Asset::File where update did not update the
internally cached storage object inside of _storageLocation.
This is probably only a real problem in persistent code, like Workflow
Activities and tests.
Added tests for File and Image assets to verify that this happens correctly.
- fix - Unable to add EventsCalendar
- fix - Some functions in InOutBoard not internationalized
- fix: Calendar/Event not handling gateway properly.
- fix: Calendar templates crushing other styles.
- fix: Using YUI to add the appropriate events when loading the Add/Edit Event
page. Should fix the strange IE bugs.
- RFE: Add wiki page variables to Wiki_Master.pm
- fix: Not translated labels no displaing
7.3.0
- NOTICE: The Template Managers group is deprecated. It has not been removed
from the system, but you do not have to be in the Template Managers group
to edit or add Templates. Those privileges have been transferred to the
Turn On Admin group.
- TESTS: The help labels were broken out from the i18n/label.t test into their own
test. An environment variable, CODE_COP, is used to enable the long
i18n/label.t and help/setHelp.t tests.
- documented the Deactivate Account Template.
- Added the setNamespace and getNamespace methods to WebGUI::International.
- Fixed bad caching via codespace in Operation::Help. The original failed all the time.
- Implemented codespace caching in WebGUI::International. This replaces the
in-memory cache by symbol table lookups into the code itself and saves
duplicating the i18n entries.
- Added accordion javascript object, which will eventually replace the
current adminbar accordion. This one is less of a cludge and uses the YUI
API.
- WebGUI now has a Wiki!
- Upgraded to YUI 0.12.0
- Upgraded to YUI-Ext 0.33 RC2
- Karma RFE: DataForm file upload patch. Thanks to mistoo for submitting the
original patch. Although I couldn't use the code in wG 7, it inspired the RFE.
Also added the feature requested in the thread to allow the files to be
emailed as attachments.
- Fixed behaviour of the Encrypt Login setting, in such way that only the form
post containing the login credentials is sent over https. After authentication
the user is redirected to http. (Martin Kamerbeek / Procolix)
- fix: RSS From Parent assets should always be hidden from navigation
- fix: profile field i18ned possibleValues with apostrophes failing
- Added a new DateTime subclass, WebGUI::DateTime, with convenience methods to
convert to and from MySQL Date/Time strings. Moving forward, this method
should be used in place of the existing WebGUI::Session::DateTime, which can
create problems when handling time zones.
- Form elements Date, DateTime, and TimeField now return MySQL Date/Time
strings when given a MySQL Date/Time string as a default value. This is now
the recommended method of storing date/time in the database.
- WebGUI::Search now accepts more rules, "where" for specifying an additional
where clause, "join" for making join clauses, and "columns" for adding more
columns to return.
- WebGUI::TabForm->addTab now returns the WebGUI::HTMLForm created.
- WebGUI::AssetLineage::getLineage can now limit the number of records returned
- fix: IP addresses for adminModeSubnets not using X-Forwarded-For properly
- add: workflow activity for expiry of email-unvalidated users. This is not
enabled by default; add an instance of it to an appropriate workflow if you
want it to run.
- fix: subscription dates
- fix: Default Rich Editor setting not rendering correctly
- fix: visitor name disappearing on preview in CS
- fix: HTTP proxy not passing form elements through
- upgrade script patches some corrupted commerce template settings
- fix: bits of other panels showing through in admin bar
- fix: Edit Branch on threads makes them not show up in CS
- The Events Calendar is now the new Calendar with some fun new features.
All your existing Events Calendars will be migrated automatically.
- rfe: multiple redirects on a page - which one?
- Major change: password recovery is now based on profile fields rather than
email account access
*** PLEASE READ THE GOTCHAS ***
- fix: Updated Snippets not being cleared from cache
- fix: IE7 Asset Manager and Admin Console bug defeated!
- fix: fixed a 508 compliance issue with login macro.
- fix: testEnvironment.pl fails in windows (Rebecca Hunt)
- rfe: add simpleReport option to testEnvironment.pl (Rebecca Hunt)
- fix: Updated Hover Help on Possible Value and Default Value when creating new Profile Fields. This should clarify things.
7.2.3
- fix: minor bug with new template vars in Auth::createAccount
- fix: How to get to List Pending Transactions screen?
- fix: Users not authorized for any payment gateway get appropriate message
7.2.2
- fix: Show Debugging option not working
- fix: Workflow form control edit button won't work. removed.
- fix: Bug in HttpProxy.pm
- fix: Storage::Image copy does not create thumbnails
- fix: Static export - redirect problems
- fixed a bug in Session::ErrorHandler::canShowPerformanceIndicators. Moving
to CIDR format in debugIp broke it. Added a new convenience method called
canShowBasedOnIP, which refactored out the identical code to share
between canShowDebug, canShowPerformanceIndicators and any other IP based
check for privileges.
- fix: RSS From Parent having no icon
- fix: HttpProxy now handles styles appropriately.
- fix: op=viewPurchaseHistory prices are now formatted correctly
- fix: A minor bug in the default viewPurchaseHistory template
- fix: Thread determination of "current" Post, and shortcuts to non-Thread Posts
- fix: make handling of profile field possible values slightly more robust
- RFE: non-required fields shown on user registration
7.2.1
- Made a change to version tag commits to deal with unusually long commit
times.
- Fixed bugs the SyncProfileToLdap workflow activity where it would ignore the
ldapAlias config setting and it crash (Martin Kamerbeek / Procolix)
- fix: entry in error log of WebGUI
- Fixed part of RSSCapable addition upgrade script in 7.2.0.
- fix: MIME types broken from change to the way File assets were streamed
- fix: New resizable textareas not obeying width/height parameters
- fix: InOutBoard not allowing re-editing of new revisions
- Added a fatal error should parsing of JSON config file fail
- Fixed a bug with the admin mode subnet feature.
- Fixed a problem with rich media ads not processing macros.
- Fixed a flaw in the new commerce tax system that caused checkouts to fail.
- fix: Bug in "Article with Files"
- fix: SQLReport pagination retains op= parameter
- fix: Invalid MIME type set for images
- Fixed a problem with the adspace upgrade in 7.2.0
- Fixed a problem with the survey upgrade in 7.2.0
7.2.0
- Added server side spellchecker (Martin Kamerbeek / Procolix)
- Added configurable sales tax. (Tiffany Patterson / Elite Marketing)
- change: made Text::Aspell optional, nullifying spellchecker if not present
- change: made all LWP user agents use env_proxy
- Help: If a Help Chapter only has 1 page, then in the TOC view it links
right to the page instead of the Chapter.
- fix: HTML::Template::Expr templates would not handle template variables
with dots in them. Added a fix to the template plugin so that dots are
translated to underscores automatically in submitted template variables.
Templates will still need to be manually updated.
- Help: Added pluggable docs for template plugins, and added a new tab
to the Help that lists template parser docs.
- Added accessors to Session/Http.pm to make testing easier.
- Test: Added t/lib/WebGUI/PseudoRequest, which is a mostly functional
Apache::Request object replacement. It doesn't do everything, but it
does enough to test Session/Http.pm, except for cookies.
- Added an option to the Syndicated Content Wobject that allows use of macros
inside the RSS Url property.
- semi-fix: WebGUI/Mail/Send.pm no longer has extraneous UTF-8 BOM
- new: RSSCapable mixin for assets that can have RSS feeds, and RSSFromParent
asset (automatic) that actually generates the feeds from them.
- new: workflow activity and hooks for deleting exported files on trash,
purge, and changeUrl
- fix: editing posts loses changes in preview
- change: Asset::getContainer no longer changes the session asset
- fix: Survey numeric multiple choice options
- fix: Matrix/can't remove picture from listing
- fix: inability to create shortcuts to threads
- fix: Style templates do not render metadata
- fix: Survey duplication not working
- fix: "Open link in new window" with WebGUI asset tree link in TinyMCE
- fix: Admin Users submenu doesn't fill in uid
- Added YUI javascript library to the core, so that we can begin converting
to a standard javascript API.
- fix: Resizable textarea no longer works in IE
- fix: EMS Manage Events broken
- fix: "orig_dependant" JavaScript error in PM quick task display
- fix: Tasks now start at zero duration in the PM system
- fix: RSS for collaboration systems now properly shows in the head rather than the body
- fix: Gantt chart bars erroneously being shifted one day to the right
- fix: Post titles containing periods result in urls containing periods
- fix: Activity list expands outside of edit workflow screen
- fix: Thread layout "flat" doesn't stick
- fix: Rich Edit omitting rows drops subsequent rows
- fix: Phishing Bug... take that spammers!
- fix: Default PM Dashboard Template extra form element not implemented yet
- refactor: move Dashboard, Folder, and HttpProxy getEditForm overrides into definition clauses
- possible fix: Dates messed up on subscriptions
- Template variables in the main Survey Template were out of date in the
documentation.
- fix: SQLReport no longer paginates or runs nested queries when downloading.
- Made Stow's warning a debug message, which is what debug messages are for.
- fix: WebGUI::Text::splitCsv no longer removes trailing empty fields
- fix: Product add-to-group would always try to add a user to a group
- Made many minor changes recommended by Perl::Critic.
- fix: No Integers or Strings as Placeholder Parameters
- Made many minor code efficiency changes.
- fix: Two cookies and incorrect Last-Modified date in HTTP header
- WebGUI::Text no longer spits out a billion warnings
- fix: workaround for IE not handling ' in SyndicatedContent was not catching everything
- fix: WebGUI::Operation::ProductManager added a tab with wrong name.
- fix: WebGUI::Operation::Commerce www_selectPaymentGateway no longer forces
user to choose gateway if they are only authorized to use one
- WebGUI::Session::Scratch->delete now returns the value deleted for
convenience, like Perl's built-in delete() function.
- fix: Auth redirectOnLogin wouldn't work if login called from Operation::execute()
- fix: WebGUI::Operation::Commerce->listTransactions now adds trailing 0's to
prices/totals.
- fix: Uncommitted Collaborations and adding threads
- fix: template variable displayLastReply is in none of the CS help files
- karma rfe: Faster rendering for editing interface
- karma rfe: Limiting access to admin mode to set of ip's
7.1.3
- fix: SQLReport now returns error if can't find DatabaseLink
- WebGUI::DatabaseLink->new now warns if can't find requested DatabaseLink
- fix: Wrong template variable name in default Matrix View template
- Tried to clean up some HttpProxy code. Still very ugly. (need rewrite?)
- fix: HttpProxy would not put correct values for multiple query params with
same name.
- Fixed a bug in the template engein that caused CS notifcations not to send
in certain circumstances.
- fix: metadata (WebGUI Help). Removed mention of the RawHeadTags macro
from the Metadata help.
- fix: Config
- fix: Article Shortcut Loses Style Information
- fix: Pagination loses search criteria
- WebGUI::Session::Stow now warns if set() is called when cache is disabled
- Fixed a bug in the LDAP auth module where LDAP links could not connect to
the LDAP server (Martin Kamerbeek / Procolix)
- Fixed a bug where the Automatic LDAP Registration setting could not be set.
(Martin Kamerbeek / Procolix)
- Fixed a bug in the Poll where using graphs could result in errors. See
gotcha.txt for details. (Martin Kamerbeek / Procolix)
- fix: Group lookups via database link
- fix: Error before logging into WebGUI site
- fix: Unlock tag
- Added some additional indicies for slightly better performance.
- fix: PM resource search popup has no scrollbars
- fix: Matrix listings create CS assets with wrong permissions
- fix: HttpProxy not requiring Apache2::Upload correctly
- Fixed a bug that could cause package imports to fail if they included
updated revisions of existing assets. This fix may also prevent other
revisionDate related errors, though none are known at this time.
- fix: Error in Storage.pm
- fix: Relative URL in viewRSS function of CS
7.1.2
- Fixed a bug where logging in/out would cause a blank page display.
- Fixed a bug that caused workflows to fail if collaboration systems and
posts for that CS were in the same version tag at commit time.
- fix: minor assetsToHide implementation bug in dashboard
- fix: Version tags could not be create()d because no default values set.
- fix: Commerce items were required to have a group.
7.1.1
- fix: some issues with asset exports not handling URLs with dots correctly
- fix: Search from root
- fix: Survey: textarea answers are trunctated
- fix: Snippet Security Fails
- add: asset exporter making appropriate symlinks for extras, uploads, and root URL
- change: asset exporter now uses one session per asset to avoid breaking state in between
- fix: Lineage length is not checked (Martin Kamerbeek / Procolix)
- fix: Cannot manage user accounts in 7.1.0
- fix: New created users don't have password
7.1.0
- fix: mysql and mysqldump were transposed in upgrade.pl --help
- fix: adding Matrix listings committing the current version tag
- fix: user searches in task resource additions in PM not displaying right without both last name and first name present
- fix: task editor in PM not actually receiving start/end date information at first
- fix: Error Displaying Multiple TimeTracking Wobjects (ekennedy)
- refactoring of PM JavaScript stuff
- fix: DHTML calendar bug & fix (maxscience)
- fix: Missing translation in calendar (Klaus)
- fixed a bug where the calendar would break if a language other than English has
been selected (Martin Kamerbeek / Procolix)
- fix: Events Calendar: error in "big" template (Martin Kamerbeek / Procolix)
- fix: PM task editor not preserving duration
- fix: PM project completion percentage updates not working right
- fix: useEmptyStyle caused invalid template to be used
- Added ability to download an SQLReport in either CSV or as a template.
(Special thanks to the Alliance for a Media Literate America for funding
this feature.)
- Added ability for Products to add a user to a group when purchased.
(Special thanks to the Alliance for a Media Literate America for funding
this feature.)
- Changed the ?op=editProduct form to a TabForm.
- fixed a small error in WebGUI::Group documentation.
- Added WebGUI::Text with some CSV functions.
- Added Karma RFE: Thumbnail size can be enterred in CS
- Added diskUsage.pl utility script to show space used by assets in a webgui
site, similar to the unix du utility (Special thanks to Volvo for funding
this feature).
- Added option to WebGUI Auth module to require strong passwords. Admins can
now require users to enter a specific combination of characters, etc.
(Special thanks to Brunswick Bowling and Billiards for funding this feature.)
- Added skeleton code for writing WebGUI utility scripts.
- Added auto-registration via LDAP. This allows users to simply login and
have a WebGUI account created if their credentials are validated by the
directory. (Special thanks to Kemin Industries for funding this feature.)
- Added a Sync Profile to LDAP workflow activity that will grab a single user
profile from LDAP instead of all of them. (Special thanks to Kemin
Industries for funding this feature.)
- fix: Article.t copy collateral test false failure.
7.0.9
- Removed the need for DateTime::Cron::Simple, which also added the ability
to use ! < and > in schedules.
- partial fix: invalid Message-ID headers in outgoing mail
- fix: HttpProxy not doing file uploads correctly
- fix: leftover discussion template variables in Default Article template
- fix: Stock Data asset insufficiently robust handling erroneous data
- refactor: move getEditForm data into definition for Collaboration asset
- Fixed some bugs in the SQLForm. Also refactored parts of the SQLForm to
reduce the number of database queries and lessen the amount of data being
uploaded when images are put in the form. (Martin Kamerbeek / Procolix)
- change: PM asset task editor now defaults start date to start of project
- Rearranged the autotag name creation to be easier to read.
- add: progressive (duration-tracked but untimed) tasks now possible in Project Manager
- fix: Shortcut causes endless loop
- fix: Template variable in Project Management System
- fix: behavior of SyncProfilesToLdap workflow activity should be more correct now
- add: multiple LDAP recursion filters possible
7.0.8
- Fixed a couple of minor bugs with the default values of the Request
Approval for Version Tag workflow activity.
- Updated the hoverhelp to denote that you can use ranges in the WebGUI
scheduler.
- fix: deleting workflows did not delete related instances and crons
- Added a "run" link to the scheduler and the running workflows listings to
aid in debugging workflow errors.
- fix: profile fields not validated by WebGUI::User
- fix: Spectre pings not using correct IP address
- fix: search functionality throwing fatal errors
- fix: DBI connect errors infinitely recurse
- add: setting cookieTTL to "session" now creates browser-session cookies
- Added a reverse option for the getAssets method in VersionTag.
- Fixed a bug that would occur when deploying a package that contained a
collaboration system with posts.
- structure: normalize signature of Asset::duplicate method
- fix: Copying Collaboration System assets fails
- fix: Collaboration System packages do not deploy
- fix: robots.txt returns wrong MIME type
- change: overlong alternate text for Weather Data icons shortened to basename
- fix: multiple problems with static export, including wrong asset context and wrong status messages
- fix: WebGUI::Asset->new interacting badly with caching
- fix: changeUrlConfirm returns to previous URL rather than new URL
- fix: performance indicators interfering with CSS
- fix: admin bar causes pages to extend forever
- fix: File Upload - documented HTTP file upload size limitations in File
Pile Assets Hover help as well as the WebGUI settings documentation for Max
Upload size.
- Eliminated several hundred queries to the database during certain user
profile field options.
- Fixed the search function that broke in 7.0.7.
- fix: typo + obsolete approve section in Collaboration System Default Thread template
- fix: attachments section of post form not working correctly on edit
- Images now create revisions as you resize them, so you can roll back to a
previous size.
7.0.7
- rfe: Image Management (funded by Formation Design Systems)
- fix: can't change default size of text fields (midellaq)
- fix: sqlform trunctate search results doesn't work (Martin Kamerbeek /
Procolix)
- fixed some of bugs in the sqlform concerning file uploads, cross table
constraints and the join selector on non-key/value pair fields (Martin
Kamerbeek / Procolix)
- fix: Add event does not work WebGUI 7.0.5 in combination with Proxy Caching
turned off (Wouter van Oijen / ProcoliX)
- When going to an image by it's webgui url in admin mode, you are now shown the image instead of being taken to the edit screen for the image.
- fixed a bug in the Layout Asset where the asset would not inherit the
Layout template of its parent on addition (Martin Kamerbeek / Procolix)
- fixed some issues with getting original values and template fields in the
overrides section of the Shortcut asset (Martin Kamerbeek / Procolix)
- fix: extra elements (tags) do not show up in HTML source (Martin Kamerbeek
/ Procolix)
- fix: Error in StockData Default View Template (Wouter van Oijen / Procolix)
- fix: Matrix 'Can instantiate template' and also fixed a bug where the style
and printable style were not set for the Collabs attached to the listings
in the Matrix (Martin Kamerbeek / Procolix)
- fix: Spectre::Admin Error Message (xhunter)
- fix: invalid getUrl usage in EventManagementSystem
- fix: assets incorrectly setting Last-Modified by revisionDate only
- fix: SyndicatedContent caching the wrong thing and not displaying after first time
- fix: Database cache trying to freeze non-references with Storable
- fix: Apache version string component came before Apache's own version number
- RFE: JavaScript confirmation rather than page load for deleteUser
- RFE: JavaScript confirmation rather than page load for deleteGroup
- RFE: show which user locked an asset in the asset manager
- fix: dashlet user preference setting causing nested dashboard to appear
- fix: saving edits to dashlet shortcuts kicks you out of your version tag
- fix: Discussion tmpl variables in Article asset
- fix: dashlet www_saveUserPrefs refusing to execute
- API change: ProfileField::new now returns undef for invalid fields
- API change: in ProfileField, the get*Fields family of methods are now class methods
- API change: 'func' and 'op' are now reserved and not usable as profile fields
- fix: project editing in project management systems not reading fields correctly
- fix: JavaScript race condition in dashlet prefs form
- fix: caching problem with overrides in dashlets
- fix: CS pagination does not work for visitors
- fixed a problem in the search indexer and made the tabform css compatible
with tinymce. (Martin Kamerbeek / Procolix)
- fixed WeatherData Wobject, noaa format had changed (ekennedy)
- fix: Matrix (updated detailed listing template to include the screenshot)
and fixed a bug in sbin/fileUpload.pl wher it didn't handle images with
uppercased extensions properly (Martin Kamerbeek / Procolix)
- new: In the Project Management asset, tasks can now have multiple resources, which may be users or groups. Original single-resource data is migrated to the new schema by the 7.0.7 upgrade script.
- fix: makePrintable operation with other styleId
- fix: RandomThread macro not working properly. Only CS's with more than one thread are considered for the random search
- new: Tasks in the Project Management asset can now be assigned non-work lag time that is added to the main work duration of the task.
- new: Projects in the Project Management asset can now be assigned observer groups; users who are not in the observer group cannot view any aspects of the project.
7.0.6
- fix: Error in DateTime.pm
- Added a cookieTTL parameter to the config file which lets you set an optional expiration time of the webgui session cookie
- RFE: By default, search results need to match ALL keywords (Len Kranendonk / www.ilance.nl)
- fix: page redirect problem
- fix: adding in groupdelete macro
- fix: semicolons missing
- fix: Typo in WebGUI/Form/Date.pm
- fix: snytax error in wobject skeleton
- fix: potential problem with posts if getThread->parent is not defined
- fix: macro_env semicolonmissing again!!!!
- Made some changes to make WebGUI compatible with the WRE for Windows.
- fix: cacheTimeout not respected as Visitor (Eric Kennedy).
- fix: Email address with just one character in the user part not accepted
- fix: Image (file) added to page shows before committing changes
- fix: Typo in fileImport.pl at line 265 (zxp)
- rfe: Workflow activity for assigning users to a group
- The prevent proxy cache setting also now sets anti-caching meta tags and
HTTP headers.
- fix: getMedia asset constructor returning wrong object type
7.0.5
- Added a --skipDelete option to upgrade.pl
- rfe: Approvers don't need to approve own changes
- Added some more tests to the suite.
- Fixed the test skeleton
- Fixed some bugs regarding Search relevance sorting (Len Kranendonk / www.ilance.nl)
- Added an option to override the session cookie name.
- Added an option to override the session cookie domain.
- fix: Search results not showing synopses
- fix: Redirects get displayed inside page layouts as '0'
- fix: Mysterious "0" Appearing When Admin Is Off
- fix: Deletion of Products
- fix: Request Tracker Thread is called Request Tracker Post
- fix: asset constructor new, does not return undef as documented
- fix: Static export in html not working through the workflow
- fix: Fixed project management display
7.0.4
- Added a forum.lastPost.user.hasRead variable to the Message Board template.
- fix: r_printable macro and op2
- fixed a bug where the Include macro could be used to read WebGUI config
files.
- fix: new by webgui: 31 months in a year
- Several new tests.
- Many POD fixes.
- fix: URI::Escape missing from testEnvironment.pl
7.0.3
- Fixed a problem with the 7.0.0-7.0.1 upgrade relating to internationalized
department names.
- fix: Missing documentation breaks the List of Available Macros (Wouter
van Oijen / ProcoliX)
- fix: Article thumbnail not working (Len Kranendonk)
- Fixed a bug in WebGUI::Asset::Post where userDefined and synopsis form
elements were not populated when previewed. (Martin Kamerbeek / Procolix)
- fix: Indent Navigation broken (Wouter van Oijen / ProcoliX)
- fix: HttpProxy not working (with fix) (Eric Kennedy)
- fix: Copyright on Default Template (Wouter van Oijen / ProcoliX)
- fix: FileUrl macro doesn't handle snippets (Wouter van Oijen / ProcoliX)
- fix: Dataform adding fields without fieldname (Wouter van Oijen / ProcoliX)
- fix: Fatal in Affiliate.pm
- Fixed several problems to make WebGUI 7 Windows compatible again.
- fix: navigation (Wouter van Oijen / ProcoliX)
- Fixed typo in template variable project.gantt.rowspan and documentation
- fix: Events Calendar Double Date (Wouter van Oijen / ProcoliX)
- fix: Data Form Text Area Box Non-Existent (Wouter van Oijen / ProcoliX)
- Added an error message to the FileUrl macro to help users figure out why it
doesn't work.
- Fixed bugs in the GroupAdd and GroupDelete macros.
- Fixed a cross-Matrix linking problem when you have two or more Matricies on
one site with the same category names.
- Deleted a template that was accidentally added to the core.
- Made some improvements to the mail subsystems.
- fix: Revised WebGUI::HTML::filter "all" so that text does not run together when
tags are removed. Added additional tests to HTML.t. (Eric Kennedy)
- fix: Shopping Cart Not Working
- fix: Editing Products Template wipes out SKU
- fix: Email to RFE List Going to Spam
- fix: 7.0.0-7.0.1 upgrade -- op called w/o passing session
- fix: spectre.pl daemon error
- Changed the Spectre tests to be a seperate option on the spectre.pl command
line, which fixed a problem with the WRE monitor, and also enabled us to
add more complete connectivity testing.
- fix: Templates XHTML compliance (Wouter van Oijen / ProcoliX)
- Fixed mail bounce processing.
- fix: Asset Manager displaying incorrectly
- fix: Cannot paste from clipboard
- Made the search indexer mor compatible with Chinese and other non-ascii
characters. (Thanks to Zhou Xiaopeng)
- fix: Splat_random Macro not so random (Wouter van Oijen / ProcoliX) (Thanks
to Colin Kuskie for pointing this out and writing some tests)
- rfe: phone validation javascript
- fix: Head Block in styles
- fix: select assetVersionTag
- fix: Infinite recursion
- fix: assetUiLevel override broken
- fix: Indexing files failes (derck)
- fix: Unable to approve New listings on Matrix
- Added the arrayRef() method to WebGUI::SQL::ResultSet, which is 12% faster
than the array() method.
- Added more tests to the test suite.
- fix: Search Feature Select Box Not Working
- Added "Save and Commit" option for environments where the appearance of
workflow is unwanted.
- fix: WebGUI::International::get can't handle spaces
- fix: makePagePrintable macro uses style name instead of styleId
- fix: Tell A Friend
- Fixed a crash problem with Spectre run once cron jobs.
- Fixed a formatting problem and a data collision problem with the Create
Cron Job workflow activity.
- fix: HTML tags in subject
7.0.2
- fix: upgrade from 6.99.4-6.99-5 can fail if site contains groups tied to ldap with no users in it.
- GroupText macro returns an error message if it can't find the group by the name the user supplies.
- fix: Unable to remove databaselinks (Thanks to misja)
- fix: Collaboration System hangs under certain conditions (Martin Kamerbeek
/ Procolix)
- fix: Insert WebGUI Image inserts image, but does not retain border, spacing
or alignment.(Martin Kamerbeek / Procolix)
- Added Chinese character support to search engine and indexer thanks to Zhou
Xiaopeng.
- fix: issue with recursive ldap filter causing it not to work properly
- fix: upgrade 7.0.0 to 7.0.1 ldap problem
- fix: Typo when trying to display pvt profile
- Added an unsubscribe link to the messages generated by collaboration
subscriptions per the laws in various countries.
- fix: MultiSearch
- fix: Unable to duplicate existing Session Id
- fix: Admins not in visitors group
- fix: Data Form Text Area ignores size settings
- Fixed a bug that didn't allow you to search a matrix.
- Fixed a bug in the upgrade that caused template problems with the WebGUI 6
template if anyone was still using that.
- Fixed a bug where the template variables currentPage.hasViewableSiblings
and currentPage.hasViewableChildren were always false. Added the
page.parent.rank template variable to the Navigation template. (Martin
Kamerbeek / Procolix)
- Fixed a bug where WebGUI::Asset::File->addRevision did not set correct
privs to the storage associated with it. (Martin Kamerbeek / Procolix)
- Added a reverse page loop option to the navigation asset (Martin
Kamerbeek / Procolix)
- fix: cs mail needs archive url
- fix: cs mail not sending in-reply-to and references headers
- fix: cs mail doesn't like code via email
- CS mail now sends out the email address of the poster as from, when it
exists.
- fix: WebGUI::Image missing methods
- Added runOnLogin and runOnLogout config file properties to Authentication to allow
for running an external script on successful login or logout.
- fix: spectre
- fix: Spectre tries to delete the same workflow instance twice
- Fixed part of the Spectre memory leak. See gotcha.txt for details.
7.0.1
- fix: User profile field "Department" needs i18n
- fix: AssetProxied Navigation context menu - items invisible in Style 02
- fix: Request Tracker Asset - Reply to a post displays Severity drop down
list
- fix: Syndicated wobject erro 6.8+
- fix: new spectre.pl error (Martin Kamerbeek / Procolix)
- fix: Can't create new account
- fix: Several new assets aren't added to config during upgrade process
- fix: Post Subject HTML
- fix: Matrix: can't instantiate template
- fix: Session id (Martin Kamerbeek / Procolix)
- fix: Style Wizard
- fix: content-type
- fix: Two cookies and incorrect Last-Modified date in HTTP header
- fix: HTTP status code 404 broken
- fix: Add missing page on Problem With Request
- fix: Avatar/photo upload not working
- fix: Shortcut with content lock fails (Thanks to Michelle Lamar)
- fix: Security bug in session env
- fix: Ldap Registration of new users (Thanks to guiuser)
- fix: Missing/Incorrect POD
- Made changes to spectre to handle finished workflows better.
- Added filter to groups and ldap connections to filter out group members in cases where the ldap group propery and the recursive group poperty are the same
7.0.0
- Welcome to a whole new world of WebGUI. After 2.5 years and 20,000 hours of
development, WebGUI 7 is finally here.
- Fixed a bug in the asset manager where you could be redirected to a wrong
page after using the delete, copy, cut, duplicate buttons.
- fix: Can't set View Purchase History Template in commerce settings
- fix: Template toolbar missing for Transaction Error Template
- fix: Page fails and cannot be edited except through the db if custom rich editor deleted.
- fix: Search returns not restricted to chosen path or asset type
- fix: Product Asset - specification labels not showing
- fix: Folders displayed for underprivileged users (wouter / Procolix)
- fix: Secure the search function
- fix: Export Functionality
- fix: Search displays already deleted files
- fix: Pagination not working in User Management System
- fix: Upgrade 6.8.10 to 6.99.5 (Thanks to Erik Svanberg for the patch)
- fix: Adding Survey Choices
- fix: User/Group problem
- fix: Edit LDAP Connection
- fix: SQL Report w/ pagination and nested queries
- fix: Unable to add Web Services Client
- Fixed a bug in spectre where it wasn't using session cookies.
- Fixed a bug in spectre where you couldn't shut it down if you started it on
an IP other than 127.0.0.1.
- Made the Include macro more secure.
- Added Len's patch to fix some caching problems.

View file

@ -21,6 +21,13 @@ save you many hours of grief.
Account Macro template
Admin Toggle Macro template
7.10.4
--------------------------------------------------------------------
* WebGUI now depends on Monkey::Patch for doing sanely scoped
monkeypatches.
* WebGUI now depends on version 0.20 of Scope::Guard.
7.10.3
--------------------------------------------------------------------
* In the Collaboration System, previously the Group to Post group

View file

@ -12,11 +12,24 @@ templates, you will need to apply these changes manually to your copies.
toggle.url => toggle_url
toggle.text => toggle_text
7.10.4
* DataForm email template - default_email
Do HTML escaping on field values. Move table tags outside of the loops so only one table
is generated.
* Template Variable template
The URL for that template has been changed from "help" to "root/import/adminconsole/help", making
the short URL Help available for WebGUI content use.
* EMS Badge Listing template - root/import/ems/ems-badge-listing-default
Changed the javascript to use a custom formatter for the Badge price.
7.10.3
* Carousel Default Template - root/import/carousel/carousel-default
Add a height parameter to the template.
* survey.css
Removed relative positioning and offset from top.

Binary file not shown.

View file

@ -0,0 +1,190 @@
#!/usr/bin/env perl
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
our ($webguiRoot);
BEGIN {
$webguiRoot = "../..";
unshift (@INC, $webguiRoot."/lib");
}
use strict;
use Getopt::Long;
use WebGUI::Session;
use WebGUI::Storage;
use WebGUI::Asset;
use List::Util qw(first);
my $toVersion = '7.10.4';
my $quiet; # this line required
my $session = start(); # this line required
# upgrade functions go here
changeTemplateHelpUrl($session);
addForkTable($session);
installForkCleanup($session);
finish($session); # this line required
#----------------------------------------------------------------------------
# Describe what our function does
#sub exampleFunction {
# my $session = shift;
# print "\tWe're doing some stuff here that you should know about... " unless $quiet;
# # and here's our code
# print "DONE!\n" unless $quiet;
#}
#----------------------------------------------------------------------------
# Describe what our function does
sub changeTemplateHelpUrl {
my $session = shift;
print "\tChange the URL for the template that displays help variables... " unless $quiet;
# and here's our code
my $template = WebGUI::Asset->newByDynamicClass($session, 'PBtmplHelp000000000001');
if ($template) {
$template->update({url => 'root/import/adminconsole/help'});
my $rs = $template->session->db->read("select revisionDate from assetData where assetId=? and revisionDate<>?",[$template->getId, $template->get("revisionDate")]);
while (my ($version) = $rs->array) {
my $old = WebGUI::Asset->new($session, $template->getId, $template->get("className"), $version);
$old->purgeRevision if defined $old;
}
}
else {
print "\n\tNO TEMPLATE FOR DISPLAYING TEMPLATE VARIABLES...";
}
print "DONE!\n" unless $quiet;
}
#----------------------------------------------------------------------------
# Creates a new table for tracking background processes
sub addForkTable {
my $session = shift;
my $db = $session->db;
my $sth = $db->dbh->table_info('', '', 'Fork', 'TABLE');
return if ($sth->fetch);
print "\tAdding Fork table..." unless $quiet;
my $sql = q{
CREATE TABLE Fork (
id CHAR(22),
userId CHAR(22),
groupId CHAR(22),
status LONGTEXT,
error TEXT,
startTime BIGINT(20),
endTime BIGINT(20),
finished BOOLEAN DEFAULT FALSE,
latch BOOLEAN DEFAULT FALSE,
PRIMARY KEY(id)
);
};
$db->write($sql);
print "DONE!\n" unless $quiet;
}
#----------------------------------------------------------------------------
# install a workflow to clean up old background processes
sub installForkCleanup {
my $session = shift;
print "\tInstalling Fork Cleanup workflow..." unless $quiet;
my $class = 'WebGUI::Workflow::Activity::RemoveOldForks';
$session->config->addToArray('workflowActivities/None', $class);
my $wf = WebGUI::Workflow->new($session, 'pbworkflow000000000001');
my $a = first { ref $_ eq $class } @{ $wf->getActivities };
unless ($a) {
$a = $wf->addActivity($class);
$a->set(title => 'Remove Old Forks');
};
print "DONE!\n" unless $quiet;
}
# -------------- DO NOT EDIT BELOW THIS LINE --------------------------------
#----------------------------------------------------------------------------
# Add a package to the import node
sub addPackage {
my $session = shift;
my $file = shift;
print "\tUpgrading package $file\n" unless $quiet;
# Make a storage location for the package
my $storage = WebGUI::Storage->createTemp( $session );
$storage->addFileFromFilesystem( $file );
# Import the package into the import node
my $package = eval {
my $node = WebGUI::Asset->getImportNode($session);
$node->importPackage( $storage, {
overwriteLatest => 1,
clearPackageFlag => 1,
setDefaultTemplate => 1,
} );
};
if ($package eq 'corrupt') {
die "Corrupt package found in $file. Stopping upgrade.\n";
}
if ($@ || !defined $package) {
die "Error during package import on $file: $@\nStopping upgrade\n.";
}
return;
}
#-------------------------------------------------
sub start {
my $configFile;
$|=1; #disable output buffering
GetOptions(
'configFile=s'=>\$configFile,
'quiet'=>\$quiet
);
my $session = WebGUI::Session->open($webguiRoot,$configFile);
$session->user({userId=>3});
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->set({name=>"Upgrade to ".$toVersion});
return $session;
}
#-------------------------------------------------
sub finish {
my $session = shift;
updateTemplates($session);
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->commit;
$session->db->write("insert into webguiVersion values (".$session->db->quote($toVersion).",'upgrade',".time().")");
$session->close();
}
#-------------------------------------------------
sub updateTemplates {
my $session = shift;
return undef unless (-d "packages-".$toVersion);
print "\tUpdating packages.\n" unless ($quiet);
opendir(DIR,"packages-".$toVersion);
my @files = readdir(DIR);
closedir(DIR);
my $newFolder = undef;
foreach my $file (@files) {
next unless ($file =~ /\.wgpkg$/);
# Fix the filename to include a path
$file = "packages-" . $toVersion . "/" . $file;
addPackage( $session, $file );
}
}
#vim:ft=perl

View file

@ -0,0 +1,123 @@
#!/usr/bin/env perl
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
our ($webguiRoot);
BEGIN {
$webguiRoot = "../..";
unshift (@INC, $webguiRoot."/lib");
}
use strict;
use Getopt::Long;
use WebGUI::Session;
use WebGUI::Storage;
use WebGUI::Asset;
my $toVersion = '7.10.5';
my $quiet; # this line required
my $session = start(); # this line required
# upgrade functions go here
finish($session); # this line required
#----------------------------------------------------------------------------
# Describe what our function does
#sub exampleFunction {
# my $session = shift;
# print "\tWe're doing some stuff here that you should know about... " unless $quiet;
# # and here's our code
# print "DONE!\n" unless $quiet;
#}
# -------------- DO NOT EDIT BELOW THIS LINE --------------------------------
#----------------------------------------------------------------------------
# Add a package to the import node
sub addPackage {
my $session = shift;
my $file = shift;
print "\tUpgrading package $file\n" unless $quiet;
# Make a storage location for the package
my $storage = WebGUI::Storage->createTemp( $session );
$storage->addFileFromFilesystem( $file );
# Import the package into the import node
my $package = eval {
my $node = WebGUI::Asset->getImportNode($session);
$node->importPackage( $storage, {
overwriteLatest => 1,
clearPackageFlag => 1,
setDefaultTemplate => 1,
} );
};
if ($package eq 'corrupt') {
die "Corrupt package found in $file. Stopping upgrade.\n";
}
if ($@ || !defined $package) {
die "Error during package import on $file: $@\nStopping upgrade\n.";
}
return;
}
#-------------------------------------------------
sub start {
my $configFile;
$|=1; #disable output buffering
GetOptions(
'configFile=s'=>\$configFile,
'quiet'=>\$quiet
);
my $session = WebGUI::Session->open($webguiRoot,$configFile);
$session->user({userId=>3});
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->set({name=>"Upgrade to ".$toVersion});
return $session;
}
#-------------------------------------------------
sub finish {
my $session = shift;
updateTemplates($session);
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->commit;
$session->db->write("insert into webguiVersion values (".$session->db->quote($toVersion).",'upgrade',".time().")");
$session->close();
}
#-------------------------------------------------
sub updateTemplates {
my $session = shift;
return undef unless (-d "packages-".$toVersion);
print "\tUpdating packages.\n" unless ($quiet);
opendir(DIR,"packages-".$toVersion);
my @files = readdir(DIR);
closedir(DIR);
my $newFolder = undef;
foreach my $file (@files) {
next unless ($file =~ /\.wgpkg$/);
# Fix the filename to include a path
$file = "packages-" . $toVersion . "/" . $file;
addPackage( $session, $file );
}
}
#vim:ft=perl

View file

@ -391,6 +391,9 @@ use WebGUI::HTML;
use WebGUI::HTMLForm;
use WebGUI::Keyword;
require WebGUI::ProgressBar;
use WebGUI::ProgressTree;
use Monkey::Patch;
use WebGUI::Fork;
use WebGUI::Search::Index;
use WebGUI::TabForm;
use WebGUI::PassiveAnalytics::Logging;
@ -836,6 +839,59 @@ sub fixUrl {
#-------------------------------------------------------------------
=head2 forkWithStatusPage ($args)
Kicks off a WebGUI::Fork running $method with $args (from the args hashref)
and redirects to a ProgressTree status page to show the progress. The
following arguments are required in $args:
=head3 method
The name of the WebGUI::Asset method to call
=head3 args
The arguments to pass that method (see WebGUI::Fork)
=head3 plugin
The WebGUI::Operation::Fork plugin to render (e.g. ProgressTree)
=head3 title
An key in Asset's i18n hash for the title of the rendered console page
=head3 redirect
The full url to redirect to after the fork has finished.
=cut
sub forkWithStatusPage {
my ( $self, $args ) = @_;
my $session = $self->session;
my $process = WebGUI::Fork->start( $session, 'WebGUI::Asset', $args->{method}, $args->{args} );
if ( my $groupId = $args->{groupId} ) {
$process->setGroup($groupId);
}
my $method = $session->form->get('proceed') || 'manageTrash';
my $i18n = WebGUI::International->new( $session, 'Asset' );
my $pairs = $process->contentPairs(
$args->{plugin}, {
title => $i18n->get( $args->{title} ),
icon => 'assets',
proceed => $args->{redirect} || '',
}
);
$session->http->setRedirect( $self->getUrl($pairs) );
return 'redirect';
} ## end sub forkWithStatusPage
#-------------------------------------------------------------------
=head2 getClassById ( $session, $assetId )
Class method that looks up a className for an object in the database, using it's assetId.
@ -2412,6 +2468,35 @@ sub setSize {
$self->assetSize($size);
}
#-------------------------------------------------------------------
=head2 setState ( $state )
Updates the asset table with the new state of the asset.
=cut
sub setState {
my ($self, $state) = @_;
my $sql = q{
UPDATE asset
SET state = ?,
stateChangedBy = ?,
stateChanged = ?
WHERE assetId = ?
};
my @props = ($state, $self->session->user->userId, time);
$self->session->db->write(
$sql, [
@props,
$self->getId,
]
);
$self->state($state);
$self->stateChangedBy($props[1]);
$self->stateChanged($props[2]);
$self->purgeCache;
}
#-------------------------------------------------------------------
@ -2477,23 +2562,7 @@ sub write {
Returns the asset's url without any site specific prefixes. If you want a browser friendly url see the getUrl() method.
# set the property
if ($propertyDefinition->{serialize}) {
# Only serialize references
if ( ref $value ) {
$setPairs{$property} = JSON->new->canonical->encode($value);
}
# Passing already serialized JSON string
elsif ( $value ) {
$setPairs{$property} = $value;
$value = JSON->new->decode( $value ); # for setting in _properties, below
}
}
else {
$setPairs{$property} = $value;
}
$self->{_properties}{$property} = $value;
}
=head3 value
The new value to set the URL to.

View file

@ -400,6 +400,22 @@ sub canEdit {
#-------------------------------------------------------------------
=head2 duplicate ( )
Extend the super class to duplicate the storage location.
=cut
sub duplicate {
my $self = shift;
my $newAsset = $self->SUPER::duplicate(@_);
my $newStorage = $self->getStorageLocation->copy;
$newAsset->update({storageId=>$newStorage->getId});
return $newAsset;
}
#-------------------------------------------------------------------
=head2 generateRecurrence (date)
Creates an recurrence event in the parent calendar for the given date

View file

@ -277,7 +277,7 @@ sub getContentLastModified {
my $shortcut = $self->getShortcut; # XXX "newById must get an assetId"
my $shortcuttedRev;
if (defined $shortcut) {
$shortcuttedRev = $shortcut->get('revisionDate');
$shortcuttedRev = $shortcut->getContentLastModified;
return $assetRev > $shortcuttedRev ? $assetRev : $shortcuttedRev;
} else {
return 0;

View file

@ -762,7 +762,7 @@ ENDHTML
. $i18n->get( "warning default template" )
. q{</p><p>}
. sprintf( q{<a href="} . $duplicateUrl . q{">%s</a>}, $i18n->get( "make duplicate label" ) )
. q{</p></div}
. q{</p></div>}
;
}

View file

@ -83,6 +83,7 @@ property mailAccount => (
tab => 'mail',
label => [ "mail account", 'Asset_Collaboration' ],
hoverHelp => [ "mail account help", 'Asset_Collaboration' ],
extras => 'autocomplete="off"',
);
property mailPassword => (
fieldType => "password",

View file

@ -107,6 +107,24 @@ sub _fetchDepartments {
}
#-------------------------------------------------------------------
=head2 getStatusList
Returns the statusList property as an array
=cut
sub getStatusList {
my $self = shift;
my $text = $self->get('statusList');
return
grep { $_ } # no empty lines
map { s/^\s+//; s/\s+$//; $_ } # trim
split(/\r\n|\r|\n/, $text); # seperated by any kind of newline
}
#-------------------------------------------------------------------
=head2 prepareView ( )
@ -174,16 +192,9 @@ sub view {
}
my $statusUserId = $self->session->scratch->get("userId") || $self->session->user->userId;
my $statusListString = $self->statusList;
my @statusListArray = split("\n",$statusListString);
my $statusListHashRef;
tie %$statusListHashRef, 'Tie::IxHash';
foreach my $status (@statusListArray) {
chomp($status);
next if $status eq "";
$statusListHashRef->{$status} = $status;
}
tie my %statusOptions, 'Tie::IxHash', (
map { $_ => $_ } $self->getStatusList
);
#$self->session->log->warn("VIEW: userId: ".$statusUserId."\n" );
my ($status) = $session->db->quickArray(
@ -222,7 +233,7 @@ sub view {
$f->radioList(
-name=>"status",
-value=>$status,
-options=>$statusListHashRef,
-options=>\%statusOptions,
-label=>$i18n->get(5),
-hoverHelp=>$i18n->get('5 description'),
);

View file

@ -298,9 +298,10 @@ sub getFolder {
my ($self, $date) = @_;
my $session = $self->session;
my $folderName = $session->datetime->epochToHuman($date, DATE_FORMAT);
my $folderUrl = join '/', $self->getUrl, $folderName;
my $folderUrl = $self->getFolderUrl($folderName);
my $folder = eval { WebGUI::Asset->newByUrl($session, $folderUrl); };
return $folder if !Exception::Class->caught();
##The requested folder doesn't exist. Make it and autocommit it.
##For a fully automatic commit, save the current tag, create a new one
@ -335,6 +336,26 @@ sub getFolder {
#-------------------------------------------------------------------
=head2 getFolderUrl ( name )
Constructs a url for a subfolder with the given name.
=cut
sub getFolderUrl {
my ($self, $name) = @_;
my $session = $self->session;
my $base = $self->getUrl;
$base =~ s/(.*)\..*/$1/;
my $url = "$base/$name";
if (my $ext = $session->setting->get('urlExtension')) {
$url .= ".$ext";
}
return $session->url->urlize($url);
}
#-------------------------------------------------------------------
=head2 getKeywordFilename ( $keyword )
Returns the name for the file containing stories that match this keyword. Used

View file

@ -45,6 +45,8 @@ sub _defaultThingId_options {
return $things;
}
use WebGUI::ProgressBar;
#-------------------------------------------------------------------
@ -2628,6 +2630,11 @@ sub www_export {
my $thingProperties = $self->getThing($thingId);
return $session->privilege->insufficient() unless $self->hasPrivileges($thingProperties->{groupIdExport});
my $i18n = WebGUI::International->new($session, 'Asset_Thingy');
my $pb = WebGUI::ProgressBar->new($session);
$pb->start($i18n->get('export label').' '.$thingProperties->{label}, $session->url->extras('assets/thingy.gif'));
$pb->update($i18n->get('Creating column headers'));
my $tempStorage = WebGUI::Storage->createTemp($session);
$fields = $session->db->read('select * from Thingy_fields where assetId =? and thingId = ? order by sequenceNumber',
[$self->getId,$thingId]);
while (my $field = $fields->hashRef) {
@ -2649,9 +2656,13 @@ sub www_export {
### Loop through the returned structure and put it through Text::CSV
# Column heads
$out = WebGUI::Text::joinCSV(@fieldLabels);
my $csv_filename = 'export_'.$thingProperties->{label}.'.csv';
$tempStorage->addFileFromScalar($csv_filename, WebGUI::Text::joinCSV(@fieldLabels));
open my $CSV, '>', $tempStorage->getPath($csv_filename);
# Data lines
$pb->update($i18n->get('Writing data'));
my $rowCounter = 0;
while (my $data = $sth->hashRef) {
my @fieldValues;
foreach my $field (@fields){
@ -2660,19 +2671,20 @@ sub www_export {
my $value = $self->getFieldValue($data->{"field_".$fieldId},$field->{properties},"%y-%m-%d","%y-%m-%d %j:%n:%s");
push(@fieldValues, $value);
}
foreach my $metaDataField (@metaDataFields){
push(@fieldValues,$data->{$metaDataField});
if ($thingProperties->{exportMetaData}) {
foreach my $metaDataField (@metaDataFields){
push(@fieldValues,$data->{$metaDataField});
}
}
$out .= "\n".WebGUI::Text::joinCSV(
@fieldValues
);
print $CSV "\n".WebGUI::Text::joinCSV( @fieldValues );
#if (! ++$rowCounter % 25) {
$pb->update($i18n->get('Writing data'));
#}
}
$fileName = "export_".$thingProperties->{label}.".csv";
$self->session->http->setFilename($fileName,"application/octet-stream");
$self->session->http->sendHeader;
return $out;
close $CSV;
$pb->update(sprintf q|<a href="%s">%s</a>|, $self->getUrl, sprintf($i18n->get('Return to %s'), $thingProperties->{label}));
return $pb->finish($tempStorage->getUrl($csv_filename));
}
#-------------------------------------------------------------------

View file

@ -46,13 +46,26 @@ Duplicates this asset and the entire subtree below it. Returns the root of the
If true, then only children, and not descendants, will be duplicated.
=head3 $state
Set this to "clipboard" if you want the resulting asset to be on the clipboard
(rather than published) when we're done.
=cut
sub duplicateBranch {
my $self = shift;
my $childrenOnly = shift;
my ($self, $childrenOnly, $state) = @_;
my $session = $self->session;
my $log = $session->log;
my $clipboard = $state && $state =~ /^clipboard/;
my $newAsset = $self->duplicate(
{ skipAutoCommitWorkflows => 1,
skipNotification => 1,
state => $state,
}
);
my $newAsset = $self->duplicate({skipAutoCommitWorkflows=>1,skipNotification=>1});
# Correctly handle positions for Layout assets
my $contentPositions = $self->get("contentPositions");
my $assetsToHide = $self->get("assetsToHide");
@ -66,7 +79,21 @@ sub duplicateBranch {
next;
}
last unless $child;
my $newChild = $childrenOnly ? $child->duplicate({skipAutoCommitWorkflows=>1, skipNotification=>1}) : $child->duplicateBranch;
my $newChild;
if ($childrenOnly) {
$newChild = $child->duplicate(
{ skipAutoCommitWorkflows => 1,
skipNotification => 1,
state => $clipboard && 'clipboard-limbo',
}
);
}
elsif($clipboard) {
$newChild = $child->duplicateBranch(0, 'clipboard-limbo');
}
else {
$newChild = $child->duplicateBranch;
}
$newChild->setParent($newAsset);
my ($oldChildId, $newChildId) = ($child->getId, $newChild->getId);
$contentPositions =~ s/\Q${oldChildId}\E/${newChildId}/g if ($contentPositions);

View file

@ -50,6 +50,58 @@ sub canPaste {
return $self->validParent($self->session); ##Lazy call to a class method
}
#-------------------------------------------------------------------
=head2 copyInFork ( $process, $args )
WebGUI::Fork method called by www_copy
=cut
sub copyInFork {
my ($process, $args) = @_;
my $session = $process->session;
my $asset = WebGUI::Asset->new($session, $args->{assetId});
my @pedigree = ('self');
my $childrenOnly = 0;
if ($args->{childrenOnly}) {
$childrenOnly = 1;
push @pedigree, 'children';
}
else {
push @pedigree, 'descendants';
}
my $ids = $asset->getLineage(\@pedigree);
my $tree = WebGUI::ProgressTree->new($session, $ids);
my $patch = Monkey::Patch::patch_class(
'WebGUI::Asset', 'duplicate', sub {
my $duplicate = shift;
my $self = shift;
my $id = $self->getId;
$tree->focus($id);
my $asset = eval { $self->$duplicate(@_) };
my $e = $@;
if ($e) {
$tree->note($id, $e);
$tree->failure($id, 'Died');
}
else {
$tree->success($id);
}
$process->update(sub { $tree->json });
die $e if $e;
return $asset;
}
);
my $newAsset = $asset->duplicateBranch($childrenOnly, 'clipboard');
$newAsset->update({ title => $newAsset->getTitle . ' (copy)'});
if ($args->{commit}) {
my $tag = WebGUI::VersionTag->getWorking($session);
$tag->requestCommit();
}
}
#-------------------------------------------------------------------
=head2 cut ( )
@ -98,6 +150,10 @@ A hash reference of options that can modify how this method works.
Assets that normally autocommit their workflows (like CS Posts, and Wiki Pages) won't if this is true.
=head4 state
A state for the duplicated asset (defaults to 'published')
=cut
sub duplicate {
@ -134,6 +190,10 @@ sub duplicate {
keywords => $keywords,
} );
if (my $state = $options->{state}) {
$newAsset->setState($state);
}
return $newAsset;
}
@ -224,7 +284,11 @@ sub paste {
# Update lineage in search index.
$self->purgeCache;
my $assetIter = $pastedAsset->getLineageIterator(['self', 'descendants']);
my $assetIter = $pastedAsset->getLineageIterator(
['self', 'descendants'], {
statesToInclude => ['clipboard','clipboard-limbo']
}
);
while ( 1 ) {
my $asset;
eval { $asset = $assetIter->() };
@ -235,15 +299,64 @@ sub paste {
last unless $asset;
$outputSub->(sprintf $i18n->get('indexing %s'), $pastedAsset->getTitle) if defined $outputSub;
$asset->setState('published');
$asset->indexContent();
}
$pastedAsset->updateHistory("pasted to parent ".$self->getId);
return 1;
}
return 0;
}
#-------------------------------------------------------------------
=head2 pasteInFork ( )
WebGUI::Fork method called by www_pasteList
=cut
sub pasteInFork {
my ( $process, $args ) = @_;
my $session = $process->session;
my $self = WebGUI::Asset->new( $session, $args->{assetId} );
my @roots = grep { $_ && $_->canEdit }
map { WebGUI::Asset->newPending( $session, $_ ) } @{ $args->{list} };
my @ids = map {
my $list
= $_->getLineage( [ 'self', 'descendants' ], { statesToInclude => [ 'clipboard', 'clipboard-limbo' ] } );
@$list;
} @roots;
my $tree = WebGUI::ProgressTree->new( $session, \@ids );
my $patch = Monkey::Patch::patch_class(
'WebGUI::Asset',
'indexContent',
sub {
my $indexContent = shift;
my $self = shift;
my $id = $self->getId;
$tree->focus($id);
my $ret = eval { $self->$indexContent(@_) };
my $e = $@;
if ($e) {
$tree->note( $id, $e );
$tree->failure( $id, 'Died' );
}
else {
$tree->success($id);
}
$process->update( sub { $tree->json } );
die $e if $e;
return $ret;
}
);
$self->paste( $_->getId ) for @roots;
} ## end sub pasteInFork
#-------------------------------------------------------------------
=head2 www_copy ( )
@ -257,89 +370,49 @@ If with children/descendants is selected, a progress bar will be rendered.
sub www_copy {
my $self = shift;
my $session = $self->session;
my $http = $session->http;
my $redir = $self->getParent->getUrl;
return $session->privilege->insufficient unless $self->canEdit;
my $with = $session->form->get('with');
my %args;
if ($with eq 'children') {
$self->_wwwCopyChildren;
$args{childrenOnly} = 1;
}
elsif ($with eq 'descendants') {
$self->_wwwCopyDescendants;
elsif ($with ne 'descendants') {
my $newAsset = $self->duplicate({
skipAutoCommitWorkflows => 1,
state => 'clipboard'
}
);
$newAsset->update({ title => $newAsset->getTitle . ' (copy)'});
my $result = WebGUI::VersionTag->autoCommitWorkingIfEnabled(
$session, {
allowComments => 1,
returnUrl => $redir,
}
);
$http->setRedirect($redir) unless $result eq 'redirect';
return 'redirect';
}
else {
$self->_wwwCopySingle;
my $tag = WebGUI::VersionTag->getWorking($session);
if ($tag->canAutoCommit) {
$args{commit} = 1;
unless ($session->setting->get('skipCommitComments')) {
$redir = $tag->autoCommitUrl($redir);
}
}
}
#-------------------------------------------------------------------
sub _wwwCopyChildren { shift->_wwwCopyProgress(1) }
#-------------------------------------------------------------------
sub _wwwCopyDescendants { shift->_wwwCopyProgress(0) }
#-------------------------------------------------------------------
sub _wwwCopyFinish {
my ($self, $newAsset) = @_;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, 'Asset');
my $title = sprintf("%s (%s)", $self->getTitle, $i18n->get('copy'));
$newAsset->update({ title => $title });
$newAsset->cut;
my $result = WebGUI::VersionTag->autoCommitWorkingIfEnabled(
$session, {
allowComments => 1,
returnUrl => $self->getUrl,
$args{assetId} = $self->getId;
$self->forkWithStatusPage({
plugin => 'ProgressTree',
title => 'Copy Assets',
redirect => $redir,
method => 'copyInFork',
args => \%args
}
);
my $redirect = $result eq 'redirect';
$session->asset($self->getContainer) unless $redirect;
return $redirect;
}
#-------------------------------------------------------------------
sub _wwwCopyProgress {
my ($self, $childrenOnly) = @_;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, 'Asset');
# This could potentially time out, so we'll render a progress bar.
my $pb = WebGUI::ProgressBar->new($session);
my @stack;
return $pb->run(
title => $i18n->get('Copy Assets'),
icon => $session->url->extras('adminConsole/assets.gif'),
code => sub {
my $bar = shift;
my $newAsset = $self->duplicateBranch($childrenOnly);
$bar->update($i18n->get('cut'));
my $redirect = $self->_wwwCopyFinish($newAsset);
return $redirect ? $self->getUrl : $self->getContainer->getUrl;
},
wrap => {
'WebGUI::Asset::duplicateBranch' => sub {
my ($bar, $original, $asset, @args) = @_;
push(@stack, $asset->getTitle);
my $ret = $asset->$original(@args);
pop(@stack);
return $ret;
},
'WebGUI::Asset::duplicate' => sub {
my ($bar, $original, $asset, @args) = @_;
my $name = join '/', @stack, $asset->getTitle;
$bar->update($name);
return $asset->$original(@args);
},
}
);
}
#-------------------------------------------------------------------
sub _wwwCopySingle {
my $self = shift;
my $newAsset = $self->duplicate({skipAutoCommitWorkflows => 1});
my $redirect = $self->_wwwCopyFinish($newAsset);
return $redirect ? undef : $self->getContainer->www_view;
}
#-------------------------------------------------------------------
@ -365,9 +438,8 @@ sub www_copyList {
foreach my $assetId ($session->form->param("assetId")) {
my $asset = WebGUI::Asset->newById($session,$assetId);
if ($asset->canEdit) {
my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1});
my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1, state => 'clipboard'});
$newAsset->update({ title=>$newAsset->getTitle.' (copy)'});
$newAsset->cut;
}
}
if ($self->session->form->process("proceed") ne "") {
@ -505,7 +577,7 @@ sub www_duplicateList {
foreach my $assetId ($session->form->param("assetId")) {
my $asset = WebGUI::Asset->newById($session,$assetId);
if ($asset->canEdit) {
my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1, });
my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1});
$newAsset->update({ title=>$newAsset->getTitle.' (copy)'});
}
}
@ -658,26 +730,25 @@ the Asset Manager.
sub www_pasteList {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient() unless $self->canEdit && $session->form->validToken;
my $form = $session->form;
my $pb = WebGUI::ProgressBar->new($session);
##Need to store the list of assetIds for the status subroutine
my @assetIds = $form->param('assetId');
##Need to set the URL that should be displayed when it is done
my $i18n = WebGUI::International->new($session, 'Asset');
$pb->start($i18n->get('Paste Assets'), $session->url->extras('adminConsole/assets.gif'));
ASSET: foreach my $clipId (@assetIds) {
next ASSET unless $clipId;
my $pasteAsset = WebGUI::Asset->newPending($session, $clipId);
if (! $pasteAsset && $pasteAsset->canEdit) {
$pb->update(sprintf $i18n->get('skipping %s'), $pasteAsset->getTitle);
next ASSET;
}
$self->paste($clipId, sub {$pb->update(@_);});
}
return $pb->finish( ($form->param('proceed') eq 'manageAssets') ? $self->getUrl('op=assetManager') : $self->getUrl );
}
return $session->privilege->insufficient() unless $self->canEdit && $session->form->validToken;
$self->forkWithStatusPage( {
plugin => 'ProgressTree',
title => 'Paste Assets',
redirect => $self->getUrl(
$form->get('proceed') eq 'manageAssets'
? 'op=assetManager'
: ()
),
method => 'pasteInFork',
args => {
assetId => $self->getId,
list => [ $form->get('assetId') ],
}
}
);
} ## end sub www_pasteList
1;

View file

@ -21,8 +21,9 @@ use Scalar::Util qw(looks_like_number);
use WebGUI::International;
use WebGUI::Exception;
use WebGUI::Session;
use URI::URL ();
use Scope::Guard;
use URI::URL;
use Scope::Guard qw(guard);
use WebGUI::ProgressTree;
=head1 NAME
@ -299,21 +300,53 @@ sub exportAsHtml {
}
sub exportBranch {
my $self = shift;
my $options = shift;
my $reportSession = shift;
my ($self, $options, $reportSession) = @_;
my $i18n = $reportSession &&
WebGUI::International->new($self->session, 'Asset');
my $depth = $options->{depth};
my $indexFileName = $options->{indexFileName};
my $extrasUploadAction = $options->{extrasUploadAction};
my $rootUrlAction = $options->{rootUrlAction};
my $exportedCount = 0;
my $report = $options->{report};
my $i18n;
if ( $reportSession ) {
$i18n = WebGUI::International->new($self->session, 'Asset');
unless ($report) {
if ($reportSession) {
# We got a report session and no report coderef, so we'll print
# messages out. NOTE: this is for backcompat, but I'm not sure we
# even need it any more. I think the only thing using it was the
# old iframe-based export status report. --frodwith
my %reports = (
'bad user privileges' => sub {
my $asset = shift;
my $url = $asset->getUrl;
$i18n->get('bad user privileges') . "\n$url"
},
'not exportable' => sub {
my $asset = shift;
my $fullPath = $asset->exportGetUrlAsPath;
"$fullPath skipped, not exportable<br />";
},
'exporting page' => sub {
my $asset = shift;
my $fullPath = $asset->exportGetUrlAsPath;
sprintf $i18n->get('exporting page'), $fullPath;
},
'collateral notes' => sub { pop },
'done' => sub { $i18n->get('done') },
);
$report = sub {
my ($asset, $key, @args) = @_;
my $code = $reports{$key};
my $message = $asset->$code();
$reportSession->output->print($message, @args);
};
}
else {
$report = sub {};
}
}
my $exportedCount = 0;
my $exportAsset = sub {
my ( $assetId ) = @_;
@ -330,26 +363,18 @@ sub exportBranch {
# skip this asset if we can't view it as this user.
unless( $asset->canView ) {
if( $reportSession ) {
my $message = sprintf( $i18n->get('bad user privileges') . "\n") . $asset->getUrl;
$reportSession->output->print($message);
}
$asset->$report('bad user privileges');
next;
}
# skip this asset if it's not exportable.
unless ( $asset->exportCheckExportable ) {
if ( $reportSession ) {
$reportSession->output->print("$fullPath skipped, not exportable<br />");
}
$asset->$report('not exportable');
next;
}
# tell the user which asset we're exporting.
if ( $reportSession ) {
my $message = sprintf $i18n->get('exporting page'), $fullPath;
$reportSession->output->print($message);
}
$asset->$report('exporting page');
# try to write the file
eval { $asset->exportWriteFile };
@ -359,9 +384,25 @@ sub exportBranch {
# next, tell the asset that we're exporting, so that it can export any
# of its collateral or other extra data.
eval { $asset->exportAssetCollateral($asset->exportGetUrlAsPath, $options, $reportSession) };
if($@) {
WebGUI::Error->throw(error => "failed to export asset collateral for URL " . $asset->getUrl . ": $@");
{
# For backcompat we want to capture anything that
# exportAssetCollateral may have printed and report it to the
# coderef. We should get rid of this as soon as we're ready to
# break that api.
my $cs = $self->session->duplicate();
open my $handle, '>', \my $output;
$cs->output->setHandle($handle);
my $guard = guard {
close $handle;
$cs->var->end;
$cs->close();
$asset->$report('collateral notes', $output) if $output;
};
my $path = $asset->exportGetUrlAsPath;
eval { $asset->exportAssetCollateral($path, $options, $cs) };
if($@) {
WebGUI::Error->throw(error => "failed to export asset collateral for URL " . $asset->getUrl . ": $@");
}
}
# we exported this one successfully, so count it
@ -371,19 +412,12 @@ sub exportBranch {
$self->session->db->write( "UPDATE asset SET lastExportedAs = ? WHERE assetId = ?",
[ $fullPath, $asset->getId ] );
$self->updateHistory("exported");
$asset->updateHistory('exported');
# tell the user we did this asset correctly
if ( $reportSession ) {
$reportSession->output->print($i18n->get('done'));
}
#use Devel::Cycle;
#warn "CHECKING on " . ref( $asset ) . ' ID: ' . $asset->getId . "\n";
#find_cycle( $asset );
$asset->$report('done');
};
my $assetIds = $self->exportGetDescendants(undef, $depth);
foreach my $assetId ( @{$assetIds} ) {
$exportAsset->( $assetId );
@ -616,6 +650,44 @@ sub exportGetUrlAsPath {
#-------------------------------------------------------------------
=head2 exportInFork
Intended to be called by WebGUI::Fork. Runs exportAsHtml on the
specified asset and keeps a json structure as the status.
=cut
sub exportInFork {
my ( $process, $args ) = @_;
my $session = $process->session;
my $self = WebGUI::Asset->new( $session, delete $args->{assetId} );
$args->{indexFileName} = delete $args->{index};
my $assetIds = $self->exportGetDescendants( undef, $args->{depth} );
my $tree = WebGUI::ProgressTree->new( $session, $assetIds );
my %reports = (
'done' => sub { $tree->success(shift) },
'exporting page' => sub { $tree->focus(shift) },
'collateral notes' => sub { $tree->note(@_) },
'bad user privileges' => sub {
$tree->failure( shift, 'Bad User Privileges' );
},
'not exportable' => sub {
$tree->failure( shift, 'Not Exportable' );
},
);
$args->{report} = sub {
my ( $asset, $key, @args ) = @_;
my $code = $reports{$key};
$code->( $asset->getId, @args );
$process->update( sub { $tree->json } );
};
$self->exportAsHtml($args);
$tree->focus(undef);
$process->update( $tree->json );
} ## end sub exportInFork
#-------------------------------------------------------------------
=head2 exportSymlinkExtrasUploads ( [ session ] )
Class or object method. Sets up the extras and uploads symlinks.
@ -926,16 +998,25 @@ Displays the export status page
=cut
sub www_exportStatus {
my $self = shift;
return $self->session->privilege->insufficient() unless ($self->session->user->isInGroup(13));
my $i18n = WebGUI::International->new($self->session, "Asset");
my $iframeUrl = $self->getUrl('func=exportGenerate');
foreach my $formVar (qw/index depth userId extrasUploadsAction rootUrlAction exportUrl/) {
$iframeUrl = $self->session->url->append($iframeUrl, $formVar . '=' . $self->session->form->process($formVar));
}
my $output = '<iframe src="' . $iframeUrl . '" title="' . $i18n->get('Page Export Status') . '" width="100%" height="500"></iframe>';
$self->getAdminConsole->render($output, $i18n->get('Page Export Status'), "Asset");
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient
unless $session->user->isInGroup(13);
my $form = $session->form;
my @vars = qw(
index depth userId extrasUploadsAction rootUrlAction exportUrl
);
$self->forkWithStatusPage({
plugin => 'ProgressTree',
title => 'Page Export Status',
method => 'exportInFork',
groupId => 13,
args => {
assetId => $self->getId,
map { $_ => scalar $form->get($_) } @vars
}
}
);
}
#-------------------------------------------------------------------

View file

@ -200,6 +200,63 @@ sub purge {
return 1;
}
#-------------------------------------------------------------------
=head2 purgeInFork
WebGUI::Fork method called by www_purgeList
=cut
sub purgeInFork {
my ( $process, $list ) = @_;
my $session = $process->session;
my @roots = grep { $_ && $_->canEdit }
map { WebGUI::Asset->newPending( $session, $_ ) } @$list;
my @ids = map {
my $list = $_->getLineage(
[ 'self', 'descendants' ], {
statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)],
statusToInclude => [qw(approved archived pending)],
}
);
@$list;
} @roots;
my $tree = WebGUI::ProgressTree->new( $session, \@ids );
my $patch = Monkey::Patch::patch_class(
'WebGUI::Asset',
'purge',
sub {
my ( $purge, $self, $options ) = @_;
my $id = $self->getId;
my $zero = '';
$tree->focus($id);
$options ||= {};
local $options->{outputSub} = sub { $zero .= $_[0] };
my $ret = eval { $self->$purge($options) };
my $e = $@;
$tree->focus($id);
if ($e) {
$tree->failure( $id, 'Died' );
$tree->note( $id, $e );
}
elsif ( !$ret ) {
$tree->failure( $id, 'Failed' );
$tree->note( $id, $zero );
}
else {
$tree->success($id);
}
$process->update( sub { $tree->json } );
die $e if $e;
return $ret;
}
);
$_->purge for @roots;
} ## end sub purgeInFork
#-------------------------------------------------------------------
@ -246,7 +303,13 @@ sub trash {
return undef;
}
my $assetIter = $self->getLineageIterator(['self','descendants']);
my $assetIter = $self->getLineageIterator(
['self','descendants'], {
statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)],
statusToInclude => [qw(approved archived pending)],
}
);
my $rootId = $self->getId;
while ( 1 ) {
my $asset;
eval { $asset = $assetIter->() };
@ -263,6 +326,18 @@ sub trash {
$outputSub->($i18n->get('Clearing cache'));
$asset->purgeCache;
$asset->updateHistory("trashed");
if ($asset->getId eq $rootId) {
$asset->setState('trash');
# setState will take care of _properties in $asset, but not in
# $self (whooops!), so we need to manually update.
my @keys = qw(state stateChangedBy stateChanged);
$self->state($asset->state);
$self->stateChangedBy($asset->stateChangedBy);
$self->stateChanged($asset->stateChanged);
}
else {
$asset->setState('trash-limbo');
}
}
# Trash any shortcuts to this asset
@ -287,6 +362,50 @@ sub trash {
return 1;
}
#-------------------------------------------------------------------
=head2 trashInFork
WebGUI::Fork method called by www_deleteList and www_delete to move assets
into the trash.
=cut
sub trashInFork {
my ( $process, $list ) = @_;
my $session = $process->session;
my @roots = grep { $_->canEdit && $_->canEditIfLocked }
map {
eval { WebGUI::Asset->newPending( $session, $_ ) }
} @$list;
my @ids = map {
my $list = $_->getLineage(
[ 'self', 'descendants' ], {
statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)],
statusToInclude => [qw(approved archived pending)],
}
);
@$list;
} @roots;
my $tree = WebGUI::ProgressTree->new( $session, \@ids );
my $patch = Monkey::Patch::patch_class(
'WebGUI::Asset',
'setState',
sub {
my ( $setState, $self, $state ) = @_;
my $id = $self->getId;
$tree->focus($id);
my $ret = $self->$setState($state);
$tree->success($id);
$process->update(sub { $tree->json });
return $ret;
}
);
$_->trash() for @roots;
} ## end sub trashInFork
require WebGUI::Workflow::Activity::DeleteExportedFiles;
sub _invokeWorkflowOnExportedFiles {
my $self = shift;
@ -316,7 +435,7 @@ sub _invokeWorkflowOnExportedFiles {
=head2 www_delete
Moves self to trash, returns www_view() method of Container or Parent if canEdit.
Moves self to trash in fork, redirects to Container or Parent if canEdit.
Otherwise returns AdminConsole rendered insufficient privilege.
=cut
@ -331,8 +450,14 @@ sub www_delete {
if ($self->getId eq $asset->getId) {
$asset = $self->getParent;
}
$self->session->asset($asset);
return $asset->www_view;
$self->forkWithStatusPage({
plugin => 'ProgressTree',
title => 'Delete Assets',
redirect => $asset->getUrl,
method => 'trashInFork',
args => [ $self->getId ],
}
);
}
#-------------------------------------------------------------------
@ -348,31 +473,20 @@ by the form variable C<proceeed>.
=cut
sub www_deleteList {
my $self = shift;
my $session = $self->session;
my $pb = WebGUI::ProgressBar->new($session);
my $i18n = WebGUI::International->new($session, 'Asset');
my $form = $session->form;
my @assetIds = $form->param('assetId');
$pb->start($i18n->get('Delete Assets'), $session->url->extras('adminConsole/assets.gif'));
return $self->session->privilege->insufficient() unless $session->form->validToken;
ASSETID: foreach my $assetId (@assetIds) {
my $asset = eval { WebGUI::Asset->newPending($session,$assetId); };
if ($@) {
$pb->update(sprintf $i18n->get('Error getting asset with assetId %s'), $assetId);
next ASSETID;
my $self = shift;
my $session = $self->session;
my $form = $session->form;
return $session->privilege->insufficient() unless $session->form->validToken;
my $method = $form->get('proceed') || 'manageTrash';
$self->forkWithStatusPage({
plugin => 'ProgressTree',
title => 'Delete Assets',
redirect => $self->getUrl("func=$method"),
method => 'trashInFork',
args => [ $form->get('assetId') ],
}
if (! ($asset->canEdit && $asset->canEditIfLocked) ) {
$pb->update(sprintf $i18n->get('You cannot edit the asset %s, skipping'), $asset->getTitle);
}
else {
$asset->trash({outputSub => sub { $pb->update(@_); } });
}
}
my $method = ($session->form->process("proceed")) ? $session->form->process('proceed') : 'manageTrash';
$pb->finish($self->getUrl('func='.$method));
}
);
} ## end sub www_deleteList
#-------------------------------------------------------------------
@ -479,29 +593,18 @@ Returns insufficient privileges unless the submitted form passes the validToken
sub www_purgeList {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
return $session->privilege->insufficient() unless $session->form->validToken;
my $pb = WebGUI::ProgressBar->new($session);
my $i18n = WebGUI::International->new($session, 'Asset');
$pb->start($i18n->get('purge'), $session->url->extras('adminConsole/assets.gif'));
ASSETID: foreach my $id ($session->form->param("assetId")) {
my $asset = eval { WebGUI::Asset->newPending($session,$id); };
if ($@) {
$pb->update(sprintf $i18n->get('Error getting asset with assetId %s'), $id);
next ASSETID;
my $method = $form->get('proceed') || 'manageTrash';
$method .= ';systemTrash=1' if $form->get('systemTrash');
$self->forkWithStatusPage({
plugin => 'ProgressTree',
title => 'purge',
redirect => $self->getUrl("func=$method"),
method => 'purgeInFork',
args => [ $form->get('assetId') ],
}
if (! $asset->canEdit) {
$pb->update(sprintf $i18n->get('You cannot edit the asset %s, skipping'), $asset->getTitle);
}
else {
$asset->purge({outputSub => sub { $pb->update(@_); } });
}
}
my $method = ($session->form->process("proceed")) ? $session->form->process('proceed') : 'manageTrash';
if ($session->form->process('systemTrash') ) {
$method .= ';systemTrash=1';
}
$pb->finish($self->getUrl('func='.$method));
);
}
#-------------------------------------------------------------------

View file

@ -400,7 +400,8 @@ sub editUserForm {
$f->password(
name=>"authWebGUI.identifier",
label=>$i18n->get(51),
value=>"password"
value=>"password",
extras=>'autocomplete="off"',
);
$f->interval(
-name=>"authWebGUI.passwordTimeout",
@ -1131,7 +1132,7 @@ sub emailRecoverPasswordFinish {
my $mail = WebGUI::Mail::Send->create($session, { to=>$email, subject=>$i18n->get('WebGUI password recovery')});
my $vars = { };
$vars->{recoverPasswordUrl} = $session->url->append($session->url->getSiteURL,'?op=auth;method=emailResetPassword;token='.$recoveryGuid);
$vars->{recoverPasswordUrl} = $session->url->append($session->url->getSiteURL,'op=auth;method=emailResetPassword;token='.$recoveryGuid);
my $template = WebGUI::Asset->newByDynamicClass($session, $session->setting->get('webguiPasswordRecoveryEmailTemplate'));
my $emailText = $template->process($vars);
WebGUI::Macro::process($session, \$emailText);

668
lib/WebGUI/Fork.pm Normal file
View file

@ -0,0 +1,668 @@
package WebGUI::Fork;
use warnings;
use strict;
use File::Spec;
use JSON;
use POSIX;
use Config;
use IO::Pipe;
use WebGUI::Session;
use WebGUI::Pluggable;
use Time::HiRes qw(sleep);
=head1 NAME
WebGUI::Fork
=head1 DESCRIPTION
Safely and portably spawn a long running process that you can check the
status of.
=head1 SYNOPSIS
package WebGUI::Some::Class;
sub doWork {
my ($process, $data) = @_;
$process->update("Starting...");
...
$process->update("About half way done...");
...
$process->update("Finished!");
}
sub www_doWork {
my $self = shift;
my $session = $self->session;
my $process = WebGUI::Fork->start(
$session, 'WebGUI::Some::Class', 'doWork', { some => 'data' }
);
# See WebGUI::Operation::Fork
my $pairs = $process->contentPairs('DoWork');
$session->http->setRedirect($self->getUrl($pairs));
return 'redirect';
}
package WebGUI::Operation::Fork::DoWork;
sub handler {
my $process = shift;
my $session = $process->session;
return $session->style->userStyle($process->status);
# or better yet, an ajaxy page that polls.
}
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=head1 METHODS
=cut
#-----------------------------------------------------------------
=head2 canView ($user?)
Returns whether the current user (or the user passed in, if there is one) has
permission to view the status of the fork. By default, only admins can view,
but see setGroup.
=cut
sub canView {
my $self = shift;
my $session = $self->session;
my $user = shift || $session->user;
$user = WebGUI::User->new( $session, $user )
unless eval { $user->isa('WebGUI::User') };
return
$user->isAdmin
|| $user->userId eq $self->getUserId
|| $user->isInGroup( $self->getGroupId );
}
#-------------------------------------------------------------------
=head2 contentPairs ($module, $pid, $extra)
Returns a bit of query string useful for redirecting to a
WebGUI::Operation::Fork plugin. $module should be the bit that comes after
WebGUI::Operation::Fork, e.g. $process->contentPairs('Foo') should return
something like "op=fork;module=Foo;pid=adlfjafo87ad9f78a7", which will
get dispatched to WebGUI::Operation::Fork::Foo::handler($process).
$extra is an optional hashref that will add further parameters onto the list
of pairs, e.g. { foo => 'bar' } becomes ';foo=bar'
=cut
sub contentPairs {
my ( $self, $module, $extra ) = @_;
my $url = $self->session->url;
my $pid = $self->getId;
my %params = (
op => 'fork',
module => $module,
pid => $self->getId,
$extra ? %$extra : ()
);
return join(
';',
map {
my $k = $_;
join( '=', map { $url->escape($_) } ( $k, $params{$k} ) );
} keys %params
);
} ## end sub contentPairs
#-----------------------------------------------------------------
=head2 create ( )
Internal class method. Creates a new Fork object inserts it into the db.
=cut
sub create {
my ( $class, $session ) = @_;
my $id = $session->id->generate;
my %data = ( userId => $session->user->userId );
$session->db->setRow( $class->tableName, 'id', \%data, $id );
bless { session => $session, id => $id }, $class;
}
#-----------------------------------------------------------------
=head2 daemonize ( $stdin, $sub )
Internal lass method. Runs the given $sub in daemon, and prints $stdin to its
stdin.
=cut
sub daemonize {
my ( $class, $stdin, $sub ) = @_;
my $pid = fork();
die "Cannot fork: $!" unless defined $pid;
if ($pid) {
# The child process will fork again and exit immediately, so we can
# wait for it (and thus not have zombie processes).
waitpid( $pid, 0 );
return;
}
eval {
# detach from controlling terminal, get us into a new process group
die "Cannot become session leader: $!" if POSIX::setsid() < 0;
# Fork again so we never get a controlling terminal
my $worker = IO::Pipe->new;
my $pid = fork();
die "Child cannot fork: $!" unless defined $pid;
# We don't want to call any destructors, as it would mess with the
# parent's mysql connection, etc.
if ($pid) {
$worker->writer;
$worker->printflush($stdin);
POSIX::_exit(0);
}
# We're now in the final target process. STDIN should be whatever the
# parent printed to us, and all output should go to /dev/null.
$worker->reader();
open STDIN, '<&', $worker or die "Cannot dup stdin: $!";
open STDOUT, '>', '/dev/null' or die "Cannot write /dev/null: $!";
open STDERR, '>&', \*STDOUT or die "Cannot dup stdout: $!";
# Standard daemon-y things...
$SIG{HUP} = 'IGNORE';
chdir '/';
umask 0;
# Forcibly close any non-std open file descriptors that remain
my $max = POSIX::sysconf(&POSIX::_SC_OPEN_MAX) || 1024;
POSIX::close($_) for ( $^F .. $max );
# Do whatever we're supposed to do
&$sub();
};
POSIX::_exit( $@ ? -1 : 0 );
} ## end sub daemonize
#-----------------------------------------------------------------
=head2 delete ( )
Clean up the information for this process from the database.
=cut
sub delete {
my $self = shift;
$self->session->db->deleteRow( $self->tableName, 'id', $self->getId );
}
#-----------------------------------------------------------------
=head2 endTime ( )
Returns the epoch time indicating when the subroutine passed to run() finished
executing, or undef if it hasn't finished. Note that even if the sub passed
to run dies, an endTime will be recorded.
=cut
sub endTime { $_[0]->get('endTime') }
#-----------------------------------------------------------------
=head2 error ( $msg )
Call this to record an error status. You probably shouldn't, though -- just
dying from your subroutine will cause this to be set.
=cut
sub error { $_[0]->set( { error => $_[1] } ) }
#-----------------------------------------------------------------
=head2 finish ( )
Mark the process as being finished. This is called for you when your
subroutine is finished. If update() wasn't computed on the last call, it will
be computed now.
=cut
sub finish {
my $self = shift;
my %props = ( finished => 1 );
if ( my $calc = delete $self->{delay} ) {
$props{status} = $calc->();
$props{latch} = 0;
}
$props{endTime} = time();
$self->set( \%props );
}
#-----------------------------------------------------------------
=head2 forkAndExec ($request)
Internal method. Forks and execs a new perl process to run $request. This is
used as a fallback if the master daemon runner is not working.
=cut
sub forkAndExec {
my ( $self, $request ) = @_;
my $id = $self->getId;
my $class = ref $self;
my $json = JSON::encode_json($request);
my @inc = map {"-I$_"} map { File::Spec->rel2abs($_) } grep { !ref } @INC;
my @argv = (@inc, "-M$class", "-e$class->runCmd()" );
$class->daemonize(
$json,
sub {
exec ($Config{perlpath}, @argv) or die "Could not exec: $!";
}
);
}
#-----------------------------------------------------------------
=head2 get ( @keys )
Get data from the database record for this process (returned as a simple list,
not an arrayref). Valid keys are: id, status, error, startTime, endTime,
finished, groupId, userId. They all have more specific accessors, but you can
use this to get several at once if you're very careful. You should probably
use the accessors, though, since some of them have extra logic.
=cut
sub get {
my ( $self, @keys ) = @_;
my $db = $self->session->db;
my $dbh = $db->dbh;
my $tbl = $dbh->quote_identifier( $self->tableName );
my $key
= @keys
? join( ',', map { $dbh->quote_identifier($_) } @keys )
: '*';
my $id = $dbh->quote( $self->getId );
my @values = $db->quickArray("SELECT $key FROM $tbl WHERE id = $id");
return ( @values > 1 ) ? @values : $values[0];
}
#-----------------------------------------------------------------
=head2 getError ( )
If the process died, this will be set to stringified $@.
=cut
sub getError { $_[0]->get('error') }
#-----------------------------------------------------------------
=head2 getGroupId
Returns the group ID (not the actual WebGUI::Group) of users who are allowed
to view this process.
=cut
sub getGroupId {
my $id = $_[0]->get('groupId');
return $id || 3;
}
#-----------------------------------------------------------------
=head2 getId ( )
The unique id for this fork. Note: this is NOT the pid, but a WebGUI guid.
=cut
sub getId { shift->{id} }
#-----------------------------------------------------------------
=head2 getStatus()
Signals the fork that it should report its next status, then polls at a
configurable, fractional interval (default: .1 seconds) waiting for the fork
to claim that its status has been updated. Returns the updated status. See
setWait() for a way to change the interval (or disable the waiting procedure
entirely). We will only wait for a maximum of 100 intervals.
=cut
sub getStatus {
my $self = shift;
if ( my $interval = $self->{interval} ) {
$self->set( { latch => 1 } );
my $maxWait;
while ($maxWait++ < 100) {
sleep $interval;
my ( $finished, $latch ) = $self->get( 'finished', 'latch' );
last if $finished || !$latch;
}
}
return $self->get('status');
}
#-----------------------------------------------------------------
=head2 getUserId
Returns the userId of the user who initiated this Fork.
=cut
sub getUserId { $_[0]->get('userId') }
#-----------------------------------------------------------------
=head2 init ( )
Spawn a master process from which Forks will fork(). The intent
is for this to be called once at server startup time, after you've preloaded
modules and before you start listening for requests. Returns a filehandle that
can be used to print requests to the master process, and which you almost
certainly shouldn't use (it's mostly for testing).
=cut
my $pipe;
sub init {
my $class = shift;
$pipe = IO::Pipe->new;
my $pid = fork();
die "Cannot fork: $!" unless defined $pid;
if ($pid) {
$pipe->writer;
return $pipe;
}
$0 = 'webgui-fork-master';
$pipe->reader;
local $/ = "\x{0}";
while ( my $request = $pipe->getline ) {
chomp $request;
eval {
$class->daemonize( $request, sub { $class->runCmd } );
};
}
exit 0;
} ## end sub init
#-----------------------------------------------------------------
=head2 isFinished ( )
A simple flag indicating that the fork is no longer running.
=cut
sub isFinished { $_[0]->get('finished') }
#-----------------------------------------------------------------
=head2 new ( $session, $id )
Returns an object capable of checking on the status of the fork indicated by
$id. Returns undef if there is no such process.
=cut
sub new {
my ( $class, $session, $id ) = @_;
my $db = $session->db;
my $tbl = $db->dbh->quote_identifier( $class->tableName );
my $sql = "SELECT COUNT(*) FROM $tbl WHERE id = ?";
my $exists = $db->quickScalar( $sql, [$id] );
return $exists
? bless( { session => $session, id => $id, interval => .1 }, $class )
: undef;
}
#-----------------------------------------------------------------
=head2 session ()
Get the WebGUI::Session this process was created with. Note: this is safe to
call in the child process, as it is a duplicated session (same session id) and
doesn't share any handles with the parent process.
=cut
sub session { $_[0]->{session} }
#-----------------------------------------------------------------
=head2 set ($properties)
Updates the database row with the properties given by the $properties hashref.
See get() for a list of valid keys.
=cut
sub set {
my ( $self, $values ) = @_;
my %row = ( id => $self->getId, %$values );
$self->session->db->setRow( $self->tableName, 'id', \%row );
}
#-----------------------------------------------------------------
=head2 setGroup($groupId)
Allow the given group (in addition to admins) the ability to check on the
status of this process
=cut
sub setGroup {
my ( $self, $groupId ) = @_;
$groupId = eval { $groupId->getId } || $groupId;
$self->set( { groupId => $groupId } );
}
#-----------------------------------------------------------------
=head2 request ($module, $subname, $data)
Internal method. Generates a hashref suitable for passing to runRequest.
=cut
sub request {
my ( $self, $module, $subname, $data ) = @_;
my $session = $self->session;
my $config = $session->config;
return {
webguiRoot => $config->getWebguiRoot,
configFile => $config->getFilename,
sessionId => $session->getId,
module => $module,
subname => $subname,
id => $self->getId,
data => $data,
};
}
#-----------------------------------------------------------------
=head2 runCmd ()
Internal class method. Decodes json off of stdin and passes it to runRequest.
=cut
sub runCmd {
my $class = shift;
my $slurp = do { local $/; <STDIN> };
$class->runRequest( JSON::decode_json($slurp) );
}
#-----------------------------------------------------------------
=head2 runRequest ($hashref)
Internal class method. Expects a hash of arguments describing what to run.
=cut
sub runRequest {
my ( $class, $args ) = @_;
my ( $root, $config, $sid ) = @{$args}{qw(webguiRoot configFile sessionId)};
my $session = WebGUI::Session->open( $root, $config, undef, undef, $sid );
my $id = $args->{id};
my $self = $class->new( $session, $id );
$self->set( { startTime => time } );
$0 = "webgui-fork-$id";
eval {
my ( $module, $subname, $data ) = @{$args}{qw(module subname data)};
WebGUI::Pluggable::run( $module, $subname, [ $self, $data ] );
};
$self->error($@) if $@;
$self->finish();
}
#-----------------------------------------------------------------
=head2 sendRequestToMaster ($request)
Internal method. Attempts to send a request to the master daemon runner.
Returns 1 on success and 0 on failure.
=cut
sub sendRequestToMaster {
my ( $self, $request ) = @_;
my $json = JSON::encode_json($request);
eval {
die 'pipe' unless $pipe && $pipe->isa('IO::Handle');
local $SIG{PIPE} = sub { die 'pipe' };
$pipe->printflush("$json\x{0}") or die 'pipe';
};
return 1 unless $@;
undef $pipe;
$self->session->log->error('Problems talking to master daemon process. Please restart the web server.');
return 0;
}
#-----------------------------------------------------------------
=head2 setWait ( $interval )
Use this to control the pace at which getStatus will poll for updated
statuses. By default, this is a tenth of a second. If you set it to 0,
getStatus will still signal the fork for an update, but will take whatever is
currently recorded as the status and return immediately.
=cut
sub setWait { $_[0]->{interval} = $_[1] }
#-----------------------------------------------------------------
=head2 start ( $session, $module, $subname, $data )
Class method. Executes $module::subname in a forked process with ($process,
$data) as its arguments. The only restriction on $data is that it be
serializable by JSON.
=head3 $0
The process name (as it appears in ps) will be set to webgui-fork-$id,
where $id is the value returned by $process->getId. It thus won't look like a
modperl process to anyone monitoring the process table (wremonitor.pl, for
example).
=cut
sub start {
my ( $class, $session, $module, $subname, $data ) = @_;
my $self = $class->create($session);
my $request = $self->request( $module, $subname, $data );
$self->sendRequestToMaster($request) or $self->forkAndExec($request);
return $self;
}
#-----------------------------------------------------------------
=head2 startTime ( )
Returns the time this process started running in epoch format.
=cut
sub startTime { $_[0]->get('startTime') }
#-----------------------------------------------------------------
=head2 tableName ( )
Class method: a constant, for convenience. The name of the table that process
data is stored in.
=cut
sub tableName {'Fork'}
#-----------------------------------------------------------------
=head2 update ( $msg )
Set a new status for the fork. This can be anything, and will overwrite the
old status. JSON is recommended for complex statuses. Optionally, $msg can
be a subroutine that returns the new status -- if your status may take a long
time to compute, you should use this, as you may be able to avoid computing
some (or all) of your status updates, depending on how often they're being
asked for. See the getStatus method for details.
=cut
sub update {
my ( $self, $msg ) = @_;
if ( ref $msg eq 'CODE' ) {
if ( $self->get('latch') ) {
$msg = $msg->();
}
else {
$self->{delay} = $msg;
return;
}
}
delete $self->{delay};
$self->set( { latch => 0, status => $msg } );
}
1;

View file

@ -0,0 +1,140 @@
package WebGUI::Fork::ProgressBar;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use warnings;
=head1 NAME
WebGUI::Fork::ProgressBar
=head1 DESCRIPTION
Renders an admin console page that polls ::Status to draw a simple progress
bar along with some kind of message.
=head1 SUBROUTINES
These subroutines are available from this package:
=cut
use Template;
use HTML::Entities;
use JSON;
my $template = <<'TEMPLATE';
<div id='loading'>[% i18n('WebGUI', 'Loading...') %]</div>
<div id='ui' style='display: none'>
<p id='message'></p>
<div id='meter'></div>
<p>
[% i18n('Fork_ProgressBar', 'time elapsed') %]:
<span id='elapsed'></span> [% i18n('Fork_ProgressBar', 'seconds') %].
</p>
</div>
<script>
(function (params) {
var bar = new YAHOO.WebGUI.Fork.ProgressBar();
YAHOO.util.Event.onDOMReady(function () {
bar.render('meter');
YAHOO.WebGUI.Fork.poll({
url : params.statusUrl,
draw : function (data) {
var status = YAHOO.lang.JSON.parse(data.status);
bar.update(status.finished, status.total);
document.getElementById('message').innerHTML = status.message;
document.getElementById('elapsed').innerHTML = data.elapsed;
},
first : function () {
document.getElementById('loading').style.display = 'none';
document.getElementById('ui').style.display = 'block';
},
finish : function() {
YAHOO.WebGUI.Fork.redirect(params.redirect);
},
error : function (msg) {
alert(msg);
}
});
});
}([% params %]));
</script>
TEMPLATE
#-------------------------------------------------------------------
=head2 handler ( process )
See WebGUI::Operation::Fork.
=cut
sub handler { renderBar( shift, $template ) }
#-------------------------------------------------------------------
=head2 renderBar ( process, template )
Renders $template, passing a "params" variable to it that is JSON of a
statusUrl to poll and a page to redirect to and an i18n function. Includes
WebGUI.Fork.redirect, poll, and ProgressBar js and CSS (as well as all their
YUI dependancies), and puts the whole template inside an adminConsole rendered
based off some form parameters.
=cut
sub renderBar {
my ( $process, $template ) = @_;
my $session = $process->session;
my $url = $session->url;
my $form = $session->form;
my $style = $session->style;
my $tt = Template->new;
my %vars = (
i18n => sub {
my ($namespace, $key) = @_;
return WebGUI::International->new($session, $namespace)->get($key);
},
params => JSON::encode_json( {
statusUrl => $url->page( $process->contentPairs('Status') ),
redirect => scalar $form->get('proceed'),
}
),
);
$tt->process( \$template, \%vars, \my $content ) or die $tt->error;
my $console = WebGUI::AdminConsole->new( $session, $form->get('icon') );
$style->setLink( $url->extras("Fork/ProgressBar.css"), { rel => 'stylesheet' } );
$style->setScript( $url->extras("$_.js") )
for ( (
map {"yui/build/$_"}
qw(
yahoo/yahoo-min
dom/dom-min
json/json-min
event/event-min
connection/connection_core-min
)
),
'Fork/ProgressBar',
'Fork/poll',
'Fork/redirect'
);
return $console->render( $content, encode_entities( $form->get('title') ) );
} ## end sub renderBar
1;

View file

@ -0,0 +1,155 @@
package WebGUI::Fork::ProgressTree;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use warnings;
=head1 NAME
WebGUI::Fork::ProgressTree
=head1 DESCRIPTION
Renders an admin console page that polls ::Status to draw a friendly graphical
representation of how progress on a tree of assets is coming along.
=head1 SUBROUTINES
These subroutines are available from this package:
=cut
use Template;
use HTML::Entities;
use JSON;
use WebGUI::Fork::ProgressBar;
my $template = <<'TEMPLATE';
<div id='loading'>[% i18n('WebGUI', 'Loading...') %]</div>
<div id='ui' style='display: none'>
<div id='meter'></div>
[% i18n('Fork_ProgressBar', 'current asset') %]: <span id='focus'></span>
(<span id='finished'></span>/<span id='total'></span>).<br />
[% i18n('Fork_ProgressBar', 'time elapsed') %]:
<span id='elapsed'></span>
[% i18n('Fork_ProgressBar', 'seconds') %].
<ul id='tree'></ul>
</div>
<script>
(function (params) {
var bar = new YAHOO.WebGUI.Fork.ProgressBar();
function setHtml(id, html) {
document.getElementById(id).innerHTML = html;
}
function draw(data) {
var tree, finished = 0, total = 0, focus, pct;
function recurse(asset, node) {
var li = document.createElement('li'), txt, notes, ul, i;
total += 1;
txt = asset.url;
if (asset.success) {
li.className = 'success';
finished += 1;
}
else if (asset.failure) {
li.className = 'failure';
txt += ' (' + asset.failure + ')';
finished += 1;
}
if (asset.focus) {
li.className += 'focus';
focus = asset.url;
}
li.appendChild(document.createTextNode(txt));
if (notes = asset.notes) {
_.each(notes, function (note) {
var p = document.createElement('p');
p.innerHTML = note;
li.appendChild(p);
});
}
if (asset.children) {
ul = document.createElement('ul');
_.each(asset.children, function (child) {
recurse(child, ul);
});
li.appendChild(ul);
}
node.appendChild(li);
}
tree = document.getElementById('tree');
tree.innerHTML = '';
_.each(JSON.parse(data.status), function (root) {
recurse(root, tree);
});
bar.update(finished, total);
setHtml('total', total);
setHtml('finished', finished);
setHtml('focus', focus || 'nothing');
setHtml('elapsed', data.elapsed);
}
YAHOO.util.Event.onDOMReady(function () {
bar.render('meter');
YAHOO.WebGUI.Fork.poll({
url : params.statusUrl,
draw : draw,
first : function () {
document.getElementById('loading').style.display = 'none';
document.getElementById('ui').style.display = 'block';
},
finish : function () {
YAHOO.WebGUI.Fork.redirect(params.redirect);
},
error : function (msg) {
alert(msg)
}
});
});
}([% params %]));
</script>
TEMPLATE
my $stylesheet = <<'STYLESHEET';
<style>
#tree li { color: black }
#tree li.focus { color: cyan }
#tree li.failure { color: red }
#tree li.success { color: green }
</style>
STYLESHEET
#-------------------------------------------------------------------
=head2 handler ( process )
See WebGUI::Operation::Fork.
=cut
sub handler {
my $process = shift;
my $session = $process->session;
my $style = $session->style;
my $url = $session->url;
$style->setRawHeadTags($stylesheet);
$style->setScript($url->extras('underscore/underscore-min.js'));
WebGUI::Fork::ProgressBar::renderBar($process, $template);
}
1;

84
lib/WebGUI/Fork/Status.pm Normal file
View file

@ -0,0 +1,84 @@
package WebGUI::Fork::Status;
use JSON;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use warnings;
=head1 NAME
WebGUI::Fork::Status
=head1 DESCRIPTION
Returns a json response of the following form:
{
"finished" : true,
"elapsed" : 10,
"status" : "whatever is in the status field. Could be anything.",
"error" : "whatever is in the error field"
}
Note that if your status is JSON, you'll have to decode that seperately, so
something like:
decoded = JSON.parse(r.responseText);
status = JSON.parse(decoded.status);
Finished is obviously true or false. Notably, it will be true in the error
case: so to status.finished && !status.error means successful completion.
Error will only be present if the process died for some reason.
Status will always be present, mostly so you can see what the last status was
before it died.
Elapsed will be the number of seconds since the process started (or until the
process finished, if it is finished).
=head1 SUBROUTINES
These subroutines are available from this package:
=cut
#-------------------------------------------------------------------
=head2 handler ( process )
See the synopsis for what kind of response this generates.
=cut
sub handler {
my $process = shift;
my $status = $process->getStatus;
my ( $finished, $startTime, $endTime, $error ) = $process->get( 'finished', 'startTime', 'endTime', 'error' );
$endTime = time() unless $finished;
my %status = (
status => $status,
elapsed => ( $endTime - $startTime ),
finished => ( $finished ? \1 : \0 ),
);
$status{error} = $error if $finished;
$process->session->http->setMimeType('text/plain');
JSON::encode_json( \%status );
} ## end sub handler
1;

View file

@ -302,25 +302,25 @@ sub toHtml {
value => 'upload',
id => $self->get('id')
})->toHtml
. "<br />";
. "\n";
}
else {
$uploadControl .= WebGUI::Form::Hidden->new($self->session, {
name => $self->get("name"),
value => $self->getOriginalValue,
id => $self->get("id")
})->toHtml()."<br />";
})->toHtml()."\n";
$uploadControl .= WebGUI::Form::Hidden->new($self->session, {
name => $self->privateName('action'),
value => 'keep',
id => $self->get("id")
})->toHtml()."<br />";
})->toHtml()."\n";
}
if (scalar(@files)) {
if ($self->get('maxAttachments') == 1) {
$self->set("");
}
$uploadControl .= $self->getFilePreview($storage);
$uploadControl .= "<br />".$self->getFilePreview($storage);
}
return $uploadControl;
}

View file

@ -187,7 +187,7 @@ Renders an HTML area field.
sub toHtml {
my $self = shift;
##Do not display a rich editor on any mobile browser.
if ($self->session->style->useMobileStyle) {
if ($self->session->style->mobileBrowser) {
return $self->SUPER::toHtml;
}
my $i18n = WebGUI::International->new($self->session);

View file

@ -113,7 +113,6 @@ sub getValue {
my ( $self, $value ) = @_;
$value ||= $self->SUPER::getValue;
$self->session->log->info( "JsonTable Got $value from form" );
$value = JSON->new->decode( $value );
for my $row ( @{$value} ) {

View file

@ -409,20 +409,18 @@ sub processReplacements {
my $session = shift;
my ($content) = @_;
my $replacements = $session->stow->get("replacements");
if (defined $replacements) {
foreach my $searchFor (keys %{$replacements}) {
my $replaceWith = $replacements->{$searchFor};
$content =~ s/\Q$searchFor/$replaceWith/gs;
}
} else {
if (! defined $replacements) {
my $sth = $session->dbSlave->read("select searchFor,replaceWith from replacements");
while (my ($searchFor,$replaceWith) = $sth->array) {
while (my ($searchFor,$replaceWith) = $sth->array) {
$replacements->{$searchFor} = $replaceWith;
$content =~ s/\Q$searchFor/$replaceWith/gs;
}
$sth->finish;
$session->stow->set("replacements",$replacements);
}
$sth->finish;
$session->stow->set("replacements",$replacements);
}
foreach my $searchFor (keys %{$replacements}) {
my $replaceWith = $replacements->{$searchFor};
$content =~ s/\b\Q$searchFor\E\b/$replaceWith/gs;
}
return $content;
}

View file

@ -76,6 +76,7 @@ Returns a hash reference containing operation and package names.
sub getOperations {
return {
'fork' => 'Fork',
'killSession' => 'ActiveSessions',
'viewActiveSessions' => 'ActiveSessions',

View file

@ -0,0 +1,74 @@
package WebGUI::Operation::Fork;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use warnings;
use WebGUI::Fork;
use WebGUI::Pluggable;
=head1 NAME
WebGUI::Operation::Fork
=head1 DESCRIPTION
URL dispatching for WebGUI::Fork monitoring
=head1 SUBROUTINES
These subroutines are available from this package:
=cut
#-------------------------------------------------------------------
=head2 www_fork ( session )
Dispatches to the proper module based on the module form parameter if op is
fork. Returns insufficient privilege page if the user doesn't pass canView on
the process before dispatching.
=cut
sub www_fork {
my $session = shift;
my $form = $session->form;
my $module = $form->get('module') || 'Status';
my $pid = $form->get('pid') || return undef;
my $process = WebGUI::Fork->new( $session, $pid );
return $session->privilege->insufficient unless $process->canView;
my $log = $session->log;
unless ($process) {
$log->error("Tried to get info for nonexistent process $pid");
return undef;
}
my $output = eval { WebGUI::Pluggable::run( "WebGUI::Fork::$module", 'handler', [$process] ); };
if ($@) {
$log->error($@);
return undef;
}
return $output;
} ## end sub www_fork
1;

View file

@ -645,6 +645,7 @@ sub www_editUser {
-name=>"username",
-label=>$i18n->get(50),
-value=>$username
-extras=>'autocomplete="off"',
);
my %status;
tie %status, 'Tie::IxHash';

View file

@ -22,6 +22,9 @@ use WebGUI::VersionTag;
use WebGUI::HTMLForm;
use WebGUI::Paginator;
use Tie::IxHash;
use WebGUI::Fork;
use Monkey::Patch;
use JSON;
=head1 NAME
@ -138,6 +141,50 @@ sub getVersionTagOptions {
return %tag;
}
#----------------------------------------------------------------------------
=head2 rollbackInFork ($process, $tagId)
WebGUI::Fork method called by www_rollbackVersionTag
=cut
sub rollbackInFork {
my ( $process, $tagId ) = @_;
my $session = $process->session;
my $tag = WebGUI::VersionTag->new( $session, $tagId );
my %status = (
finished => 0,
total => $process->session->db->quickScalar( 'SELECT count(*) FROM assetData WHERE tagId = ?', [$tagId] ),
message => '',
);
my $update = sub {
$process->update( sub { JSON::encode_json( \%status ) } );
};
my $patch = Monkey::Patch::patch_class(
'WebGUI::Asset',
'purgeRevision',
sub {
my $purgeRevision = shift;
my $self = shift;
$self->$purgeRevision(@_);
$status{finished}++;
$update->();
}
);
$tag->rollback( {
outputSub => sub {
$status{message} = shift;
$update->();
}
}
);
# need to get at least one of these in for the degenerate case of no
# revisions in tag
$update->();
} ## end sub rollbackInFork
#-------------------------------------------------------------------
=head2 www_approveVersionTag ( session )
@ -656,8 +703,9 @@ sub www_manageRevisionsInTag {
# Process any actions
my $action = lc $session->form->get('action');
my $form = $session->form;
my $validToken = $session->form->validToken;
if ( $action eq "purge" && $validToken) {
if ( $form->get('purge') && $validToken) {
# Purge these revisions
my @assetInfo = $session->form->get('assetInfo');
for my $assetInfo ( @assetInfo ) {
@ -672,7 +720,7 @@ sub www_manageRevisionsInTag {
return www_manageVersions( $session );
}
}
elsif ( $action eq "move to:" && $validToken) {
elsif ( $form->get('moveto') && $validToken) {
# Get the new version tag
my $moveToTagId = $session->form->get('moveToTagId');
my $moveToTag;
@ -700,7 +748,7 @@ sub www_manageRevisionsInTag {
return www_manageVersions( $session );
}
}
elsif ( $action eq "update version tag" && $validToken) {
elsif ( $form->get('update') && $validToken) {
my $startTime = WebGUI::DateTime->new($session,$session->form->process("startTime","dateTime"))->toDatabase;
my $endTime = WebGUI::DateTime->new($session,$session->form->process("endTime","dateTime"))->toDatabase;
@ -786,19 +834,19 @@ sub www_manageRevisionsInTag {
value => WebGUI::DateTime->new($session,$filterEndTime)->epoch,
})
. '<br />'
. '<input type="submit" name="action" value="'. $i18n->get('manageRevisionsInTag update') . '" />'
. '<input type="submit" name="update" value="'. $i18n->get('manageRevisionsInTag update') . '" />'
. '</td>'
. '</tr>'
. '<tr><td colspan="5">&nbsp;</td></tr>'
. '<tr>'
. '<td colspan="5">'
. $i18n->get("manageRevisionsInTag with selected")
. '<input type="submit" name="action" value="'. $i18n->get("manageRevisionsInTag move") . '" />'
. '<input type="submit" name="moveto" value="'. $i18n->get("manageRevisionsInTag move") . '" />'
. WebGUI::Form::SelectBox( $session, {
name => 'moveToTagId',
options => \%moveToTagOptions,
} )
. '&nbsp;<input type="submit" name="action" value="'. $i18n->get('manageRevisionsInTag purge') . '" class="red" />'
. '&nbsp;<input type="submit" name="purge" value="'. $i18n->get('manageRevisionsInTag purge') . '" class="red" />'
. '</td>'
. '</tr>'
. '<tr>'
@ -854,16 +902,27 @@ sub www_rollbackVersionTag {
return $session->privilege->adminOnly() unless canView($session);
my $tagId = $session->form->process("tagId");
return $session->privilege->vitalComponent() if ($tagId eq "pbversion0000000000001");
my $pb = WebGUI::ProgressBar->new($session);
my $i18n = WebGUI::International->new($session, 'VersionTag');
$pb->start($i18n->get('rollback version tag'), $session->url->extras('adminConsole/versionTags.gif'));
if ($tagId) {
my $tag = WebGUI::VersionTag->new($session, $tagId);
$tag->rollback({ outputSub => sub { $pb->update(@_) }, }) if defined $tag;
}
my $process = WebGUI::Fork->start(
$session, 'WebGUI::Operation::VersionTag', 'rollbackInFork', $tagId
);
my $i18n = WebGUI::International->new($session, 'VersionTag');
my $method = $session->form->process("proceed");
$method = $method eq "manageCommittedVersions" ? $method : 'manageVersions';
$pb->finish(WebGUI::Asset->getDefault($session)->getUrl('op='.$method));
my $redir = WebGUI::Asset->getDefault($session)->getUrl("op=$method");
$session->http->setRedirect(
$session->url->page(
$process->contentPairs(
'ProgressBar', {
icon => 'versions',
title => $i18n->get('rollback version tag'),
proceed => $redir,
}
)
)
);
return 'redirect';
}

172
lib/WebGUI/ProgressTree.pm Normal file
View file

@ -0,0 +1,172 @@
package WebGUI::ProgressTree;
use warnings;
use strict;
=head1 NAME
WebGUI::ProgressTree
=head1 DESCRIPTION
Helper functions for maintaining a JSON represtentation of the progress of an
operation that modifies a tree of assets. See WebGUI::Fork::ProgressTree for a
status page that renders this.
=head1 SYNOPSIS
my $tree = WebGUI::ProgressTree->new($session, \@assetIds);
$tree->success($assetId);
$tree->failure($assetId, $reason);
$tree->note($assetId, 'something about this one...');
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=head1 METHODS
=cut
#-------------------------------------------------------------------
=head2 new ($session, $assetIds)
Constructs new tree object for tracking the progress of $assetIds.
=cut
sub new {
my ( $class, $session, $assetIds ) = @_;
my $db = $session->db;
my $dbh = $db->dbh;
my $set = join( ',', map { $dbh->quote($_) } @$assetIds );
my $sql = qq{
SELECT a.assetId, a.parentId, d.url
FROM asset a INNER JOIN assetData d ON a.assetId = d.assetId
WHERE a.assetId IN ($set)
ORDER BY a.lineage ASC, d.revisionDate DESC
};
my $sth = $db->read($sql);
my ( %flat, @roots );
while ( my $asset = $sth->hashRef ) {
my ( $id, $parentId ) = delete @{$asset}{ 'assetId', 'parentId' };
# We'll get back multiple rows for each asset, but the first one is
# the latest. Skip the others.
next if $flat{$id};
$flat{$id} = $asset;
if ( my $parent = $flat{$parentId} ) {
push( @{ $parent->{children} }, $asset );
}
else {
push( @roots, $asset );
}
}
my $self = {
session => $session,
tree => \@roots,
flat => \%flat,
};
bless $self, $class;
} ## end sub new
#-------------------------------------------------------------------
=head2 success ($assetId)
Whatever we were doing to $assetId succeeded. Woohoo!
=cut
sub success {
my ( $self, $assetId ) = @_;
$self->{flat}->{$assetId}->{success} = 1;
}
#-------------------------------------------------------------------
=head2 failure ($assetId, $reason)
Whatever we were doing to $assetId didn't work for $reason. Aww.
=cut
sub failure {
my ( $self, $assetId, $reason ) = @_;
$self->{flat}->{$assetId}->{failure} = $reason;
}
#-------------------------------------------------------------------
=head2 note ($assetId, $note)
Add some extra text. WebGUI::Fork::ProgressTree displays these as paragraphs
under the node for this asset.
=cut
sub note {
my ( $self, $assetId, $note ) = @_;
push( @{ $self->{flat}->{$assetId}->{notes} }, $note );
}
#-------------------------------------------------------------------
=head2 focus ($assetId)
Make a note that this is the asset that we are currently doing something with.
=cut
sub focus {
my ( $self, $assetId ) = @_;
if ( my $last = delete $self->{last} ) {
delete $last->{focus};
}
if ($assetId) {
my $focus = $self->{last} = $self->{flat}->{$assetId};
$focus->{focus} = 1;
}
}
#-------------------------------------------------------------------
=head2 tree
A hashy representation of the status of this tree of assets.
=cut
sub tree { $_[0]->{tree} }
#-------------------------------------------------------------------
=head2 json
$self->tree encoded as json.
=cut
sub json { JSON::encode_json( $_[0]->tree ) }
#-------------------------------------------------------------------
=head2 session
The WebGUI::Session this progress tree is associated with.
=cut
sub session { $_[0]->{session} }
1;

View file

@ -648,7 +648,7 @@ sub quickCSV {
my $sql = shift;
my $params = shift;
my $csv = Text::CSV_XS->new({ eol => "\n" });
my $csv = Text::CSV_XS->new({ eol => "\n", binary => 1 });
my $sth = $self->prepare($sql);
$sth->execute(@$params);
@ -656,9 +656,12 @@ sub quickCSV {
return undef unless $csv->combine($sth->getColumnNames);
my $output = $csv->string;
while (my @data = $sth->fetchrow_array) {
return undef unless $csv->combine(@data);
$output .= $csv->string;
while (my @data = $sth->array) {
if ( ! $csv->combine(@data) ) {
$self->session->log->error( "Problem creating CSV row: " . $csv->error_diag );
return undef;
}
$output .= $csv->string();
}
$sth->finish;

View file

@ -110,6 +110,25 @@ sub makePrintable {
#-------------------------------------------------------------------
=head2 mobileBrowser ( )
Returns true if the user's browser matches any of the mobile browsers set in the config file.
=cut
sub mobileBrowser {
my $self = shift;
my $session = $self->session;
my $ua = $session->env->get('HTTP_USER_AGENT');
for my $mobileUA (@{ $session->config->get('mobileUserAgents') }) {
if ($ua =~ m/$mobileUA/) {
return 1;
}
}
}
#-------------------------------------------------------------------
=head2 useMobileStyle
Returns a true value if we are on a mobile display.
@ -130,7 +149,6 @@ sub useMobileStyle {
if (! $session->setting->get('useMobileStyle')) {
return $self->{_useMobileStyle} = 0;
}
if ($session->request->browser->mobile) {
return $self->{_useMobileStyle} = 1;
}

View file

@ -40,8 +40,8 @@ use List::MoreUtils qw(any);
use File::Copy ();
use File::Temp ();
use Try::Tiny;
use Monkey::Patch qw( patch_object );
use Scope::Guard;
use Try::Tiny;
use WebGUI::Paths -inc;
use namespace::clean;
@ -537,6 +537,26 @@ sub originalConfig {
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
=head2 overrideSetting (name, val)
Overrides WebGUI::Test->session->setting->get($name) to return $val until the
handle this method returns goes out of scope.
=cut
sub overrideSetting {
my ($class, $name, $val) = @_;
patch_object $class->session->setting => get => sub {
my $get = shift;
return $val if $_[1] eq $name;
goto &$get;
};
}
#----------------------------------------------------------------------------
=head2 cleanupAdminInbox ( )
Push a list of Asset objects onto the stack of assets to be automatically purged

View file

@ -38,6 +38,23 @@ These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 autoCommitUrl ( $base )
Returns the url autoCommitWorkingIfEnabled would redirect to if it were going
to.
=cut
sub autoCommitUrl {
my $self = shift;
my $session = $self->session;
my $url = $session->url;
my $base = shift || $url->page;
my $id = $self->getId;
return $url->append($base, "op=commitVersionTag;tagId=$id");
}
#-------------------------------------------------------------------
@ -76,25 +93,13 @@ sub autoCommitWorkingIfEnabled {
return undef
unless $versionTag;
#Auto commit is no longer determined from autoRequestCommit
# auto commit assets
# save and commit button and site wide auto commit work the same
# Do not auto commit if tag is system wide tag or tag belongs to someone else
if (
$options->{override}
|| ( $class->getVersionTagMode($session) eq q{autoCommit}
&& ! $versionTag->get(q{isSiteWide})
&& $versionTag->get(q{createdBy}) eq $session->user()->userId()
)
) {
if ($options->{override} || $versionTag->canAutoCommit) {
if ($session->setting->get("skipCommitComments") || !$options->{allowComments}) {
$versionTag->requestCommit;
return 'commit';
}
else {
my $url = $options->{returnUrl} || $session->url->page;
$url = $session->url->append($url, "op=commitVersionTag;tagId=" . $versionTag->getId);
my $url = $versionTag->autoCommitUrl($options->{returnUrl});
$session->http->setRedirect($url);
return 'redirect';
}
@ -104,6 +109,24 @@ sub autoCommitWorkingIfEnabled {
#-------------------------------------------------------------------
=head2 canAutoCommit
Returns true if we would autocommit this tag without an override.
=cut
sub canAutoCommit {
my $self = shift;
my $session = $self->session;
my $class = ref $self;
my $mode = $class->getVersionTagMode($session);
return $mode eq 'autoCommit'
&& !$self->get('isSiteWide')
&& $self->get('createdBy') eq $session->user->userId;
}
#-------------------------------------------------------------------
=head2 clearWorking ( )
Makes it so this tag is no longer the working tag for any user.

View file

@ -76,6 +76,22 @@ These methods are available from this class:
#-------------------------------------------------------------------
=head2 cleanup ( )
Override this activity to add a cleanup routine to be run if an instance
is deleted with this activity currently in a waiting state. This is a stub
and will do nothing unless overridden.
=cut
sub cleanup {
my $self = shift;
my $instance = shift;
return 1;
}
#-------------------------------------------------------------------
=head2 create ( session, workflowId [, id, classname ] )
Creates a new instance of this activity in a workflow.

View file

@ -0,0 +1,82 @@
package WebGUI::Workflow::Activity::RemoveOldForks;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use warnings;
use strict;
use base 'WebGUI::Workflow::Activity';
use WebGUI::International;
use WebGUI::Fork;
=head1 NAME
WebGUI::Workflow::Activity::RemoveOldForks
=head1 DESCRIPTION
Remove forks that are older than a configurable threshold.
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 definition ( session, definition )
See WebGUI::Workflow::Activity::definition() for details.
=cut
sub definition {
my ( $class, $session, $definition ) = @_;
my $i18n = WebGUI::International->new( $session, 'Workflow_Activity_RemoveOldForks' );
my %def = (
name => $i18n->get('activityName'),
properties => {
interval => {
fieldType => 'interval',
label => $i18n->get('interval'),
defaultValue => 60 * 60 * 24 * 7,
hoverHelp => $i18n->get('interval help')
}
}
);
push @$definition, \%def;
return $class->SUPER::definition( $session, $definition );
} ## end sub definition
#-------------------------------------------------------------------
=head2 execute ( [ object ] )
See WebGUI::Workflow::Activity::execute() for details.
=cut
sub execute {
my $self = shift;
my $db = $self->session->db;
my $tbl = $db->dbh->quote_identifier( WebGUI::Fork->tableName );
my $time = time - $self->get('interval');
$db->write( "DELETE FROM $tbl WHERE endTime <= ?", [$time] );
return $self->COMPLETE;
}
1;

View file

@ -44,6 +44,23 @@ These methods are available from this class:
#-------------------------------------------------------------------
=head2 cleanup ( )
Override this activity to add a cleanup routine to be run if an instance
is deleted with this activity currently in a waiting state. This is a stub
and will do nothing unless overridden.
=cut
sub cleanup {
my $self = shift;
my $instance = shift;
$self->setMessageCompleted($instance);
return 1;
}
#-------------------------------------------------------------------
=head2 definition ( session, definition )
See WebGUI::Workflow::Activity::definition() for details.

View file

@ -101,11 +101,22 @@ A boolean, that if true will not notify Spectre of the delete.
=cut
sub delete {
my $self = shift;
my $self = shift;
my $session = $self->session;
my $skipNotify = shift;
$self->session->db->write("delete from WorkflowInstanceScratch where instanceId=?",[$self->getId]);
$self->session->db->deleteRow("WorkflowInstance","instanceId",$self->getId);
WebGUI::Workflow::Spectre->new($self->session)->notify("workflow/deleteInstance",$self->getId) unless ($skipNotify);
if ( $self->hasNextActivity ) {
#We are deleting in the middle of a workflow - Get the current activity and call the cleanup routine
my $activity = $self->getNextActivity;
eval { $activity->cleanup($self) };
if ($@) {
$session->errorHandler->error("Caught exception executing cleanup routine which was not run on workflow activity ".$activity->getId." for instance ".$self->getId.". The following error was reported: ".$@);
}
}
$session->db->write("delete from WorkflowInstanceScratch where instanceId=?",[$self->getId]);
$session->db->deleteRow("WorkflowInstance","instanceId",$self->getId);
WebGUI::Workflow::Spectre->new($session)->notify("workflow/deleteInstance",$self->getId) unless ($skipNotify);
# We will need to remember that we were deleted if we get realtime-run
# during start().

View file

@ -1127,6 +1127,24 @@ search has been done.|,
lastUpdated => 1231180362,
},
'Creating column headers' => {
message => q|Creating column headers.|,
lastUpdated => 1231180362,
context => q|Status message in the Export Thingy progress bar.|,
},
'Writing data' => {
message => q|Writing data.|,
lastUpdated => 1231180362,
context => q|Status message in the Export Thingy progress bar.|,
},
'Return to %s' => {
message => q|Return to %s.|,
lastUpdated => 1231180362,
context => q|Status message in the Export Thingy progress bar. %s is the name of the Thing that is being exported.|,
},
};
1;

View file

@ -0,0 +1,22 @@
package WebGUI::i18n::English::Fork_ProgressBar;
use strict;
our $I18N = {
'time elapsed' => {
message => 'Time Elapsed',
lastUpdated => 1286466369,
context => 'Used as a label to indicate how many seconds have gone by since the forked process started running',
},
'seconds' => {
message => 'seconds',
lastUpdated => 1286466433,
},
'current asset' => {
message => 'Current Asset',
lastUpdated => 1286466701,
context => 'Used as a label to indicate which asset is in "focus"',
},
};
1;

View file

@ -0,0 +1,20 @@
package WebGUI::i18n::English::Workflow_Activity_RemoveOldForks;
use strict;
our $I18N = {
'interval help' => {
message => 'How long do we wait after process completion before deleting it?',
lastUpdated => 1285358250,
},
'interval' => {
message => q|Interval|,
lastUpdated => 1285358250,
},
'activityName' => {
message => q|Remove Old Forks|,
lastUpdated => 1285358250,
},
};
1;

View file

@ -137,7 +137,7 @@ checkModule('JavaScript::Packer', '0.04' );
checkModule('CSS::Packer', '0.2' );
checkModule('Business::Tax::VAT::Validation', '0.20' );
checkModule('Crypt::SSLeay', '0.57' );
checkModule('Scope::Guard', '0.03' );
checkModule('Scope::Guard', '0.20' );
checkModule('Digest::SHA', '5.47' );
checkModule("CSS::Minifier::XS", "0.03" );
checkModule("JavaScript::Minifier::XS", "0.05" );
@ -165,6 +165,7 @@ checkModule('Email::Valid', );
checkModule('Facebook::Graph', '0.0505' );
checkModule('HTTP::BrowserDetect', );
checkModule('Search::QueryParser', );
checkModule('Monkey::Patch', '0.03' );
failAndExit("Required modules are missing, running no more checks.") if $missingModule;

File diff suppressed because one or more lines are too long

View file

@ -17,6 +17,7 @@ use WebGUI::Test;
use WebGUI::Session;
use WebGUI::Asset;
use WebGUI::VersionTag;
use Test::MockObject;
use Test::More; # increment this value for each test you create
plan tests => 29;
@ -147,12 +148,20 @@ sub copied {
return undef;
}
my @methods = qw(Single Children Descendants);
my $process = Test::MockObject->new->mock(update => sub {});
my @methods = (
# single duplicate doesn't fork, so we can just test the www method to
# make sure it gets it right
sub { shift->www_copy },
sub { shift->duplicateBranch(1, 'clipboard') },
sub { shift->duplicateBranch(0, 'clipboard') },
);
my @prefixes = qw(single children descendants);
for my $i (0..2) {
my $meth = "_wwwCopy$methods[$i]";
my $meth = $methods[$i];
$root->$meth();
my $clip = copied();
is_tree_of_folders($clip, $i+1, $meth);
is_tree_of_folders($clip, $i+1, @prefixes[$i]);
$clip->purge;
}

View file

@ -142,4 +142,13 @@ is ($event7->get('startDate'), '2000-09-01', 'startDate bumped by 1 day');
is ($event7->get('endTime'), '00:00:00', 'endTime set to 00:00:00 if the hour is more than 23');
is ($event7->get('endDate'), '2000-09-02', 'endDate bumped by 1 day');
#######################################
#
# duplicate
#
#######################################
my $event6b = $event6->duplicate();
isnt($event6b->get('storageId'), $event6->get('storageId'), 'duplicating an asset creates a new storage location');
done_testing;

View file

@ -0,0 +1,66 @@
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use FindBin;
use strict;
use lib "$FindBin::Bin/../../lib";
## The goal of this test is to test the link between the asset and its shortcut
# and that changes to the asset are propagated to the shortcut
use Scalar::Util qw( blessed );
use WebGUI::Test;
use WebGUI::Session;
use Test::More;
use WebGUI::Asset::Shortcut;
use WebGUI::Asset::Snippet;
#----------------------------------------------------------------------------
# Init
my $session = WebGUI::Test->session;
my $node = WebGUI::Asset->getImportNode($session);
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->set({name=>"Shortcut Test"});
WebGUI::Test->addToCleanup($versionTag);
# Make a snippet to shortcut
my $now = time();
my $snippet = $node->addChild({
className => "WebGUI::Asset::Snippet",
},
undef, $now-50);
my $shortcut = $node->addChild({
className => "WebGUI::Asset::Shortcut",
shortcutToAssetId => $snippet->getId,
},
undef, $now-10);
$versionTag->commit;
$session->db->write(q|update assetData set lastModified=? where assetId=?|,[WebGUI::Test->webguiBirthday, $snippet->getId]);
foreach my $asset ($snippet, $shortcut) {
$asset = $asset->cloneFromDb;
}
#----------------------------------------------------------------------------
# Tests
plan tests => 2;
is( $shortcut->getContentLastModified, $now-10, "getContentLastModified: returns date of shortcut since it has a newer revision date.");
$snippet->update({snippet => 'updated', }, $now-5);
diag $snippet->get('lastModified');
diag $snippet->getContentLastModified;
$shortcut = $shortcut->cloneFromDb; ##Wipe the cached version of the shortcut.
is( $shortcut->getContentLastModified, $snippet->get('lastModified'), "returns lastModified when shortcutted asset has a more recent date");

View file

@ -17,7 +17,7 @@ use Test::MockObject::Extends;
use WebGUI::Test;
use WebGUI::Test::MockAsset;
use WebGUI::Session;
use Test::More tests => 8; # increment this value for each test you create
use Test::More tests => 9; # increment this value for each test you create
use Test::Deep;
use Data::Dumper;
@ -126,6 +126,14 @@ cmp_deeply(
$session->request->setup_body({ });
$session->scratch->delete('userId');
################################################################
#
# getStatusList
#
################################################################
$board->update({statusList => "In\r\nOut\rHome\nLunch"});
is_deeply [$board->getStatusList], [qw(In Out Home Lunch)], 'getStatusList';
################################################################
#
# view

View file

@ -119,13 +119,37 @@ my $dt = DateTime->from_epoch(epoch => $now, time_zone => $session->datetime->ge
my $folderName = $dt->strftime('%B_%d_%Y');
$folderName =~ s/^(\w+_)0/$1/;
is($todayFolder->getTitle, $folderName, '... folder has the right name');
my $folderUrl = join '/', $archive->getUrl, lc $folderName;
is($todayFolder->getUrl, $folderUrl, '... folder has the right URL');
my $folderUrl = $archive->getFolderUrl($folderName);
is($todayFolder->get('url'), $folderUrl, '... folder has the right URL');
is($todayFolder->getParent->getId, $archive->getId, '... created folder has the right parent');
is($todayFolder->get('state'), 'published', '... created folder is published');
is($todayFolder->get('status'), 'approved', '... created folder is approved');
is($todayFolder->get('styleTemplateId'), $archive->get('styleTemplateId'), '... created folder has correct styleTemplateId');
{
my $undo = WebGUI::Test->overrideSetting(urlExtension => 'ext');
my $arch2 = $home->addChild({
className => $class,
title => 'Extension Tester',
});
addToCleanup($arch2);
is $arch2->get('url'),
'home/extension-tester.ext',
'ext added';
is $arch2->getFolderUrl('blah'),
'home/extension-tester/blah.ext',
'folder url: strip extension from parent and add to child';
my $folder = $arch2->getFolder($now);
ok defined $folder, 'getFolder with url extension';
is $folder->get('url'),
$arch2->getFolderUrl($folder->getMenuTitle),
'getFolderUrl and folder getUrl match';
}
my $sameFolder = $archive->getFolder($now);
is($sameFolder->getId, $todayFolder->getId, 'call with same time returns the same folder');
undef $sameFolder;

106
t/Fork.t Normal file
View file

@ -0,0 +1,106 @@
# vim:syntax=perl
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#------------------------------------------------------------------
# WebGUI::Fork tests
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/lib";
use lib "$FindBin::Bin/../lib";
use Test::More;
use Test::Deep;
use Data::Dumper;
use JSON;
use WebGUI::Test;
use WebGUI::Session;
use WebGUI::Fork;
my $class = 'WebGUI::Fork';
my $testClass = 'WebGUI::Test::Fork';
my $pipe = $class->init();
my $session = WebGUI::Test->session;
# test simplest (non-forking) case
my $process = $class->create($session);
my $request = $process->request( $testClass, 'simple', ['data'] );
cmp_bag(
[ keys %$request ],
[qw(webguiRoot configFile sessionId id module subname data)],
'request hash has the right keys'
);
my $now = time;
$class->runRequest($request);
ok $process->isFinished, 'finished';
my $error = $process->getError;
ok( !$error, 'no errors' ) or diag " Expected nothing, got: $error\n";
$process->setWait(0);
is $process->getStatus, 'data', 'proper status';
my $started = $process->startTime;
ok( ( $started >= $now ), 'sane startTime' );
ok( ( $process->endTime >= $started ), 'sane endTime' );
$process->delete;
note "Testing error case\n";
$process = $class->create($session);
$request = $process->request( $testClass, 'error', ['error'] );
$class->runRequest($request);
ok $process->isFinished, 'finished';
is $process->getError, "error\n", 'has error code';
$process->setWait(0);
my $status = $process->getStatus;
ok( !$status, 'no discernable status' ) or diag $status;
ok( ( $process->endTime >= $started ), 'sane endTime' );
my $forkCount = 0;
my $forkAndExec = $class->can('forkAndExec');
my $replace = sub {
my $self = shift;
$forkCount++;
$self->$forkAndExec(@_);
};
{
no strict 'refs';
no warnings 'redefine';
*{ $class . '::forkAndExec' } = $replace;
}
sub backgroundTest {
note "$_[0]\n";
$process = $class->start( $session, $testClass, 'complex', ['data'] );
my $sleeping;
while ( !$process->isFinished && $sleeping++ < 10 ) {
sleep 1;
}
ok $process->isFinished, 'finished';
is $process->getStatus, 'baz', 'correct status'
or diag $process->getError . "\n";
$process->delete;
}
backgroundTest('talk to background');
is $forkCount, 0, 'we did not fork';
close $pipe;
backgroundTest('On-demand fork');
is $forkCount, 1, 'we did fork';
done_testing;
#vim:ft=perl

View file

@ -127,6 +127,7 @@ my @htmlTextSets = (
my $numTests = scalar @filterSets
+ scalar @macroParamSets
+ scalar @htmlTextSets
+ 3
;
plan tests => $numTests;
@ -145,3 +146,7 @@ foreach my $testSet (@htmlTextSets) {
my $text = WebGUI::HTML::html2text($testSet->{inputText});
is($text, $testSet->{output}, $testSet->{comment});
}
is(WebGUI::HTML::processReplacements($session, 'grass'), 'grass', 'processReplacements: grass is not replaced');
is(WebGUI::HTML::processReplacements($session, 'shitake'), 'shitake', '... shitake is not replaced');
is(WebGUI::HTML::processReplacements($session, 'This is shit.'), 'This is crap.', '... shit is replaced');

10
t/SQL.t
View file

@ -299,3 +299,13 @@ cmp_deeply(
'Check table structure',
);
#----------------------------------------------------------------------------
# REGRESSIONS
# 11940 : quickCSV chokes on newlines
$session->db->write(
'INSERT INTO testTable (myIndex,message,myKey) VALUES (?,?,?)',
[ 10, "a\ntest", 'B' ],
);
ok( $session->db->quickCSV( 'SELECT * FROM testTable' ), 'get some output even with newlines in data' );

20
t/lib/WebGUI/Test/Fork.pm Normal file
View file

@ -0,0 +1,20 @@
package WebGUI::Test::Fork;
sub simple {
my ( $self, $arr ) = @_;
$self->update( $arr->[0] );
}
sub error {
my ( $self, $arr ) = @_;
die "$arr->[0]\n";
}
sub complex {
my $self = shift;
$self->update( sub {'foo'} );
$self->update( sub {'bar'} );
$self->update( sub {'baz'} );
}
1;

View file

@ -2,13 +2,13 @@
<rss version="2.0">
<channel>
<title>PM captur&#xF3; a tres delincuentes que robaron agencia bancaria en San Mart&#xED;n</title>
<link>http://www.vtv.gob.ve/rss-noticias-nacionales</link>
<link>http://ejemplo.local/rss-noticias-locales</link>
<description>RSS Noticias Nacionales</description>
<language>es</language>
<item>
<title>PM captur&#xF3; a tres delincuentes que robaron agencia bancaria en San Mart&#xED;n</title>
<link>http://www.vtv.gob.ve/noticias-nacionales/25087</link>
<description>&lt;p&gt;Efectivos de la Polic&#xED;a Metropolitana (PM) de Caracas capturaron, este lunes en horas de la ma&#xF1;ana, a tres delincuentes implicados en el robo perpetrado en el Banco Industrial de Venezuela (BIV) ubicado dentro de la oficina del Instituto Postal Telegr&#xE1;fico de Venezuela, Ipostel, en la avenida Jos&#xE9; &#xC1;ngel Lamas, San Mart&#xED;n.&lt;/p&gt;</description>
<link>http://ejemplo.local/noticias-locales/99999</link>
<description>&lt;p&gt;Efectivos de la Polic&#xED;a Metropolitana (PM) capturaron, este lunes en horas de la ma&#xF1;ana, a tres delincuentes implicados en un robo.</description>
<pubDate>Mon, 19 Oct 2009 15:42:17 -0400</pubDate>
</item>
</channel>

View file

@ -2,13 +2,13 @@
<rss version="2.0">
<channel>
<title>PM capturó a tres delincuentes que robaron agencia bancaria en San Martín</title>
<link>http://www.vtv.gob.ve/rss-noticias-nacionales</link>
<description>RSS Noticias Nacionales</description>
<link>http://ejemplo.local/rss-noticias-locales</link>
<description>RSS Noticias Locales</description>
<language>es</language>
<item>
<title>PM capturó a tres delincuentes que robaron agencia bancaria en San Martín</title>
<link>http://www.vtv.gob.ve/noticias-nacionales/25087</link>
<description>&lt;p&gt;Efectivos de la Policía Metropolitana (PM) de Caracas capturaron, este lunes en horas de la mañana, a tres delincuentes implicados en el robo perpetrado en el Banco Industrial de Venezuela (BIV) ubicado dentro de la oficina del Instituto Postal Telegráfico de Venezuela, Ipostel, en la avenida José Ángel Lamas, San Martín.&lt;/p&gt;</description>
<link>http://ejemplo.local/noticias-locales/99999</link>
<description>&lt;p&gt;Efectivos de la Policía Metropolitana (PM) capturaron, este lunes en horas de la mañana, a tres delincuentes implicados en un robo.</description>
<pubDate>Mon, 19 Oct 2009 15:42:17 -0400</pubDate>
</item>
</channel>

View file

@ -2,13 +2,13 @@
<rss version="2.0">
<channel>
<title>PM capturó a tres delincuentes que robaron agencia bancaria en San Martín</title>
<link>http://www.vtv.gob.ve/rss-noticias-nacionales</link>
<description>RSS Noticias Nacionales</description>
<link>http://ejemplo.local/rss-noticias-locales</link>
<description>RSS Noticias Locales</description>
<language>es</language>
<item>
<title>PM capturó a tres delincuentes que robaron agencia bancaria en San Martín</title>
<link>http://www.vtv.gob.ve/noticias-nacionales/25087</link>
<description>&lt;p&gt;Efectivos de la Policía Metropolitana (PM) de Caracas capturaron, este lunes en horas de la mañana, a tres delincuentes implicados en el robo perpetrado en el Banco Industrial de Venezuela (BIV) ubicado dentro de la oficina del Instituto Postal Telegráfico de Venezuela, Ipostel, en la avenida José Ángel Lamas, San Martín.&lt;/p&gt;</description>
<link>http://ejemplo.local/noticias-locales/99999</link>
<description>&lt;p&gt;Efectivos de la Policía Metropolitana (PM) Caracas capturaron, este lunes en horas de la mañana, a tres delincuentes implicados en un robo.</description>
<pubDate>Mon, 19 Oct 2009 15:42:17 -0400</pubDate>
</item>
</channel>

View file

@ -0,0 +1,20 @@
.webgui-fork-pb {
border: thin solid black;
position: relative;
line-height: 20pt;
height: 20pt;
}
.webgui-fork-pb .webgui-fork-pb-bar {
background-color: lime;
height: 100%;
}
.webgui-fork-pb .webgui-fork-pb-caption {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
font-size: 18pt;
}

View file

@ -0,0 +1,30 @@
/*global YAHOO, WebGUI, document */
/* Dependencies: yahoo, dom */
(function () {
var dom = YAHOO.util.Dom,
ns = YAHOO.namespace('WebGUI.Fork'),
cls = ns.ProgressBar = function () {},
proto = cls.prototype;
proto.render = function (node) {
var bar, cap;
if (!node.tagName) {
node = document.getElementById(node);
}
dom.addClass(node, 'webgui-fork-pb');
bar = document.createElement('div');
cap = document.createElement('div');
dom.addClass(bar, 'webgui-fork-pb-bar');
dom.addClass(cap, 'webgui-fork-pb-caption');
node.appendChild(bar);
node.appendChild(cap);
this.domNode = node;
this.bar = bar;
this.caption = cap;
};
proto.update = function (done, total) {
var pct = (total > 0 ? Math.floor((done/total)*100) : 100) + '%';
this.caption.innerHTML = pct;
this.bar.style.width = pct;
};
}());

42
www/extras/Fork/poll.js Normal file
View file

@ -0,0 +1,42 @@
/*global YAHOO, setTimeout */
/* Dependencies: yahoo, connection_core, json */
(function () {
var ns = YAHOO.namespace('WebGUI.Fork'), JSON = YAHOO.lang.JSON;
ns.poll = function(args) {
function fetch() {
var first = true;
YAHOO.util.Connect.asyncRequest('GET', args.url, {
success: function (o) {
var data, e;
if (o.status != 200) {
args.error("Server returned bad response");
return;
}
data = JSON.parse(o.responseText);
e = data.error;
if (e) {
args.error(e);
return;
}
args.draw(data);
if (args.first && first) {
first = false;
args.first();
}
if (data.finished) {
args.finish();
}
else {
setTimeout(fetch, args.interval || 1000);
}
},
failure: function (o) {
args.error("Could not communicate with server");
}
});
}
fetch();
};
}());

View file

@ -0,0 +1,16 @@
/*global YAHOO, setTimeout, window */
/* Dependencies: yahoo */
(function () {
var ns = YAHOO.namespace('WebGUI.Fork');
ns.redirect = function (redir, after) {
if (!redir) {
return;
}
setTimeout(function() {
// The idea here is to only allow local redirects
var loc = window.location;
loc.href = loc.protocol + '//' + loc.host + redir;
}, after || 1000);
};
}());

View file

@ -314,11 +314,6 @@
return;
}
if (!id.match(/^[A-Za-z0-9_-]{22}$/)) {
alert('Error: bad response trying to save address.');
return;
}
function updateOne(dropdown) {
var opt = _.detect(dropdown.options, function (o) {
return o.text === label;