contrib modules security updates
This commit is contained in:
@@ -1,3 +1,458 @@
|
||||
Feeds 7.x 2.0 Beta 2, 2016-02-17
|
||||
--------------------------------
|
||||
|
||||
- By MegaChriz: test dependencies should be specified in the main module.info
|
||||
file and be in the form project:module (see also issue 2651854).
|
||||
- Issue #2662730 by joachim: 'clear-block' CSS class on admin form should be
|
||||
'clearfix'.
|
||||
- Issue #2638722 by MegaChriz: Improve documentation for
|
||||
hook_feeds_processor_targets().
|
||||
- Issue #1183440 by twistor, MegaChriz, stefan.r, drclaw, pcambra,
|
||||
olofjohansson, Calystod, colan, svendecabooter, Bobík, Manish Jain,
|
||||
AdamGerthel, mErilainen, kervi, Pocketpain et al: Multilingual Feeds - Make
|
||||
field import language-aware.
|
||||
- Issue #1428272 by OnkelTem, eosrei, jtsnow, Jerenus, liquidcms, acouch,
|
||||
derhasi, Niremizov, MegaChriz: Added support of encoding conversions to the
|
||||
CSV Parser.
|
||||
- Issue #2644868 by diamondsea: Skip verification of the certificate's name when
|
||||
accepting invalid SSL certificates.
|
||||
- Issue #2309471 by Josh Waihi: File Fetcher doesn't obey allowed extensions.
|
||||
- Issue #2117535 by gbirch, MegaChriz, cbfannin, batje, id.alan: fixed Undefined
|
||||
variable: original_author in _parser_common_syndication_atom10_parse().
|
||||
- Issue #2636342 by AndyF, MegaChriz: Improved documentation of
|
||||
FeedsConfigurable::__get().
|
||||
- Issue #2637118 by MegaChriz: Use "plural label" for "label plural" from entity
|
||||
info if available.
|
||||
- Issue #2602508 by MegaChriz: Fixed clear out boolean field when an empty value
|
||||
is provided.
|
||||
- Issue #1891404 by MegaChriz, twistor, jenlampton: Add a mapper for Updated
|
||||
date (changed).
|
||||
- Issue #2147341 by milesw: fixed missing bundle property on entity of type
|
||||
taxonomy_term when replacing existing terms.
|
||||
- Issue #2624344 by grahamC, MegaChriz: Import via pushImport() keeps looping /
|
||||
never completes.
|
||||
- Issue #2629620 by GuyPaddock, MegaChriz: Fixed template for TSV contains the
|
||||
word "TAB" instead of tabs.
|
||||
- Issue #2385601 by GerZah, MegaChriz, Jerenus: Sorting feed importers by
|
||||
readable names.
|
||||
- Issue #2619788 by pcambra: Get instance info from the entity instead of from
|
||||
the source for file mappings.
|
||||
- By MegaChriz: add entity_translation as a test dependency for issue #1183440.
|
||||
- Issue #2529538 by twistor, MegaChriz, stefan.r, drclaw, olofjohansson, Manish
|
||||
Jain, et al: Added generic entity language support.
|
||||
- Issue #1950182 by mikran, MegaChriz, twistor: Only update when mapped fields
|
||||
are updated.
|
||||
- Issue #2556461 by MegaChriz: Fixed importing two hierarchical vocabularies
|
||||
with overlapping id's.
|
||||
- Issue #2584443 by zniki.ru: http_request_create_absolute_url() ignore last
|
||||
path in $base_url.
|
||||
- Issue #2533030 by rrfegade, MegaChriz, twistor: Spelling errors in D7.
|
||||
- Issue #1393898 by MegaChriz, twistor: Order Log view by flid instead of
|
||||
log_time.
|
||||
- Issue #2581135 by Anas_maw, MegaChriz: views integration for feeds source
|
||||
import date.
|
||||
- Issue #2584157 by MegaChriz: fixed missing mapping targets in the UI for
|
||||
contrib processors.
|
||||
- Issue #2574789 by MegaChriz: Wrong processor settings set in
|
||||
FeedsUIUserInterfaceTestCase::testImporterImport().
|
||||
- Issue #2557581 by joelpittet: CSV column names escaped & upload field double
|
||||
escaping.
|
||||
- By MegaChriz: add i18n_taxonomy as a test dependency for issue #2529538.
|
||||
- Issue #2542416 by twistor, MegaChriz: Allow sources and targets to be marked
|
||||
as deprecated and hide them from the UI.
|
||||
- Issue #2530670 by MegaChriz: Filter log in Views by "Message".
|
||||
- Issue #2397151 by Pravin Ajaaz: importer UI for CSVs should quote column names
|
||||
that contain commas.
|
||||
- Issue #2531858 by twistor: Add a FeedsSource::pushImport() method.
|
||||
- Issue #2531706 by twistor: relation "cache_feeds_http" does not exist.
|
||||
- Issue #2531828 by twistor: Simplify db queries in FeedsProcessor.
|
||||
- Issue #1286298 by fietserwin, Triskelion, MegaChriz, charginghawk, osopolar:
|
||||
Don't create new items, only update existing.
|
||||
|
||||
Feeds 7.x 2.0 Beta 1, 2015-07-02
|
||||
--------------------------------
|
||||
|
||||
- Issue #2038525 by twistor, larsdesigns, DamienMcKenna: SimplePie Plugin
|
||||
Installation Documentation in README.txt.
|
||||
- Issue #1449464 by klausi, cbergmann: Own cache Bin for feeds.
|
||||
- Issue #2515196 by twistor: Only transliterate downloaded files.
|
||||
- Issue #2510788 by twistor: Remove query string from path in FeedsEnclosure.
|
||||
- Issue #2514300 by twistor: Fatal error when I try to import feed items.
|
||||
- Issue #2511738 by deminy: Incorrect File Inclusion.
|
||||
- Issue #2509444 by twistor: Field Feeds Presave Not Importing User Fields.
|
||||
- Issue #2509464 by twistor, joelpittet: Feeds module cannot find its parser
|
||||
module due to filesystem restriction.
|
||||
- Issue #2509464 by twistor, Max1: Feeds module cannot find its parser module
|
||||
due to filesystem restriction.
|
||||
- Issue #2364103 by Luxian, MegaChriz: Feeds error log crashes when log messages
|
||||
are too long.
|
||||
- Issue #1953008 by MegaChriz, twistor, klausi: PHP Fatal error: Nesting level
|
||||
too deep - recursive dependency? in FeedsProcessor.inc on line 199.
|
||||
- Issue #1107522 by MegaChriz, twistor, ditcheva, nielsonm, franz, Niklas
|
||||
Fiekas, cthiebault, Uhkis, gcb, mparker17, guillaumev: Framework for expected
|
||||
behavior when importing empty/blank values + text field fix.
|
||||
- Issue #2092895 by mikran, MegaChriz, twistor: Block users not included in
|
||||
feed.
|
||||
- Issue #2500185 by angel.h, MegaChriz: Error when having an entity without base
|
||||
table.
|
||||
- Issue #2496735 by twistor, diarmy: For PHP 5.6.0 and above, Feeds should allow
|
||||
the use of cURL if open_basedir is enabled.
|
||||
- Issue #1848498 by twistor: Respect allowed file extensions in file mapper.
|
||||
- Issue #2502419 by klausi: Log messages XSS attack vector.
|
||||
- Issue #2495145 by twistor, cashwilliams, greggles, klausi: Possible XSS in
|
||||
PuSHSubscriber.inc.
|
||||
- Issue #2488036 by MegaChriz, orannezelehcim: Modules that define both an
|
||||
importer and a plugin can not be disabled.
|
||||
- Issue #2333029 by twistor, MegaChriz: Extend mapping API to allow for defaults
|
||||
and multiple callbacks.
|
||||
- Issue #2497507 by twistor: Pass plugin definition to FeedsPlugin objects.
|
||||
- Add test for #2489006.
|
||||
- Issue #2489006 by donquixote: Uninitialized array in
|
||||
taxonomy_feeds_set_target().
|
||||
- Issue #2497729 by twistor: Implement all methods on FeedsMissingPlugin.
|
||||
- Issue #2469219 by MegaChriz, twistor: Remove the Generic Entity Processor.
|
||||
- Issue #1058424 by thijsvdanker: Port date mapper patch to d7 version to
|
||||
support dates before 13 Dec 1901.
|
||||
- Issue #2427497 by ttaylor249: Fetching feed via SSL through proxy doesn't
|
||||
work.
|
||||
- Issue #1978722 by klausi, ultimike: Entity property info for feeds node is
|
||||
broken.
|
||||
- Issue #1988970 by msti: FeedsCSV parser - download template should use the
|
||||
default delimiter.
|
||||
- Issue #2468401 by jiff: HTTP fetcher does not correctly decode urlencoded
|
||||
basic auth params.
|
||||
- Issue #1815070 by twistor, joelpittet: No more mapping for numeric (boolean,
|
||||
decimal, integer, floats, and lists of them) fields.
|
||||
- Issue #2339383 by mikran, MegaChriz, joelpittet: Items missing from feeds do
|
||||
not affect item hash even when action is taken based on that.
|
||||
- Issue #2333009 by MegaChriz: Add importer validator for Views.
|
||||
- Issue #2415283 by MegaChriz: Some tests are not executed by testbot.
|
||||
- Issue #1829212 by alan-io1, agupta, MegaChriz, ac: SQLSTATE[42S22]: Column not
|
||||
found: 1054 Unknown column 'feeds_item.entity_type' in 'on clause'.
|
||||
- Issue #2419111 by make77, MegaChriz: Configuration option to allow invalid SSL
|
||||
certificates is not used when option "Auto detect feeds" is enabled.
|
||||
- Issue #2357981 by MegaChriz: hook_feeds_presave: $entity_id is missing.
|
||||
- Issue #2011240 by ressa, Abelito: How to import the description of a file?.
|
||||
- Issue #2397219 by joachim, MegaChriz: docs for my_module_mapper_unique() don't
|
||||
match implementation in tests.
|
||||
- Issue #2397199 by joachim, MegaChriz: summary line for
|
||||
hook_feeds_processor_targets_alter() docs mentions nodes & is too long.
|
||||
- Remove unused code from common_syndication_parser.inc.
|
||||
- Issue #1982286 by AdamPS: Recoverable fatal error.
|
||||
- Issue #2341407 by vinmassaro, twistor: Fix missing file/image mappings caused
|
||||
by #1080386 by adding :uri to mappings.
|
||||
- Issue #2379915 by hydrant-mark, twistor: Taxonomy Mapper: Assumes there will
|
||||
only ever be one matching term.
|
||||
- Issue #2387419 by MegaChriz: Auto detect dependencies when putting Feeds
|
||||
importer in a feature.
|
||||
- Issue #2363779 by Niremizov, MegaChriz: CSVParser Source form translation fix.
|
||||
- Issue #2053355 by ufku, vinmassaro: Notice: Undefined variable: file
|
||||
FeedsParser.inc:388.
|
||||
- Issue #2390199 by GuyPaddock: Unhelpful failure upon uploading an empty CSV
|
||||
file.
|
||||
- Issue #2308343 by MegaChriz, twistor, joelpittet, kruser: File upload
|
||||
disappears with Bootstrap Theme (abuse of #description in
|
||||
theme_feeds_upload()).
|
||||
- Issue #1887632 by joelpittet: Exception: Empty configuration identifier.
|
||||
- Issue #2379407 by twistor, MegaChriz: Make module required if plugins are in
|
||||
use.
|
||||
- Issue #2248009 by twistor | djdevin: Fixed Remove the population of
|
||||
->source_config from FeedsPlugin.
|
||||
- Issue #2218999 by fietserwin: Fixed Warning: Invalid argument supplied for
|
||||
foreach() in element_children() (line 6420 of includes\common.inc).
|
||||
- Issue #2349245 by Niremizov: Fixed error on importing empty csv file with no
|
||||
headers.
|
||||
- Issue #2339983 by mikran: Fixed Unpublished nodes message has a wrong
|
||||
format_plural() parameter.
|
||||
- Issue #2328605 by ekes, twistor: Fixed Unique item checking:
|
||||
FeedsProcessor::existingEntityId().
|
||||
- Issue #2305919 by twistor: Fixed Return 404 when trying to edit a non-existent
|
||||
feed.
|
||||
- Issue #1062178 by mansspams, MegaChriz, gmclelland, specky_rum, Dave Reid |
|
||||
iccle: Added configuration option to allow invalid/unverified or (self
|
||||
certified) SSL certificates.
|
||||
- Issue #1470530 by stefan.r, GaëlG, mikran, Cottser, gnucifer, MegaChriz,
|
||||
vinmassaro, kostajh, Mithrandir, riho, jaanhoinatski, nrambeck, byronveale,
|
||||
dbassendine, PsycleInteractive, imclean: Added Unpublish/Delete nodes not
|
||||
included in feed.
|
||||
- Issue #2305929 by twistor, MegaChriz: Show message that mapping settings must
|
||||
be saved after changes.
|
||||
- Issue #2304247 by MegaChriz, undertext | twistor: Update included Feeds Import
|
||||
Feature.
|
||||
- Issue #2307379 by twistor: Fixed Add FeedsConfigurable::hasConfigForm().
|
||||
- Issue #661606 by MegaChriz, twistor, Cottser, scottrigby, manojbisht_drupal,
|
||||
Mohammed J. Razem, agileadam, emilyf, tmsimont, bradjones1, hairqles,
|
||||
ldavisrobeson, selim13, a.ross, chromix, g089h515r806 | lunk_rat: Added
|
||||
Support unique targets in mappers.
|
||||
- Issue #2008168 by JeroenT | scottalan: Update included Feeds News Feature.
|
||||
- Issue #2224643 by hanoii, MegaChriz: Added Support for input format
|
||||
configuration on a per-field basis .
|
||||
- Issue #1981504 by twistor | Dale Baldwin: Fixed Status Report has a
|
||||
notification to install SimplePie library when SimplePie module isn't even
|
||||
installed.
|
||||
- Issue #962912 by twistor, MegaChriz, Peacog, Niklas Fiekas | willmoy: Added
|
||||
Mapping to node summary.
|
||||
- Backport assertFieldByXPath fix from 8.x.
|
||||
- Issue #2175525 by MegaChriz | Max2505: Added User admin is not authorized to
|
||||
create content type.
|
||||
- Issue #2275893 by twistor, dagomar: Fixed Process in background doesn't work
|
||||
on non-periodic imports.
|
||||
- Issue #2275893 Add tests for import in background.
|
||||
- Add a long csv file where the guids are ordered.
|
||||
- Fix FeedsWebTestCase::removeMappings.
|
||||
- Issue #1561200 by szt, osopolar: Added Use machine names for better
|
||||
identification of similar field names.
|
||||
- Issue #2192819 by twistor, klausi: FeedsHTTPFetcherResult should store the
|
||||
result between batches.
|
||||
- Issue #1231332 by klausi, twistor | nyl auster: Periodic import imports only
|
||||
one file per cron.
|
||||
- Issue #2192851 by klausi: Hook_feeds_after_import() should have access to
|
||||
exceptions.
|
||||
- Issue #2190551 by twistor: Hook_feeds_before_import only executes during form
|
||||
submission.
|
||||
- Issue #1852048 by ianthomas_uk: Comments suggest exceptions are ignore, when
|
||||
in fact they are rethrown.
|
||||
- Issue #1722180 by pcambra: Clear plugins cache on feeds_cache_clear.
|
||||
- Issue #2093651 by twistor: Simplify target callbacks.
|
||||
- Issue #1305698 by dman: Additional validation when creating terms - assert the
|
||||
Vocabulary is valid.
|
||||
- Issue #1113312 by Niklas Fiekas: Show import page in admin overlay.
|
||||
- Issue #2053355 by osopolar: Notice: Undefined variable: file
|
||||
FeedsParser.inc:388.
|
||||
- Issue #1537776 by David_Rothstein | RyFo18: Importing a single 21st century
|
||||
year defaults to the current year.
|
||||
- Issue #1333266 by dastagg | webchick: Link to feed importers broken when no
|
||||
feed importers exist.
|
||||
- Issue #1951736 by twistor, John Morahan: Discovery sometimes fails.
|
||||
- Issue #1996240 by msti, MegaChriz: Duplicate fields in CSV template.
|
||||
- Issue #930652 by twistor, tristanoneil | alex_b: Expiry batching broken.
|
||||
- Issue #1884516 by aaronbauman: Import with taxonomy term mapping fails if term
|
||||
name exceeds db size.
|
||||
- Issue #2046335 by twistor, j0rd: Http:// prefix and error 1002.
|
||||
- Issue #1485870 by brad.bulger, twistor: Remove custom error reporting for
|
||||
SimplePie.
|
||||
- Issue #2178563 by twistor | alibama: Entityform does not seem to work.
|
||||
- Issue #2174303 by twistor | bdanin: Feeds importer importer -- context and
|
||||
mapping not working.
|
||||
- Issue #1107522 by ditcheva, Uhkis, cthiebault, franz, nielsonm, Niklas Fiekas,
|
||||
twistor, mparker17, Lasac, guillaumev | jptl: Framework for expected behavior
|
||||
when importing empty/blank values + text field fix.
|
||||
- Issue #1033202 by jamesdixon, jamsilver, twistor, james.williams, Steven
|
||||
Jones, vordude, j0rd, mErilainen, gmario, rickmanelius, imclean, dasjo,
|
||||
guillaumev, jlyon, gilgabar, elliotttf, mukesh.agarwal17, patcon, wesnick,
|
||||
Bevan, kreynen, Grayside, fago, spotzero: [Meta] Generic entity processor.
|
||||
- Issue #2149829 by chilic: Import/Update 0 value to text field.
|
||||
- Issue #2150989 by chilic | osopolar: SimpleTest fails: Undefined index:
|
||||
content-type, Notice file: http_request.inc Line: 57.
|
||||
- Issue #1984962 by mvd81NL, redsd, twistor: Fixed Use a 0 as mapping source.
|
||||
- Issue #1159806 by VladimirAus, bigjim | Slim Pickens: Added proxy support.
|
||||
- Issue #1989196 by beeradb: Fixed Never Pass FeedsDateTime objects into
|
||||
date_create().
|
||||
- Issue #1222750 by Brandonian, twistor, lyricnz: Added SimplePie 1.3 support.
|
||||
- Issue #1080386 by elizzle, twistor, rhouse, AndyF, chadmkidner, retiredpro,
|
||||
slefevre1, moonray, asgorobets, jwmeyerson: Added How to get title and alt
|
||||
fields into your image import for drupal 7 feeds.
|
||||
- Use user-supplied id if available on import.
|
||||
- Issue #777888 by WorldFallz, liquidcms, firfin, tmsimont | timwood: Followup
|
||||
to importing importers.
|
||||
- Fix validation for importing importers.
|
||||
|
||||
Feeds 7.x 2.0 Alpha 8, 2012-04-22
|
||||
---------------------------------
|
||||
|
||||
- Issue #1555974 by twistor | andyg5000: Fixed Save button should also add field
|
||||
mapping; Add button should also save config changes.
|
||||
- Allow 0 as a term name.
|
||||
- Issue #630288 by kulfi: Added import date compatibility issues with Google
|
||||
Groups' feeds.
|
||||
- Issue #1878254 by facine: Fixed SimplePie download page url.
|
||||
- Issue #1166100 by twistor | Niklas Fiekas: Fixed Notices and warnings on
|
||||
mapping page, when no content type is selected for NodeProcessor.
|
||||
- Issue #1941302 by jshimota01: Fixed Radio buttons hidden in IE 7.
|
||||
- Fix formatting of feeds_ui.css
|
||||
- Issue #1362378 by gordon, colan, Sutharsan, MegaChriz | mukesh.agarwal17:
|
||||
Added is_new addtribute to feeds_item.
|
||||
- Fixed: missing comma.
|
||||
- Issue #1961998 by gordon: Added new hook hook_feeds_before_update().
|
||||
- Issue #1025468 by 30equals: Added Allow operations to be performed on disabled
|
||||
Importers.
|
||||
- Issue #1953008 by MegaChriz | carn1x: Fixed PHP Fatal error: Nesting level
|
||||
too deep - recursive dependency? in FeedsProcessor.inc on line 199.
|
||||
- git commit -m Issue
|
||||
- Fix capitalization of Feeds on Importer import page.
|
||||
- Issue #777888 by WorldFallz, liquidcms, firfin | timwood: Added UI to import
|
||||
views-style exportable (a.k.a. How to import importers?).
|
||||
- Issue #697842 by eiriksm, franz, Webmaster Perpignan: Added Support array of
|
||||
values for dates.
|
||||
- Tests for #1019688.
|
||||
- Issue #1019688 by johnv, philipz, twistor, sin, ts145nera, David Hernández:
|
||||
Fixed Taxonomy mapper options: term name +tid, term name, tid, guid (avoids
|
||||
mapping error for Numeric taxonomy term, too).
|
||||
- Issue #1958864 by DamienMcKenna: Fixed 'Division by zero' error in
|
||||
feeds_update_7208.
|
||||
- Issue #1147734 by philipnorton42, znerol, David_Rothstein, twistor | johnv:
|
||||
Added Import files in public://feeds/ vs. private://feeds/.
|
||||
- Issue #857216 by yareckon, wuinfo: behavior on importing empty/NULL/invalid
|
||||
dates.
|
||||
- Fix coding standards.
|
||||
- Ensure that headers is always set.
|
||||
- Fix uid validation for Node processor.
|
||||
- Issue #1201638 by rfay: Fixed Plugins should be listed in info file.
|
||||
- Issue #1848726 by ojohansson, twistor: Fixed Hash check fails when running
|
||||
multiple importers on cron.
|
||||
- Issue #1620110 by Cottser, JvE | Alan D.: Fixed Update default views fixes
|
||||
'Missing text format: 1.' in watchdog.
|
||||
- Issue #1156982 by dooug, dotman: Fixed SQLSTATE[HY000]: General error: 1366
|
||||
Incorrect integer value: '' for column 'last_comment_uid()' at row 1.
|
||||
- Issue #1660950 by valthebald: Fixed FeedsSource expects stored to be array,
|
||||
which is not always the case.
|
||||
- Use bundle() method.
|
||||
- Use plugin key rather than class name.
|
||||
- Issue #1300940 by colin_young: Fixed Undefined Index: unique in
|
||||
FeedsCSVParser.inc.
|
||||
- Issue #1874322: Remove PHP version check from Common Syndication Parser.
|
||||
- Issue #1870528 by cmriley: Fixed Undefined index: content_type() in
|
||||
feeds_rules_event_info() (line 56 feeds/feeds.rules.inc.
|
||||
- Issue #1711648 Upgrade path.
|
||||
- Issue #1711648 by twistor: Fixes for tests.
|
||||
- Issue #1711648 by twistor: Abstract bundle handling.
|
||||
- Issue #1827780 by pfrenssen: Type hinting missing from
|
||||
hook_feeds_parser_sources_alter() callback example.
|
||||
- Issue #1825016 by David_Rothstein: Make it easier for the FeedsCSVParser
|
||||
source form to be altered.
|
||||
- Issue #1744570 by Staratel: Added Invoke Before import event before start
|
||||
import.
|
||||
- Move static method to member method.
|
||||
- Issue #1843146 by twistor: Add pluginType() method to be able to determine the
|
||||
plugin type without having to lookup the inheritance.
|
||||
- Always set is_new on node create rather than node_save().
|
||||
- Re-fix replace existing behavior.
|
||||
- Fix replace existing behavior.
|
||||
- Issue #1165506 by Mile23 | 7wonders: Added pipe delimiter to csv parser.
|
||||
- Use user_delete_multiple()
|
||||
- Issue #1835106 by twistor: Make entity loading generic.
|
||||
- Issue #1480902 by Staratel | faunt: Added Are there timeouts on Feed
|
||||
importers?.
|
||||
- Issue #912682 by alex_b, snyderp, dman | dwhogg: Added CSV Parser: Support
|
||||
Mac-style line endings.
|
||||
- Issue #1817992 by OnkelTem: Fixed Notice: Undefined property: stdClass:: in
|
||||
feeds_tokens()
|
||||
|
||||
Feeds 7.x 2.0 Alpha 7, 2012-10-12
|
||||
---------------------------------
|
||||
|
||||
- Make sure target array is always initialized.
|
||||
- Issue #1807920 by Rob_Feature: Fixed Imported nodes have NULL format.
|
||||
- Make datetime tests fake unit tests.
|
||||
|
||||
Feeds 7.x 2.0 Alpha 6, 2012-10-10
|
||||
---------------------------------
|
||||
|
||||
- Port authorization from 6.x to 7.x.
|
||||
- Make FeedsDateTimeTest a real unit test.
|
||||
- Don't hard code delta to 10.
|
||||
- Issue #1567508 by kruser, beansboxchrispang: Fixed User import is incorrectly
|
||||
changing passwords.
|
||||
- Fix failing test after making them stricter.
|
||||
- Issue #837922 by twistor, andrewlevine: Added removeMappings() to test suite
|
||||
and more extensive addMapping testing.
|
||||
- Issue #1074520 by ccheu: Commit #783098 not included in 7.x-2.0-alpha3.
|
||||
- Fix for mapping ui and multiple mappings with configuration.
|
||||
- Re-organize FeedsProcessor::process() so that it is clearer.
|
||||
- Issue #1191498 by dooug, twistor: Set drupal_set_message() to not repeat
|
||||
'Missing Feeds plugin...'.
|
||||
- Allow for 0 to be used in the number mapping.
|
||||
- Issue #1420360 by anarchocoder: Fixed Common Syndication Parser sometimes
|
||||
creates php warning when feed item title is empty.
|
||||
- Issue #1728634 by David_Rothstein: Fixed User and node 'created' dates (from
|
||||
feeds_to_unixtime()) default to the wrong timezone.
|
||||
- Coder review fixes.
|
||||
- Dang it. Add feeds_alter() back because contrib depends on it.
|
||||
- Remove stupid feeds_alter().
|
||||
- Issue #1466170 by peter.sogaard: Added Text format support in taxonomy term
|
||||
fields.
|
||||
- Use consistent set target callbacks.
|
||||
- Move text and numeric mappers to their own include.
|
||||
- Simplify forced updating.
|
||||
- Make link mapper less retarded.
|
||||
- Issue #1551852 by Sergii: Fixed Text field mapper: support for FeedsElement
|
||||
object.
|
||||
- Issue #1715124 by theduke: Fixed feeds_importer() 'config' column to short -
|
||||
change size to big.
|
||||
- Issue #1784436 by stefan.norman: Fixed FeedsTermProcessor existingEntityId
|
||||
doesn't honour id column in feeds_item().
|
||||
- Issue #1792318 by psynaptic: Fixed Invalid multibyte sequence in
|
||||
tests/feeds.test.
|
||||
- Add quick test for skip_hash_check.
|
||||
- Issue #1364116 by axel.rutz | Les Lim: Added Option to skip hash check on
|
||||
re-import.
|
||||
- Revert term specific forced updating.
|
||||
- Remove debug statements.
|
||||
- Issue #1410296 by logaritmisk, vinmassaro: Added Reorder mappings.
|
||||
- Don't set unique flag for things that don't have it.
|
||||
- Add test for new mapping config.
|
||||
- Issue #860748 by Niklas Fiekas, franz, twistor | infojunkie: Added Config for
|
||||
mappers?.
|
||||
- Add support for SimplePie 1.3
|
||||
- Fix path alias test for taxonomy terms.
|
||||
- Un-prefix node path test.
|
||||
- Move path mapping to path.inc and add tests.
|
||||
- Issue #1724200 by balazs.hegedus: Added Support for drupal_http_request()
|
||||
timeout.
|
||||
- Issue #1736976 by Chaulky: Added Trim feed urls to remove accidental spaces.
|
||||
- Issue #1110762 by vaartio, franz, bancarddata, axel.rutz: Fixed Feeds does not
|
||||
catch file exceptions properly in file mapper and FeedsParser.inc getFile()
|
||||
function.
|
||||
- Issue #1742740 by Staratel: Fixed Add magic method __isset() to
|
||||
FeedsConfigurable to use default entity wrapping.
|
||||
- Issue #1739704 by axel.rutz: Fixed Node lookup by title in Nodeprocessor does
|
||||
not respect nodetype.
|
||||
- Issue #1690434 by twistor | jzornig: Fixed Notice: Undefined variable: job in
|
||||
FeedsSource->scheduleClear() (line 319 of sites/all/modules/feeds/includes/FeedsSource.inc).
|
||||
- Issue #1703074 by theduke: Added Include parser result in
|
||||
hook_feeds_presave().
|
||||
- Issue #1070604 by Bobík: Added Feed's nid in mappings.
|
||||
- Issue #1300500 by milesw | derekwormdahl: Fixed Not loading node status in
|
||||
FeedsNodeProcessor.inc is causing issue with Content Access module.
|
||||
- Coder review fixes.
|
||||
- Add stricter test for integer value.
|
||||
- Fix doc strings.
|
||||
- Issue #1112876 by chx: Added Support digest auth.
|
||||
- Get FeedsMapperFileTestCase tests to pass.
|
||||
- Issue #1688294 by twistor | Rob_Feature: Fixed Invalid URL (even though it's
|
||||
valid).
|
||||
- Issue #1402788 by tbosviel | Cottser: Added All files are imported as MIME
|
||||
application/octet-stream.
|
||||
- Issue #1271502 by twistor | oobie11: Fixed Im getting an error when I run
|
||||
cron.
|
||||
- Issue #1460282 by elliotttf, MegaChriz | jday: Fixed Call to undefined method
|
||||
FeedsMissingPlugin::entityType() .
|
||||
- Use entity type if entity label is not set.
|
||||
- Issue #1661014 by twistor | johnv: Fixed feeds_entity_load() called too often
|
||||
/ is not cached.
|
||||
- Issue #1665450: Path alias target when using pathauto.
|
||||
- Fixed: feeds_mapper_file.test.
|
||||
- Method for downloading and extracting the simplepie library during tests.
|
||||
- Try to shut up SimplePie.
|
||||
- Allow setting a variable for the library path.
|
||||
- Fix for taxonomy mapper test.
|
||||
- Make descriptions in taxonomy term processor consistent.
|
||||
- Make labels for taxonomy processor consistent.
|
||||
- Issue #1044874 by marek.trunkat, bdragon | seth.vincent: Fixed Debug message:
|
||||
missing handler: feeds_log() feed_nid() field.
|
||||
- Issue #1424992 by stevector: Added implementation of hook_entity_info_alter()
|
||||
so that ->feed_nid() is available to Rules.
|
||||
- Issue #1662550 by pvhee: Fixed Invoke hook_feeds_after_import() after writing
|
||||
to the logs.
|
||||
- Issue #1454666: Fixing unlock feature to also work with locked imports.
|
||||
- Issue #1241754: Add targets for author name and email in node processor.
|
||||
- Issue #1454666: Add tool to reset locked feeds
|
||||
- Issue #1612246: Fixing introduced error on filepath generation.
|
||||
- Small fix on comment alignment
|
||||
- Issue #1632286 by facine: Fixed Cannot redeclare
|
||||
FeedsTermProcessor::setTargetElement().
|
||||
- Issue #1001590 by tristanoneil, twistor, Bevan: Path alias mapping target.
|
||||
|
||||
Feeds 7.x 2.0 Alpha 5, 2012-05-28
|
||||
---------------------------------
|
||||
|
||||
@@ -216,7 +671,7 @@ Feeds 7.x 2.0 Alpha 2, 2010-11-02
|
||||
FeedsSource::state() has changed.
|
||||
- Remove 6.x upgrade hooks.
|
||||
- #923318: Fix Fatal error: Call to a member function import() on a non-object
|
||||
occuring on cron.
|
||||
occurring on cron.
|
||||
- Clean up basic settings form.
|
||||
- Make getConfig() include configuration defaults.
|
||||
|
||||
|
@@ -49,10 +49,20 @@ Installation
|
||||
Feeds News, Feeds Import, Feeds Fast News (more info below).
|
||||
- Make sure cron is correctly configured http://drupal.org/cron
|
||||
- Go to import/ to import data.
|
||||
- To use SimplePie parser, download either the compiled or minified SimplePie
|
||||
and place simplepie_[version].compiled.php into feeds/libraries as
|
||||
simplepie.compiled.php. Recommended version: 1.3.
|
||||
http://simplepie.org/
|
||||
|
||||
SimplePie Installation
|
||||
======================
|
||||
|
||||
- To install the SimplePie parser plugin, complete the following steps:
|
||||
1. Download SimplePie from http://simplepie.org/downloads. The recommended
|
||||
version is: 1.3.
|
||||
2. Decompress the downloaded zip file.
|
||||
3. Rename the uncompressed folder to 'simplepie'.
|
||||
For example rename 'simplepie-simplepie-e9472a1' to 'simplepie'.
|
||||
4. Move the folder to sites/all/libraries. The final directory structure
|
||||
should be sites/all/libraries/simplepie.
|
||||
5. Flush the Drupal cache.
|
||||
6. The SimplePie parser should be available now in the list of parsers.
|
||||
|
||||
Feature modules
|
||||
===============
|
||||
@@ -191,12 +201,20 @@ Default: 50
|
||||
Name: http_request_timeout
|
||||
Default: 15
|
||||
Description: Timeout in seconds to wait for an HTTP get request to finish.
|
||||
Note: This setting could be overridden per importer in admin UI :
|
||||
admin/structure/feeds/<your_importer>/settings/<your_fetcher> page.
|
||||
|
||||
Name: feeds_never_use_curl
|
||||
Default: FALSE
|
||||
Description: Flag to stop feeds from using its cURL for http requests. See
|
||||
http_request_use_curl().
|
||||
|
||||
Name: feeds_use_mbstring
|
||||
Default: TRUE
|
||||
Description: The extension mbstring is used to convert encodings during parsing.
|
||||
The reason that this can be turned off is to be able to test Feeds
|
||||
behavior when the extension is not available.
|
||||
|
||||
Glossary
|
||||
========
|
||||
|
||||
|
@@ -87,9 +87,9 @@ function hook_feeds_plugins() {
|
||||
/**
|
||||
* Invoked after a feed source has been parsed, before it will be processed.
|
||||
*
|
||||
* @param $source
|
||||
* @param FeedsSource $source
|
||||
* FeedsSource object that describes the source that has been imported.
|
||||
* @param $result
|
||||
* @param FeedsParserResult $result
|
||||
* FeedsParserResult object that has been parsed from the source.
|
||||
*/
|
||||
function hook_feeds_after_parse(FeedsSource $source, FeedsParserResult $result) {
|
||||
@@ -97,37 +97,110 @@ function hook_feeds_after_parse(FeedsSource $source, FeedsParserResult $result)
|
||||
$result->title = 'Import number ' . my_module_import_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before a feed source import starts.
|
||||
*
|
||||
* @param FeedsSource $source
|
||||
* FeedsSource object that describes the source that is going to be imported.
|
||||
*/
|
||||
function hook_feeds_before_import(FeedsSource $source) {
|
||||
// See feeds_rules module's implementation for an example.
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before a feed item is updated/created/replaced.
|
||||
*
|
||||
* This is called every time a feed item is processed no matter if the item gets
|
||||
* updated or not.
|
||||
*
|
||||
* @param FeedsSource $source
|
||||
* The source for the current feed.
|
||||
* @param array $item
|
||||
* All the current item from the feed.
|
||||
* @param int|null $entity_id
|
||||
* The id of the current item which is going to be updated. If this is a new
|
||||
* item, then NULL is passed.
|
||||
*/
|
||||
function hook_feeds_before_update(FeedsSource $source, $item, $entity_id) {
|
||||
if ($entity_id) {
|
||||
$processor = $source->importer->processor;
|
||||
db_update('foo_bar')
|
||||
->fields(array('entity_type' => $processor->entityType(), 'entity_id' => $entity_id, 'last_seen' => REQUEST_TIME))
|
||||
->condition('entity_type', $processor->entityType())
|
||||
->condition('entity_id', $entity_id)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before a feed item is saved.
|
||||
*
|
||||
* @param $source
|
||||
* FeedsSource object that describes the source that is being imported.
|
||||
* @param FeedsSource $source
|
||||
* FeedsSource object that describes the source that is being imported.
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $item
|
||||
* @param array $item
|
||||
* The parser result for this entity.
|
||||
* @param int|null $entity_id
|
||||
* The id of the current item which is going to be updated. If this is a new
|
||||
* item, then NULL is passed.
|
||||
*/
|
||||
function hook_feeds_presave(FeedsSource $source, $entity, $item) {
|
||||
function hook_feeds_presave(FeedsSource $source, $entity, $item, $entity_id) {
|
||||
if ($entity->feeds_item->entity_type == 'node') {
|
||||
// Skip saving this entity.
|
||||
$entity->feeds_item->skip = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a feed item has been saved.
|
||||
*
|
||||
* @param FeedsSource $source
|
||||
* FeedsSource object that describes the source that is being imported.
|
||||
* @param $entity
|
||||
* The entity object that has just been saved.
|
||||
* @param array $item
|
||||
* The parser result for this entity.
|
||||
* @param int|null $entity_id
|
||||
* The id of the current item which is going to be updated. If this is a new
|
||||
* item, then NULL is passed.
|
||||
*/
|
||||
function hook_feeds_after_save(FeedsSource $source, $entity, $item, $entity_id) {
|
||||
// Use $entity->nid of the saved node.
|
||||
|
||||
// Although the $entity object is passed by reference, any changes made in
|
||||
// this function will be ignored by the FeedsProcessor.
|
||||
$config = $source->importer->getConfig();
|
||||
|
||||
if ($config['processor']['config']['purge_unseen_items'] && isset($entity->feeds_item)) {
|
||||
$feeds_item = $entity->feeds_item;
|
||||
$feeds_item->batch_id = feeds_delete_get_current_batch($feeds_item->feed_nid);
|
||||
|
||||
drupal_write_record('feeds_delete_item', $feeds_item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a feed source has been imported.
|
||||
*
|
||||
* @param $source
|
||||
* @param FeedsSource $source
|
||||
* FeedsSource object that describes the source that has been imported.
|
||||
*/
|
||||
function hook_feeds_after_import(FeedsSource $source) {
|
||||
// See geotaxonomy module's implementation for an example.
|
||||
|
||||
// We can also check for an exception in this hook. The exception should not
|
||||
// be thrown here, Feeds will handle it.
|
||||
if (isset($source->exception)) {
|
||||
watchdog('mymodule', 'An exception occurred during importing!', array(), WATCHDOG_ERROR);
|
||||
mymodule_panic_reaction($source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a feed source has been cleared of its items.
|
||||
*
|
||||
* @param $source
|
||||
* @param FeedsSource $source
|
||||
* FeedsSource object that describes the source that has been cleared.
|
||||
*/
|
||||
function hook_feeds_after_clear(FeedsSource $source) {
|
||||
@@ -154,7 +227,7 @@ function hook_feeds_after_clear(FeedsSource $source) {
|
||||
function hook_feeds_parser_sources_alter(&$sources, $content_type) {
|
||||
$sources['my_source'] = array(
|
||||
'name' => t('Images in description element'),
|
||||
'description' => t('Images occuring in the description element of a feed item.'),
|
||||
'description' => t('Images occurring in the description element of a feed item.'),
|
||||
'callback' => 'my_source_get_source',
|
||||
);
|
||||
}
|
||||
@@ -175,118 +248,193 @@ function hook_feeds_parser_sources_alter(&$sources, $content_type) {
|
||||
* @return
|
||||
* The value to be extracted from the source.
|
||||
*
|
||||
* @see hook_feeds_parser_sources_alter().
|
||||
* @see locale_feeds_get_source().
|
||||
* @see hook_feeds_parser_sources_alter()
|
||||
* @see locale_feeds_get_source()
|
||||
*/
|
||||
function my_source_get_source($source, FeedsParserResult $result, $key) {
|
||||
function my_source_get_source(FeedsSource $source, FeedsParserResult $result, $key) {
|
||||
$item = $result->currentItem();
|
||||
return my_source_parse_images($item['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter mapping targets for entities. Use this hook to add additional target
|
||||
* options to the mapping form of Node processors.
|
||||
* Adds mapping targets for processors.
|
||||
*
|
||||
* This hook allows additional target options to be added to the processors
|
||||
* mapping form.
|
||||
*
|
||||
* If the key in $targets[] does not correspond to the actual key on the node
|
||||
* object ($node->key), real_target MUST be specified. See mappers/link.inc
|
||||
*
|
||||
* For an example implementation, see mappers/content.inc
|
||||
* For an example implementation, see mappers/text.inc
|
||||
*
|
||||
* @param &$targets
|
||||
* Array containing the targets to be offered to the user. Add to this array
|
||||
* to expose additional options. Remove from this array to suppress options.
|
||||
* Remove with caution.
|
||||
* @param $entity_type
|
||||
* @param string $entity_type
|
||||
* The entity type of the target, for instance a 'node' entity.
|
||||
* @param $bundle_name
|
||||
* The bundle name for which to alter targets.
|
||||
* @param string $bundle
|
||||
* The entity bundle to return targets for.
|
||||
*
|
||||
* @return array
|
||||
* An array whose keys are the target name and whose values are arrays
|
||||
* containing the following keys:
|
||||
* - name: A human readable, translated label for the target.
|
||||
* - description: (optional) A human readable, translated description for the
|
||||
* target.
|
||||
* - callback: The callback used to set the value on the target.
|
||||
* - real_target: (optional) the name of the property on the entity that will
|
||||
* be set by the callback. Specify this if the target name is not equal to
|
||||
* the entity property name. This information will be used to clear the
|
||||
* right target at the beginning of the mapping process.
|
||||
* - optional_unique: (optional) A boolean that indicates whether or not the
|
||||
* target can be used as an unique target. If you set this to TRUE, be sure
|
||||
* to also specify "unique_callbacks".
|
||||
* - unique_callbacks: (optional) An array of callbacks that are used to
|
||||
* retrieve existing entity ids. Existing entities can be updated based on
|
||||
* unique targets.
|
||||
* - form_callbacks: (optional) An array of callbacks that are used to return
|
||||
* a form with additional configuration for a target.
|
||||
* - summary_callbacks: (optional) An array of callbacks that are used to
|
||||
* display values of additional target configuration.
|
||||
* - preprocess_callbacks: (optional) An array of callbacks that are used to
|
||||
* set or change mapping options.
|
||||
* - deprecated: (optional) A boolean that if TRUE, hides the target from the
|
||||
* UI. Use this if you want to rename targets for consistency, but don't
|
||||
* want to break importers that are using the old target name. If an
|
||||
* importer uses this target it will show up as "DEPRECATED" in the UI.
|
||||
*/
|
||||
function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function hook_feeds_processor_targets($entity_type, $bundle) {
|
||||
$targets = array();
|
||||
|
||||
if ($entity_type == 'node') {
|
||||
// Example 1: provide the minimal info for a target. Description is
|
||||
// optional, but recommended.
|
||||
// @see my_module_set_target()
|
||||
$targets['my_node_field'] = array(
|
||||
'name' => t('My custom node field'),
|
||||
'description' => t('Description of what my custom node field does.'),
|
||||
'callback' => 'my_module_set_target',
|
||||
);
|
||||
|
||||
// Specify both summary_callback and form_callback to add a per mapping
|
||||
// configuration form.
|
||||
'summary_callback' => 'my_module_summary_callback',
|
||||
'form_callback' => 'my_module_form_callback',
|
||||
);
|
||||
$targets['my_node_field2'] = array(
|
||||
'name' => t('My Second custom node field'),
|
||||
'description' => t('Description of what my second custom node field does.'),
|
||||
// Example 2: specify "real_target" if the target name is different from
|
||||
// the entity property name.
|
||||
// Here the target is called "my_node_field2:uri", but the entity property
|
||||
// is called "my_node_field2". This will ensure that the property
|
||||
// "my_node_field2" is cleared out that the beginning of the mapping
|
||||
// process.
|
||||
$targets['my_node_field2:uri'] = array(
|
||||
'name' => t('My third custom node field'),
|
||||
'description' => t('A target that sets a property that does not have the same name as the target.'),
|
||||
'callback' => 'my_module_set_target2',
|
||||
'real_target' => 'my_node_field_two', // Specify real target field on node.
|
||||
'real_target' => 'my_node_field2',
|
||||
);
|
||||
|
||||
// Example 3: you can make your target selectable as an unique target by
|
||||
// setting "optional_unique" to TRUE and specify one or more callbacks to
|
||||
// retrieve existing entity id's.
|
||||
// @see my_module_mapper_unique()
|
||||
$targets['my_node_field3'] = array(
|
||||
'name' => t('My third custom node field'),
|
||||
'description' => t('A field that can be set as an unique target.'),
|
||||
'callback' => 'my_module_set_target3',
|
||||
'optional_unique' => TRUE,
|
||||
'unique_callbacks' => array('my_module_mapper_unique'),
|
||||
);
|
||||
|
||||
// Example 4: use the form and summary callbacks to add additional
|
||||
// configuration options for your target. Use the form callbacks to provide
|
||||
// a form to set the target configuration. Use the summary callbacks to
|
||||
// display the target configuration.
|
||||
// @see my_module_form_callback()
|
||||
// @see my_module_summary_callback()
|
||||
$targets['my_node_field4'] = array(
|
||||
'name' => t('My fourth custom node field'),
|
||||
'description' => t('A field with additional configuration.'),
|
||||
'callback' => 'my_module_set_target4',
|
||||
'form_callbacks' => array('my_module_form_callback'),
|
||||
'summary_callbacks' => array('my_module_summary_callback'),
|
||||
);
|
||||
|
||||
// Example 5: use preprocess callbacks to set or change mapping options.
|
||||
// @see my_module_preprocess_callback()
|
||||
$targets['my_node_field5'] = array(
|
||||
'name' => t('My fifth custom node field'),
|
||||
'description' => t('A field with additional configuration.'),
|
||||
'callback' => 'my_module_set_target5',
|
||||
'preprocess_callbacks' => array('my_module_preprocess_callback'),
|
||||
);
|
||||
|
||||
// Example 6: when you want to remove or rename previously provided targets,
|
||||
// you can set "deprecated" to TRUE for the old target name. This will make
|
||||
// the target to be no longer selectable in the UI. If an importer uses this
|
||||
// target it will show up as "DEPRECATED" in the UI.
|
||||
// If you want that the target continues to work, you can still specify the
|
||||
// callback.
|
||||
$targets['deprecated_target'] = array(
|
||||
'name' => t('A target that cannot be chosen in the UI.'),
|
||||
'deprecated' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters the target array.
|
||||
*
|
||||
* This hook allows modifying the target array.
|
||||
*
|
||||
* @param array &$targets
|
||||
* Array containing the targets to be offered to the user. Add to this array
|
||||
* to expose additional options.
|
||||
* @param string $entity_type
|
||||
* The entity type of the target, for instance a 'node' entity.
|
||||
* @param string $bundle
|
||||
* The entity bundle to return targets for.
|
||||
*
|
||||
* @see hook_feeds_processor_targets()
|
||||
*/
|
||||
function hook_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) {
|
||||
// Example: set an existing target as optional unique.
|
||||
if ($entity_type == 'node' && $bundle == 'article') {
|
||||
if (isset($targets['nid'])) {
|
||||
$targets['nid']['unique_callbacks'][] = 'my_module_mapper_unique';
|
||||
$targets['nid']['optional_unique'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example callback specified in hook_feeds_processor_targets_alter().
|
||||
* Example callback specified in hook_feeds_processor_targets().
|
||||
*
|
||||
* @param $source
|
||||
* @param FeedsSource $source
|
||||
* Field mapper source settings.
|
||||
* @param $entity
|
||||
* @param object $entity
|
||||
* An entity object, for instance a node object.
|
||||
* @param $target
|
||||
* @param string $target
|
||||
* A string identifying the target on the node.
|
||||
* @param $value
|
||||
* @param array $values
|
||||
* The value to populate the target with.
|
||||
* @param $mapping
|
||||
* @param array $mapping
|
||||
* Associative array of the mapping settings from the per mapping
|
||||
* configuration form.
|
||||
*/
|
||||
function my_module_set_target($source, $entity, $target, $value, $mapping) {
|
||||
$entity->{$target}[$entity->language][0]['value'] = $value;
|
||||
function my_module_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$entity->{$target}[$entity->language][0]['value'] = reset($values);
|
||||
if (isset($source->importer->processor->config['input_format'])) {
|
||||
$entity->{$target}[$entity->language][0]['format'] =
|
||||
$source->importer->processor->config['input_format'];
|
||||
$entity->{$target}[$entity->language][0]['format'] = $source->importer->processor->config['input_format'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of the summary_callback specified in
|
||||
* hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @param $mapping
|
||||
* Associative array of the mapping settings.
|
||||
* @param $target
|
||||
* Array of target settings, as defined by the processor or
|
||||
* hook_feeds_processor_targets_alter().
|
||||
* @param $form
|
||||
* The whole mapping form.
|
||||
* @param $form_state
|
||||
* The form state of the mapping form.
|
||||
*
|
||||
* @return
|
||||
* Returns, as a string that may contain HTML, the summary to display while
|
||||
* the full form isn't visible.
|
||||
* If the return value is empty, no summary and no option to view the form
|
||||
* will be displayed.
|
||||
*/
|
||||
function my_module_summary_callback($mapping, $target, $form, $form_state) {
|
||||
if (empty($mapping['my_setting'])) {
|
||||
return t('My setting <strong>not</strong> active');
|
||||
}
|
||||
else {
|
||||
return t('My setting <strong>active</strong>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of the form_callback specified in
|
||||
* hook_feeds_processor_targets_alter().
|
||||
* Example of the form_callback specified in hook_feeds_processor_targets().
|
||||
*
|
||||
* The arguments are the same that my_module_summary_callback() gets.
|
||||
*
|
||||
* @see my_module_summary_callback()
|
||||
*
|
||||
* @return
|
||||
* @return array
|
||||
* The per mapping configuration form. Once the form is saved, $mapping will
|
||||
* be populated with the form values.
|
||||
*
|
||||
* @see my_module_summary_callback()
|
||||
*/
|
||||
function my_module_form_callback($mapping, $target, $form, $form_state) {
|
||||
function my_module_form_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
return array(
|
||||
'my_setting' => array(
|
||||
'#type' => 'checkbox',
|
||||
@@ -296,6 +444,84 @@ function my_module_form_callback($mapping, $target, $form, $form_state) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of the summary_callback specified in hook_feeds_processor_targets().
|
||||
*
|
||||
* @param array $mapping
|
||||
* Associative array of the mapping settings.
|
||||
* @param string $target
|
||||
* Array of target settings, as defined by the processor or
|
||||
* hook_feeds_processor_targets_alter().
|
||||
* @param array $form
|
||||
* The whole mapping form.
|
||||
* @param array $form_state
|
||||
* The form state of the mapping form.
|
||||
*
|
||||
* @return string
|
||||
* Returns, as a string that may contain HTML, the summary to display while
|
||||
* the full form isn't visible.
|
||||
* If the return value is empty, no summary and no option to view the form
|
||||
* will be displayed.
|
||||
*/
|
||||
function my_module_summary_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
if (empty($mapping['my_setting'])) {
|
||||
return t('My setting <strong>not</strong> active');
|
||||
}
|
||||
else {
|
||||
return t('My setting <strong>active</strong>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of the unique_callbacks specified in hook_feeds_processor_targets().
|
||||
*
|
||||
* @param FeedsSource $source
|
||||
* The Feed source.
|
||||
* @param string $entity_type
|
||||
* Entity type for the entity to be processed.
|
||||
* @param string $bundle
|
||||
* Bundle name for the entity to be processed.
|
||||
* @param string $target
|
||||
* A string identifying the unique target on the entity.
|
||||
* @param array $values
|
||||
* The unique values to be checked.
|
||||
*
|
||||
* @return int|null
|
||||
* The existing entity id, or NULL if no existing entity is found.
|
||||
*
|
||||
* @see hook_feeds_processor_targets()
|
||||
* @see FeedsProcessor::existingEntityId()
|
||||
*/
|
||||
function my_module_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) {
|
||||
list($field_name, $column) = explode(':', $target . ':value');
|
||||
// Example for if the target is a field.
|
||||
$query = new EntityFieldQuery();
|
||||
$result = $query
|
||||
->entityCondition('entity_type', $entity_type)
|
||||
->entityCondition('bundle', $bundle)
|
||||
->fieldCondition($field_name, $column, $values)
|
||||
->execute();
|
||||
|
||||
if (!empty($result[$entity_type])) {
|
||||
return key($result[$entity_type]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of the preprocess_callbacks specified in hook_feeds_processor_targets().
|
||||
*
|
||||
* @param array $target
|
||||
* The full target definition.
|
||||
* @param array &$mapping
|
||||
* The mapping configuration.
|
||||
*
|
||||
* @see hook_feeds_processor_targets()
|
||||
*/
|
||||
function my_module_preprocess_callback(array $target, array &$mapping) {
|
||||
// Add in default values.
|
||||
$mapping += array('setting_value' => TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
@@ -1,7 +1,23 @@
|
||||
/**
|
||||
* @file
|
||||
* Feeds CSS magic.
|
||||
*/
|
||||
|
||||
#edit-feeds-FeedsFileFetcher-upload-wrapper .file-info {
|
||||
/* For file uploads, display previously uploaded files on the left side. */
|
||||
.feeds-file-info {
|
||||
float: left;
|
||||
width: 200px;
|
||||
border-right: 1px solid #ddd;
|
||||
border-right: 1px solid #ccc;
|
||||
margin-right: 10px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* For file uploads, display upload field on the right side. */
|
||||
.feeds-file-upload {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* Make sure file upload fields don't get too wide. */
|
||||
.feeds-file-upload .form-file {
|
||||
width: auto;
|
||||
}
|
||||
|
89
sites/all/modules/feeds/feeds.feeds.inc
Normal file
89
sites/all/modules/feeds/feeds.feeds.inc
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Feeds hooks.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function feeds_feeds_processor_targets($entity_type, $bundle) {
|
||||
// Record that we've been called.
|
||||
// @see _feeds_feeds_processor_targets_alter()
|
||||
$called = &drupal_static('feeds_feeds_processor_targets', FALSE);
|
||||
$called = TRUE;
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*/
|
||||
function feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) {
|
||||
// This hook gets called last, so that we normalize the whole array.
|
||||
feeds_normalize_targets($targets);
|
||||
|
||||
// Since a hook can be invoked multiple times during a request, reset the
|
||||
// "feeds_feeds_processor_targets" variable.
|
||||
// @see _feeds_feeds_processor_targets_alter()
|
||||
drupal_static_reset('feeds_feeds_processor_targets');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the target array.
|
||||
*
|
||||
* @param array &$targets
|
||||
* The Feeds target array.
|
||||
*/
|
||||
function feeds_normalize_targets(array &$targets) {
|
||||
static $defaults = array(
|
||||
'description' => '',
|
||||
'summary_callbacks' => array(),
|
||||
'form_callbacks' => array(),
|
||||
'preprocess_callbacks' => array(),
|
||||
'unique_callbacks' => array(),
|
||||
);
|
||||
|
||||
foreach (array_keys($targets) as $target) {
|
||||
$targets[$target] += $defaults;
|
||||
|
||||
// Filter out any uncallable keys.
|
||||
_feeds_filter_callback_arrays($targets[$target]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the callbacks of a single target array.
|
||||
*
|
||||
* @param array &$target
|
||||
* The target arary.
|
||||
*/
|
||||
function _feeds_filter_callback_arrays(array &$target) {
|
||||
// Migrate keys summary_callback and form_callback to the new keys.
|
||||
if (isset($target['summary_callback'])) {
|
||||
$target['summary_callbacks'][] = $target['summary_callback'];
|
||||
}
|
||||
if (isset($target['form_callback'])) {
|
||||
$target['form_callbacks'][] = $target['form_callback'];
|
||||
}
|
||||
unset($target['summary_callback'], $target['form_callback']);
|
||||
|
||||
static $callback_keys = array(
|
||||
'summary_callbacks',
|
||||
'form_callbacks',
|
||||
'preprocess_callbacks',
|
||||
'unique_callbacks',
|
||||
);
|
||||
|
||||
// Filter out any incorrect callbacks. Do it here so it only has to be done
|
||||
// once.
|
||||
foreach ($callback_keys as $callback_key) {
|
||||
$target[$callback_key] = array_filter($target[$callback_key], 'is_callable');
|
||||
}
|
||||
|
||||
// This makes checking in FeedsProcessor::mapToTarget() simpler.
|
||||
if (empty($target['callback']) || !is_callable($target['callback'])) {
|
||||
unset($target['callback']);
|
||||
}
|
||||
}
|
@@ -5,39 +5,84 @@ core = 7.x
|
||||
dependencies[] = ctools
|
||||
dependencies[] = job_scheduler
|
||||
|
||||
test_dependencies[] = date:date
|
||||
test_dependencies[] = entity_translation:entity_translation
|
||||
test_dependencies[] = feeds_xpathparser:feeds_xpathparser
|
||||
test_dependencies[] = link:link
|
||||
test_dependencies[] = i18n:i18n_taxonomy
|
||||
|
||||
files[] = includes/FeedsConfigurable.inc
|
||||
files[] = includes/FeedsImporter.inc
|
||||
files[] = includes/FeedsSource.inc
|
||||
files[] = libraries/ParserCSV.inc
|
||||
files[] = libraries/http_request.inc
|
||||
files[] = libraries/PuSHSubscriber.inc
|
||||
|
||||
; Plugins
|
||||
files[] = plugins/FeedsCSVParser.inc
|
||||
files[] = plugins/FeedsFetcher.inc
|
||||
files[] = plugins/FeedsFileFetcher.inc
|
||||
files[] = plugins/FeedsHTTPFetcher.inc
|
||||
files[] = plugins/FeedsNodeProcessor.inc
|
||||
files[] = plugins/FeedsOPMLParser.inc
|
||||
files[] = plugins/FeedsParser.inc
|
||||
files[] = plugins/FeedsPlugin.inc
|
||||
files[] = plugins/FeedsProcessor.inc
|
||||
files[] = plugins/FeedsSimplePieParser.inc
|
||||
files[] = plugins/FeedsSitemapParser.inc
|
||||
files[] = plugins/FeedsSyndicationParser.inc
|
||||
files[] = plugins/FeedsTermProcessor.inc
|
||||
files[] = plugins/FeedsUserProcessor.inc
|
||||
|
||||
; Tests
|
||||
files[] = tests/feeds.test
|
||||
files[] = tests/common_syndication_parser.test
|
||||
files[] = tests/feeds_date_time.test
|
||||
files[] = tests/feeds_mapper_date.test
|
||||
files[] = tests/feeds_mapper_date_multiple.test
|
||||
files[] = tests/feeds_mapper_field.test
|
||||
files[] = tests/feeds_mapper_file.test
|
||||
files[] = tests/feeds_mapper_hooks.test
|
||||
files[] = tests/feeds_mapper_link.test
|
||||
files[] = tests/feeds_mapper_list.test
|
||||
files[] = tests/feeds_mapper_multilingual_fields.test
|
||||
files[] = tests/feeds_mapper_path.test
|
||||
files[] = tests/feeds_mapper_profile.test
|
||||
files[] = tests/feeds_mapper_unique.test
|
||||
files[] = tests/feeds_mapper.test
|
||||
files[] = tests/feeds_mapper_config.test
|
||||
files[] = tests/feeds_fetcher_file.test
|
||||
files[] = tests/feeds_mapper_format_config.test
|
||||
files[] = tests/feeds_fetcher_http.test
|
||||
files[] = tests/feeds_i18n.test
|
||||
files[] = tests/feeds_i18n_node.test
|
||||
files[] = tests/feeds_i18n_taxonomy.test
|
||||
files[] = tests/feeds_parser_csv.test
|
||||
files[] = tests/feeds_parser_sitemap.test
|
||||
files[] = tests/feeds_parser_syndication.test
|
||||
files[] = tests/feeds_processor_node.test
|
||||
files[] = tests/feeds_processor_term.test
|
||||
files[] = tests/feeds_processor_user.test
|
||||
files[] = tests/feeds_push.test
|
||||
files[] = tests/feeds_scheduler.test
|
||||
files[] = tests/feeds_mapper_link.test
|
||||
files[] = tests/feeds_mapper_summary.test
|
||||
files[] = tests/feeds_mapper_taxonomy.test
|
||||
files[] = tests/http_request.test
|
||||
files[] = tests/parser_csv.test
|
||||
|
||||
; Views integration
|
||||
files[] = views/feeds_views_handler_argument_importer_id.inc
|
||||
files[] = views/feeds_views_handler_field_importer_name.inc
|
||||
files[] = views/feeds_views_handler_field_log_message.inc
|
||||
files[] = views/feeds_views_handler_field_severity.inc
|
||||
files[] = views/feeds_views_handler_field_source.inc
|
||||
files[] = views/feeds_views_handler_filter_severity.inc
|
||||
files[] = views/feeds_views_plugin_argument_validate_feed_nid.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-10-24
|
||||
version = "7.x-2.0-alpha7"
|
||||
; Information added by Drupal.org packaging script on 2016-02-21
|
||||
version = "7.x-2.0-beta2"
|
||||
core = "7.x"
|
||||
project = "feeds"
|
||||
datestamp = "1351111319"
|
||||
datestamp = "1456055647"
|
||||
|
||||
|
@@ -5,6 +5,64 @@
|
||||
* Schema definitions install/update/uninstall hooks.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function feeds_requirements($phase) {
|
||||
$t = get_t();
|
||||
|
||||
$requirements = array();
|
||||
|
||||
module_load_include('module', 'feeds');
|
||||
// Check if we have any SimplePie importers.
|
||||
$needs_simplepie = FALSE;
|
||||
foreach (feeds_importer_load_all() as $importer) {
|
||||
if ($importer->config['parser']['plugin_key'] === 'FeedsSimplePieParser') {
|
||||
$needs_simplepie = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$needs_simplepie) {
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
$requirements['simplepie'] = array(
|
||||
'title' => $t('SimplePie'),
|
||||
'value' => $t('Installed'),
|
||||
'description' => $t('The SimplePie library is required for Feeds SimplePie Parser.'),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
);
|
||||
|
||||
if (!feeds_simplepie_exists()) {
|
||||
$requirements['simplepie']['value'] = $t('Not installed');
|
||||
|
||||
$folder = drupal_get_path('module', 'feeds') . '/libraries';
|
||||
if (module_exists('libraries')) {
|
||||
$folder = 'sites/all/libraries/simplepie';
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'!url' => 'http://simplepie.org/downloads/',
|
||||
'%folder' => $folder,
|
||||
'%file' => 'simplepie.compiled.php',
|
||||
);
|
||||
$requirements['simplepie']['description'] .= $t('<br />Download the compiled, single-file version of the library from the <a href="!url">SimplePie download page</a>, place it into %folder and rename it to %file.', $args);
|
||||
$requirements['simplepie']['severity'] = REQUIREMENT_ERROR;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement hook_uninstall()
|
||||
*/
|
||||
function feeds_uninstall() {
|
||||
variable_del('http_request_timeout');
|
||||
variable_del('feeds_reschedule');
|
||||
variable_del('feeds_use_mbstring');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
@@ -294,6 +352,10 @@ function feeds_schema() {
|
||||
'type' => array('type'),
|
||||
),
|
||||
);
|
||||
|
||||
$schema['cache_feeds_http'] = drupal_get_schema_unprocessed('system', 'cache');
|
||||
$schema['cache_feeds_http']['description'] = 'Cache table for Feeds downloads.';
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
@@ -561,3 +623,86 @@ function feeds_update_7207() {
|
||||
'serialize' => TRUE,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update to use generic bundle handling.
|
||||
*/
|
||||
function feeds_update_7208(&$sandbox) {
|
||||
|
||||
if (!isset($sandbox['importers'])) {
|
||||
// Get all importers.
|
||||
$sandbox['importers'] = db_query("SELECT id FROM {feeds_importer}")->fetchCol();
|
||||
$sandbox['total'] = count($sandbox['importers']);
|
||||
}
|
||||
|
||||
$importer = array_pop($sandbox['importers']);
|
||||
$config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $importer))->fetchField();
|
||||
|
||||
if ($config) {
|
||||
$config = unserialize($config);
|
||||
|
||||
switch ($config['processor']['plugin_key']) {
|
||||
case 'FeedsNodeProcessor':
|
||||
$config_key = 'content_type';
|
||||
break;
|
||||
|
||||
case 'FeedsTermProcessor':
|
||||
$config_key = 'vocabulary';
|
||||
break;
|
||||
|
||||
default:
|
||||
$config_key = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($config_key && isset($config['processor']['config'][$config_key])) {
|
||||
$config['processor']['config']['bundle'] = $config['processor']['config'][$config_key];
|
||||
unset($config['processor']['config'][$config_key]);
|
||||
|
||||
// Update databse.
|
||||
db_update('feeds_importer')
|
||||
->fields(array(
|
||||
'config' => serialize($config),
|
||||
))
|
||||
->condition('id', $importer)
|
||||
->execute();
|
||||
}
|
||||
|
||||
$sandbox['#finished'] = 1 - count($sandbox['importers']) / $sandbox['total'];
|
||||
}
|
||||
else {
|
||||
$sandbox['#finished'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedules feeds jobs.
|
||||
*/
|
||||
function feeds_update_7209() {
|
||||
// Reschedule all importers.
|
||||
variable_set('feeds_reschedule', TRUE);
|
||||
|
||||
// Our expire callback has changed names, remove all existing callbacks.
|
||||
db_delete('job_schedule')
|
||||
->condition('name', 'feeds_importer_expire')
|
||||
->execute();
|
||||
|
||||
DrupalQueue::get('feeds_importer_expire')->deleteQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. Update removed.
|
||||
*/
|
||||
function feeds_update_7211(&$sandbox) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {cache_feeds_http} table.
|
||||
*/
|
||||
function feeds_update_7212() {
|
||||
if (!db_table_exists('cache_feeds_http')) {
|
||||
$schema = drupal_get_schema_unprocessed('system', 'cache');
|
||||
$schema['description'] = 'Cache table for Feeds downloads.';
|
||||
db_create_table('cache_feeds_http', $schema);
|
||||
}
|
||||
}
|
||||
|
@@ -27,9 +27,15 @@ define('FEEDS_BATCH_ACTIVE', 0.0);
|
||||
*/
|
||||
function feeds_hook_info() {
|
||||
$hooks = array(
|
||||
'feeds_plugins',
|
||||
'feeds_after_parse',
|
||||
'feeds_before_import',
|
||||
'feeds_before_update',
|
||||
'feeds_presave',
|
||||
'feeds_after_save',
|
||||
'feeds_after_import',
|
||||
'feeds_after_clear',
|
||||
'feeds_processor_targets',
|
||||
'feeds_processor_targets_alter',
|
||||
'feeds_parser_sources_alter',
|
||||
);
|
||||
@@ -41,20 +47,25 @@ function feeds_hook_info() {
|
||||
* Implements hook_cron().
|
||||
*/
|
||||
function feeds_cron() {
|
||||
if ($importers = feeds_reschedule()) {
|
||||
foreach ($importers as $id) {
|
||||
feeds_importer($id)->schedule();
|
||||
$rows = db_query("SELECT feed_nid FROM {feeds_source} WHERE id = :id", array(':id' => $id));
|
||||
foreach ($rows as $row) {
|
||||
feeds_source($id, $row->feed_nid)->schedule();
|
||||
}
|
||||
}
|
||||
feeds_reschedule(FALSE);
|
||||
}
|
||||
// Expire old log entries.
|
||||
db_delete('feeds_log')
|
||||
->condition('request_time', REQUEST_TIME - 604800, '<')
|
||||
->execute();
|
||||
|
||||
// Find importers that need to be rescheduled.
|
||||
if (!$importers = feeds_reschedule()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo Maybe we should queue this somehow as well. This could be potentially
|
||||
// very long.
|
||||
$sources = db_query("SELECT feed_nid, id FROM {feeds_source} WHERE id IN (:ids)", array(':ids' => $importers));
|
||||
|
||||
foreach ($sources as $source) {
|
||||
feeds_source($source->id, $source->feed_nid)->schedule();
|
||||
}
|
||||
|
||||
feeds_reschedule(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,8 +81,8 @@ function feeds_cron_job_scheduler_info() {
|
||||
$info['feeds_source_clear'] = array(
|
||||
'queue name' => 'feeds_source_clear',
|
||||
);
|
||||
$info['feeds_importer_expire'] = array(
|
||||
'queue name' => 'feeds_importer_expire',
|
||||
$info['feeds_source_expire'] = array(
|
||||
'queue name' => 'feeds_source_expire',
|
||||
);
|
||||
$info['feeds_push_unsubscribe'] = array(
|
||||
'queue name' => 'feeds_push_unsubscribe',
|
||||
@@ -86,72 +97,66 @@ function feeds_cron_queue_info() {
|
||||
$queues = array();
|
||||
$queues['feeds_source_import'] = array(
|
||||
'worker callback' => 'feeds_source_import',
|
||||
'time' => 15,
|
||||
'time' => 60,
|
||||
);
|
||||
$queues['feeds_source_clear'] = array(
|
||||
'worker callback' => 'feeds_source_clear',
|
||||
'time' => 15,
|
||||
);
|
||||
$queues['feeds_importer_expire'] = array(
|
||||
'worker callback' => 'feeds_importer_expire',
|
||||
'time' => 15,
|
||||
$queues['feeds_source_expire'] = array(
|
||||
'worker callback' => 'feeds_source_expire',
|
||||
);
|
||||
$queues['feeds_push_unsubscribe'] = array(
|
||||
'worker callback' => 'feeds_push_unsubscribe',
|
||||
'time' => 15,
|
||||
);
|
||||
|
||||
return $queues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler callback for importing from a source.
|
||||
*/
|
||||
function feeds_source_import($job) {
|
||||
$source = feeds_source($job['type'], $job['id']);
|
||||
try {
|
||||
$source->existing()->import();
|
||||
}
|
||||
catch (FeedsNotExistingException $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$source->log('import', $e->getMessage(), array(), WATCHDOG_ERROR);
|
||||
}
|
||||
function feeds_source_import(array $job) {
|
||||
$source = _feeds_queue_worker_helper($job, 'import');
|
||||
$source->scheduleImport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler callback for deleting all items from a source.
|
||||
*/
|
||||
function feeds_source_clear($job) {
|
||||
$source = feeds_source($job['type'], $job['id']);
|
||||
try {
|
||||
$source->existing()->clear();
|
||||
}
|
||||
catch (FeedsNotExistingException $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$source->log('clear', $e->getMessage(), array(), WATCHDOG_ERROR);
|
||||
}
|
||||
function feeds_source_clear(array $job) {
|
||||
$source = _feeds_queue_worker_helper($job, 'clear');
|
||||
$source->scheduleClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler callback for expiring content.
|
||||
*/
|
||||
function feeds_importer_expire($job) {
|
||||
$importer = feeds_importer($job['type']);
|
||||
function feeds_source_expire(array $job) {
|
||||
$source = _feeds_queue_worker_helper($job, 'expire');
|
||||
$source->scheduleExpire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a method on a feed source.
|
||||
*
|
||||
* @param array $job
|
||||
* The job being run.
|
||||
* @param string $method
|
||||
* The method to execute.
|
||||
*/
|
||||
function _feeds_queue_worker_helper(array $job, $method) {
|
||||
$source = feeds_source($job['type'], $job['id']);
|
||||
try {
|
||||
$importer->existing()->expire();
|
||||
$source->existing()->$method();
|
||||
}
|
||||
catch (FeedsNotExistingException $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$importer->log('expire', $e->getMessage(), array(), WATCHDOG_ERROR);
|
||||
$source->log($method, $e->getMessage(), array(), WATCHDOG_ERROR);
|
||||
}
|
||||
$importer->scheduleExpire();
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,28 +198,36 @@ function feeds_batch($method, $importer_id, $feed_nid = 0, &$context) {
|
||||
/**
|
||||
* Reschedule one or all importers.
|
||||
*
|
||||
* @param $importer_id
|
||||
* @param string $importer_id
|
||||
* If TRUE, all importers will be rescheduled, if FALSE, no importers will
|
||||
* be rescheduled, if an importer id, only importer of that id will be
|
||||
* rescheduled.
|
||||
*
|
||||
* @return
|
||||
* TRUE if all importers need rescheduling. FALSE if no rescheduling is
|
||||
* required. An array of importers that need rescheduling.
|
||||
* @return array
|
||||
* An list of importers that need rescheduling.
|
||||
*/
|
||||
function feeds_reschedule($importer_id = NULL) {
|
||||
$reschedule = variable_get('feeds_reschedule', FALSE);
|
||||
|
||||
if ($importer_id === TRUE || $importer_id === FALSE) {
|
||||
$reschedule = $importer_id;
|
||||
}
|
||||
elseif (is_string($importer_id) && $reschedule !== TRUE) {
|
||||
$reschedule = is_array($reschedule) ? $reschedule : array();
|
||||
$reschedule = array_filter((array) $reschedule);
|
||||
$reschedule[$importer_id] = $importer_id;
|
||||
}
|
||||
variable_set('feeds_reschedule', $reschedule);
|
||||
|
||||
if (isset($importer_id)) {
|
||||
variable_set('feeds_reschedule', $reschedule);
|
||||
}
|
||||
|
||||
if ($reschedule === TRUE) {
|
||||
return feeds_enabled_importers();
|
||||
}
|
||||
elseif ($reschedule === FALSE) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $reschedule;
|
||||
}
|
||||
|
||||
@@ -269,7 +282,7 @@ function feeds_menu() {
|
||||
'access callback' => 'feeds_page_access',
|
||||
'file' => 'feeds.pages.inc',
|
||||
);
|
||||
$items['import/%'] = array(
|
||||
$items['import/%feeds_importer'] = array(
|
||||
'title callback' => 'feeds_importer_title',
|
||||
'title arguments' => array(1),
|
||||
'page callback' => 'drupal_get_form',
|
||||
@@ -278,12 +291,12 @@ function feeds_menu() {
|
||||
'access arguments' => array('import', 1),
|
||||
'file' => 'feeds.pages.inc',
|
||||
);
|
||||
$items['import/%/import'] = array(
|
||||
$items['import/%feeds_importer/import'] = array(
|
||||
'title' => 'Import',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'weight' => -10,
|
||||
);
|
||||
$items['import/%/delete-items'] = array(
|
||||
$items['import/%feeds_importer/delete-items'] = array(
|
||||
'title' => 'Delete items',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('feeds_delete_tab_form', 1),
|
||||
@@ -292,7 +305,7 @@ function feeds_menu() {
|
||||
'file' => 'feeds.pages.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
);
|
||||
$items['import/%/unlock'] = array(
|
||||
$items['import/%feeds_importer/unlock'] = array(
|
||||
'title' => 'Unlock',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('feeds_unlock_tab_form', 1),
|
||||
@@ -301,7 +314,7 @@ function feeds_menu() {
|
||||
'file' => 'feeds.pages.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
);
|
||||
$items['import/%/template'] = array(
|
||||
$items['import/%feeds_importer/template'] = array(
|
||||
'page callback' => 'feeds_importer_template',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => 'feeds_access',
|
||||
@@ -347,18 +360,37 @@ function feeds_menu() {
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_admin_paths().
|
||||
*/
|
||||
function feeds_admin_paths() {
|
||||
$paths = array(
|
||||
'import' => TRUE,
|
||||
'import/*' => TRUE,
|
||||
'node/*/import' => TRUE,
|
||||
'node/*/delete-items' => TRUE,
|
||||
'node/*/log' => TRUE,
|
||||
);
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu loader callback.
|
||||
*/
|
||||
function feeds_importer_load($id) {
|
||||
return feeds_importer($id);
|
||||
try {
|
||||
return feeds_importer($id)->existing();
|
||||
}
|
||||
catch (FeedsNotExistingException $e) {}
|
||||
catch (InvalidArgumentException $e) {}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title callback.
|
||||
*/
|
||||
function feeds_importer_title($id) {
|
||||
$importer = feeds_importer($id);
|
||||
function feeds_importer_title(FeedsImporter $importer) {
|
||||
return $importer->config['name'];
|
||||
}
|
||||
|
||||
@@ -400,9 +432,13 @@ function feeds_access($action, $param) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$importer_id = FALSE;
|
||||
if (is_string($param)) {
|
||||
$importer_id = $param;
|
||||
}
|
||||
elseif ($param instanceof FeedsImporter) {
|
||||
$importer_id = $param->id;
|
||||
}
|
||||
elseif ($param->type) {
|
||||
$importer_id = feeds_get_importer_id($param->type);
|
||||
}
|
||||
@@ -416,6 +452,16 @@ function feeds_access($action, $param) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback to determine if the user can import Feeds importers.
|
||||
*
|
||||
* Feeds imports require an additional access check because they are PHP
|
||||
* code and PHP is more locked down than administer feeds.
|
||||
*/
|
||||
function feeds_importer_import_access() {
|
||||
return user_access('administer feeds') && user_access('use PHP for settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu access callback.
|
||||
*/
|
||||
@@ -588,6 +634,11 @@ function feeds_node_presave($node) {
|
||||
}
|
||||
$last_title = NULL;
|
||||
$last_feeds = NULL;
|
||||
|
||||
// Update "changed" value if there was mapped to that.
|
||||
if (isset($node->feeds_item->node_changed)) {
|
||||
$node->changed = $node->feeds_item->node_changed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -602,9 +653,8 @@ function feeds_node_insert($node) {
|
||||
if (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) {
|
||||
$source->startImport();
|
||||
}
|
||||
// Schedule source and importer.
|
||||
// Schedule the source.
|
||||
$source->schedule();
|
||||
feeds_importer($importer_id)->schedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,6 +726,125 @@ function feeds_field_extra_fields() {
|
||||
return $extras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_pipe_COMPONENT_alter() for component "feeds_importer".
|
||||
*
|
||||
* Automatically adds dependencies when a Feed importer is selected in Features.
|
||||
*/
|
||||
function feeds_features_pipe_feeds_importer_alter(&$pipe, $data, &$export) {
|
||||
foreach ($data as $importer_id) {
|
||||
if ($importer = feeds_importer_load($importer_id)) {
|
||||
$export['dependencies'] = array_merge($export['dependencies'], $importer->dependencies());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_system_info_alter().
|
||||
*
|
||||
* Goes through a list of all modules that provide Feeds plugins and makes them
|
||||
* required if there are any importers using those plugins.
|
||||
*/
|
||||
function feeds_system_info_alter(array &$info, $file, $type) {
|
||||
if ($type !== 'module' || !module_hook($file->name, 'feeds_plugins')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't make Feeds require itself, otherwise you can't disable Feeds until
|
||||
// all importers are deleted.
|
||||
if ($file->name === 'feeds' || !function_exists('ctools_include')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the plugins that belong to the current module.
|
||||
ctools_include('plugins');
|
||||
$module_plugins = array();
|
||||
foreach (ctools_get_plugins('feeds', 'plugins') as $plugin_id => $plugin) {
|
||||
if ($file->name === $plugin['module']) {
|
||||
$module_plugins[$plugin_id] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any importers are using any plugins from the current module.
|
||||
foreach (feeds_importer_load_all(TRUE) as $importer) {
|
||||
|
||||
// Skip importers that are defined in code and are provided by the current
|
||||
// module. This ensures that modules that define both an importer and a
|
||||
// plugin can still be disabled.
|
||||
if ($importer->export_type == EXPORT_IN_CODE) {
|
||||
$configs = ctools_export_load_object('feeds_importer', 'names', array($importer->id));
|
||||
if (isset($configs[$importer->id]) && $configs[$importer->id]->export_module === $file->name) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$configuration = $importer->getConfig();
|
||||
|
||||
foreach (array('fetcher', 'parser', 'processor') as $plugin_type) {
|
||||
$plugin_key = $configuration[$plugin_type]['plugin_key'];
|
||||
if (isset($module_plugins[$plugin_key])) {
|
||||
$info['required'] = TRUE;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($info['required'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (module_exists('feeds_ui') && user_access('administer feeds')) {
|
||||
$info['explanation'] = t('Feeds is currently using this module for one or more <a href="@link">importers</a>', array('@link' => url('admin/structure/feeds')));
|
||||
}
|
||||
else {
|
||||
$info['explanation'] = t('Feeds is currently using this module for one or more importers');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_module_implements_alter().
|
||||
*/
|
||||
function feeds_module_implements_alter(array &$implementations, $hook) {
|
||||
if ($hook === 'feeds_processor_targets_alter') {
|
||||
// We need two implementations of this hook, so we add one that gets
|
||||
// called first, and move the normal one to last.
|
||||
$implementations = array('_feeds' => FALSE) + $implementations;
|
||||
|
||||
// Move normal implementation to last.
|
||||
$group = $implementations['feeds'];
|
||||
unset($implementations['feeds']);
|
||||
$implementations['feeds'] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see feeds_feeds_processor_targets()
|
||||
* @see feeds_feeds_processor_targets_alter()
|
||||
*/
|
||||
function _feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) {
|
||||
// If hook_feeds_processor_targets() hasn't been called, for instance, by
|
||||
// older processors, invoke it ourself.
|
||||
if (!drupal_static('feeds_feeds_processor_targets', FALSE)) {
|
||||
$targets += module_invoke_all('feeds_processor_targets', $entity_type, $bundle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_flush_caches().
|
||||
*/
|
||||
function feeds_flush_caches() {
|
||||
// The update to add the table needs to have run. Taken from
|
||||
// https://www.drupal.org/node/2511858
|
||||
include_once DRUPAL_ROOT . '/includes/install.inc';
|
||||
|
||||
if (drupal_get_installed_schema_version('feeds') >= 7212) {
|
||||
return array('cache_feeds_http');
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
@@ -707,10 +876,28 @@ function feeds_importer_load_all($load_disabled = FALSE) {
|
||||
$feeds[$config->id] = feeds_importer($config->id);
|
||||
}
|
||||
}
|
||||
uasort($feeds, 'feeds_importer_name_sort');
|
||||
}
|
||||
return $feeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts importers by name.
|
||||
*
|
||||
* Callback for uasort().
|
||||
*
|
||||
* @param FeedsImporter $a
|
||||
* The first FeedsImporter for comparison.
|
||||
* @param FeedsImporter $b
|
||||
* The second FeedsImporter for comparison.
|
||||
*
|
||||
* @return int
|
||||
* The comparison result for uasort().
|
||||
*/
|
||||
function feeds_importer_name_sort(FeedsImporter $a, FeedsImporter $b) {
|
||||
return strcasecmp($a->config['name'], $b->config['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of enabled importer ids.
|
||||
*
|
||||
@@ -762,6 +949,7 @@ function _feeds_importer_digest() {
|
||||
function feeds_cache_clear($rebuild_menu = TRUE) {
|
||||
cache_clear_all('_feeds_importer_digest', 'cache');
|
||||
drupal_static_reset('_feeds_importer_digest');
|
||||
cache_clear_all('plugins:feeds:plugins', 'cache');
|
||||
ctools_include('export');
|
||||
ctools_export_load_object_reset('feeds_importer');
|
||||
drupal_static_reset('_node_types_build');
|
||||
@@ -902,22 +1090,21 @@ function feeds_source($importer_id, $feed_nid = 0) {
|
||||
/**
|
||||
* Gets an instance of a class for a given plugin and id.
|
||||
*
|
||||
* @param $plugin
|
||||
* @param string $plugin
|
||||
* A string that is the key of the plugin to load.
|
||||
* @param $id
|
||||
* @param string $id
|
||||
* A string that is the id of the object.
|
||||
*
|
||||
* @return
|
||||
* @return FeedsPlugin
|
||||
* A FeedsPlugin object.
|
||||
*
|
||||
* @throws Exception
|
||||
* If plugin can't be instantiated.
|
||||
*/
|
||||
function feeds_plugin($plugin, $id) {
|
||||
ctools_include('plugins');
|
||||
|
||||
if ($class = ctools_plugin_load_class('feeds', 'plugins', $plugin, 'handler')) {
|
||||
return FeedsConfigurable::instance($class, $id);
|
||||
return FeedsPlugin::instance($class, $id, ctools_get_plugins('feeds', 'plugins', $plugin));
|
||||
}
|
||||
|
||||
$args = array('%plugin' => $plugin, '@id' => $id);
|
||||
if (user_access('administer feeds')) {
|
||||
$args['@link'] = url('admin/structure/feeds/' . $id);
|
||||
@@ -926,8 +1113,10 @@ function feeds_plugin($plugin, $id) {
|
||||
else {
|
||||
drupal_set_message(t('Missing Feeds plugin %plugin. Please contact your site administrator.', $args), 'warning', FALSE);
|
||||
}
|
||||
|
||||
$class = ctools_plugin_load_class('feeds', 'plugins', 'FeedsMissingPlugin', 'handler');
|
||||
return FeedsConfigurable::instance($class, $id);
|
||||
|
||||
return FeedsPlugin::instance($class, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -942,77 +1131,73 @@ function feeds_plugin($plugin, $id) {
|
||||
/**
|
||||
* Includes a library file.
|
||||
*
|
||||
* @param $file
|
||||
* @param string $file
|
||||
* The filename to load from.
|
||||
* @param $library
|
||||
* @param string $library
|
||||
* The name of the library. If libraries module is installed,
|
||||
* feeds_include_library() will look for libraries with this name managed by
|
||||
* libraries module.
|
||||
*/
|
||||
function feeds_include_library($file, $library) {
|
||||
static $included = array();
|
||||
static $ignore_deprecated = array('simplepie');
|
||||
|
||||
if (!isset($included[$file])) {
|
||||
// Disable deprecated warning for libraries known for throwing them
|
||||
if (in_array($library, $ignore_deprecated)) {
|
||||
$level = error_reporting();
|
||||
// We can safely use E_DEPRECATED since Drupal 7 requires PHP 5.3+
|
||||
error_reporting($level ^ E_DEPRECATED ^ E_STRICT);
|
||||
}
|
||||
$key = $library . '/' . $file;
|
||||
|
||||
if (!isset($included[$key])) {
|
||||
$included[$key] = FALSE;
|
||||
|
||||
$library_dir = variable_get('feeds_library_dir', FALSE);
|
||||
$feeds_library_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file";
|
||||
$libraries_path = module_exists('libraries') ? libraries_get_path($library) : FALSE;
|
||||
|
||||
// Try first whether libraries module is present and load the file from
|
||||
// there. If this fails, require the library from the local path.
|
||||
if (module_exists('libraries') && file_exists(libraries_get_path($library) . "/$file")) {
|
||||
require libraries_get_path($library) . "/$file";
|
||||
$included[$file] = TRUE;
|
||||
if ($libraries_path && is_file("$libraries_path/$file")) {
|
||||
require "$libraries_path/$file";
|
||||
$included[$key] = TRUE;
|
||||
}
|
||||
elseif ($library_dir && file_exists("$library_dir/$library/$file")) {
|
||||
require "$library_dir/$library/$file";
|
||||
$included[$file] = TRUE;
|
||||
elseif (is_file(DRUPAL_ROOT . '/sites/all/libraries/' . $key)) {
|
||||
require DRUPAL_ROOT . '/sites/all/libraries/' . $key;
|
||||
$included[$key] = TRUE;
|
||||
}
|
||||
elseif (file_exists($feeds_library_path)) {
|
||||
elseif ($library_dir && is_file($library_dir . '/' . $key)) {
|
||||
require $library_dir . '/' . $key;
|
||||
$included[$key] = TRUE;
|
||||
}
|
||||
elseif (is_file($feeds_library_path)) {
|
||||
// @todo: Throws "Deprecated function: Assigning the return value of new
|
||||
// by reference is deprecated."
|
||||
require $feeds_library_path;
|
||||
$included[$file] = TRUE;
|
||||
}
|
||||
// Restore error reporting level
|
||||
if (isset($level)) {
|
||||
error_reporting($level);
|
||||
$included[$key] = TRUE;
|
||||
}
|
||||
}
|
||||
if (isset($included[$file])) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
return $included[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a library is present.
|
||||
*
|
||||
* @param $file
|
||||
* @param string $file
|
||||
* The filename to load from.
|
||||
* @param $library
|
||||
* @param string $library
|
||||
* The name of the library. If libraries module is installed,
|
||||
* feeds_library_exists() will look for libraries with this name managed by
|
||||
* libraries module.
|
||||
*/
|
||||
function feeds_library_exists($file, $library) {
|
||||
|
||||
if (module_exists('libraries') && file_exists(libraries_get_path($library) . "/$file")) {
|
||||
$path = module_exists('libraries') ? libraries_get_path($library) : FALSE;
|
||||
if ($path && is_file($path . '/' . $file)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
elseif (file_exists(DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file")) {
|
||||
elseif (is_file(DRUPAL_ROOT . "/sites/all/libraries/$library/$file")) {
|
||||
return TRUE;
|
||||
}
|
||||
elseif (is_file(DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file")) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
elseif ($library_dir = variable_get('feeds_library_dir', FALSE)) {
|
||||
if (file_exists("$library_dir/$library/$file")) {
|
||||
if (is_file("$library_dir/$library/$file")) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -1024,7 +1209,9 @@ function feeds_library_exists($file, $library) {
|
||||
* Checks whether simplepie exists.
|
||||
*/
|
||||
function feeds_simplepie_exists() {
|
||||
return (feeds_library_exists('simplepie.compiled.php', 'simplepie') ||
|
||||
return (
|
||||
feeds_library_exists('autoloader.php', 'simplepie') ||
|
||||
feeds_library_exists('simplepie.compiled.php', 'simplepie') ||
|
||||
feeds_library_exists('simplepie.mini.php', 'simplepie') ||
|
||||
feeds_library_exists('simplepie.inc', 'simplepie')
|
||||
);
|
||||
@@ -1034,13 +1221,19 @@ function feeds_simplepie_exists() {
|
||||
* Includes the simplepie library.
|
||||
*/
|
||||
function feeds_include_simplepie() {
|
||||
$files = array('simplepie.mini.php', 'simplepie.compiled.php', 'simplepie.inc');
|
||||
$files = array(
|
||||
'autoloader.php',
|
||||
'simplepie.mini.php',
|
||||
'simplepie.compiled.php',
|
||||
'simplepie.inc',
|
||||
);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (feeds_include_library($file, 'simplepie')) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -1142,26 +1335,20 @@ function feeds_get_subscription_jobs() {
|
||||
* Implements hook_entity_property_info_alter().
|
||||
*/
|
||||
function feeds_entity_property_info_alter(&$info) {
|
||||
// Gather entities supported by Feeds processors.
|
||||
$processors = FeedsPlugin::byType('processor');
|
||||
$supported_entities = array();
|
||||
foreach ($processors as $processor) {
|
||||
$instance = feeds_plugin($processor['handler']['class'], '__none__');
|
||||
if (method_exists($instance, 'entityType')) {
|
||||
$supported_entities[] = $instance->entityType();
|
||||
}
|
||||
}
|
||||
// Feeds processors can fake the entity info. Only set the property for
|
||||
// defined entities.
|
||||
$supported_entities = array_intersect(array_keys($info), $supported_entities);
|
||||
|
||||
foreach ($supported_entities as $entity_type) {
|
||||
foreach ($info as $entity_type => $entity_info) {
|
||||
$info[$entity_type]['properties']['feed_nid'] = array(
|
||||
'label' => 'Feed NID',
|
||||
'type' => 'integer',
|
||||
'description' => t('Nid of the Feed Node that imported this entity.'),
|
||||
'getter callback' => 'feeds_get_feed_nid_entity_callback',
|
||||
);
|
||||
$info[$entity_type]['properties']['feed_node'] = array(
|
||||
'label' => 'Feed node',
|
||||
'type' => 'node',
|
||||
'description' => t('Feed Node that imported this entity.'),
|
||||
'getter callback' => 'feeds_get_feed_nid_entity_callback',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1171,10 +1358,65 @@ function feeds_entity_property_info_alter(&$info) {
|
||||
function feeds_get_feed_nid_entity_callback($entity, array $options, $name, $entity_type) {
|
||||
list($entity_id, , ) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
$feed_nid = feeds_get_feed_nid($entity_id, $entity_type);
|
||||
|
||||
if ($feed_nid === FALSE) {
|
||||
return NULL;
|
||||
$feed_nid = NULL;
|
||||
if ($entity_id) {
|
||||
$feed_nid = feeds_get_feed_nid($entity_id, $entity_type);
|
||||
if ($feed_nid === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// If the entity has no ID (yet) try read the feed nid from the object
|
||||
// directly.
|
||||
elseif (isset($entity->feeds_item->feed_nid)) {
|
||||
$feed_nid = $entity->feeds_item->feed_nid;
|
||||
}
|
||||
return $feed_nid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_download().
|
||||
*/
|
||||
function feeds_file_download($uri) {
|
||||
$id = db_query("SELECT id FROM {feeds_source} WHERE source = :uri", array(':uri' => $uri))->fetchField();
|
||||
|
||||
if (!$id) {
|
||||
// File is not associated with a feed.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the file record based on the URI. If not in the database just return.
|
||||
$files = file_load_multiple(array(), array('uri' => $uri));
|
||||
foreach ($files as $item) {
|
||||
// Since some database servers sometimes use a case-insensitive comparison
|
||||
// by default, double check that the filename is an exact match.
|
||||
if ($item->uri === $uri) {
|
||||
$file = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isset($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this file belongs to Feeds.
|
||||
$usage_list = file_usage_list($file);
|
||||
if (!isset($usage_list['feeds'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!feeds_access('import', $id)) {
|
||||
// User does not have permission to import this feed.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return file headers.
|
||||
return file_get_content_headers($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds API version.
|
||||
*/
|
||||
function feeds_api_version() {
|
||||
$version = feeds_ctools_plugin_api('feeds', 'plugins');
|
||||
return $version['version'];
|
||||
}
|
||||
|
@@ -36,7 +36,22 @@ function feeds_page() {
|
||||
}
|
||||
}
|
||||
if (empty($rows)) {
|
||||
drupal_set_message(t('There are no importers, go to <a href="@importers">Feed importers</a> to create one or enable an existing one.', array('@importers' => url('admin/structure/feeds'))));
|
||||
// The feeds_ui module is enabled.
|
||||
if (module_exists('feeds_ui') && user_access('administer feeds')) {
|
||||
drupal_set_message(t('There are no importers, go to <a href="@importers">Feed importers</a> to create one or enable an existing one.', array('@importers' => url('admin/structure/feeds'))));
|
||||
}
|
||||
else {
|
||||
// The feeds_ui module is not enabled but the current user has access to
|
||||
// Modules to enable it.
|
||||
if (user_access('administer modules')) {
|
||||
drupal_set_message(t('The Feeds UI Admin module is not enabled and there are no importers, go to <a href="@modules">Modules</a> and enable Feeds Admin UI. Then go to <a href="@importers">Feed importers</a> to create one or enable an existing one.', array('@modules' => url('admin/modules'), '@importers' => url('admin/structure/feeds'))));
|
||||
}
|
||||
else {
|
||||
// The feeds_ui module is not enabled and the current user cannot
|
||||
// enable it.
|
||||
drupal_set_message(t("The Feeds UI Admin module is not enabled. Please contact the Administrator for your site and ask them to enable it."));
|
||||
}
|
||||
}
|
||||
}
|
||||
$header = array(
|
||||
t('Import'),
|
||||
@@ -48,11 +63,10 @@ function feeds_page() {
|
||||
/**
|
||||
* Render a feeds import form on import/[config] pages.
|
||||
*/
|
||||
function feeds_import_form($form, &$form_state, $importer_id) {
|
||||
$source = feeds_source($importer_id, empty($form['nid']['#value']) ? 0 : $form['nid']['#value']);
|
||||
function feeds_import_form(array $form, array &$form_state, FeedsImporter $importer) {
|
||||
$source = feeds_source($importer->id);
|
||||
|
||||
$form = array();
|
||||
$form['#importer_id'] = $importer_id;
|
||||
$form['#importer_id'] = $importer->id;
|
||||
// @todo Move this into fetcher?
|
||||
$form['#attributes']['enctype'] = 'multipart/form-data';
|
||||
$form['source_status'] = array(
|
||||
@@ -111,7 +125,6 @@ function feeds_import_form_submit($form, &$form_state) {
|
||||
|
||||
// Add to schedule, make sure importer is scheduled, too.
|
||||
$source->schedule();
|
||||
$source->importer->schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,9 +168,9 @@ function feeds_import_tab_form_submit($form, &$form_state) {
|
||||
* Used on both node pages and configuration pages.
|
||||
* Therefore $node may be missing.
|
||||
*/
|
||||
function feeds_delete_tab_form($form, &$form_state, $importer_id, $node = NULL) {
|
||||
function feeds_delete_tab_form(array $form, array &$form_state, FeedsImporter $importer = NULL, $node = NULL) {
|
||||
if (empty($node)) {
|
||||
$source = feeds_source($importer_id);
|
||||
$source = feeds_source($importer->id);
|
||||
$form['#redirect'] = 'import/' . $source->id;
|
||||
}
|
||||
else {
|
||||
@@ -199,9 +212,9 @@ function feeds_delete_tab_form_submit($form, &$form_state) {
|
||||
* Used on both node pages and configuration pages.
|
||||
* Therefore $node may be missing.
|
||||
*/
|
||||
function feeds_unlock_tab_form($form, &$form_state, $importer_id, $node = NULL) {
|
||||
function feeds_unlock_tab_form($form, &$form_state, FeedsImporter $importer = NULL, $node = NULL) {
|
||||
if (empty($node)) {
|
||||
$source = feeds_source($importer_id);
|
||||
$source = feeds_source($importer->id);
|
||||
$form['#redirect'] = 'import/' . $source->id;
|
||||
}
|
||||
else {
|
||||
@@ -270,8 +283,7 @@ function feeds_fetcher_callback($importer, $feed_nid = 0) {
|
||||
/**
|
||||
* Template generation
|
||||
*/
|
||||
function feeds_importer_template($importer_id) {
|
||||
$importer = feeds_importer($importer_id);
|
||||
function feeds_importer_template(FeedsImporter $importer) {
|
||||
if ($importer->parser instanceof FeedsCSVParser) {
|
||||
return $importer->parser->getTemplate();
|
||||
}
|
||||
@@ -335,28 +347,33 @@ function theme_feeds_upload($variables) {
|
||||
$element = $variables['element'];
|
||||
drupal_add_css(drupal_get_path('module', 'feeds') . '/feeds.css');
|
||||
_form_set_class($element, array('form-file'));
|
||||
$description = '';
|
||||
$summary = '';
|
||||
if (!empty($element['#file_info'])) {
|
||||
$file = $element['#file_info'];
|
||||
$wrapper = file_stream_wrapper_get_instance_by_uri($file->uri);
|
||||
$description .= '<div class="file-info">';
|
||||
$description .= '<div class="file-name">';
|
||||
$description .= l($file->filename, $wrapper->getExternalUrl());
|
||||
$description .= '</div>';
|
||||
$description .= '<div class="file-size">';
|
||||
$description .= format_size($file->filesize);
|
||||
$description .= '</div>';
|
||||
$description .= '<div class="file-mime">';
|
||||
$description .= check_plain($file->filemime);
|
||||
$description .= '</div>';
|
||||
$description .= '</div>';
|
||||
$summary .= '<div class="feeds-file-info">';
|
||||
$summary .= '<div class="feeds-file-name">';
|
||||
if ($wrapper) {
|
||||
$summary .= l($file->filename, $wrapper->getExternalUrl());
|
||||
}
|
||||
else {
|
||||
$summary .= t('URI scheme %scheme not available.', array('%scheme' => file_uri_scheme($uri)));
|
||||
}
|
||||
$summary .= '</div>';
|
||||
$summary .= '<div class="file-size">';
|
||||
$summary .= format_size($file->filesize);
|
||||
$summary .= '</div>';
|
||||
$summary .= '<div class="feeds-file-mime">';
|
||||
$summary .= check_plain($file->filemime);
|
||||
$summary .= '</div>';
|
||||
$summary .= '</div>';
|
||||
}
|
||||
$description .= '<div class="file-upload">';
|
||||
$description .= '<input type="file" name="' . $element['#name'] . '"' . ($element['#attributes'] ? ' ' . drupal_attributes($element['#attributes']) : '') . ' id="' . $element['#id'] . '" size="' . $element['#size'] . "\" />\n";
|
||||
$description .= '</div>';
|
||||
$element['#description'] = $description;
|
||||
// Prepend the summary to the form field.
|
||||
$element['#children'] = '<div class="feeds-file">' . $summary . '<div class="feeds-file-upload">' . $element['#children'];
|
||||
// Render file upload field using theme_form_element().
|
||||
$output = theme('form_element', $element);
|
||||
// Close "feeds-file" and "feeds-file-upload" divs.
|
||||
$output .= '</div></div>';
|
||||
|
||||
// For some reason not unsetting #title leads to printing the title twice.
|
||||
unset($element['#title']);
|
||||
return theme('form_element', $element);
|
||||
return $output;
|
||||
}
|
||||
|
@@ -21,7 +21,8 @@ function _feeds_feeds_plugins() {
|
||||
),
|
||||
);
|
||||
$info['FeedsMissingPlugin'] = array(
|
||||
'hidden' => TRUE,
|
||||
'name' => 'Missing plugin',
|
||||
'description' => 'There is a problem with your configuration.',
|
||||
'handler' => array(
|
||||
'class' => 'FeedsMissingPlugin',
|
||||
'file' => 'FeedsPlugin.inc',
|
||||
@@ -85,17 +86,19 @@ function _feeds_feeds_plugins() {
|
||||
'path' => $path,
|
||||
),
|
||||
);
|
||||
$info['FeedsSyndicationParser'] = array(
|
||||
'name' => 'Common syndication parser',
|
||||
'description' => 'Parse RSS and Atom feeds.',
|
||||
'help' => 'Parse XML feeds in RSS 1, RSS 2 and Atom format.',
|
||||
'handler' => array(
|
||||
'parent' => 'FeedsParser',
|
||||
'class' => 'FeedsSyndicationParser',
|
||||
'file' => 'FeedsSyndicationParser.inc',
|
||||
'path' => $path,
|
||||
),
|
||||
);
|
||||
if (extension_loaded('SimpleXML')) {
|
||||
$info['FeedsSyndicationParser'] = array(
|
||||
'name' => 'Common syndication parser',
|
||||
'description' => 'Parse RSS and Atom feeds.',
|
||||
'help' => 'Parse XML feeds in RSS 1, RSS 2 and Atom format.',
|
||||
'handler' => array(
|
||||
'parent' => 'FeedsParser',
|
||||
'class' => 'FeedsSyndicationParser',
|
||||
'file' => 'FeedsSyndicationParser.inc',
|
||||
'path' => $path,
|
||||
),
|
||||
);
|
||||
}
|
||||
$info['FeedsOPMLParser'] = array(
|
||||
'name' => 'OPML parser',
|
||||
'description' => 'Parse OPML files.',
|
||||
@@ -164,5 +167,6 @@ function _feeds_feeds_plugins() {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
@@ -9,9 +9,21 @@
|
||||
* Implements hook_rules_event_info().
|
||||
*/
|
||||
function feeds_rules_event_info() {
|
||||
$info = array();
|
||||
// General events definitions.
|
||||
$info = array(
|
||||
'feeds_before_import' => array(
|
||||
'label' => t('Before importing feed'),
|
||||
'group' => t('Feeds'),
|
||||
'variables' => array('source' => array('type' => 'feeds_source', 'label' => 'Feeds source')),
|
||||
),
|
||||
'feeds_after_import' => array(
|
||||
'label' => t('After importing feed'),
|
||||
'group' => t('Feeds'),
|
||||
'variables' => array('source' => array('type' => 'feeds_source', 'label' => 'Feeds source')),
|
||||
),
|
||||
);
|
||||
// Per importer events definitions.
|
||||
$entity_info = entity_get_info();
|
||||
|
||||
foreach (feeds_importer_load_all() as $importer) {
|
||||
$config = $importer->getConfig();
|
||||
$processor = feeds_plugin($config['processor']['plugin_key'], $importer->id);
|
||||
@@ -40,8 +52,7 @@ function feeds_rules_event_info() {
|
||||
);
|
||||
// Add bundle information if the node processor is used.
|
||||
if ($processor instanceof FeedsNodeProcessor) {
|
||||
$config = $processor->getConfig();
|
||||
$info['feeds_import_'. $importer->id]['variables'][$entity_type]['bundle'] = $config['content_type'];
|
||||
$info['feeds_import_'. $importer->id]['variables'][$entity_type]['bundle'] = $processor->bundle();
|
||||
}
|
||||
}
|
||||
return $info;
|
||||
@@ -64,6 +75,32 @@ function feeds_rules_action_info() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_rules_data_info().
|
||||
*/
|
||||
function feeds_rules_data_info() {
|
||||
return array(
|
||||
'feeds_source' => array(
|
||||
'label' => t('Feeds source'),
|
||||
'group' => t('Feeds'),
|
||||
'wrap' => TRUE,
|
||||
'property info' => array(
|
||||
'id' => array(
|
||||
'label' => t('ID'),
|
||||
'type' => 'text',
|
||||
'description' => t("The machine readable name of the source importer."),
|
||||
),
|
||||
'imported' => array(
|
||||
'label' => t('Date imported'),
|
||||
'type' => 'date',
|
||||
'description' => t("The date the source was last imported."),
|
||||
),
|
||||
// @TODO: fetcher, parser, state ...
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark feeds import item as skipped.
|
||||
*/
|
||||
|
@@ -25,7 +25,7 @@ function feeds_token_info() {
|
||||
function feeds_tokens($type, $tokens, array $data, array $options) {
|
||||
$replacements = array();
|
||||
|
||||
if ($type == 'node' && !empty($data['node'])) {
|
||||
if ($type == 'node' && !empty($data['node']) && !empty($data['node']->nid)) {
|
||||
|
||||
$sanitize = !empty($options['sanitize']);
|
||||
|
||||
|
@@ -7,8 +7,7 @@
|
||||
/**
|
||||
* Implements hook_ctools_plugin_api().
|
||||
*/
|
||||
function feeds_import_ctools_plugin_api() {
|
||||
list($module, $api) = func_get_args();
|
||||
function feeds_import_ctools_plugin_api($module = NULL, $api = NULL) {
|
||||
if ($module == "feeds" && $api == "feeds_importer_default") {
|
||||
return array("version" => "1");
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ function feeds_import_feeds_importer_default() {
|
||||
'processor' => array(
|
||||
'plugin_key' => 'FeedsNodeProcessor',
|
||||
'config' => array(
|
||||
'content_type' => 'article',
|
||||
'bundle' => 'article',
|
||||
'update_existing' => 1,
|
||||
'expire' => '-1',
|
||||
'mappings' => array(
|
||||
@@ -91,8 +91,9 @@ function feeds_import_feeds_importer_default() {
|
||||
'processor' => array(
|
||||
'plugin_key' => 'FeedsUserProcessor',
|
||||
'config' => array(
|
||||
'bundle' => 'user',
|
||||
'roles' => array(),
|
||||
'update_existing' => FALSE,
|
||||
'update_existing' => 0,
|
||||
'status' => 1,
|
||||
'mappings' => array(
|
||||
0 => array(
|
||||
|
@@ -2,13 +2,17 @@ name = Feeds Import
|
||||
description = An example of a node importer and a user importer.
|
||||
core = 7.x
|
||||
package = Feeds
|
||||
php = 5.2.4
|
||||
version = 7.x-2.0-alpha7
|
||||
project = feeds
|
||||
dependencies[] = ctools
|
||||
dependencies[] = feeds
|
||||
datestamp = 1351111319
|
||||
features[ctools][] = feeds:feeds_importer_default:1
|
||||
features[features_api][] = api:1
|
||||
features[features_api][] = api:2
|
||||
features[feeds_importer][] = node
|
||||
features[feeds_importer][] = user
|
||||
files[] = feeds_import.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-21
|
||||
version = "7.x-2.0-beta2"
|
||||
core = "7.x"
|
||||
project = "feeds"
|
||||
datestamp = "1456055647"
|
||||
|
||||
|
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* feeds_news.features.field.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_field_default_fields().
|
||||
*/
|
||||
function feeds_news_field_default_fields() {
|
||||
$fields = array();
|
||||
|
||||
// Exported field: 'node-feed_item-field_feed_item_description'
|
||||
$fields['node-feed_item-field_feed_item_description'] = array(
|
||||
'field_config' => array(
|
||||
'active' => '1',
|
||||
'cardinality' => '1',
|
||||
'deleted' => '0',
|
||||
'entity_types' => array(),
|
||||
'field_name' => 'field_feed_item_description',
|
||||
'foreign keys' => array(
|
||||
'format' => array(
|
||||
'columns' => array(
|
||||
'format' => 'format',
|
||||
),
|
||||
'table' => 'filter_format',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'format' => array(
|
||||
0 => 'format',
|
||||
),
|
||||
),
|
||||
'module' => 'text',
|
||||
'settings' => array(),
|
||||
'translatable' => '1',
|
||||
'type' => 'text_with_summary',
|
||||
),
|
||||
'field_instance' => array(
|
||||
'bundle' => 'feed_item',
|
||||
'default_value' => NULL,
|
||||
'deleted' => '0',
|
||||
'description' => '',
|
||||
'display' => array(
|
||||
'default' => array(
|
||||
'label' => 'hidden',
|
||||
'module' => 'text',
|
||||
'settings' => array(),
|
||||
'type' => 'text_default',
|
||||
'weight' => '0',
|
||||
),
|
||||
'full' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'rss' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'teaser' => array(
|
||||
'label' => 'hidden',
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'trim_length' => 600,
|
||||
),
|
||||
'type' => 'text_trimmed',
|
||||
'weight' => '0',
|
||||
),
|
||||
),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_feed_item_description',
|
||||
'label' => 'Description',
|
||||
'required' => 0,
|
||||
'settings' => array(
|
||||
'display_summary' => 0,
|
||||
'text_processing' => '1',
|
||||
'user_register_form' => FALSE,
|
||||
),
|
||||
'widget' => array(
|
||||
'active' => 1,
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'rows' => '20',
|
||||
'summary_rows' => 5,
|
||||
),
|
||||
'type' => 'text_textarea_with_summary',
|
||||
'weight' => '-4',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Translatables
|
||||
// Included for use with string extractors like potx.
|
||||
t('Description');
|
||||
|
||||
return $fields;
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* feeds_news.features.field_base.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_default_field_bases().
|
||||
*/
|
||||
function feeds_news_field_default_field_bases() {
|
||||
$field_bases = array();
|
||||
|
||||
// Exported field_base: 'field_feed_item_description'
|
||||
$field_bases['field_feed_item_description'] = array(
|
||||
'active' => 1,
|
||||
'cardinality' => 1,
|
||||
'deleted' => 0,
|
||||
'entity_types' => array(),
|
||||
'field_name' => 'field_feed_item_description',
|
||||
'foreign keys' => array(
|
||||
'format' => array(
|
||||
'columns' => array(
|
||||
'format' => 'format',
|
||||
),
|
||||
'table' => 'filter_format',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'format' => array(
|
||||
0 => 'format',
|
||||
),
|
||||
),
|
||||
'locked' => 0,
|
||||
'module' => 'text',
|
||||
'settings' => array(),
|
||||
'translatable' => 1,
|
||||
'type' => 'text_with_summary',
|
||||
);
|
||||
|
||||
return $field_bases;
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* feeds_news.features.field_instance.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_default_field_instances().
|
||||
*/
|
||||
function feeds_news_field_default_field_instances() {
|
||||
$field_instances = array();
|
||||
|
||||
// Exported field_instance: 'node-feed_item-field_feed_item_description'
|
||||
$field_instances['node-feed_item-field_feed_item_description'] = array(
|
||||
'bundle' => 'feed_item',
|
||||
'default_value' => NULL,
|
||||
'deleted' => 0,
|
||||
'description' => '',
|
||||
'display' => array(
|
||||
'default' => array(
|
||||
'label' => 'hidden',
|
||||
'module' => 'text',
|
||||
'settings' => array(),
|
||||
'type' => 'text_default',
|
||||
'weight' => 0,
|
||||
),
|
||||
'full' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'rss' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'teaser' => array(
|
||||
'label' => 'hidden',
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'trim_length' => 600,
|
||||
),
|
||||
'type' => 'text_trimmed',
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_feed_item_description',
|
||||
'label' => 'Description',
|
||||
'required' => 0,
|
||||
'settings' => array(
|
||||
'display_summary' => 0,
|
||||
'text_processing' => 1,
|
||||
'user_register_form' => FALSE,
|
||||
),
|
||||
'widget' => array(
|
||||
'active' => 1,
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'rows' => 20,
|
||||
'summary_rows' => 5,
|
||||
),
|
||||
'type' => 'text_textarea_with_summary',
|
||||
'weight' => -4,
|
||||
),
|
||||
);
|
||||
|
||||
// Translatables
|
||||
// Included for use with string extractors like potx.
|
||||
t('Description');
|
||||
|
||||
return $field_instances;
|
||||
}
|
@@ -5,27 +5,23 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_ctools_plugin_api().
|
||||
* Implements hook_ctools_plugin_api().
|
||||
*/
|
||||
function feeds_news_ctools_plugin_api() {
|
||||
list($module, $api) = func_get_args();
|
||||
function feeds_news_ctools_plugin_api($module = NULL, $api = NULL) {
|
||||
if ($module == "feeds" && $api == "feeds_importer_default") {
|
||||
return array("version" => 1);
|
||||
return array("version" => "1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_views_api().
|
||||
* Implements hook_views_api().
|
||||
*/
|
||||
function feeds_news_views_api() {
|
||||
list($module, $api) = func_get_args();
|
||||
if ($module == "views" && $api == "views_default") {
|
||||
return array("version" => 3.0);
|
||||
}
|
||||
function feeds_news_views_api($module = NULL, $api = NULL) {
|
||||
return array("api" => "3.0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_node_info().
|
||||
* Implements hook_node_info().
|
||||
*/
|
||||
function feeds_news_node_info() {
|
||||
$items = array(
|
||||
|
@@ -5,12 +5,12 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_feeds_importer_default().
|
||||
* Implements hook_feeds_importer_default().
|
||||
*/
|
||||
function feeds_news_feeds_importer_default() {
|
||||
$export = array();
|
||||
|
||||
$feeds_importer = new stdClass;
|
||||
$feeds_importer = new stdClass();
|
||||
$feeds_importer->disabled = FALSE; /* Edit this to true to make a default feeds_importer disabled initially */
|
||||
$feeds_importer->api_version = 1;
|
||||
$feeds_importer->id = 'feed';
|
||||
@@ -32,7 +32,7 @@ function feeds_news_feeds_importer_default() {
|
||||
'processor' => array(
|
||||
'plugin_key' => 'FeedsNodeProcessor',
|
||||
'config' => array(
|
||||
'content_type' => 'feed_item',
|
||||
'bundle' => 'feed_item',
|
||||
'update_existing' => '0',
|
||||
'expire' => '-1',
|
||||
'mappings' => array(
|
||||
@@ -75,7 +75,7 @@ function feeds_news_feeds_importer_default() {
|
||||
);
|
||||
$export['feed'] = $feeds_importer;
|
||||
|
||||
$feeds_importer = new stdClass;
|
||||
$feeds_importer = new stdClass();
|
||||
$feeds_importer->disabled = FALSE; /* Edit this to true to make a default feeds_importer disabled initially */
|
||||
$feeds_importer->api_version = 1;
|
||||
$feeds_importer->id = 'opml';
|
||||
@@ -95,7 +95,7 @@ function feeds_news_feeds_importer_default() {
|
||||
'processor' => array(
|
||||
'plugin_key' => 'FeedsNodeProcessor',
|
||||
'config' => array(
|
||||
'content_type' => 'feed',
|
||||
'bundle' => 'feed',
|
||||
'update_existing' => 0,
|
||||
'expire' => '-1',
|
||||
'mappings' => array(
|
||||
|
@@ -1,25 +1,27 @@
|
||||
core = "7.x"
|
||||
dependencies[] = "features"
|
||||
dependencies[] = "feeds"
|
||||
dependencies[] = "views"
|
||||
description = "A news aggregator built with feeds, creates nodes from imported feed items. With OPML import."
|
||||
features[ctools][] = "feeds:feeds_importer_default:1"
|
||||
features[ctools][] = "views:views_default:3.0"
|
||||
features[feeds_importer][] = "feed"
|
||||
features[feeds_importer][] = "opml"
|
||||
features[field][] = "node-feed_item-field_feed_item_description"
|
||||
features[node][] = "feed"
|
||||
features[node][] = "feed_item"
|
||||
features[views_view][] = "feeds_defaults_feed_items"
|
||||
files[] = "feeds_news.module"
|
||||
files[] = "feeds_news.test"
|
||||
name = "Feeds News"
|
||||
package = "Feeds"
|
||||
php = "5.2.4"
|
||||
name = Feeds News
|
||||
description = A news aggregator built with feeds, creates nodes from imported feed items. With OPML import.
|
||||
core = 7.x
|
||||
package = Feeds
|
||||
dependencies[] = ctools
|
||||
dependencies[] = features
|
||||
dependencies[] = feeds
|
||||
dependencies[] = views
|
||||
features[ctools][] = feeds:feeds_importer_default:1
|
||||
features[ctools][] = views:views_default:3.0
|
||||
features[features_api][] = api:2
|
||||
features[feeds_importer][] = feed
|
||||
features[feeds_importer][] = opml
|
||||
features[field_base][] = field_feed_item_description
|
||||
features[field_instance][] = node-feed_item-field_feed_item_description
|
||||
features[node][] = feed
|
||||
features[node][] = feed_item
|
||||
features[views_view][] = feeds_defaults_feed_items
|
||||
files[] = feeds_news.module
|
||||
files[] = feeds_news.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-10-24
|
||||
version = "7.x-2.0-alpha7"
|
||||
; Information added by Drupal.org packaging script on 2016-02-21
|
||||
version = "7.x-2.0-beta2"
|
||||
core = "7.x"
|
||||
project = "feeds"
|
||||
datestamp = "1351111319"
|
||||
datestamp = "1456055647"
|
||||
|
||||
|
@@ -5,23 +5,24 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_views_default_views().
|
||||
* Implements hook_views_default_views().
|
||||
*/
|
||||
function feeds_news_views_default_views() {
|
||||
$export = array();
|
||||
|
||||
$view = new view;
|
||||
$view = new view();
|
||||
$view->name = 'feeds_defaults_feed_items';
|
||||
$view->description = 'Show feed items for a feed node. Use together with default importer configuration "Feed".';
|
||||
$view->tag = 'Feeds defaults';
|
||||
$view->base_table = 'node';
|
||||
$view->human_name = '';
|
||||
$view->core = 0;
|
||||
$view->api_version = '3.0-alpha1';
|
||||
$view->api_version = '3.0';
|
||||
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
|
||||
|
||||
/* Display: Defaults */
|
||||
$handler = $view->new_display('default', 'Defaults', 'default');
|
||||
$handler->display->display_options['use_more_always'] = FALSE;
|
||||
$handler->display->display_options['access']['type'] = 'perm';
|
||||
$handler->display->display_options['cache']['type'] = 'none';
|
||||
$handler->display->display_options['query']['type'] = 'views_query';
|
||||
@@ -30,20 +31,17 @@ function feeds_news_views_default_views() {
|
||||
$handler->display->display_options['pager']['type'] = 'full';
|
||||
$handler->display->display_options['style_plugin'] = 'default';
|
||||
$handler->display->display_options['row_plugin'] = 'node';
|
||||
$handler->display->display_options['row_options']['links'] = 1;
|
||||
$handler->display->display_options['row_options']['comments'] = 0;
|
||||
/* No results behavior: Global: Text area */
|
||||
$handler->display->display_options['empty']['text']['id'] = 'area';
|
||||
$handler->display->display_options['empty']['text']['table'] = 'views';
|
||||
$handler->display->display_options['empty']['text']['field'] = 'area';
|
||||
$handler->display->display_options['empty']['text']['empty'] = FALSE;
|
||||
$handler->display->display_options['empty']['text']['content'] = 'There are no items for this feed at the moment.';
|
||||
$handler->display->display_options['empty']['text']['format'] = '1';
|
||||
/* Relationship: Feeds item: Owner feed */
|
||||
$handler->display->display_options['relationships']['feed_nid_1']['id'] = 'feed_nid_1';
|
||||
$handler->display->display_options['relationships']['feed_nid_1']['table'] = 'feeds_item';
|
||||
$handler->display->display_options['relationships']['feed_nid_1']['field'] = 'feed_nid';
|
||||
$handler->display->display_options['relationships']['feed_nid_1']['required'] = 1;
|
||||
$handler->display->display_options['relationships']['feed_nid_1']['required'] = TRUE;
|
||||
/* Sort criterion: Content: Post date */
|
||||
$handler->display->display_options['sorts']['created']['id'] = 'created';
|
||||
$handler->display->display_options['sorts']['created']['table'] = 'node';
|
||||
@@ -55,17 +53,15 @@ function feeds_news_views_default_views() {
|
||||
$handler->display->display_options['arguments']['nid']['field'] = 'nid';
|
||||
$handler->display->display_options['arguments']['nid']['relationship'] = 'feed_nid_1';
|
||||
$handler->display->display_options['arguments']['nid']['default_action'] = 'not found';
|
||||
$handler->display->display_options['arguments']['nid']['title_enable'] = 1;
|
||||
$handler->display->display_options['arguments']['nid']['title_enable'] = TRUE;
|
||||
$handler->display->display_options['arguments']['nid']['title'] = 'Articles from %1';
|
||||
$handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
|
||||
$handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
|
||||
$handler->display->display_options['arguments']['nid']['specify_validation'] = 1;
|
||||
$handler->display->display_options['arguments']['nid']['specify_validation'] = TRUE;
|
||||
$handler->display->display_options['arguments']['nid']['validate']['type'] = 'node';
|
||||
$handler->display->display_options['arguments']['nid']['validate_options']['types'] = array(
|
||||
'feed' => 'feed',
|
||||
);
|
||||
$handler->display->display_options['arguments']['nid']['break_phrase'] = 0;
|
||||
$handler->display->display_options['arguments']['nid']['not'] = 0;
|
||||
/* Filter criterion: Content: Type */
|
||||
$handler->display->display_options['filters']['type']['id'] = 'type';
|
||||
$handler->display->display_options['filters']['type']['table'] = 'node';
|
||||
@@ -81,23 +77,6 @@ function feeds_news_views_default_views() {
|
||||
$handler->display->display_options['menu']['type'] = 'tab';
|
||||
$handler->display->display_options['menu']['title'] = 'View items';
|
||||
$handler->display->display_options['menu']['weight'] = '-9';
|
||||
$translatables['feeds_defaults_feed_items'] = array(
|
||||
t('Defaults'),
|
||||
t('more'),
|
||||
t('Apply'),
|
||||
t('Reset'),
|
||||
t('Sort by'),
|
||||
t('Asc'),
|
||||
t('Desc'),
|
||||
t('Items per page'),
|
||||
t('- All -'),
|
||||
t('Offset'),
|
||||
t('There are no items for this feed at the moment.'),
|
||||
t('Owner feed'),
|
||||
t('All'),
|
||||
t('Articles from %1'),
|
||||
t('Page'),
|
||||
);
|
||||
$export['feeds_defaults_feed_items'] = $view;
|
||||
|
||||
return $export;
|
||||
|
@@ -93,23 +93,17 @@ function feeds_ui_overview_form($form, &$form_status) {
|
||||
$importer_form['status'] = array(
|
||||
'#markup' => $status,
|
||||
);
|
||||
if (!$importer->disabled) {
|
||||
$importer_form['operations'] = array(
|
||||
'#markup' =>
|
||||
l($edit, 'admin/structure/feeds/' . $importer->id) . ' | ' .
|
||||
l(t('Export'), 'admin/structure/feeds/' . $importer->id . '/export') . ' | ' .
|
||||
l(t('Clone'), 'admin/structure/feeds/' . $importer->id . '/clone') .
|
||||
(empty($delete) ? '' : ' | ' . l($delete, 'admin/structure/feeds/' . $importer->id . '/delete')),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$importer_form['operations']['#markup'] = ' ';
|
||||
}
|
||||
$importer_form['operations'] = array(
|
||||
'#markup' =>
|
||||
l($edit, 'admin/structure/feeds/' . $importer->id) . ' | ' .
|
||||
l(t('Export'), 'admin/structure/feeds/' . $importer->id . '/export') . ' | ' .
|
||||
l(t('Clone'), 'admin/structure/feeds/' . $importer->id . '/clone') .
|
||||
(empty($delete) ? '' : ' | ' . l($delete, 'admin/structure/feeds/' . $importer->id . '/delete')),
|
||||
);
|
||||
|
||||
$importer_form[$importer->id] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !$importer->disabled,
|
||||
'#attributes' => array('class' => array('feeds-ui-trigger-submit')),
|
||||
);
|
||||
|
||||
if ($importer->disabled) {
|
||||
@@ -122,7 +116,6 @@ function feeds_ui_overview_form($form, &$form_status) {
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
'#attributes' => array('class' => array('feeds-ui-hidden-submit')),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
@@ -150,7 +143,6 @@ function feeds_ui_overview_form_submit($form, &$form_state) {
|
||||
* of $from_importer.
|
||||
*/
|
||||
function feeds_ui_create_form($form, &$form_state, $from_importer = NULL) {
|
||||
$form['#attached']['js'][] = drupal_get_path('module', 'feeds_ui') . '/feeds_ui.js';
|
||||
$form['#from_importer'] = $from_importer;
|
||||
$form['name'] = array(
|
||||
'#type' => 'textfield',
|
||||
@@ -281,10 +273,10 @@ function feeds_ui_export_form($form, &$form_state, $importer) {
|
||||
/**
|
||||
* Edit feed configuration.
|
||||
*/
|
||||
function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
|
||||
function feeds_ui_edit_page(FeedsImporter $importer, $active = 'help', $plugin_key = '') {
|
||||
// Get plugins and configuration.
|
||||
$plugins = FeedsPlugin::all();
|
||||
|
||||
$config = $importer->config;
|
||||
// Base path for changing the active container.
|
||||
$path = 'admin/structure/feeds/' . $importer->id;
|
||||
@@ -299,12 +291,14 @@ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
$active_container['body'] = '<div class="help feeds-admin-ui">' . feeds_ui_edit_help() . '</div>';
|
||||
unset($active_container['actions']);
|
||||
break;
|
||||
|
||||
case 'fetcher':
|
||||
case 'parser':
|
||||
case 'processor':
|
||||
$active_container['title'] = t('Select a !plugin_type', array('!plugin_type' => $active));
|
||||
$active_container['body'] = drupal_get_form('feeds_ui_plugin_form', $importer, $active);
|
||||
break;
|
||||
|
||||
case 'settings':
|
||||
drupal_add_js(drupal_get_path('module', 'ctools') . '/js/dependent.js');
|
||||
ctools_include('dependent');
|
||||
@@ -319,8 +313,10 @@ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
$active_container['body'] = feeds_get_form($plugin, 'configForm');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mapping':
|
||||
$active_container['title'] = t('Mapping for !processor', array('!processor' => $plugins[$config['processor']['plugin_key']]['name']));
|
||||
$processor_name = isset($plugins[$config['processor']['plugin_key']]['name']) ? $plugins[$config['processor']['plugin_key']]['name'] : $plugins['FeedsMissingPlugin']['name'];
|
||||
$active_container['title'] = t('Mapping for @processor', array('@processor' => $processor_name));
|
||||
$active_container['body'] = drupal_get_form('feeds_ui_mapping_form', $importer);
|
||||
break;
|
||||
}
|
||||
@@ -354,9 +350,9 @@ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
$config_info[] = $info;
|
||||
|
||||
// Fetcher.
|
||||
$fetcher = $plugins[$config['fetcher']['plugin_key']];
|
||||
$fetcher = isset($plugins[$config['fetcher']['plugin_key']]) ? $plugins[$config['fetcher']['plugin_key']] : $plugins['FeedsMissingPlugin'];
|
||||
$actions = array();
|
||||
if (feeds_get_form($importer->fetcher, 'configForm')) {
|
||||
if ($importer->fetcher->hasConfigForm()) {
|
||||
$actions = array(l(t('Settings'), $path . '/settings/' . $config['fetcher']['plugin_key']));
|
||||
}
|
||||
$info['title'] = t('Fetcher');
|
||||
@@ -371,9 +367,9 @@ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
$config_info[] = $info;
|
||||
|
||||
// Parser.
|
||||
$parser = $plugins[$config['parser']['plugin_key']];
|
||||
$parser = isset($plugins[$config['parser']['plugin_key']]) ? $plugins[$config['parser']['plugin_key']] : $plugins['FeedsMissingPlugin'];
|
||||
$actions = array();
|
||||
if (feeds_get_form($importer->parser, 'configForm')) {
|
||||
if ($importer->parser->hasConfigForm()) {
|
||||
$actions = array(l(t('Settings'), $path . '/settings/' . $config['parser']['plugin_key']));
|
||||
}
|
||||
$info['title'] = t('Parser');
|
||||
@@ -388,9 +384,9 @@ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
$config_info[] = $info;
|
||||
|
||||
// Processor.
|
||||
$processor = $plugins[$config['processor']['plugin_key']];
|
||||
$processor = isset($plugins[$config['processor']['plugin_key']]) ? $plugins[$config['processor']['plugin_key']] : $plugins['FeedsMissingPlugin'];
|
||||
$actions = array();
|
||||
if (feeds_get_form($importer->processor, 'configForm')) {
|
||||
if ($importer->processor->hasConfigForm()) {
|
||||
$actions[] = l(t('Settings'), $path . '/settings/' . $config['processor']['plugin_key']);
|
||||
}
|
||||
$actions[] = l(t('Mapping'), $path . '/mapping');
|
||||
@@ -427,24 +423,25 @@ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') {
|
||||
function feeds_ui_plugin_form($form, &$form_state, $importer, $type) {
|
||||
$plugins = FeedsPlugin::byType($type);
|
||||
|
||||
$form = array();
|
||||
$form['#importer'] = $importer->id;
|
||||
$form['#plugin_type'] = $type;
|
||||
|
||||
$importer_key = $importer->config[$type]['plugin_key'];
|
||||
|
||||
foreach ($plugins as $key => $plugin) {
|
||||
|
||||
$form['plugin_key'][$key] = array(
|
||||
'#type' => 'radio',
|
||||
'#parents' => array('plugin_key'),
|
||||
'#title' => check_plain($plugin['name']),
|
||||
'#description' => filter_xss(isset($plugin['help']) ? $plugin['help'] : $plugin['description']),
|
||||
'#return_value' => $key,
|
||||
'#default_value' => ($plugin['handler']['class'] == get_class($importer->$type)) ? $key : '',
|
||||
'#default_value' => ($key == $importer_key) ? $key : '',
|
||||
);
|
||||
}
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
'#attributes' => array('class' => array('feeds-ui-hidden-submit')),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
@@ -465,7 +462,6 @@ function feeds_ui_plugin_form_submit($form, &$form_state) {
|
||||
*/
|
||||
function theme_feeds_ui_plugin_form($variables) {
|
||||
$form = $variables['form'];
|
||||
drupal_add_js(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.js');
|
||||
$output = '';
|
||||
|
||||
foreach (element_children($form['plugin_key']) as $key) {
|
||||
@@ -495,21 +491,31 @@ function theme_feeds_ui_plugin_form($variables) {
|
||||
* FeedsProcessor, a flag can tell whether mapping is supported or not.
|
||||
*/
|
||||
function feeds_ui_mapping_form($form, &$form_state, $importer) {
|
||||
drupal_add_js(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.js');
|
||||
|
||||
$form = array();
|
||||
$form['#importer'] = $importer->id;
|
||||
$form['#mappings'] = $mappings = $importer->processor->getMappings();
|
||||
$form['help']['#markup'] = feeds_ui_mapping_help();
|
||||
$form['#prefix'] = '<div id="feeds-ui-mapping-form-wrapper">';
|
||||
$form['#suffix'] = '</div>';
|
||||
|
||||
// Show message when target configuration gets changed.
|
||||
if (!empty($form_state['mapping_settings'])) {
|
||||
$form['#mapping_settings'] = $form_state['mapping_settings'];
|
||||
$form['changed'] = array(
|
||||
'#theme_wrappers' => array('container'),
|
||||
'#attributes' => array('class' => array('messages', 'warning')),
|
||||
'#markup' => t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.'),
|
||||
);
|
||||
}
|
||||
|
||||
// Get mapping sources from parsers and targets from processor, format them
|
||||
// for output.
|
||||
// Some parsers do not define mapping sources but let them define on the fly.
|
||||
if ($sources = $importer->parser->getMappingSources()) {
|
||||
$source_options = _feeds_ui_format_options($sources);
|
||||
foreach ($sources as $k => $source) {
|
||||
if (!empty($source['deprecated'])) {
|
||||
continue;
|
||||
}
|
||||
$legend['sources'][$k]['name']['#markup'] = empty($source['name']) ? $k : $source['name'];
|
||||
$legend['sources'][$k]['description']['#markup'] = empty($source['description']) ? '' : $source['description'];
|
||||
}
|
||||
@@ -521,6 +527,9 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) {
|
||||
$target_options = _feeds_ui_format_options($targets);
|
||||
$legend['targets'] = array();
|
||||
foreach ($targets as $k => $target) {
|
||||
if (!empty($target['deprecated'])) {
|
||||
continue;
|
||||
}
|
||||
$legend['targets'][$k]['name']['#markup'] = empty($target['name']) ? $k : $target['name'];
|
||||
$legend['targets'][$k]['description']['#markup'] = empty($target['description']) ? '' : $target['description'];
|
||||
}
|
||||
@@ -572,31 +581,36 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) {
|
||||
if (isset($source_options)) {
|
||||
$form['source'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => array('' => t('Select a source')) + $source_options,
|
||||
'#title' => t('Source'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $source_options,
|
||||
'#empty_option' => t('- Select a source -'),
|
||||
'#description' => t('An element from the feed.'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form['source'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Source'),
|
||||
'#title_display' => 'invisible',
|
||||
'#size' => 20,
|
||||
'#default_value' => t('Name of source field'),
|
||||
'#attributes' => array('class' => array('hide-text-on-focus')),
|
||||
'#default_value' => '',
|
||||
'#description' => t('The name of source field.'),
|
||||
);
|
||||
}
|
||||
$form['target'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => array('' => t('Select a target')) + $target_options,
|
||||
'#title' => t('Target'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $target_options,
|
||||
'#empty_option' => t('- Select a target -'),
|
||||
'#description' => t('The field that stores the data.'),
|
||||
);
|
||||
$form['add'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Add'),
|
||||
'#submit' => array('feeds_ui_mapping_form_add_submit'),
|
||||
'#validate' => array('feeds_ui_mapping_form_add_validate'),
|
||||
);
|
||||
$form['save'] = array(
|
||||
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['save'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
'#attributes' => array('class' => array('feeds-ui-hidden-submit')),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
@@ -626,12 +640,10 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
|
||||
}
|
||||
|
||||
if ($form_state['mapping_settings_edit'] === $i) {
|
||||
// Build the form.
|
||||
if (isset($target['form_callback'])) {
|
||||
$settings_form = call_user_func($target['form_callback'], $mapping, $target, $form, $form_state);
|
||||
}
|
||||
else {
|
||||
$settings_form = array();
|
||||
$settings_form = array();
|
||||
|
||||
foreach ($target['form_callbacks'] as $callback) {
|
||||
$settings_form += call_user_func($callback, $mapping, $target, $form, $form_state);
|
||||
}
|
||||
|
||||
// Merge in the optional unique form.
|
||||
@@ -656,13 +668,15 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
|
||||
}
|
||||
else {
|
||||
// Build the summary.
|
||||
if (isset($target['summary_callback'])) {
|
||||
$summary = call_user_func($target['summary_callback'], $mapping, $target, $form, $form_state);
|
||||
}
|
||||
else {
|
||||
$summary = '';
|
||||
$summary = array();
|
||||
|
||||
foreach ($target['summary_callbacks'] as $callback) {
|
||||
$summary[] = call_user_func($callback, $mapping, $target, $form, $form_state);
|
||||
}
|
||||
|
||||
// Filter out empty summary values.
|
||||
$summary = implode('<br />', array_filter($summary));
|
||||
|
||||
// Append the optional unique summary.
|
||||
if ($optional_unique_summary = feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state)) {
|
||||
$summary .= ' ' . $optional_unique_summary;
|
||||
@@ -685,6 +699,7 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
|
||||
);
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -721,40 +736,36 @@ function feeds_ui_mapping_settings_form_callback($form, $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation for source and target selection.
|
||||
* Validation handler for feeds_ui_mapping_form().
|
||||
*/
|
||||
function feeds_ui_mapping_form_add_validate($form, &$form_state) {
|
||||
if ($form_state['values']['source'] == '') {
|
||||
form_set_error('source', t('You must select a mapping source.'));
|
||||
}
|
||||
if ($form_state['values']['target'] == '') {
|
||||
form_set_error('target', t('You must select a mapping target.'));
|
||||
function feeds_ui_mapping_form_validate($form, &$form_state) {
|
||||
if (!strlen($form_state['values']['source']) xor !strlen($form_state['values']['target'])) {
|
||||
|
||||
// Check triggering_element here so we can react differently for ajax
|
||||
// submissions.
|
||||
switch ($form_state['triggering_element']['#name']) {
|
||||
|
||||
// Regular form submission.
|
||||
case 'op':
|
||||
if (!strlen($form_state['values']['source'])) {
|
||||
form_error($form['source'], t('You must select a mapping source.'));
|
||||
}
|
||||
else {
|
||||
form_error($form['target'], t('You must select a mapping target.'));
|
||||
}
|
||||
break;
|
||||
|
||||
// Be more relaxed on ajax submission.
|
||||
default:
|
||||
form_set_value($form['source'], '', $form_state);
|
||||
form_set_value($form['target'], '', $form_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for add button on feeds_ui_mapping_form().
|
||||
*/
|
||||
function feeds_ui_mapping_form_add_submit($form, &$form_state) {
|
||||
$importer = feeds_importer($form['#importer']);
|
||||
try {
|
||||
$mappings = $form['#mappings'];
|
||||
$mappings[] = array(
|
||||
'source' => $form_state['values']['source'],
|
||||
'target' => $form_state['values']['target'],
|
||||
'unique' => FALSE,
|
||||
);
|
||||
$importer->processor->addConfig(array('mappings' => $mappings));
|
||||
$importer->save();
|
||||
drupal_set_message(t('Mapping has been added.'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
drupal_set_message($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for save button on feeds_ui_mapping_form().
|
||||
* Submission handler for feeds_ui_mapping_form().
|
||||
*/
|
||||
function feeds_ui_mapping_form_submit($form, &$form_state) {
|
||||
$importer = feeds_importer($form['#importer']);
|
||||
@@ -787,6 +798,7 @@ function feeds_ui_mapping_form_submit($form, &$form_state) {
|
||||
foreach ($remove_flags as $k) {
|
||||
unset($mappings[$k]);
|
||||
unset($form_state['values']['mapping_weight'][$k]);
|
||||
drupal_set_message(t('Mapping has been removed.'), 'status', FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,6 +810,23 @@ function feeds_ui_mapping_form_submit($form, &$form_state) {
|
||||
}
|
||||
|
||||
$processor->addConfig(array('mappings' => $mappings));
|
||||
|
||||
if (strlen($form_state['values']['source']) && strlen($form_state['values']['target'])) {
|
||||
try {
|
||||
$mappings = $processor->getMappings();
|
||||
$mappings[] = array(
|
||||
'source' => $form_state['values']['source'],
|
||||
'target' => $form_state['values']['target'],
|
||||
'unique' => FALSE,
|
||||
);
|
||||
$processor->addConfig(array('mappings' => $mappings));
|
||||
drupal_set_message(t('Mapping has been added.'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
drupal_set_message($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
$importer->save();
|
||||
drupal_set_message(t('Your changes have been saved.'));
|
||||
}
|
||||
@@ -807,11 +836,17 @@ function feeds_ui_mapping_form_submit($form, &$form_state) {
|
||||
* FeedsProcessor::getMappingTargets() and format them into
|
||||
* a Form API options array.
|
||||
*/
|
||||
function _feeds_ui_format_options($options) {
|
||||
function _feeds_ui_format_options($options, $show_deprecated = FALSE) {
|
||||
$result = array();
|
||||
foreach ($options as $k => $v) {
|
||||
if (!$show_deprecated && is_array($v) && !empty($v['deprecated'])) {
|
||||
continue;
|
||||
}
|
||||
if (is_array($v) && !empty($v['name'])) {
|
||||
$result[$k] = $v['name'];
|
||||
$result[$k] = $v['name'] . ' (' . $k . ')';
|
||||
if (!empty($v['deprecated'])) {
|
||||
$result[$k] .= ' - ' . t('DEPRECATED');
|
||||
}
|
||||
}
|
||||
elseif (is_array($v)) {
|
||||
$result[$k] = $k;
|
||||
@@ -862,7 +897,6 @@ function feeds_ui_mapping_settings_optional_unique_form($mapping, $target, $form
|
||||
*/
|
||||
function theme_feeds_ui_overview_form($variables) {
|
||||
$form = $variables['form'];
|
||||
drupal_add_js(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.js');
|
||||
drupal_add_css(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.css');
|
||||
|
||||
// Iterate through all importers and build a table.
|
||||
@@ -908,7 +942,7 @@ function theme_feeds_ui_edit_page($variables) {
|
||||
drupal_add_css(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.css');
|
||||
|
||||
// Outer wrapper.
|
||||
$output = '<div class="feeds-settings clear-block">';
|
||||
$output = '<div class="feeds-settings clearfix">';
|
||||
|
||||
// Build left bar.
|
||||
$output .= '<div class="left-bar">';
|
||||
@@ -995,6 +1029,13 @@ function theme_feeds_ui_container($variables) {
|
||||
function theme_feeds_ui_mapping_form($variables) {
|
||||
$form = $variables['form'];
|
||||
|
||||
$targets = feeds_importer($form['#importer'])->processor->getMappingTargets();
|
||||
$targets = _feeds_ui_format_options($targets, TRUE);
|
||||
|
||||
$sources = feeds_importer($form['#importer'])->parser->getMappingSources();
|
||||
// Some parsers do not define source options.
|
||||
$sources = $sources ? _feeds_ui_format_options($sources, TRUE) : array();
|
||||
|
||||
// Build the actual mapping table.
|
||||
$header = array(
|
||||
t('Source'),
|
||||
@@ -1006,12 +1047,15 @@ function theme_feeds_ui_mapping_form($variables) {
|
||||
$rows = array();
|
||||
if (is_array($form['#mappings'])) {
|
||||
foreach ($form['#mappings'] as $i => $mapping) {
|
||||
// Some parsers do not define source options.
|
||||
$source = isset($form['source']['#options'][$mapping['source']]) ? $form['source']['#options'][$mapping['source']] : $mapping['source'];
|
||||
$target = isset($form['target']['#options'][$mapping['target']]) ? check_plain($form['target']['#options'][$mapping['target']]) : '<em>' . t('Missing') . '</em>';
|
||||
$source = isset($sources[$mapping['source']]) ? check_plain($sources[$mapping['source']]) : check_plain($mapping['source']);
|
||||
$target = isset($targets[$mapping['target']]) ? check_plain($targets[$mapping['target']]) : '<em>' . t('Missing') . '</em>';
|
||||
// Add indicator to target if target configuration changed.
|
||||
if (isset($form['#mapping_settings'][$i])) {
|
||||
$target .= '<span class="warning">*</span>';
|
||||
}
|
||||
$rows[] = array(
|
||||
'data' => array(
|
||||
check_plain($source),
|
||||
$source,
|
||||
$target,
|
||||
drupal_render($form['config'][$i]),
|
||||
drupal_render($form['remove_flags'][$i]),
|
||||
@@ -1036,7 +1080,12 @@ function theme_feeds_ui_mapping_form($variables) {
|
||||
drupal_render($form['add']),
|
||||
'',
|
||||
);
|
||||
$output = '<div class="help feeds-admin-ui">' . drupal_render($form['help']) . '</div>';
|
||||
$output = '';
|
||||
if (!empty($form['changed'])) {
|
||||
// This form element is only available if target configuration changed.
|
||||
$output .= drupal_render($form['changed']);
|
||||
}
|
||||
$output .= '<div class="help feeds-admin-ui">' . drupal_render($form['help']) . '</div>';
|
||||
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'feeds-ui-mapping-overview')));
|
||||
|
||||
// Build the help table that explains available sources.
|
||||
@@ -1057,7 +1106,7 @@ function theme_feeds_ui_mapping_form($variables) {
|
||||
$rows = array();
|
||||
foreach (element_children($form['legendset']['legend']['targets']) as $k) {
|
||||
$rows[] = array(
|
||||
check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['name'])),
|
||||
check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['name']) . ' (' . $k . ')'),
|
||||
check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['description'])),
|
||||
);
|
||||
}
|
||||
@@ -1075,3 +1124,114 @@ function theme_feeds_ui_mapping_form($variables) {
|
||||
drupal_add_tabledrag('feeds-ui-mapping-overview', 'order', 'sibling', 'feeds-ui-mapping-weight');
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page callback to import a Feeds importer.
|
||||
*/
|
||||
function feeds_ui_importer_import($form, &$form_state) {
|
||||
$form['id'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Importer id'),
|
||||
'#description' => t('Enter the id to use for this importer if it is different from the source importer. Leave blank to use the id of the importer.'),
|
||||
);
|
||||
$form['id_override'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Replace an existing importer if one exists with the same id.'),
|
||||
);
|
||||
$form['bypass_validation'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Bypass importer validation'),
|
||||
'#description' => t('Bypass the validation of plugins when importing.'),
|
||||
);
|
||||
$form['importer'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#rows' => 10,
|
||||
);
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Import'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation handler for feeds_ui_importer_import().
|
||||
*
|
||||
* @see feeds_ui_importer_import_submit()
|
||||
*/
|
||||
function feeds_ui_importer_import_validate($form, &$form_state) {
|
||||
$form_state['values']['importer'] = trim($form_state['values']['importer']);
|
||||
$form_state['values']['id'] = trim($form_state['values']['id']);
|
||||
|
||||
if (!empty($form_state['values']['id']) && preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['id'])) {
|
||||
form_error($form['id'], t('Feeds importer id must be alphanumeric with underscores only.'));
|
||||
}
|
||||
|
||||
if (substr($form_state['values']['importer'], 0, 5) == '<?php') {
|
||||
$form_state['values']['importer'] = substr($form_state['values']['importer'], 5);
|
||||
}
|
||||
|
||||
$feeds_importer = NULL;
|
||||
ob_start();
|
||||
eval($form_state['values']['importer']);
|
||||
ob_end_clean();
|
||||
|
||||
if (!is_object($feeds_importer)) {
|
||||
return form_error($form['importer'], t('Unable to interpret Feeds importer code.'));
|
||||
}
|
||||
|
||||
if (empty($feeds_importer->api_version) || $feeds_importer->api_version < 1) {
|
||||
form_error($form['importer'], t('The importer is not compatible with this version of Feeds.'));
|
||||
}
|
||||
elseif (version_compare($feeds_importer->api_version, feeds_api_version(), '>')) {
|
||||
form_error($form['importer'], t('That importer is created for the version %import_version of Feeds, but you only have version %api_version.', array(
|
||||
'%import_version' => $feeds_importer->api_version,
|
||||
'%api_version' => feeds_api_version())));
|
||||
}
|
||||
|
||||
// Change to user-supplied id.
|
||||
if ($form_state['values']['id']) {
|
||||
$feeds_importer->id = $form_state['values']['id'];
|
||||
}
|
||||
|
||||
$exists = feeds_ui_importer_machine_name_exists($feeds_importer->id);
|
||||
|
||||
if ($exists && !$form_state['values']['id_override']) {
|
||||
if (feeds_importer($feeds_importer->id)->export_type != EXPORT_IN_CODE) {
|
||||
return form_error($form['id'], t('Feeds importer already exists with that id.'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$form_state['values']['bypass_validation']) {
|
||||
foreach (array('fetcher', 'parser', 'processor') as $type) {
|
||||
$plugin = feeds_plugin($feeds_importer->config[$type]['plugin_key'], $feeds_importer->id);
|
||||
if (get_class($plugin) == 'FeedsMissingPlugin') {
|
||||
form_error($form['importer'], t('The plugin %plugin is unavailable.', array('%plugin' => $feeds_importer->config[$type]['plugin_key'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form_state['importer'] = $feeds_importer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for feeds_ui_importer_import().
|
||||
*
|
||||
* @see feeds_ui_importer_import_validate()
|
||||
*/
|
||||
function feeds_ui_importer_import_submit($form, &$form_state) {
|
||||
$importer = $form_state['importer'];
|
||||
|
||||
// Create a copy of the importer to preserve config.
|
||||
$save = feeds_importer($importer->id);
|
||||
$save->setConfig($importer->config);
|
||||
foreach (array('fetcher', 'parser', 'processor') as $type) {
|
||||
$save->setPlugin($importer->config[$type]['plugin_key']);
|
||||
$save->$type->setConfig($importer->config[$type]['config']);
|
||||
}
|
||||
$save->save();
|
||||
|
||||
drupal_set_message(t('Successfully imported the %id feeds importer.', array('%id' => $importer->id)));
|
||||
$form_state['redirect'] = 'admin/structure/feeds';
|
||||
}
|
||||
|
@@ -1,90 +1,106 @@
|
||||
|
||||
/* Feeds admin overview form. */
|
||||
table.feeds-admin-importers thead th {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
table.feeds-admin-importers td.disabled {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
table.feeds-admin-importers tr.disabled.odd,
|
||||
table.feeds-admin-importers tr.disabled.even {
|
||||
border-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
table.feeds-admin-importers tr.disabled.odd {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Feeds edit form layout. */
|
||||
div.feeds-settings {
|
||||
}
|
||||
}
|
||||
|
||||
div.left-bar {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 240px;
|
||||
border-right: 1px solid #DDD;
|
||||
padding: 10px 10px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.configuration {
|
||||
padding: 10px 0 0 250px;
|
||||
margin-left: -240px;
|
||||
}
|
||||
div.configuration-squeeze {
|
||||
margin-left: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
div.configuration-squeeze {
|
||||
margin-left: 250px;
|
||||
}
|
||||
|
||||
/* Container theming. */
|
||||
div.feeds-container {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
div.feeds-container h4 {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
div.feeds-container.plain {
|
||||
background-color: #EEE;
|
||||
border-bottom: 1px solid #DDD;
|
||||
border-top: 2px solid #DDD;
|
||||
padding: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
div.feeds-container.plain h4 {
|
||||
font-size: 1.0em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div.feeds-container-body p {
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
}
|
||||
div.feeds-container-body div.item-list ul {
|
||||
margin: 0;
|
||||
}
|
||||
div.feeds-container-body div.item-list ul li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-image: none;
|
||||
}
|
||||
ul.container-actions {
|
||||
font-family: Arial, Helvetica;
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
ul.container-actions li {
|
||||
list-style-type: none;
|
||||
text-align: right;
|
||||
background-image: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.container-actions .form-item,
|
||||
ul.container-actions li form,
|
||||
ul.container-actions li form input {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
div.feeds-container.plain h4 {
|
||||
font-size: 1.0em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.feeds-container-body p {
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.feeds-container-body div.item-list ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.feeds-container-body div.item-list ul li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
ul.container-actions {
|
||||
font-family: Arial, Helvetica;
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul.container-actions li {
|
||||
list-style-type: none;
|
||||
text-align: right;
|
||||
background-image: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative; /* Fix for IE 7 compatibility mode. */
|
||||
}
|
||||
|
||||
ul.container-actions .form-item,
|
||||
ul.container-actions li form,
|
||||
ul.container-actions li form input {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Mapping form. */
|
||||
#center table form {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
@@ -7,9 +7,9 @@ configure = admin/structure/feeds
|
||||
|
||||
files[] = feeds_ui.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-10-24
|
||||
version = "7.x-2.0-alpha7"
|
||||
; Information added by Drupal.org packaging script on 2016-02-21
|
||||
version = "7.x-2.0-beta2"
|
||||
core = "7.x"
|
||||
project = "feeds"
|
||||
datestamp = "1351111319"
|
||||
datestamp = "1456055647"
|
||||
|
||||
|
@@ -1,62 +0,0 @@
|
||||
|
||||
Drupal.behaviors.feeds = function() {
|
||||
|
||||
// Hide text in specific input fields.
|
||||
$('.hide-text-on-focus').focus(function() {
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
|
||||
// Hide submit buttons of .feeds-ui-hidden-submit class.
|
||||
$('input.form-submit.feeds-ui-hidden-submit').hide();
|
||||
|
||||
/**
|
||||
* Tune checkboxes on mapping forms.
|
||||
* @see feeds_ui_mapping_form() in feeds_ui.admin.inc
|
||||
*/
|
||||
|
||||
// Attach submit behavior to elements with feeds-ui-trigger-submit class.
|
||||
$('.feeds-ui-trigger-submit').click(function() {
|
||||
// Use click, not form.submit() - submit() would use the wrong submission
|
||||
// handler.
|
||||
$('input.form-submit.feeds-ui-hidden-submit').click();
|
||||
});
|
||||
|
||||
// Replace checkbox with .feeds-ui-checkbox-link class with a link.
|
||||
$('.feeds-ui-checkbox-link:not(.processed)').each(function(i) {
|
||||
$(this).addClass('processed').after(
|
||||
'<a href="#" onclick="return false;" class="feeds-ui-trigger-remove">' + $('label', this).text() + '</a>'
|
||||
).hide();
|
||||
});
|
||||
|
||||
// Check the box and then submit.
|
||||
$('.feeds-ui-trigger-remove').click(function() {
|
||||
// Use click, not form.submit() - submit() would use the wrong submission
|
||||
// handler.
|
||||
$(this).prev().children().children().children().attr('checked', 1);
|
||||
$('input.form-submit.feeds-ui-hidden-submit').click();
|
||||
});
|
||||
|
||||
// Replace radio with .feeds-ui-radio-link class with a link.
|
||||
$('.feeds-ui-radio-link:not(.processed)').parent().each(function(i) {
|
||||
checked = '';
|
||||
if ($(this).children('input').attr('checked')) {
|
||||
checked = ' checked';
|
||||
}
|
||||
$(this).addClass('processed').after(
|
||||
'<a href="#" onclick="return false;" class="feeds-ui-check-submit' + checked + '" id="' + $(this).children('input').attr('id') + '">' + $(this).parent().text() + '</a>'
|
||||
);
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
// Hide the the radio that is selected.
|
||||
$('.feeds-ui-check-submit.checked').parent().hide();
|
||||
|
||||
// Check the radio and then submit.
|
||||
$('.feeds-ui-check-submit').click(function() {
|
||||
// Use click, not form.submit() - submit() would use the wrong submission
|
||||
// handler.
|
||||
$('#' + $(this).attr('id')).attr('checked', 1);
|
||||
$('input.form-submit.feeds-ui-hidden-submit').click();
|
||||
});
|
||||
};
|
@@ -35,6 +35,14 @@ function feeds_ui_menu() {
|
||||
'file' => 'feeds_ui.admin.inc',
|
||||
'type' => MENU_LOCAL_ACTION,
|
||||
);
|
||||
$items['admin/structure/feeds/import'] = array(
|
||||
'title' => 'Import importer',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('feeds_ui_importer_import'),
|
||||
'access callback' => 'feeds_importer_import_access',
|
||||
'file' => 'feeds_ui.admin.inc',
|
||||
'type' => MENU_LOCAL_ACTION,
|
||||
);
|
||||
$items['admin/structure/feeds/%feeds_importer'] = array(
|
||||
'title callback' => 'feeds_ui_importer_title',
|
||||
'title arguments' => array(3),
|
||||
|
@@ -17,6 +17,10 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp(array('php'), array('use PHP for settings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* UI functionality tests on
|
||||
* feeds_ui_overview(),
|
||||
@@ -87,7 +91,7 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase {
|
||||
'content_type' => 'page',
|
||||
'import_period' => 3600,
|
||||
);
|
||||
$this->drupalPost('admin/structure/feeds/test_feed/settings', $edit, 'Save');
|
||||
$this->setSettings('test_feed', NULL, $edit);
|
||||
|
||||
// Assert results of change.
|
||||
$this->assertText('Syndication feed');
|
||||
@@ -98,11 +102,8 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase {
|
||||
$this->assertLink('Basic page');
|
||||
|
||||
// Configure processor.
|
||||
$edit = array(
|
||||
'content_type' => 'article',
|
||||
);
|
||||
$this->drupalPost('admin/structure/feeds/test_feed/settings/FeedsNodeProcessor', $edit, 'Save');
|
||||
$this->assertFieldByName('content_type', 'article');
|
||||
$this->setSettings('test_feed', 'FeedsNodeProcessor', array('bundle' => 'article'));
|
||||
$this->assertFieldByName('bundle', 'article');
|
||||
|
||||
// Create a feed node.
|
||||
$edit = array(
|
||||
@@ -114,4 +115,46 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase {
|
||||
|
||||
// @todo Refreshing/deleting feed items. Needs to live in feeds.test
|
||||
}
|
||||
|
||||
public function testImporterImport() {
|
||||
$name = $this->randomString();
|
||||
$id = drupal_strtolower($this->randomName());
|
||||
$this->createImporterConfiguration($name, $id);
|
||||
$this->setPlugin($id, 'FeedsCSVParser');
|
||||
$this->setPlugin($id, 'FeedsFileFetcher');
|
||||
$this->setPlugin($id, 'FeedsUserProcessor');
|
||||
|
||||
$this->setSettings($id, 'FeedsFileFetcher', array('allowed_extensions' => 'xml'));
|
||||
$this->setSettings($id, 'FeedsCSVParser', array('delimiter' => '|'));
|
||||
$this->setSettings($id, 'FeedsUserProcessor', array('skip_hash_check' => TRUE));
|
||||
|
||||
$this->drupalGet('admin/structure/feeds/' . $id . '/export');
|
||||
|
||||
$export = $this->xpath('//textarea[1]/text()');
|
||||
$export = (string) $export[0];
|
||||
|
||||
// Delete this importer.
|
||||
$this->drupalPost('admin/structure/feeds/' . $id . '/delete', array(), t('Delete'));
|
||||
|
||||
// Try to import.
|
||||
$this->drupalPost('admin/structure/feeds/import', array('importer' => $export), t('Import'));
|
||||
|
||||
$this->assertText("Successfully imported the $id feeds importer.");
|
||||
|
||||
// Check that the settings are correct.
|
||||
$importer = feeds_importer($id);
|
||||
$this->assertEqual('FeedsFileFetcher', get_class($importer->fetcher));
|
||||
$this->assertEqual('FeedsCSVParser', get_class($importer->parser));
|
||||
$this->assertEqual('FeedsUserProcessor', get_class($importer->processor));
|
||||
|
||||
$config = $importer->fetcher->getConfig();
|
||||
$this->assertEqual('xml', $config['allowed_extensions']);
|
||||
|
||||
$config = $importer->parser->getConfig();
|
||||
$this->assertEqual('|', $config['delimiter']);
|
||||
|
||||
$config = $importer->processor->getConfig();
|
||||
$this->assertTrue($config['skip_hash_check']);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -15,6 +15,10 @@ class FeedsNotExistingException extends Exception {
|
||||
* Base class for configurable classes. Captures configuration handling, form
|
||||
* handling and distinguishes between in-memory configuration and persistent
|
||||
* configuration.
|
||||
*
|
||||
* Due to the magic method __get(), protected properties from this class and
|
||||
* from classes that extend this class will be publicly readable (but not
|
||||
* writeable).
|
||||
*/
|
||||
abstract class FeedsConfigurable {
|
||||
|
||||
@@ -44,17 +48,20 @@ abstract class FeedsConfigurable {
|
||||
protected $disabled;
|
||||
|
||||
/**
|
||||
* Instantiate a FeedsConfigurable object.
|
||||
* Instantiates a FeedsConfigurable object.
|
||||
*
|
||||
* Don't use directly, use feeds_importer() or feeds_plugin()
|
||||
* instead.
|
||||
* Don't use directly, use feeds_importer() or feeds_plugin() instead.
|
||||
*
|
||||
* @see feeds_importer()
|
||||
* @see feeds_plugin()
|
||||
*/
|
||||
public static function instance($class, $id) {
|
||||
// This is useful at least as long as we're developing.
|
||||
if (empty($id)) {
|
||||
throw new Exception(t('Empty configuration identifier.'));
|
||||
if (!strlen($id)) {
|
||||
throw new InvalidArgumentException(t('Empty configuration identifier.'));
|
||||
}
|
||||
static $instances = array();
|
||||
|
||||
$instances = &drupal_static(__METHOD__, array());
|
||||
|
||||
if (!isset($instances[$class][$id])) {
|
||||
$instances[$class][$id] = new $class($id);
|
||||
}
|
||||
@@ -136,8 +143,13 @@ abstract class FeedsConfigurable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override magic method __get(). Make sure that $this->config goes through
|
||||
* getConfig().
|
||||
* Overrides magic method __get().
|
||||
*
|
||||
* - Makes sure that external reads of FeedsConfigurable::config go through
|
||||
* ::getConfig();
|
||||
* - Makes private and protected properties from this class and protected
|
||||
* properties from child classes publicly readable.
|
||||
* - Prevents warnings when accessing non-existent properties.
|
||||
*/
|
||||
public function __get($name) {
|
||||
if ($name == 'config') {
|
||||
@@ -169,6 +181,17 @@ abstract class FeedsConfigurable {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the configurable has a config form.
|
||||
*
|
||||
* @return bool
|
||||
* True if the configurable has a config form, and false if not.
|
||||
*/
|
||||
public function hasConfigForm() {
|
||||
$form_state = array();
|
||||
return (bool) $this->configForm($form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return configuration form for this object. The keys of the configuration
|
||||
* form must match the keys of the array returned by configDefaults().
|
||||
@@ -202,6 +225,16 @@ abstract class FeedsConfigurable {
|
||||
drupal_set_message(t('Your changes have been saved.'));
|
||||
feeds_cache_clear(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of required modules.
|
||||
*
|
||||
* @return array
|
||||
* The modules that this configurable requires.
|
||||
*/
|
||||
public function dependencies() {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,6 +298,8 @@ function feeds_form_submit($form, &$form_state) {
|
||||
|
||||
/**
|
||||
* Helper for Feeds validate and submit callbacks.
|
||||
*
|
||||
* @todo This is all terrible. Remove.
|
||||
*/
|
||||
function _feeds_form_helper($form, &$form_state, $action) {
|
||||
$method = $form['#feeds_form_method'] . $action;
|
||||
@@ -277,12 +312,13 @@ function _feeds_form_helper($form, &$form_state, $action) {
|
||||
// This will re-initialize all of the plugins anyway, causing some tricky
|
||||
// saving issues in certain cases.
|
||||
// See http://drupal.org/node/1672880.
|
||||
|
||||
if ($class == variable_get('feeds_importer_class', 'FeedsImporter')) {
|
||||
$form['#configurable'] = feeds_importer($id);
|
||||
}
|
||||
else {
|
||||
$form['#configurable'] = feeds_plugin($class, $id);
|
||||
$importer = feeds_importer($id);
|
||||
$plugin_key = $importer->config[$form['#configurable']->pluginType()]['plugin_key'];
|
||||
$form['#configurable'] = feeds_plugin($plugin_key, $id);
|
||||
}
|
||||
|
||||
if (method_exists($form['#configurable'], $method)) {
|
||||
|
@@ -54,49 +54,6 @@ class FeedsImporter extends FeedsConfigurable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove items older than $time.
|
||||
*
|
||||
* @param $time
|
||||
* All items older than REQUEST_TIME - $time will be deleted. If not
|
||||
* given, internal processor settings will be used.
|
||||
*
|
||||
* @return
|
||||
* FEEDS_BATCH_COMPLETE if the expiry process finished. A decimal between
|
||||
* 0.0 and 0.9 periodic if expiry is still in progress.
|
||||
*
|
||||
* @throws
|
||||
* Throws Exception if an error occurs when expiring items.
|
||||
*/
|
||||
public function expire($time = NULL) {
|
||||
return $this->processor->expire($time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule all periodic tasks for this importer.
|
||||
*/
|
||||
public function schedule() {
|
||||
$this->scheduleExpire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule expiry of items.
|
||||
*/
|
||||
public function scheduleExpire() {
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'period' => 0,
|
||||
'periodic' => TRUE,
|
||||
);
|
||||
if (FEEDS_EXPIRE_NEVER != $this->processor->expiryTime()) {
|
||||
$job['period'] = 3600;
|
||||
JobScheduler::get('feeds_importer_expire')->set($job);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_importer_expire')->remove($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report how many items *should* be created on one page load by this
|
||||
* importer.
|
||||
@@ -155,23 +112,17 @@ class FeedsImporter extends FeedsConfigurable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete configuration. Removes configuration information
|
||||
* from database, does not delete configuration itself.
|
||||
* Deletes configuration.
|
||||
*
|
||||
* Removes configuration information from database, does not delete
|
||||
* configuration itself.
|
||||
*/
|
||||
public function delete() {
|
||||
db_delete('feeds_importer')
|
||||
->condition('id', $this->id)
|
||||
->execute();
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => 0,
|
||||
);
|
||||
if ($this->export_type & EXPORT_IN_CODE) {
|
||||
feeds_reschedule($this->id);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_importer_expire')->remove($job);
|
||||
}
|
||||
|
||||
feeds_reschedule($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,6 +271,17 @@ class FeedsImporter extends FeedsConfigurable {
|
||||
}
|
||||
parent::configFormSubmit($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FeedsConfigurable::dependencies().
|
||||
*/
|
||||
public function dependencies() {
|
||||
$dependencies = parent::dependencies();
|
||||
foreach ($this->plugin_types as $plugin_type) {
|
||||
$dependencies = array_merge($dependencies, $this->$plugin_type->dependencies());
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Distinguish exceptions occuring when handling locks.
|
||||
* Distinguish exceptions occurring when handling locks.
|
||||
*/
|
||||
class FeedsLockException extends Exception {}
|
||||
|
||||
@@ -18,6 +18,7 @@ define('FEEDS_FETCH', 'fetch');
|
||||
define('FEEDS_PARSE', 'parse');
|
||||
define('FEEDS_PROCESS', 'process');
|
||||
define('FEEDS_PROCESS_CLEAR', 'process_clear');
|
||||
define('FEEDS_PROCESS_EXPIRE', 'process_expire');
|
||||
|
||||
/**
|
||||
* Declares an interface for a class that defines default values and form
|
||||
@@ -86,9 +87,16 @@ class FeedsState {
|
||||
public $created;
|
||||
public $updated;
|
||||
public $deleted;
|
||||
public $unpublished;
|
||||
public $blocked;
|
||||
public $skipped;
|
||||
public $failed;
|
||||
|
||||
/**
|
||||
* IDs of entities to be removed.
|
||||
*/
|
||||
public $removeList;
|
||||
|
||||
/**
|
||||
* Constructor, initialize variables.
|
||||
*/
|
||||
@@ -98,6 +106,8 @@ class FeedsState {
|
||||
$this->created =
|
||||
$this->updated =
|
||||
$this->deleted =
|
||||
$this->unpublished =
|
||||
$this->blocked =
|
||||
$this->skipped =
|
||||
$this->failed = 0;
|
||||
}
|
||||
@@ -124,7 +134,7 @@ class FeedsState {
|
||||
$this->progress = FEEDS_BATCH_COMPLETE;
|
||||
}
|
||||
elseif ($total) {
|
||||
$this->progress = $progress / $total;
|
||||
$this->progress = (float) $progress / $total;
|
||||
if ($this->progress == FEEDS_BATCH_COMPLETE && $total != $progress) {
|
||||
$this->progress = 0.99;
|
||||
}
|
||||
@@ -178,13 +188,18 @@ class FeedsSource extends FeedsConfigurable {
|
||||
// Timestamp when this source was imported the last time.
|
||||
protected $imported;
|
||||
|
||||
// Holds an exception object in case an exception occurs during importing.
|
||||
protected $exception;
|
||||
|
||||
/**
|
||||
* Instantiate a unique object per class/id/feed_nid. Don't use
|
||||
* directly, use feeds_source() instead.
|
||||
*/
|
||||
public static function instance($importer_id, $feed_nid) {
|
||||
$class = variable_get('feeds_source_class', 'FeedsSource');
|
||||
static $instances = array();
|
||||
|
||||
$instances = &drupal_static(__METHOD__, array());
|
||||
|
||||
if (!isset($instances[$class][$importer_id][$feed_nid])) {
|
||||
$instances[$class][$importer_id][$feed_nid] = new $class($importer_id, $feed_nid);
|
||||
}
|
||||
@@ -273,6 +288,7 @@ class FeedsSource extends FeedsConfigurable {
|
||||
*/
|
||||
public function schedule() {
|
||||
$this->scheduleImport();
|
||||
$this->scheduleExpire();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,19 +301,44 @@ class FeedsSource extends FeedsConfigurable {
|
||||
if (is_numeric($fetcher_period)) {
|
||||
$period = $fetcher_period;
|
||||
}
|
||||
$period = $this->progressImporting() === FEEDS_BATCH_COMPLETE ? $period : 0;
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => $this->feed_nid,
|
||||
// Schedule as soon as possible if a batch is active.
|
||||
'period' => $period,
|
||||
'periodic' => TRUE,
|
||||
);
|
||||
if ($period != FEEDS_SCHEDULE_NEVER) {
|
||||
if ($period == FEEDS_SCHEDULE_NEVER && $this->progressImporting() === FEEDS_BATCH_COMPLETE) {
|
||||
JobScheduler::get('feeds_source_import')->remove($job);
|
||||
}
|
||||
elseif ($this->progressImporting() === FEEDS_BATCH_COMPLETE) {
|
||||
JobScheduler::get('feeds_source_import')->set($job);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_source_import')->remove($job);
|
||||
// Feed is not fully imported yet, so we put this job back in the queue
|
||||
// immediately for further processing.
|
||||
$queue = DrupalQueue::get('feeds_source_import');
|
||||
$queue->createItem($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule background expire tasks.
|
||||
*/
|
||||
public function scheduleExpire() {
|
||||
// Schedule as soon as possible if a batch is active.
|
||||
$period = $this->progressExpiring() === FEEDS_BATCH_COMPLETE ? 3600 : 0;
|
||||
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => $this->feed_nid,
|
||||
'period' => $period,
|
||||
'periodic' => TRUE,
|
||||
);
|
||||
if ($this->importer->processor->expiryTime() == FEEDS_EXPIRE_NEVER) {
|
||||
JobScheduler::get('feeds_source_expire')->remove($job);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_source_expire')->set($job);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +380,7 @@ class FeedsSource extends FeedsConfigurable {
|
||||
try {
|
||||
// If fetcher result is empty, we are starting a new import, log.
|
||||
if (empty($this->fetcher_result)) {
|
||||
module_invoke_all('feeds_before_import', $this);
|
||||
$this->state[FEEDS_START] = time();
|
||||
}
|
||||
|
||||
@@ -355,27 +397,91 @@ class FeedsSource extends FeedsConfigurable {
|
||||
|
||||
// Process.
|
||||
$this->importer->processor->process($this, $parser_result);
|
||||
|
||||
// Import finished without exceptions, so unset any potentially previously
|
||||
// recorded exceptions.
|
||||
unset($this->exception);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing.
|
||||
// $e is stored and re-thrown once we've had a chance to log our progress.
|
||||
// Set the exception so that other modules can check if an exception
|
||||
// occurred in hook_feeds_after_import().
|
||||
$this->exception = $e;
|
||||
}
|
||||
$this->releaseLock();
|
||||
|
||||
// Clean up.
|
||||
$result = $this->progressImporting();
|
||||
if ($result == FEEDS_BATCH_COMPLETE || isset($e)) {
|
||||
$this->imported = time();
|
||||
$this->log('import', 'Imported in !s s', array('!s' => $this->imported - $this->state[FEEDS_START]), WATCHDOG_INFO);
|
||||
$this->log('import', 'Imported in @s seconds.', array('@s' => $this->imported - $this->state[FEEDS_START]), WATCHDOG_INFO);
|
||||
module_invoke_all('feeds_after_import', $this);
|
||||
unset($this->fetcher_result, $this->state);
|
||||
}
|
||||
$this->save();
|
||||
|
||||
$this->releaseLock();
|
||||
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a fetcher result all at once in memory.
|
||||
*
|
||||
* @param FeedsFetcherResult $fetcher_result
|
||||
* The fetcher result to process.
|
||||
*
|
||||
* @throws Exception
|
||||
* Thrown if an error occurs when importing.
|
||||
*/
|
||||
public function pushImport(FeedsFetcherResult $fetcher_result) {
|
||||
// Since locks only work during a request, check if an import is active.
|
||||
if (!empty($this->fetcher_result) || !empty($this->state)) {
|
||||
throw new RuntimeException('The feed is currently importing.');
|
||||
}
|
||||
|
||||
$this->acquireLock();
|
||||
$start = time();
|
||||
|
||||
try {
|
||||
module_invoke_all('feeds_before_import', $this);
|
||||
|
||||
// Parse.
|
||||
do {
|
||||
$parser_result = $this->importer->parser->parse($this, $fetcher_result);
|
||||
module_invoke_all('feeds_after_parse', $this, $parser_result);
|
||||
|
||||
// Process.
|
||||
$this->importer->processor->process($this, $parser_result);
|
||||
|
||||
} while ($this->progressParsing() !== FEEDS_BATCH_COMPLETE);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// $e is stored and re-thrown once we've had a chance to log our progress.
|
||||
// Set the exception so that other modules can check if an exception
|
||||
// occurred in hook_feeds_after_import().
|
||||
$this->exception = $e;
|
||||
}
|
||||
|
||||
module_invoke_all('feeds_after_import', $this);
|
||||
|
||||
$this->imported = time();
|
||||
$this->log('import', 'Imported in @s seconds.', array('@s' => $this->imported - $start), WATCHDOG_INFO);
|
||||
|
||||
unset($this->fetcher_result, $this->state);
|
||||
|
||||
$this->save();
|
||||
|
||||
$this->releaseLock();
|
||||
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items from a feed.
|
||||
*
|
||||
@@ -398,7 +504,7 @@ class FeedsSource extends FeedsConfigurable {
|
||||
$this->importer->processor->clear($this);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing.
|
||||
// $e is stored and re-thrown once we've had a chance to log our progress.
|
||||
}
|
||||
$this->releaseLock();
|
||||
|
||||
@@ -415,6 +521,26 @@ class FeedsSource extends FeedsConfigurable {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all expired items from a feed.
|
||||
*/
|
||||
public function expire() {
|
||||
$this->acquireLock();
|
||||
try {
|
||||
$result = $this->importer->processor->expire($this);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Will throw after the lock is released.
|
||||
}
|
||||
$this->releaseLock();
|
||||
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report progress as float between 0 and 1. 1 = FEEDS_BATCH_COMPLETE.
|
||||
*/
|
||||
@@ -449,6 +575,13 @@ class FeedsSource extends FeedsConfigurable {
|
||||
return $this->state(FEEDS_PROCESS_CLEAR)->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report progress on expiry.
|
||||
*/
|
||||
public function progressExpiring() {
|
||||
return $this->state(FEEDS_PROCESS_EXPIRE)->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a state object for a given stage. Lazy instantiates new states.
|
||||
*
|
||||
@@ -526,6 +659,9 @@ class FeedsSource extends FeedsConfigurable {
|
||||
if (!empty($record->state)) {
|
||||
$this->state = unserialize($record->state);
|
||||
}
|
||||
if (!is_array($this->state)) {
|
||||
$this->state = array();
|
||||
}
|
||||
if (!empty($record->fetcher_result)) {
|
||||
$this->fetcher_result = unserialize($record->fetcher_result);
|
||||
}
|
||||
@@ -552,6 +688,7 @@ class FeedsSource extends FeedsConfigurable {
|
||||
'id' => $this->feed_nid,
|
||||
);
|
||||
JobScheduler::get('feeds_source_import')->remove($job);
|
||||
JobScheduler::get('feeds_source_expire')->remove($job);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -722,4 +859,13 @@ class FeedsSource extends FeedsConfigurable {
|
||||
protected function releaseLock() {
|
||||
lock_release("feeds_source_{$this->id}_{$this->feed_nid}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FeedsConfigurable::dependencies().
|
||||
*/
|
||||
public function dependencies() {
|
||||
$dependencies = parent::dependencies();
|
||||
return array_merge($dependencies, $this->importer()->dependencies());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -66,6 +66,8 @@ class ParserCSVIterator implements Iterator {
|
||||
*/
|
||||
class ParserCSV {
|
||||
private $delimiter;
|
||||
private $fromEncoding;
|
||||
private $toEncoding;
|
||||
private $skipFirstLine;
|
||||
private $columnNames;
|
||||
private $timeout;
|
||||
@@ -73,9 +75,12 @@ class ParserCSV {
|
||||
private $startByte;
|
||||
private $lineLimit;
|
||||
private $lastLinePos;
|
||||
private $useMbString;
|
||||
|
||||
public function __construct() {
|
||||
$this->delimiter = ',';
|
||||
$this->fromEncoding = 'UTF-8';
|
||||
$this->toEncoding = 'UTF-8';
|
||||
$this->skipFirstLine = FALSE;
|
||||
$this->columnNames = FALSE;
|
||||
$this->timeout = FALSE;
|
||||
@@ -83,6 +88,10 @@ class ParserCSV {
|
||||
$this->startByte = 0;
|
||||
$this->lineLimit = 0;
|
||||
$this->lastLinePos = 0;
|
||||
ini_set('auto_detect_line_endings', TRUE);
|
||||
if (extension_loaded('mbstring') && variable_get('feeds_use_mbstring', TRUE)) {
|
||||
$this->useMbString = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,6 +102,18 @@ class ParserCSV {
|
||||
$this->delimiter = $delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source file encoding.
|
||||
*
|
||||
* By default, the encoding is UTF-8.
|
||||
*
|
||||
* @param string $encoding
|
||||
* The encoding to set.
|
||||
*/
|
||||
public function setEncoding($encoding) {
|
||||
$this->fromEncoding = $encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to TRUE if the parser should skip the first line of the CSV text,
|
||||
* which might be desired if the first line contains the column names.
|
||||
@@ -196,7 +217,7 @@ class ParserCSV {
|
||||
for ($lineIterator->rewind($this->startByte); $lineIterator->valid(); $lineIterator->next()) {
|
||||
|
||||
// Make really sure we've got lines without trailing newlines.
|
||||
$line = trim($lineIterator->current(), "\r\n");
|
||||
$line = trim($this->fixEncoding($lineIterator->current()), "\r\n");
|
||||
|
||||
// Skip empty lines.
|
||||
if (empty($line)) {
|
||||
@@ -236,7 +257,7 @@ class ParserCSV {
|
||||
}
|
||||
// Ok, so, on with fetching the next line, as mentioned above.
|
||||
$currentField .= "\n";
|
||||
$line = trim($lineIterator->current(), "\r\n");
|
||||
$line = trim($this->fixEncoding($lineIterator->current()), "\r\n");
|
||||
$currentIndex = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -324,4 +345,38 @@ class ParserCSV {
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts encoding of input data.
|
||||
*
|
||||
* @param string $data
|
||||
* A chunk of data.
|
||||
*
|
||||
* @return string
|
||||
* The encoded data.
|
||||
*
|
||||
* @throws ParserCSVEncodingException
|
||||
* Thrown when a given encoding does not match.
|
||||
*/
|
||||
public function fixEncoding($data) {
|
||||
if ($this->useMbString) {
|
||||
if (mb_check_encoding($data, $this->fromEncoding)) {
|
||||
if ($this->toEncoding != $this->fromEncoding) {
|
||||
// Convert encoding. The conversion is to UTF-8 by default to prevent
|
||||
// SQL errors.
|
||||
$data = mb_convert_encoding($data, $this->toEncoding, $this->fromEncoding);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new ParserCSVEncodingException(t('Source file is not in %encoding encoding.', array('%encoding' => $this->fromEncoding)));
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when an encoding error occurs during parsing.
|
||||
*/
|
||||
class ParserCSVEncodingException extends Exception {}
|
||||
|
@@ -161,7 +161,7 @@ class PuSHSubscriber {
|
||||
if (isset($_SERVER['HTTP_X_HUB_SIGNATURE']) && ($sub = $this->subscription())) {
|
||||
$result = array();
|
||||
parse_str($_SERVER['HTTP_X_HUB_SIGNATURE'], $result);
|
||||
if (isset($result['sha1']) && $result['sha1'] == hash_hmac('sha1', $raw, $sub->secret)) {
|
||||
if (isset($result['sha1']) && $result['sha1'] === hash_hmac('sha1', $raw, $sub->secret)) {
|
||||
return $raw;
|
||||
}
|
||||
else {
|
||||
@@ -183,47 +183,44 @@ class PuSHSubscriber {
|
||||
* method handles the challenge.
|
||||
*/
|
||||
public function verifyRequest() {
|
||||
if (isset($_GET['hub_challenge'])) {
|
||||
/**
|
||||
* If a subscription is present, compare the verify token. If the token
|
||||
* matches, set the status on the subscription record and confirm
|
||||
* positive.
|
||||
*
|
||||
* If we cannot find a matching subscription and the hub checks on
|
||||
* 'unsubscribe' confirm positive.
|
||||
*
|
||||
* In all other cases confirm negative.
|
||||
*/
|
||||
if ($sub = $this->subscription()) {
|
||||
if ($_GET['hub_verify_token'] == $sub->post_fields['hub.verify_token']) {
|
||||
if ($_GET['hub_mode'] == 'subscribe' && $sub->status == 'subscribe') {
|
||||
$sub->status = 'subscribed';
|
||||
$sub->post_fields = array();
|
||||
$sub->save();
|
||||
$this->log('Verified "subscribe" request.');
|
||||
$verify = TRUE;
|
||||
}
|
||||
elseif ($_GET['hub_mode'] == 'unsubscribe' && $sub->status == 'unsubscribe') {
|
||||
$sub->status = 'unsubscribed';
|
||||
$sub->post_fields = array();
|
||||
$sub->save();
|
||||
$this->log('Verified "unsubscribe" request.');
|
||||
$verify = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($_GET['hub_mode'] == 'unsubscribe') {
|
||||
$this->log('Verified "unsubscribe" request.');
|
||||
$verify = TRUE;
|
||||
}
|
||||
if ($verify) {
|
||||
header('HTTP/1.1 200 "Found"', NULL, 200);
|
||||
print $_GET['hub_challenge'];
|
||||
drupal_exit();
|
||||
}
|
||||
if (!isset($_GET['hub_challenge'])) {
|
||||
return $this->rejectRequest();
|
||||
}
|
||||
header('HTTP/1.1 404 "Not Found"', NULL, 404);
|
||||
$this->log('Could not verify subscription.', 'error');
|
||||
|
||||
// Don't accept modes of 'subscribed' or 'unsubscribed'.
|
||||
if ($_GET['hub_mode'] !== 'subscribe' && $_GET['hub_mode'] !== 'unsubscribe') {
|
||||
return $this->rejectRequest();
|
||||
}
|
||||
|
||||
// No available subscription.
|
||||
if (!$sub = $this->subscription()) {
|
||||
return $this->rejectRequest();
|
||||
}
|
||||
|
||||
// Not what we asked for.
|
||||
if ($_GET['hub_mode'] !== $sub->status) {
|
||||
return $this->rejectRequest();
|
||||
}
|
||||
|
||||
// Wrong URL.
|
||||
if ($_GET['hub_topic'] !== $sub->topic) {
|
||||
return $this->rejectRequest();
|
||||
}
|
||||
|
||||
if ($sub->status === 'subscribe') {
|
||||
$sub->status = 'subscribed';
|
||||
$this->log('Verified "subscribe" request.');
|
||||
}
|
||||
else {
|
||||
$sub->status = 'unsubscribed';
|
||||
$this->log('Verified "unsubscribe" request.');
|
||||
}
|
||||
|
||||
$sub->post_fields = array();
|
||||
$sub->save();
|
||||
|
||||
header('HTTP/1.1 200 "Found"', NULL, 200);
|
||||
print check_plain($_GET['hub_challenge']);
|
||||
drupal_exit();
|
||||
}
|
||||
|
||||
@@ -244,7 +241,7 @@ class PuSHSubscriber {
|
||||
* @todo Make concurrency safe.
|
||||
*/
|
||||
protected function request($hub, $topic, $mode, $callback_url) {
|
||||
$secret = hash('sha1', uniqid(rand(), TRUE));
|
||||
$secret = drupal_random_key(40);
|
||||
$post_fields = array(
|
||||
'hub.callback' => $callback_url,
|
||||
'hub.mode' => $mode,
|
||||
@@ -252,7 +249,6 @@ class PuSHSubscriber {
|
||||
'hub.verify' => 'sync',
|
||||
'hub.lease_seconds' => '', // Permanent subscription.
|
||||
'hub.secret' => $secret,
|
||||
'hub.verify_token' => md5(session_id() . rand()),
|
||||
);
|
||||
$sub = new $this->subscription_class($this->domain, $this->subscriber_id, $hub, $topic, $secret, $mode, $post_fields);
|
||||
$sub->save();
|
||||
@@ -310,6 +306,16 @@ class PuSHSubscriber {
|
||||
protected function log($msg, $level = 'status') {
|
||||
$this->env->log("{$this->domain}:{$this->subscriber_id}\t$msg", $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects a request subscription request.
|
||||
*/
|
||||
protected function rejectRequest() {
|
||||
header('HTTP/1.1 404 "Not Found"', NULL, 404);
|
||||
$this->log('Could not verify subscription.', 'error');
|
||||
drupal_exit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -19,12 +19,7 @@
|
||||
* stdClass The structured datas extracted from the feed.
|
||||
*/
|
||||
function common_syndication_parser_parse($string) {
|
||||
if (!defined('LIBXML_VERSION') || (version_compare(phpversion(), '5.1.0', '<'))) {
|
||||
@ $xml = simplexml_load_string($string, NULL);
|
||||
}
|
||||
else {
|
||||
@ $xml = simplexml_load_string($string, NULL, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NOCDATA);
|
||||
}
|
||||
@ $xml = simplexml_load_string($string, NULL, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NOCDATA);
|
||||
|
||||
// Got a malformed XML.
|
||||
if ($xml === FALSE || is_null($xml)) {
|
||||
@@ -43,18 +38,6 @@ function common_syndication_parser_parse($string) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached version of the <var>$url</var>
|
||||
*/
|
||||
function _parser_common_syndication_cache_get($url) {
|
||||
$cache_file = _parser_common_syndication_sanitize_cache() . '/' . md5($url);
|
||||
if (file_exists($cache_file)) {
|
||||
$file_content = file_get_contents($cache_file);
|
||||
return unserialize($file_content);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the feed format of a SimpleXML parsed object structure.
|
||||
*
|
||||
@@ -182,16 +165,14 @@ function _parser_common_syndication_atom10_parse($feed_XML) {
|
||||
}
|
||||
}
|
||||
|
||||
$author_found = FALSE;
|
||||
$original_author = '';
|
||||
if (!empty($news->source->author->name)) {
|
||||
$original_author = "{$news->source->author->name}";
|
||||
$author_found = TRUE;
|
||||
}
|
||||
elseif (!empty($news->author->name)) {
|
||||
$original_author = "{$news->author->name}";
|
||||
$author_found = TRUE;
|
||||
}
|
||||
if (!empty($feed_XML->author->name) && !$author_found) {
|
||||
elseif (!empty($feed_XML->author->name)) {
|
||||
$original_author = "{$feed_XML->author->name}";
|
||||
}
|
||||
|
||||
@@ -262,9 +243,8 @@ function _parser_common_syndication_RDF10_parse($feed_XML) {
|
||||
'rss' => 'http://purl.org/rss/1.0/',
|
||||
);
|
||||
|
||||
// Get all namespaces declared in the feed element, with special handling
|
||||
// for PHP versions prior to 5.1.2 as they don't handle namespaces.
|
||||
$namespaces = version_compare(phpversion(), '5.1.2', '<') ? array() : $feed_XML->getNamespaces(TRUE);
|
||||
// Get all namespaces declared in the feed element.
|
||||
$namespaces = $feed_XML->getNamespaces(TRUE);
|
||||
|
||||
// Process the <rss:channel> resource containing feed metadata:
|
||||
foreach ($feed_XML->children($canonical_namespaces['rss'])->channel as $rss_channel) {
|
||||
@@ -379,11 +359,9 @@ function _parser_common_syndication_RSS20_parse($feed_XML) {
|
||||
|
||||
$category = $news->xpath('category');
|
||||
// Get children for current namespace.
|
||||
if (version_compare(phpversion(), '5.1.2', '>')) {
|
||||
$content = (array)$news->children($ns["content"]);
|
||||
$dc = (array)$news->children($ns["dc"]);
|
||||
$georss = (array)$news->children($ns["georss"]);
|
||||
}
|
||||
$content = (array)$news->children($ns["content"]);
|
||||
$dc = (array)$news->children($ns["dc"]);
|
||||
$georss = (array)$news->children($ns["georss"]);
|
||||
$news = (array) $news;
|
||||
$news['category'] = $category;
|
||||
|
||||
@@ -505,12 +483,25 @@ function _parser_common_syndication_RSS20_parse($feed_XML) {
|
||||
*/
|
||||
function _parser_common_syndication_parse_date($date_str) {
|
||||
// PHP < 5.3 doesn't like the GMT- notation for parsing timezones.
|
||||
$date_str = str_replace("GMT-", "-", $date_str);
|
||||
$date_str = str_replace("GMT+", "+", $date_str);
|
||||
$date_str = str_replace('GMT-', '-', $date_str);
|
||||
$date_str = str_replace('GMT+', '+', $date_str);
|
||||
$parsed_date = strtotime($date_str);
|
||||
|
||||
if ($parsed_date === FALSE || $parsed_date == -1) {
|
||||
$parsed_date = _parser_common_syndication_parse_w3cdtf($date_str);
|
||||
}
|
||||
|
||||
if (($parsed_date === FALSE || $parsed_date == -1)) {
|
||||
// PHP does not support the UT timezone. Fake it. The system that generated
|
||||
// this, Google Groups, probably meant UTC.
|
||||
$date_str = strtolower(trim($date_str));
|
||||
$last_three = substr($date_str, strlen($date_str) - 3, 3);
|
||||
|
||||
if ($last_three == ' ut') {
|
||||
$parsed_date = strtotime($date_str . 'c');
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_date === FALSE ? time() : $parsed_date;
|
||||
}
|
||||
|
||||
|
@@ -24,8 +24,10 @@ define('HTTP_REQUEST_PCRE_TAG_ATTRIBUTES', '/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\
|
||||
class HRCurlException extends Exception {}
|
||||
|
||||
/**
|
||||
* Discover RSS or atom feeds at the given URL. If document in given URL is an
|
||||
* HTML document, function attempts to discover RSS or Atom feeds.
|
||||
* Discovers RSS or atom feeds at the given URL.
|
||||
*
|
||||
* If document in given URL is an HTML document, function attempts to discover
|
||||
* RSS or Atom feeds.
|
||||
*
|
||||
* @param string $url
|
||||
* The url of the feed to retrieve.
|
||||
@@ -36,7 +38,7 @@ class HRCurlException extends Exception {}
|
||||
* The discovered feed, or FALSE if the URL is not reachable or there was an
|
||||
* error.
|
||||
*/
|
||||
function http_request_get_common_syndication($url, $settings = NULL) {
|
||||
function http_request_get_common_syndication($url, $settings = array()) {
|
||||
|
||||
$accept_invalid_cert = isset($settings['accept_invalid_cert']) ? $settings['accept_invalid_cert'] : FALSE;
|
||||
$download = http_request_get($url, NULL, NULL, $accept_invalid_cert);
|
||||
@@ -47,12 +49,12 @@ function http_request_get_common_syndication($url, $settings = NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Drop the data into a seperate variable so all manipulations of the html
|
||||
// Drop the data into a separate variable so all manipulations of the html
|
||||
// will not effect the actual object that exists in the static cache.
|
||||
// @see http_request_get.
|
||||
$downloaded_string = $download->data;
|
||||
// If this happens to be a feed then just return the url.
|
||||
if (http_request_is_feed($download->headers['content-type'], $downloaded_string)) {
|
||||
if (isset($download->headers['content-type']) && http_request_is_feed($download->headers['content-type'], $downloaded_string)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
@@ -77,11 +79,13 @@ function http_request_get_common_syndication($url, $settings = NULL) {
|
||||
* If the URL uses authentication, supply the password.
|
||||
* @param bool $accept_invalid_cert
|
||||
* Whether to accept invalid certificates.
|
||||
|
||||
* @param integer $timeout
|
||||
* Timeout in seconds to wait for an HTTP get request to finish.
|
||||
*
|
||||
* @return stdClass
|
||||
* An object that describes the data downloaded from $url.
|
||||
*/
|
||||
function http_request_get($url, $username = NULL, $password = NULL, $accept_invalid_cert = FALSE) {
|
||||
function http_request_get($url, $username = NULL, $password = NULL, $accept_invalid_cert = FALSE, $timeout = NULL) {
|
||||
// Intra-pagedownload cache, avoid to download the same content twice within
|
||||
// one page download (it's possible, compatible and parse calls).
|
||||
static $download_cache = array();
|
||||
@@ -89,12 +93,15 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
return $download_cache[$url];
|
||||
}
|
||||
|
||||
// Determine request timeout.
|
||||
$request_timeout = !empty($timeout) ? $timeout : variable_get('http_request_timeout', 30);
|
||||
|
||||
if (!$username && valid_url($url, TRUE)) {
|
||||
// Handle password protected feeds.
|
||||
$url_parts = parse_url($url);
|
||||
if (!empty($url_parts['user'])) {
|
||||
$password = $url_parts['pass'];
|
||||
$username = $url_parts['user'];
|
||||
$password = urldecode($url_parts['pass']);
|
||||
$username = urldecode($url_parts['user']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +110,7 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
// Only download and parse data if really needs refresh.
|
||||
// Based on "Last-Modified" and "If-Modified-Since".
|
||||
$headers = array();
|
||||
if ($cache = cache_get('feeds_http_download_' . md5($url))) {
|
||||
if ($cache = http_request_get_cache($url)) {
|
||||
$last_result = $cache->data;
|
||||
$last_headers = array_change_key_case($last_result->headers);
|
||||
|
||||
@@ -134,6 +141,7 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
if ($curl) {
|
||||
$headers[] = 'User-Agent: Drupal (+http://drupal.org/)';
|
||||
$result = new stdClass();
|
||||
$result->headers = array();
|
||||
|
||||
// Parse the URL and make sure we can handle the schema.
|
||||
// cURL can only support either http:// or https://.
|
||||
@@ -149,6 +157,7 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
case 'https':
|
||||
// Valid scheme.
|
||||
break;
|
||||
|
||||
default:
|
||||
$result->error = 'invalid schema ' . $uri['scheme'];
|
||||
$result->code = -1003;
|
||||
@@ -168,9 +177,26 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
curl_setopt($download, CURLOPT_HEADER, TRUE);
|
||||
curl_setopt($download, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($download, CURLOPT_ENCODING, '');
|
||||
curl_setopt($download, CURLOPT_TIMEOUT, variable_get('http_request_timeout', 30));
|
||||
curl_setopt($download, CURLOPT_TIMEOUT, $request_timeout);
|
||||
|
||||
$proxy_server = variable_get('proxy_server');
|
||||
|
||||
if ($proxy_server && _drupal_http_use_proxy($uri['host'])) {
|
||||
curl_setopt($download, CURLOPT_PROXY, $proxy_server);
|
||||
curl_setopt($download, CURLOPT_PROXYPORT, variable_get('proxy_port', 8080));
|
||||
|
||||
// Proxy user/password.
|
||||
if ($proxy_username = variable_get('proxy_username')) {
|
||||
$username_password = $proxy_username . ':' . variable_get('proxy_password', '');
|
||||
|
||||
curl_setopt($download, CURLOPT_PROXYUSERPWD, $username_password);
|
||||
curl_setopt($download, CURLOPT_PROXYAUTH, variable_get('proxy_auth_method', CURLAUTH_BASIC));
|
||||
}
|
||||
}
|
||||
|
||||
if ($accept_invalid_cert) {
|
||||
curl_setopt($download, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($download, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
}
|
||||
$header = '';
|
||||
$data = curl_exec($download);
|
||||
@@ -179,18 +205,35 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
t('cURL error (@code) @error for @url', array(
|
||||
'@code' => curl_errno($download),
|
||||
'@error' => curl_error($download),
|
||||
'@url' => $url
|
||||
'@url' => $url,
|
||||
)), curl_errno($download)
|
||||
);
|
||||
}
|
||||
|
||||
// When using a proxy, remove extra data from the header which is not
|
||||
// considered by CURLINFO_HEADER_SIZE (possibly cURL bug).
|
||||
// This data is only added when to HTTP header when working with a proxy.
|
||||
// Example string added: <HTTP/1.0 200 Connection established\r\n\r\n>
|
||||
// This was fixed in libcurl version 7.30.0 (0x71e00) (April 12, 2013),
|
||||
// so this workaround only removes the proxy-added headers if we are using
|
||||
// an older version of libcurl.
|
||||
$curl_ver = curl_version();
|
||||
|
||||
if ($proxy_server && $curl_ver['version_number'] < 0x71e00 && _drupal_http_use_proxy($uri['host'])) {
|
||||
$http_header_break = "\r\n\r\n";
|
||||
$response = explode($http_header_break, $data);
|
||||
if (count($response) > 2) {
|
||||
$data = substr($data, strlen($response[0] . $http_header_break), strlen($data));
|
||||
}
|
||||
}
|
||||
|
||||
$header_size = curl_getinfo($download, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($data, 0, $header_size - 1);
|
||||
$result->data = substr($data, $header_size);
|
||||
$headers = preg_split("/(\r\n){2}/", $header);
|
||||
$header_lines = preg_split("/\r\n|\n|\r/", end($headers));
|
||||
$result->headers = array();
|
||||
array_shift($header_lines); // skip HTTP response status
|
||||
// Skip HTTP response status.
|
||||
array_shift($header_lines);
|
||||
|
||||
while ($line = trim(array_shift($header_lines))) {
|
||||
list($header, $value) = explode(':', $line, 2);
|
||||
@@ -212,7 +255,8 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result = drupal_http_request($url, array('headers' => $headers, 'timeout' => variable_get('http_request_timeout', 30)));
|
||||
$result = drupal_http_request($url, array('headers' => $headers, 'timeout' => $request_timeout));
|
||||
$result->headers = isset($result->headers) ? $result->headers : array();
|
||||
}
|
||||
|
||||
$result->code = isset($result->code) ? $result->code : 200;
|
||||
@@ -227,13 +271,13 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
else {
|
||||
// It's a tragedy, this file must exist and contain good data.
|
||||
// In this case, clear cache and repeat.
|
||||
cache_clear_all('feeds_http_download_' . md5($url), 'cache');
|
||||
return http_request_get($url, $username, $password);
|
||||
http_request_clear_cache($url);
|
||||
return http_request_get($url, $username, $password, $accept_invalid_cert, $request_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Set caches.
|
||||
cache_set('feeds_http_download_' . md5($url), $result);
|
||||
http_request_set_cache($url, $result);
|
||||
$download_cache[$url] = $result;
|
||||
|
||||
return $result;
|
||||
@@ -243,7 +287,7 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
|
||||
* Decides if it's possible to use cURL or not.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if curl is available, FALSE otherwise.
|
||||
* TRUE if cURL may be used, FALSE otherwise.
|
||||
*/
|
||||
function http_request_use_curl() {
|
||||
// Allow site administrators to choose to not use cURL.
|
||||
@@ -251,29 +295,67 @@ function http_request_use_curl() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Check availability of cURL on the system.
|
||||
$basedir = ini_get("open_basedir");
|
||||
return function_exists('curl_init') && !ini_get('safe_mode') && empty($basedir);
|
||||
// Check that the PHP cURL extension has been enabled.
|
||||
if (!extension_loaded('curl')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// cURL below PHP 5.6.0 must not have open_basedir or safe_mode enabled.
|
||||
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
|
||||
return !ini_get('safe_mode') && !ini_get('open_basedir');
|
||||
}
|
||||
|
||||
// cURL in PHP 5.6.0 and above re-enables CURLOPT_FOLLOWLOCATION with
|
||||
// open_basedir so there is no need to check for this.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache for a specific URL.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL to clear.
|
||||
*/
|
||||
function http_request_clear_cache($url) {
|
||||
cache_clear_all('feeds_http_download_' . md5($url), 'cache');
|
||||
cache_clear_all(hash('sha256', $url), 'cache_feeds_http');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache for a specific URL.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL to find the cached item.
|
||||
*
|
||||
* @return object|false
|
||||
* The cache or FALSE on failure.
|
||||
*/
|
||||
function http_request_get_cache($url) {
|
||||
return cache_get(hash('sha256', $url), 'cache_feeds_http');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache for a specific URL.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL to cache.
|
||||
* @param stdClass $result
|
||||
* The result of the HTTP request.
|
||||
*/
|
||||
function http_request_set_cache($url, stdClass $result) {
|
||||
cache_set(hash('sha256', $url), $result, 'cache_feeds_http');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the provided $content_type is a feed.
|
||||
*
|
||||
* @param string $content_type
|
||||
* The Content-Type header.
|
||||
* The Content-Type header.
|
||||
*
|
||||
* @param string $data
|
||||
* The actual data from the http request.
|
||||
* The actual data from the http request.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if this is a parsable feed.
|
||||
* Returns TRUE if this is a parsable feed.
|
||||
*/
|
||||
function http_request_is_feed($content_type, $data) {
|
||||
$pos = strpos($content_type, ';');
|
||||
@@ -294,10 +376,10 @@ function http_request_is_feed($content_type, $data) {
|
||||
* Finds potential feed tags in the HTML document.
|
||||
*
|
||||
* @param string $html
|
||||
* The html string to search.
|
||||
* The html string to search.
|
||||
*
|
||||
* @return array
|
||||
* An array of href to feeds.
|
||||
* An array of href to feeds.
|
||||
*/
|
||||
function http_request_find_feeds($html) {
|
||||
$matches = array();
|
||||
@@ -313,7 +395,11 @@ function http_request_find_feeds($html) {
|
||||
preg_match_all(HTTP_REQUEST_PCRE_TAG_ATTRIBUTES, $link_tag, $attributes, PREG_SET_ORDER);
|
||||
foreach ($attributes as $attribute) {
|
||||
// Find the key value pairs, attribute[1] is key and attribute[2] is the
|
||||
// value.
|
||||
// value. However, if the link tag used single quotes, the value might
|
||||
// be in attribute[3] instead.
|
||||
if (empty($attribute[2])) {
|
||||
$attribute[2] = $attribute[3];
|
||||
}
|
||||
if (!empty($attribute[1]) && !empty($attribute[2])) {
|
||||
$candidate[drupal_strtolower($attribute[1])] = drupal_strtolower(decode_entities($attribute[2]));
|
||||
}
|
||||
@@ -355,27 +441,48 @@ function http_request_create_absolute_url($url, $base_url) {
|
||||
// Produces variables $scheme, $host, $user, $pass, $path, $query and
|
||||
// $fragment.
|
||||
$parsed_url = parse_url($base_url);
|
||||
if ($parsed_url === FALSE) {
|
||||
// Invalid $base_url.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$path = dirname($parsed_url['path']);
|
||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
if (strlen($path) > 0 && substr($path, -1) != '/') {
|
||||
// Path ends not with '/', so remove all before previous '/'.
|
||||
$path = dirname($path);
|
||||
}
|
||||
|
||||
// Adding to the existing path.
|
||||
$cparts = array();
|
||||
if ($url{0} == '/') {
|
||||
$cparts = array_filter(explode("/", $url));
|
||||
}
|
||||
else {
|
||||
// Backtracking from the existing path.
|
||||
$cparts = array_merge(array_filter(explode("/", $path)), array_filter(explode("/", $url)));
|
||||
foreach ($cparts as $i => $part) {
|
||||
if ($part == '.') {
|
||||
$cparts[$i] = NULL;
|
||||
}
|
||||
if ($part == '..') {
|
||||
$cparts[$i - 1] = NULL;
|
||||
$cparts[$i] = NULL;
|
||||
}
|
||||
}
|
||||
$cparts = array_filter($cparts);
|
||||
$path_cparts = array_filter(explode("/", $path));
|
||||
$url_cparts = array_filter(explode("/", $url));
|
||||
$cparts = array_merge($path_cparts, $url_cparts);
|
||||
}
|
||||
|
||||
$remove_parts = 0;
|
||||
// Start from behind.
|
||||
$reverse_cparts = array_reverse($cparts);
|
||||
foreach ($reverse_cparts as $i => &$part) {
|
||||
if ($part == '.') {
|
||||
$part = NULL;
|
||||
}
|
||||
elseif ($part == '..') {
|
||||
$part = NULL;
|
||||
$remove_parts++;
|
||||
}
|
||||
elseif ($remove_parts > 0) {
|
||||
// If the current part isn't "..", and we had ".." before, then delete
|
||||
// the part.
|
||||
$part = NULL;
|
||||
$remove_parts--;
|
||||
}
|
||||
}
|
||||
$cparts = array_filter(array_reverse($reverse_cparts));
|
||||
$path = implode("/", $cparts);
|
||||
|
||||
// Build the prefix to the path.
|
||||
|
@@ -6,13 +6,13 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see FeedsNodeProcessor::getMappingTargets().
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*
|
||||
* @todo Only provides "end date" target if field allows it.
|
||||
*/
|
||||
function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function date_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
|
||||
$info = field_info_field($name);
|
||||
if (in_array($info['type'], array('date', 'datestamp', 'datetime'))) {
|
||||
@@ -30,33 +30,33 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_set_target().
|
||||
*
|
||||
* @param $node
|
||||
* The target node.
|
||||
* @param $field_name
|
||||
* The name of field on the target node to map to.
|
||||
* @param $feed_element
|
||||
* The value to be mapped. Should be either a (flexible) date string
|
||||
* or a FeedsDateTimeElement object.
|
||||
*
|
||||
* @todo Support array of values for dates.
|
||||
* Callback for setting date values.
|
||||
*/
|
||||
function date_feeds_set_target($source, $entity, $target, $feed_element) {
|
||||
function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
list($field_name, $sub_field) = explode(':', $target, 2);
|
||||
if (!($feed_element instanceof FeedsDateTimeElement)) {
|
||||
if (is_array($feed_element)) {
|
||||
$feed_element = $feed_element[0];
|
||||
}
|
||||
if ($sub_field == 'end') {
|
||||
$feed_element = new FeedsDateTimeElement(NULL, $feed_element);
|
||||
}
|
||||
else {
|
||||
$feed_element = new FeedsDateTimeElement($feed_element, NULL);
|
||||
|
||||
$delta = 0;
|
||||
foreach ($values as $value) {
|
||||
|
||||
if (!($value instanceof FeedsDateTimeElement)) {
|
||||
|
||||
if (empty($value) || !is_numeric($value) && is_string($value) && !date_create($value)) {
|
||||
$value = new FeedsDateTimeElement(NULL, NULL);
|
||||
}
|
||||
elseif ($sub_field == 'end') {
|
||||
$value = new FeedsDateTimeElement(NULL, $value);
|
||||
}
|
||||
else {
|
||||
$value = new FeedsDateTimeElement($value, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
$value->buildDateField($entity, $field_name, $delta, $mapping['language']);
|
||||
$delta++;
|
||||
}
|
||||
$feed_element->buildDateField($entity, $field_name);
|
||||
}
|
||||
|
83
sites/all/modules/feeds/mappers/entity_translation.inc
Normal file
83
sites/all/modules/feeds/mappers/entity_translation.inc
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* On behalf implementation of Feeds mapping API for entity_translation.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_presave().
|
||||
*/
|
||||
function entity_translation_feeds_presave(FeedsSource $source, $entity, $item, $entity_id) {
|
||||
$entity_type = $entity->feeds_item->entity_type;
|
||||
|
||||
// Check that it's a real entity type, and translation is enabled.
|
||||
if (!entity_get_info($entity_type) || !entity_translation_enabled($entity_type, $entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$handler = entity_translation_get_handler($entity_type, $entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
$languages_seen = array();
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle) as $instance) {
|
||||
$field_name = $instance['field_name'];
|
||||
|
||||
// No values in this field, skip it.
|
||||
if (empty($entity->$field_name) || !is_array($entity->$field_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not translatable.
|
||||
$info = field_info_field($field_name);
|
||||
if (!$info || !$info['translatable']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Init the translation handler.
|
||||
if (empty($handler->getTranslations()->original)) {
|
||||
$handler->initTranslations();
|
||||
}
|
||||
|
||||
// Avoid invalid user configuration. Entity translation does this when
|
||||
// loading the translation overview page.
|
||||
if (count($entity->$field_name) === 1 && key($entity->$field_name) === LANGUAGE_NONE && $handler->getLanguage() !== LANGUAGE_NONE) {
|
||||
$entity->{$field_name}[$handler->getLanguage()] = $entity->{$field_name}[LANGUAGE_NONE];
|
||||
$entity->{$field_name}[LANGUAGE_NONE] = array();
|
||||
}
|
||||
|
||||
// Look for languages we haven't created a translation for yet.
|
||||
foreach (array_diff_key($entity->$field_name, $languages_seen) as $language => $v) {
|
||||
if ($language === LANGUAGE_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$languages_seen[$language] = TRUE;
|
||||
|
||||
if ($language === $handler->getLanguage()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation = array(
|
||||
'translate' => 0,
|
||||
'status' => 1,
|
||||
'language' => $language,
|
||||
'source' => $handler->getLanguage(),
|
||||
);
|
||||
|
||||
$handler->setTranslation($translation, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through every language for the site, and remove translations for the
|
||||
// ones that don't have any values.
|
||||
foreach (language_list() as $language) {
|
||||
if (!isset($languages_seen[$language->language])) {
|
||||
$handler->removeTranslation($language->language);
|
||||
}
|
||||
}
|
||||
}
|
50
sites/all/modules/feeds/mappers/field.inc
Normal file
50
sites/all/modules/feeds/mappers/field.inc
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* On behalf implementation of Feeds mapping API for field.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_presave().
|
||||
*/
|
||||
function field_feeds_presave(FeedsSource $source, $entity, $item, $entity_id) {
|
||||
$entity_type = $entity->feeds_item->entity_type;
|
||||
|
||||
// Not a real entity.
|
||||
if (!entity_get_info($entity_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gather the fields that Feeds is mapping to.
|
||||
$feeds_fields = array();
|
||||
foreach ($source->importer()->processor->getMappings() as $mapping) {
|
||||
list($field) = explode(':', $mapping['target']);
|
||||
$feeds_fields[$field] = TRUE;
|
||||
}
|
||||
|
||||
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle) as $instance) {
|
||||
$field_name = $instance['field_name'];
|
||||
|
||||
// Skip fields that Feeds isn't mapping to, and empty fields.
|
||||
if (!isset($feeds_fields[$field_name]) || empty($entity->$field_name) || !is_array($entity->$field_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$info = field_info_field($field_name);
|
||||
|
||||
foreach ($entity->$field_name as $language => $values) {
|
||||
// Filter out empty values.
|
||||
$values = _field_filter_items($info, $values);
|
||||
|
||||
// Check that the number of values doesn't exceed the field cardinality.
|
||||
if ($info['cardinality'] != FIELD_CARDINALITY_UNLIMITED && count($values) > $info['cardinality']) {
|
||||
$values = array_slice($values, 0, $info['cardinality']);
|
||||
}
|
||||
|
||||
$entity->{$field_name}[$language] = $values;
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,91 +4,139 @@
|
||||
* @file
|
||||
* On behalf implementation of Feeds mapping API for file.module and
|
||||
* image.module.
|
||||
*
|
||||
* Does actually not include mappers for field types defined in fields module
|
||||
* (because there aren't any) but mappers for all fields that contain their
|
||||
* value simply in $entity->fieldname['und'][$i]['value'].
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see FeedsNodeProcessor::getMappingTargets().
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function file_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
|
||||
$info = field_info_field($name);
|
||||
|
||||
if (in_array($info['type'], array('file', 'image'))) {
|
||||
$targets[$name] = array(
|
||||
'name' => check_plain($instance['label']),
|
||||
$targets[$name . ':uri'] = array(
|
||||
'name' => t('@label: URI', array('@label' => $instance['label'])),
|
||||
'callback' => 'file_feeds_set_target',
|
||||
'description' => t('The @label field of the node.', array('@label' => $instance['label'])),
|
||||
'description' => t('The URI of the @label field.', array('@label' => $instance['label'])),
|
||||
'real_target' => $name,
|
||||
);
|
||||
|
||||
// Keep the old target name for backwards compatibility, but hide it from
|
||||
// the UI.
|
||||
$targets[$name] = $targets[$name . ':uri'];
|
||||
$targets[$name]['deprecated'] = TRUE;
|
||||
|
||||
if ($info['type'] == 'image') {
|
||||
$targets[$name . ':alt'] = array(
|
||||
'name' => t('@label: Alt', array('@label' => $instance['label'])),
|
||||
'callback' => 'file_feeds_set_target',
|
||||
'description' => t('The alt tag of the @label field.', array('@label' => $instance['label'])),
|
||||
'real_target' => $name,
|
||||
);
|
||||
$targets[$name . ':title'] = array(
|
||||
'name' => t('@label: Title', array('@label' => $instance['label'])),
|
||||
'callback' => 'file_feeds_set_target',
|
||||
'description' => t('The title of the @label field.', array('@label' => $instance['label'])),
|
||||
'real_target' => $name,
|
||||
);
|
||||
}
|
||||
elseif ($info['type'] === 'file') {
|
||||
$targets[$name . ':description'] = array(
|
||||
'name' => t('@label: Description', array('@label' => $instance['label'])),
|
||||
'callback' => 'file_feeds_set_target',
|
||||
'description' => t('The description of the @label field.', array('@label' => $instance['label'])),
|
||||
'real_target' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mapping. Here is where the actual mapping happens.
|
||||
*
|
||||
* When the callback is invoked, $target contains the name of the field the
|
||||
* user has decided to map to and $value contains the value of the feed item
|
||||
* element the user has picked as a source.
|
||||
* Callback for mapping file fields.
|
||||
*/
|
||||
function file_feeds_set_target($source, $entity, $target, $value) {
|
||||
if (empty($value)) {
|
||||
return;
|
||||
}
|
||||
module_load_include('inc', 'file');
|
||||
function file_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$language = $mapping['language'];
|
||||
|
||||
// Make sure $value is an array of objects of type FeedsEnclosure.
|
||||
if (!is_array($value)) {
|
||||
$value = array($value);
|
||||
}
|
||||
foreach ($value as $k => $v) {
|
||||
if (!($v instanceof FeedsEnclosure)) {
|
||||
if (is_string($v)) {
|
||||
$value[$k] = new FeedsEnclosure($v, file_get_mimetype($v));
|
||||
}
|
||||
else {
|
||||
unset($value[$k]);
|
||||
// Add default of uri for backwards compatibility.
|
||||
list($field_name, $sub_field) = explode(':', $target . ':uri');
|
||||
$info = field_info_field($field_name);
|
||||
|
||||
if ($sub_field == 'uri') {
|
||||
|
||||
foreach ($values as $k => $v) {
|
||||
if (!($v instanceof FeedsEnclosure)) {
|
||||
if (is_string($v)) {
|
||||
$values[$k] = new FeedsEnclosure($v, file_get_mimetype($v));
|
||||
}
|
||||
else {
|
||||
// Set the value for FALSE rather than remove it to keep our deltas
|
||||
// correct.
|
||||
$values[$k] = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine file destination.
|
||||
// @todo This needs review and debugging.
|
||||
list($entity_id, $vid, $bundle_name) = entity_extract_ids($entity->feeds_item->entity_type, $entity);
|
||||
$instance_info = field_info_instance($entity->feeds_item->entity_type, $target, $bundle_name);
|
||||
$info = field_info_field($target);
|
||||
$data = array();
|
||||
if (!empty($entity->uid)) {
|
||||
$data[$entity->feeds_item->entity_type] = $entity;
|
||||
if ($entity instanceof Entity) {
|
||||
$entity_type = $entity->entityType();
|
||||
$bundle = $entity->bundle();
|
||||
}
|
||||
else {
|
||||
$entity_type = $source->importer->processor->entityType();
|
||||
$bundle = $source->importer->processor->bundle();
|
||||
}
|
||||
$instance_info = field_info_instance($entity_type, $field_name, $bundle);
|
||||
|
||||
// Determine file destination.
|
||||
// @todo This needs review and debugging.
|
||||
$data = array();
|
||||
if (!empty($entity->uid)) {
|
||||
$data[$entity_type] = $entity;
|
||||
}
|
||||
|
||||
$destination = file_field_widget_uri($info, $instance_info, $data);
|
||||
}
|
||||
$destination = file_field_widget_uri($info, $instance_info, $data);
|
||||
|
||||
// Populate entity.
|
||||
$i = 0;
|
||||
$field = isset($entity->$target) ? $entity->$target : array();
|
||||
foreach ($value as $v) {
|
||||
try {
|
||||
$file = $v->getFile($destination);
|
||||
$field = isset($entity->$field_name) ? $entity->$field_name : array($language => array());
|
||||
$delta = 0;
|
||||
foreach ($values as $v) {
|
||||
if ($info['cardinality'] == $delta) {
|
||||
break;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
watchdog_exception('Feeds', $e, nl2br(check_plain($e)));
|
||||
|
||||
if (!isset($field[$language][$delta])) {
|
||||
$field[$language][$delta] = array();
|
||||
}
|
||||
if ($file) {
|
||||
$field['und'][$i] = (array)$file;
|
||||
$field['und'][$i]['display'] = 1; // @todo: Figure out how to properly populate this field.
|
||||
if ($info['cardinality'] == 1) {
|
||||
|
||||
switch ($sub_field) {
|
||||
case 'alt':
|
||||
case 'title':
|
||||
case 'description':
|
||||
$field[$language][$delta][$sub_field] = $v;
|
||||
break;
|
||||
|
||||
case 'uri':
|
||||
if ($v) {
|
||||
try {
|
||||
$v->setAllowedExtensions($instance_info['settings']['file_extensions']);
|
||||
$field[$language][$delta] += (array) $v->getFile($destination);
|
||||
// @todo: Figure out how to properly populate this field.
|
||||
$field[$language][$delta]['display'] = 1;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
watchdog('feeds', check_plain($e->getMessage()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
$delta++;
|
||||
}
|
||||
$entity->{$target} = $field;
|
||||
|
||||
$entity->$field_name = $field;
|
||||
}
|
||||
|
@@ -6,11 +6,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see FeedsProcessor::getMappingTargets()
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function link_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
|
||||
$info = field_info_field($name);
|
||||
if ($info['type'] == 'link_field') {
|
||||
@@ -32,45 +32,31 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mapping. Here is where the actual mapping happens.
|
||||
*
|
||||
* When the callback is invoked, $target contains the name of the field the
|
||||
* user has decided to map to and $value contains the value of the feed item
|
||||
* element the user has picked as a source.
|
||||
* Callback for mapping link fields.
|
||||
*/
|
||||
function link_feeds_set_target($source, $entity, $target, $value) {
|
||||
if (empty($value)) {
|
||||
return;
|
||||
}
|
||||
function link_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$language = $mapping['language'];
|
||||
|
||||
// Handle non-multiple value fields.
|
||||
if (!is_array($value)) {
|
||||
$value = array($value);
|
||||
}
|
||||
|
||||
// Iterate over all values.
|
||||
list($field_name, $column) = explode(':', $target);
|
||||
$info = field_info_field($field_name);
|
||||
|
||||
$field = isset($entity->$field_name) ? $entity->$field_name : array();
|
||||
$field = isset($entity->$field_name) ? $entity->$field_name : array($language => array());
|
||||
$delta = 0;
|
||||
|
||||
foreach ($value as $v) {
|
||||
if ($info['cardinality'] == $delta) {
|
||||
break;
|
||||
foreach ($values as $value) {
|
||||
if (is_object($value) && ($value instanceof FeedsElement)) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
|
||||
if (is_object($v) && ($v instanceof FeedsElement)) {
|
||||
$v = $v->getValue();
|
||||
}
|
||||
|
||||
if (is_scalar($v)) {
|
||||
$field['und'][$delta][$column] = $v;
|
||||
$delta++;
|
||||
if (is_scalar($value)) {
|
||||
$field[$language][$delta][$column] = (string) $value;
|
||||
}
|
||||
$delta++;
|
||||
}
|
||||
|
||||
$entity->$field_name = $field;
|
||||
}
|
||||
|
76
sites/all/modules/feeds/mappers/list.inc
Normal file
76
sites/all/modules/feeds/mappers/list.inc
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* On behalf implementation of Feeds mapping API for list.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function list_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
|
||||
$info = field_info_field($name);
|
||||
|
||||
switch ($info['type']) {
|
||||
|
||||
case 'list_integer':
|
||||
case 'list_float':
|
||||
$targets[$name] = array(
|
||||
'name' => check_plain($instance['label']),
|
||||
'callback' => 'number_feeds_set_target',
|
||||
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'list_boolean':
|
||||
$targets[$name] = array(
|
||||
'name' => check_plain($instance['label']),
|
||||
'callback' => 'list_feeds_set_boolean_target',
|
||||
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'list_text':
|
||||
$targets[$name] = array(
|
||||
'name' => check_plain($instance['label']),
|
||||
'callback' => 'text_feeds_set_target',
|
||||
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for setting list_boolean fields.
|
||||
*/
|
||||
function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$language = $mapping['language'];
|
||||
|
||||
$field = isset($entity->$target) ? $entity->$target : array($language => array());
|
||||
|
||||
foreach ($values as $value) {
|
||||
|
||||
if (is_object($value) && ($value instanceof FeedsElement)) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
|
||||
if (is_string($value) && strlen($value) == 0) {
|
||||
// Don't convert an empty string to a boolean.
|
||||
continue;
|
||||
}
|
||||
if (is_null($value)) {
|
||||
// Don't convert a NULL value to a boolean.
|
||||
continue;
|
||||
}
|
||||
|
||||
$field[$language][] = array('value' => (int) (bool) $value);
|
||||
}
|
||||
|
||||
$entity->$target = $field;
|
||||
}
|
118
sites/all/modules/feeds/mappers/locale.inc
Normal file
118
sites/all/modules/feeds/mappers/locale.inc
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* On behalf implementation of Feeds mapping API for locale.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*/
|
||||
function locale_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle_name) {
|
||||
foreach (array_keys($targets) as $target) {
|
||||
$targets[$target]['preprocess_callbacks'][] = 'locale_feeds_preprocess_callback';
|
||||
$targets[$target]['summary_callbacks'][] = 'locale_feeds_summary_callback';
|
||||
$targets[$target]['form_callbacks'][] = 'locale_feeds_form_callback';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess callback that sets the configured mapping language.
|
||||
*/
|
||||
function locale_feeds_preprocess_callback(array $target, array &$mapping) {
|
||||
if (empty($mapping['field_language'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mapping['language'] = $mapping['field_language'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary callback.
|
||||
*/
|
||||
function locale_feeds_summary_callback(array $mapping, array $target, array $form, array $form_state) {
|
||||
$entity_type = $form_state['build_info']['args'][0]->processor->entityType();
|
||||
$translatable = _locale_feeds_target_is_translatable($entity_type, $mapping['target']);
|
||||
|
||||
$mapping += array('field_language' => LANGUAGE_NONE);
|
||||
|
||||
$language_options = array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name');
|
||||
|
||||
$error = NULL;
|
||||
if ($mapping['field_language'] !== LANGUAGE_NONE && !$translatable) {
|
||||
// This is an invalid configuration that can come from disabling
|
||||
// entity_translation.
|
||||
$error = t('Field not translatable');
|
||||
}
|
||||
if (!isset($language_options[$mapping['field_language']])) {
|
||||
// This is an invalid configuration that can be caused by disabling or
|
||||
// removing the language in question.
|
||||
$error = t('Language \'@lang\' not available', array('@lang' => $mapping['field_language']));
|
||||
}
|
||||
|
||||
// Nothing to see here.
|
||||
if (!$error && !$translatable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
return t('Language: <strong>Error: @error</strong>', array('@error' => $error));
|
||||
}
|
||||
|
||||
return t('Language: %lang', array('%lang' => $language_options[$mapping['field_language']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Form callback.
|
||||
*/
|
||||
function locale_feeds_form_callback(array $mapping, array $target, array $form, array $form_state) {
|
||||
$form = array();
|
||||
|
||||
$entity_type = $form_state['build_info']['args'][0]->processor->entityType();
|
||||
|
||||
$translatable = _locale_feeds_target_is_translatable($entity_type, $mapping['target']);
|
||||
$mapping += array('field_language' => LANGUAGE_NONE);
|
||||
|
||||
// This is an invalid configuration that can come from disabling
|
||||
// entity_translation.
|
||||
$error = $mapping['field_language'] !== LANGUAGE_NONE && !$translatable;
|
||||
|
||||
// Nothing to see here.
|
||||
if (!$error && !$translatable) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
$language_options = array(LANGUAGE_NONE => t('Language neutral'));
|
||||
|
||||
if (!$error) {
|
||||
$language_options += locale_language_list('name');
|
||||
}
|
||||
|
||||
$form['field_language'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Language'),
|
||||
'#options' => $language_options,
|
||||
'#default_value' => $mapping['field_language'],
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a target is translatable.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type.
|
||||
* @param string $target
|
||||
* The target.
|
||||
*
|
||||
* @return bool
|
||||
* Returns true if the target is translatable, false if not.
|
||||
*/
|
||||
function _locale_feeds_target_is_translatable($entity_type, $target) {
|
||||
list($field_name) = explode(':', $target, 2);
|
||||
|
||||
$info = field_info_field($field_name);
|
||||
|
||||
return !empty($info) && field_is_translatable($entity_type, $info);
|
||||
}
|
@@ -6,15 +6,12 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see FeedsProcessor::getMappingTargets()
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function number_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
$numeric_types = array(
|
||||
'list_integer',
|
||||
'list_float',
|
||||
'list_boolean',
|
||||
'number_integer',
|
||||
'number_decimal',
|
||||
'number_float',
|
||||
@@ -30,43 +27,27 @@ function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_n
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mapping numerics.
|
||||
*
|
||||
* Ensure that $value is a numeric to avoid database errors.
|
||||
* Callback for mapping number fields.
|
||||
*/
|
||||
function number_feeds_set_target($source, $entity, $target, $value) {
|
||||
|
||||
// Do not perform the regular empty() check here. 0 is a valid value. That's
|
||||
// really just a performance thing anyway.
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = array($value);
|
||||
}
|
||||
|
||||
$info = field_info_field($target);
|
||||
function number_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$language = $mapping['language'];
|
||||
|
||||
// Iterate over all values.
|
||||
$field = isset($entity->$target) ? $entity->$target : array('und' => array());
|
||||
$field = isset($entity->$target) ? $entity->$target : array($language => array());
|
||||
|
||||
// Allow for multiple mappings to the same target.
|
||||
$delta = count($field['und']);
|
||||
foreach ($values as $value) {
|
||||
|
||||
foreach ($value as $v) {
|
||||
|
||||
if ($info['cardinality'] == $delta) {
|
||||
break;
|
||||
if (is_object($value) && ($value instanceof FeedsElement)) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
|
||||
if (is_object($v) && ($v instanceof FeedsElement)) {
|
||||
$v = $v->getValue();
|
||||
}
|
||||
|
||||
if (is_numeric($v)) {
|
||||
$field['und'][$delta]['value'] = $v;
|
||||
$delta++;
|
||||
if (is_numeric($value)) {
|
||||
$field[$language][] = array('value' => $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,11 +6,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see FeedsNodeProcessor::getMappingTargets().
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function path_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
switch ($entity_type) {
|
||||
case 'node':
|
||||
case 'taxonomy_term':
|
||||
@@ -19,28 +19,27 @@ function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
|
||||
'name' => t('Path alias'),
|
||||
'description' => t('URL path alias of the node.'),
|
||||
'callback' => 'path_feeds_set_target',
|
||||
'summary_callback' => 'path_feeds_summary_callback',
|
||||
'form_callback' => 'path_feeds_form_callback',
|
||||
'summary_callbacks' => array('path_feeds_summary_callback'),
|
||||
'form_callbacks' => array('path_feeds_form_callback'),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mapping. Here is where the actual mapping happens.
|
||||
*
|
||||
* When the callback is invoked, $target contains the name of the field the
|
||||
* user has decided to map to and $value contains the value of the feed item
|
||||
* element the user has picked as a source.
|
||||
* Callback for mapping path aliases.
|
||||
*/
|
||||
function path_feeds_set_target($source, $entity, $target, $value, $mapping) {
|
||||
if (empty($value)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
// Path alias cannot be multi-valued, so use the first value.
|
||||
if (is_array($value)) {
|
||||
$value = $value[0];
|
||||
function path_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$alias = FALSE;
|
||||
// Path alias cannot be multi-valued, so use the first non-empty value.
|
||||
foreach ($values as $value) {
|
||||
$value = ltrim(trim($value), '/');
|
||||
if (strlen($value)) {
|
||||
$alias = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$entity->path = array();
|
||||
@@ -58,37 +57,17 @@ function path_feeds_set_target($source, $entity, $target, $value, $mapping) {
|
||||
}
|
||||
}
|
||||
|
||||
$entity->path['pathauto'] = FALSE;
|
||||
// Allow pathauto to set the path alias if the option is set, and this value
|
||||
// is empty.
|
||||
if (!empty($mapping['pathauto_override']) && !$value) {
|
||||
$entity->path['pathauto'] = TRUE;
|
||||
}
|
||||
else {
|
||||
$entity->path['alias'] = ltrim($value, '/');
|
||||
}
|
||||
// Allow pathauto to set the path alias if the option is set, and the value is
|
||||
// empty.
|
||||
$entity->path['pathauto'] = !empty($mapping['pathauto_override']) && $alias === FALSE;
|
||||
|
||||
$entity->path['alias'] = (string) $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping configuration summary for path.module.
|
||||
*
|
||||
* @param $mapping
|
||||
* Associative array of the mapping settings.
|
||||
* @param $target
|
||||
* Array of target settings, as defined by the processor or
|
||||
* hook_feeds_processor_targets_alter().
|
||||
* @param $form
|
||||
* The whole mapping form.
|
||||
* @param $form_state
|
||||
* The form state of the mapping form.
|
||||
*
|
||||
* @return
|
||||
* Returns, as a string that may contain HTML, the summary to display while
|
||||
* the full form isn't visible.
|
||||
* If the return value is empty, no summary and no option to view the form
|
||||
* will be displayed.
|
||||
*/
|
||||
function path_feeds_summary_callback($mapping, $target, $form, $form_state) {
|
||||
function path_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
if (!module_exists('pathauto')) {
|
||||
return;
|
||||
}
|
||||
@@ -104,12 +83,8 @@ function path_feeds_summary_callback($mapping, $target, $form, $form_state) {
|
||||
|
||||
/**
|
||||
* Settings form callback.
|
||||
*
|
||||
* @return
|
||||
* The per mapping configuration form. Once the form is saved, $mapping will
|
||||
* be populated with the form values.
|
||||
*/
|
||||
function path_feeds_form_callback($mapping, $target, $form, $form_state) {
|
||||
function path_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
return array(
|
||||
'pathauto_override' => array(
|
||||
'#type' => 'checkbox',
|
||||
|
@@ -2,16 +2,17 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* On behalf implementation of Feeds mapping API for user profiles.
|
||||
* On behalf implementation of Feeds mapping API for profile.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_target_alter().
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function profile_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
if ($entity_type != 'user') {
|
||||
return;
|
||||
return $targets;
|
||||
}
|
||||
|
||||
$categories = profile_user_categories();
|
||||
@@ -25,11 +26,13 @@ function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user profile target after import.
|
||||
*/
|
||||
function profile_feeds_set_target($source, $entity, $target, $value, $mapping) {
|
||||
$entity->$target = $value;
|
||||
function profile_feeds_set_target(FeedsSource $source, $entity, $target, array $values) {
|
||||
$entity->$target = reset($values);
|
||||
}
|
||||
|
@@ -2,15 +2,28 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Mapper that exposes a node's taxonomy vocabularies as mapping targets.
|
||||
* On behalf implementation of Feeds mapping API for taxonomy.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_parser_sources_alter().
|
||||
*
|
||||
* @todo: Upgrade to 7.
|
||||
* Search by term name.
|
||||
*/
|
||||
function taxonomy_feeds_parser_sources_alter(&$sources, $content_type) {
|
||||
define('FEEDS_TAXONOMY_SEARCH_TERM_NAME', 0);
|
||||
|
||||
/**
|
||||
* Search by term id.
|
||||
*/
|
||||
define('FEEDS_TAXONOMY_SEARCH_TERM_ID', 1);
|
||||
|
||||
/**
|
||||
* Search by GUID.
|
||||
*/
|
||||
define('FEEDS_TAXONOMY_SEARCH_TERM_GUID', 2);
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_parser_sources_alter().
|
||||
*/
|
||||
function taxonomy_feeds_parser_sources_alter(array &$sources, $content_type) {
|
||||
if (!empty($content_type)) {
|
||||
foreach (taxonomy_get_vocabularies($content_type) as $vocabulary) {
|
||||
$sources['parent:taxonomy:' . $vocabulary->machine_name] = array(
|
||||
@@ -36,77 +49,161 @@ function taxonomy_feeds_get_source(FeedsSource $source, FeedsParserResult $resul
|
||||
$result[] = new FeedsTermElement($term);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function taxonomy_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
|
||||
$info = field_info_field($name);
|
||||
if ($info['type'] == 'taxonomy_term_reference') {
|
||||
$targets[$name] = array(
|
||||
'name' => check_plain($instance['label']),
|
||||
'callback' => 'taxonomy_feeds_set_target',
|
||||
'description' => t('The @label field of the node.', array('@label' => $instance['label'])),
|
||||
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
|
||||
'summary_callbacks' => array('taxonomy_feeds_summary_callback'),
|
||||
'form_callbacks' => array('taxonomy_feeds_form_callback'),
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($entity_type == 'taxonomy_term') {
|
||||
$targets['tid']['name'] = t('Term id');
|
||||
$targets['tid']['description'] = t('The tid of the taxonomy term. NOTE: use this feature with care, node ids are usually assigned by Drupal.');
|
||||
unset($targets['vocabulary']);
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mapping. Here is where the actual mapping happens.
|
||||
*
|
||||
* @todo Do not create new terms for non-autotag fields.
|
||||
* Callback for mapping taxonomy terms.
|
||||
*/
|
||||
function taxonomy_feeds_set_target($source, $entity, $target, $terms) {
|
||||
if (empty($terms)) {
|
||||
return;
|
||||
}
|
||||
function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array $terms, array $mapping) {
|
||||
$language = $mapping['language'];
|
||||
|
||||
// Handle non-multiple values.
|
||||
if (!is_array($terms)) {
|
||||
$terms = array($terms);
|
||||
}
|
||||
// Add in default values.
|
||||
$mapping += array(
|
||||
'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME,
|
||||
'autocreate' => FALSE,
|
||||
);
|
||||
|
||||
$info = field_info_field($target);
|
||||
|
||||
// See http://drupal.org/node/881530
|
||||
if (isset($info['settings']['allowed_values'][0]['vocabulary'])) {
|
||||
$vocabulary = taxonomy_vocabulary_machine_name_load($info['settings']['allowed_values'][0]['vocabulary']);
|
||||
}
|
||||
else {
|
||||
$vocabulary = taxonomy_vocabulary_load($info['settings']['allowed_values'][0]['vid']);
|
||||
$cache = &drupal_static(__FUNCTION__);
|
||||
if (!isset($cache['allowed_values'][$target])) {
|
||||
$cache['allowed_values'][$target] = taxonomy_allowed_values($info);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$entity->$target = isset($entity->$target) ? $entity->$target : array();
|
||||
if (!isset($cache['allowed_vocabularies'][$target])) {
|
||||
foreach ($info['settings']['allowed_values'] as $tree) {
|
||||
if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
|
||||
$cache['allowed_vocabularies'][$target][$vocabulary->vid] = $vocabulary->machine_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some kind of configuration issue. Perhaps the vocabulary was deleted.
|
||||
// Nothing we can do about it.
|
||||
if (empty($cache['allowed_vocabularies'][$target])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', 'taxonomy_term')
|
||||
->entityCondition('bundle', $cache['allowed_vocabularies'][$target])
|
||||
->range(0, 1);
|
||||
|
||||
|
||||
$field = isset($entity->$target) ? $entity->$target : array($language => array());
|
||||
|
||||
if (!isset($field[$language])) {
|
||||
$field[$language] = array();
|
||||
}
|
||||
|
||||
// Allow for multiple mappings to the same target.
|
||||
$delta = count($field[$language]);
|
||||
|
||||
// Iterate over all values.
|
||||
foreach ($terms as $term) {
|
||||
$tid = 0;
|
||||
|
||||
if ($info['cardinality'] == $delta) {
|
||||
break;
|
||||
}
|
||||
|
||||
$tid = FALSE;
|
||||
|
||||
// FeedsTermElement already is a term.
|
||||
if ($term instanceof FeedsTermElement) {
|
||||
$tid = $term->tid;
|
||||
}
|
||||
elseif (is_numeric($term)) {
|
||||
$tid = $term;
|
||||
}
|
||||
elseif (is_string($term)) {
|
||||
$tid = taxonomy_term_check_term($term, $vocabulary->vid);
|
||||
}
|
||||
if ($tid) {
|
||||
$entity->{$target}['und'][$i]['tid'] = $tid;
|
||||
else {
|
||||
switch ($mapping['term_search']) {
|
||||
|
||||
// Lookup by name.
|
||||
case FEEDS_TAXONOMY_SEARCH_TERM_NAME:
|
||||
$term = trim($term);
|
||||
$name_query = clone $query;
|
||||
if (strlen($term) && $tids = $name_query->propertyCondition('name', $term)->execute()) {
|
||||
|
||||
// Find the first allowed term.
|
||||
foreach ($tids['taxonomy_term'] as $possible_term) {
|
||||
if (isset($cache['allowed_values'][$target][$possible_term->tid])) {
|
||||
$tid = $possible_term->tid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($mapping['autocreate'] && strlen($term)) {
|
||||
$term = (object) array(
|
||||
'name' => drupal_substr($term, 0, 255),
|
||||
'vid' => key($cache['allowed_vocabularies'][$target]),
|
||||
'vocabulary_machine_name' => reset($cache['allowed_vocabularies'][$target]),
|
||||
);
|
||||
// Set language if the taxonomy is multilingual.
|
||||
if ($language !== LANGUAGE_NONE) {
|
||||
$info = entity_get_info('taxonomy_term');
|
||||
if (!empty($info['entity keys']['language'])) {
|
||||
$term->{$info['entity keys']['language']} = $language;
|
||||
}
|
||||
}
|
||||
taxonomy_term_save($term);
|
||||
$tid = $term->tid;
|
||||
// Add to the list of allowed values.
|
||||
$cache['allowed_values'][$target][$tid] = $term->name;
|
||||
}
|
||||
break;
|
||||
|
||||
// Lookup by tid.
|
||||
case FEEDS_TAXONOMY_SEARCH_TERM_ID:
|
||||
if (is_numeric($term)) {
|
||||
$tid = (int) $term;
|
||||
}
|
||||
break;
|
||||
|
||||
// Lookup by GUID.
|
||||
case FEEDS_TAXONOMY_SEARCH_TERM_GUID:
|
||||
$tid = taxonomy_feeds_term_lookup_term_by_guid($term);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($info['cardinality'] == 1) {
|
||||
break;
|
||||
if ($tid && isset($cache['allowed_values'][$target][$tid])) {
|
||||
$field[$language][] = array('tid' => $tid);
|
||||
$delta++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
$entity->$target = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all terms associated with the given node, within one vocabulary.
|
||||
* Finds all terms associated with the given node, within one vocabulary.
|
||||
*/
|
||||
function taxonomy_feeds_node_get_terms($node, $key = 'tid') {
|
||||
$terms = &drupal_static(__FUNCTION__);
|
||||
@@ -118,7 +215,7 @@ function taxonomy_feeds_node_get_terms($node, $key = 'tid') {
|
||||
foreach ($fields as $field_name => $field) {
|
||||
if ($field['type'] == 'taxonomy_term_reference' && field_info_instance('node', $field_name, $node->type)) {
|
||||
if (($items = field_get_items('node', $node, $field_name)) && is_array($items)) {
|
||||
$tids = array_merge($tids, array_map('_taxonomy_extract_tid', $items));
|
||||
$tids = array_merge($tids, array_map('_taxonomy_feeds_extract_tid', $items));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,53 +231,86 @@ function taxonomy_feeds_node_get_terms($node, $key = 'tid') {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used in taxonomy_feeds_node_get_terms(). Extracts
|
||||
* tid from array item returned by field_get_items().
|
||||
* Extracts tid from array item returned by field_get_items().
|
||||
*
|
||||
* @param $item tid information in a form of single element array (key == 'tid', value == tid we're looking for)
|
||||
* @param array $item
|
||||
* Tid information in the form of a single element array
|
||||
* (key == 'tid', value == tid we're looking for)
|
||||
*
|
||||
* @return tid extracted from $item.
|
||||
* @return int
|
||||
* Term id extracted from $item.
|
||||
*
|
||||
* @see taxonomy_feeds_node_get_terms()
|
||||
* @see field_get_items()
|
||||
*/
|
||||
function _taxonomy_extract_tid($item) {
|
||||
function _taxonomy_feeds_extract_tid($item) {
|
||||
return $item['tid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a term identified by name and vocabulary exists. Creates a
|
||||
* new term if it does not exist.
|
||||
* Looks up a term by GUID, assumes SQL storage backend.
|
||||
*
|
||||
* @param $name
|
||||
* A term name.
|
||||
* @param $vid
|
||||
* A vocabulary id.
|
||||
* @param string $guid
|
||||
* The Feeds GUID to compare against.
|
||||
*
|
||||
* @return
|
||||
* A term id.
|
||||
* @return int|FALSE
|
||||
* The term id, or FALSE if one was not found.
|
||||
*/
|
||||
function taxonomy_term_check_term($name, $vid) {
|
||||
$name = trim($name);
|
||||
$term = taxonomy_term_lookup_term($name, $vid);
|
||||
if (empty($term)) {
|
||||
$term = new stdClass();
|
||||
$term->name = $name;
|
||||
$term->vid = $vid;
|
||||
taxonomy_term_save($term);
|
||||
return $term->tid;
|
||||
}
|
||||
return $term->tid;
|
||||
function taxonomy_feeds_term_lookup_term_by_guid($guid) {
|
||||
return db_select('feeds_item')
|
||||
->fields('feeds_item', array('entity_id'))
|
||||
->condition('entity_type', 'taxonomy_term')
|
||||
->condition('guid', $guid)
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a term, assumes SQL storage backend.
|
||||
* Mapping configuration summary for taxonomy.module.
|
||||
*/
|
||||
function taxonomy_term_lookup_term($name, $vid) {
|
||||
return db_select('taxonomy_term_data', 'td')
|
||||
->fields('td', array('tid', 'name'))
|
||||
->condition('name', $name)
|
||||
->condition('vid', $vid)
|
||||
->execute()
|
||||
->fetchObject();
|
||||
}
|
||||
function taxonomy_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
$options = _taxonomy_feeds_form_callback_options();
|
||||
if (empty($mapping['term_search'])) {
|
||||
return t('Search taxonomy terms by: <strong>@search</strong>', array('@search' => $options[FEEDS_TAXONOMY_SEARCH_TERM_NAME]));
|
||||
}
|
||||
return t('Search taxonomy terms by: <strong>@search</strong>', array('@search' => $options[$mapping['term_search']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings form callback.
|
||||
*/
|
||||
function taxonomy_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
return array(
|
||||
'term_search' => array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Search taxonomy terms by'),
|
||||
'#options' => _taxonomy_feeds_form_callback_options(),
|
||||
'#default_value' => !empty($mapping['term_search']) ? $mapping['term_search'] : FEEDS_TAXONOMY_SEARCH_TERM_NAME,
|
||||
),
|
||||
'autocreate' => array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Auto create'),
|
||||
'#description' => t("Create the term if it doesn't exist."),
|
||||
'#default_value' => !empty($mapping['autocreate']) ? $mapping['autocreate'] : 0,
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
':input[name$="[settings][term_search]"]' => array('value' => FEEDS_TAXONOMY_SEARCH_TERM_NAME),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available term search methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of taxonomy search option titles.
|
||||
*/
|
||||
function _taxonomy_feeds_form_callback_options() {
|
||||
return array(
|
||||
FEEDS_TAXONOMY_SEARCH_TERM_NAME => 'Term name',
|
||||
FEEDS_TAXONOMY_SEARCH_TERM_ID => 'Term ID',
|
||||
FEEDS_TAXONOMY_SEARCH_TERM_GUID => 'GUID',
|
||||
);
|
||||
}
|
||||
|
@@ -6,13 +6,12 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_feeds_processor_targets_alter().
|
||||
*
|
||||
* @see FeedsProcessor::getMappingTargets()
|
||||
* Implements hook_feeds_processor_targets().
|
||||
*/
|
||||
function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
|
||||
function text_feeds_processor_targets($entity_type, $bundle_name) {
|
||||
$targets = array();
|
||||
|
||||
$text_types = array(
|
||||
'list_text',
|
||||
'text',
|
||||
'text_long',
|
||||
'text_with_summary',
|
||||
@@ -26,54 +25,118 @@ function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
|
||||
'callback' => 'text_feeds_set_target',
|
||||
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
|
||||
);
|
||||
if ($info['type'] == 'text_with_summary') {
|
||||
// Allow mapping to summary.
|
||||
$targets[$name . ':summary'] = array(
|
||||
'name' => t('@name: Summary', array('@name' => $instance['label'])),
|
||||
'callback' => 'text_feeds_set_target',
|
||||
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
|
||||
'real_target' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($instance['settings']['text_processing'])) {
|
||||
$targets[$name]['summary_callbacks'] = array('text_feeds_summary_callback');
|
||||
$targets[$name]['form_callbacks'] = array('text_feeds_form_callback');
|
||||
}
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mapping text fields.
|
||||
*/
|
||||
function text_feeds_set_target($source, $entity, $target, $value) {
|
||||
if (empty($value)) {
|
||||
return;
|
||||
}
|
||||
function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
|
||||
$language = $mapping['language'];
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = array($value);
|
||||
}
|
||||
list($field_name, $column) = explode(':', $target . ':value');
|
||||
|
||||
if (isset($source->importer->processor->config['input_format'])) {
|
||||
if ($column === 'value' && isset($source->importer->processor->config['input_format'])) {
|
||||
$format = $source->importer->processor->config['input_format'];
|
||||
// Add in default values.
|
||||
$mapping += array(
|
||||
'format' => $format,
|
||||
);
|
||||
}
|
||||
|
||||
$info = field_info_field($target);
|
||||
$field = isset($entity->$field_name) ? $entity->$field_name : array($language => array());
|
||||
|
||||
// Iterate over all values.
|
||||
$field = isset($entity->$target) ? $entity->$target : array('und' => array());
|
||||
$delta = 0;
|
||||
foreach ($values as $value) {
|
||||
|
||||
// Allow for multiple mappings to the same target.
|
||||
$delta = count($field['und']);
|
||||
|
||||
foreach ($value as $v) {
|
||||
|
||||
if ($info['cardinality'] == $delta) {
|
||||
break;
|
||||
if (is_object($value) && $value instanceof FeedsElement) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
|
||||
if (is_object($v) && ($v instanceof FeedsElement)) {
|
||||
$v = $v->getValue();
|
||||
}
|
||||
if (is_scalar($value) && strlen($value)) {
|
||||
|
||||
if (is_scalar($v)) {
|
||||
$field['und'][$delta]['value'] = $v;
|
||||
$field[$language][$delta][$column] = (string) $value;
|
||||
|
||||
if (isset($format)) {
|
||||
$field['und'][$delta]['format'] = $format;
|
||||
if (isset($mapping['format'])) {
|
||||
$field[$language][$delta]['format'] = $mapping['format'];
|
||||
}
|
||||
|
||||
$delta++;
|
||||
}
|
||||
|
||||
$delta++;
|
||||
}
|
||||
|
||||
$entity->$target = $field;
|
||||
$entity->$field_name = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary callback for text field targets.
|
||||
*
|
||||
* Displays which text format will be used for the text field target.
|
||||
*
|
||||
* @see text_feeds_processor_targets()
|
||||
* @see text_feeds_form_callback()
|
||||
*/
|
||||
function text_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
global $user;
|
||||
$formats = filter_formats($user);
|
||||
|
||||
// Processor-wide input format setting.
|
||||
$importer = feeds_importer($form['#importer']);
|
||||
$default_format = !empty($importer->processor->config['input_format']) ? $importer->processor->config['input_format'] : filter_fallback_format();
|
||||
$mapping += array(
|
||||
'format' => $default_format,
|
||||
);
|
||||
|
||||
return t('Text format: %format', array('%format' => $formats[$mapping['format']]->name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Form callback for text field targets.
|
||||
*
|
||||
* Allows to select a text format for the text field target.
|
||||
*
|
||||
* @see text_feeds_processor_targets()
|
||||
* @see text_feeds_summary_callback()
|
||||
*/
|
||||
function text_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
|
||||
global $user;
|
||||
$formats_options = array();
|
||||
$formats = filter_formats($user);
|
||||
foreach ($formats as $id => $format) {
|
||||
$formats_options[$id] = $format->name;
|
||||
}
|
||||
|
||||
// Processor-wide text format setting.
|
||||
$importer = feeds_importer($form['#importer']);
|
||||
$default_format = !empty($importer->processor->config['input_format']) ? $importer->processor->config['input_format'] : filter_fallback_format();
|
||||
$mapping += array(
|
||||
'format' => $default_format,
|
||||
);
|
||||
|
||||
return array(
|
||||
'format' => array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Text format'),
|
||||
'#options' => $formats_options,
|
||||
'#default_value' => $mapping['format'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@@ -20,15 +20,20 @@ class FeedsCSVParser extends FeedsParser {
|
||||
// Load and configure parser.
|
||||
feeds_include_library('ParserCSV.inc', 'ParserCSV');
|
||||
$parser = new ParserCSV();
|
||||
$delimiter = $source_config['delimiter'] == 'TAB' ? "\t" : $source_config['delimiter'];
|
||||
$delimiter = $this->getDelimiterChar($source_config);
|
||||
$parser->setDelimiter($delimiter);
|
||||
if (isset($source_config['encoding'])) {
|
||||
// Encoding can only be set when the mbstring extension is loaded.
|
||||
$parser->setEncoding($source_config['encoding']);
|
||||
}
|
||||
|
||||
$iterator = new ParserCSVIterator($fetcher_result->getFilePath());
|
||||
if (empty($source_config['no_headers'])) {
|
||||
// Get first line and use it for column names, convert them to lower case.
|
||||
$header = $this->parseHeader($parser, $iterator);
|
||||
if (!$header) {
|
||||
return;
|
||||
drupal_set_message(t('The CSV file is empty.'), 'warning', FALSE);
|
||||
return new FeedsParserResult();
|
||||
}
|
||||
$parser->setColumnNames($header);
|
||||
}
|
||||
@@ -100,12 +105,20 @@ class FeedsCSVParser extends FeedsParser {
|
||||
return parent::getSourceElement($source, $result, drupal_strtolower($element_key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::getMappingSourceList() to use only lower keys.
|
||||
*/
|
||||
public function getMappingSourceList() {
|
||||
return array_map('drupal_strtolower', parent::getMappingSourceList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Define defaults.
|
||||
*/
|
||||
public function sourceDefaults() {
|
||||
return array(
|
||||
'delimiter' => $this->config['delimiter'],
|
||||
'encoding' => $this->config['encoding'],
|
||||
'no_headers' => $this->config['no_headers'],
|
||||
);
|
||||
}
|
||||
@@ -122,26 +135,40 @@ class FeedsCSVParser extends FeedsParser {
|
||||
$mappings = feeds_importer($this->id)->processor->config['mappings'];
|
||||
$sources = $uniques = array();
|
||||
foreach ($mappings as $mapping) {
|
||||
$sources[] = check_plain($mapping['source']);
|
||||
if ($mapping['unique']) {
|
||||
$uniques[] = check_plain($mapping['source']);
|
||||
if (strpos($mapping['source'], ',') !== FALSE) {
|
||||
$sources[] = '"' . $mapping['source'] . '"';
|
||||
}
|
||||
else {
|
||||
$sources[] = $mapping['source'];
|
||||
}
|
||||
if (!empty($mapping['unique'])) {
|
||||
$uniques[] = $mapping['source'];
|
||||
}
|
||||
}
|
||||
$sources = array_unique($sources);
|
||||
|
||||
$output = t('Import !csv_files with one or more of these columns: !columns.', array('!csv_files' => l(t('CSV files'), 'http://en.wikipedia.org/wiki/Comma-separated_values'), '!columns' => implode(', ', $sources)));
|
||||
$output = t('Import !csv_files with one or more of these columns: @columns.', array('!csv_files' => l(t('CSV files'), 'http://en.wikipedia.org/wiki/Comma-separated_values'), '@columns' => implode(', ', $sources)));
|
||||
$items = array();
|
||||
$items[] = format_plural(count($uniques), t('Column <strong>!column</strong> is mandatory and considered unique: only one item per !column value will be created.', array('!column' => implode(', ', $uniques))), t('Columns <strong>!columns</strong> are mandatory and values in these columns are considered unique: only one entry per value in one of these column will be created.', array('!columns' => implode(', ', $uniques))));
|
||||
$items[] = format_plural(count($uniques), 'Column <strong>@columns</strong> is mandatory and considered unique: only one item per @columns value will be created.', 'Columns <strong>@columns</strong> are mandatory and values in these columns are considered unique: only one entry per value in one of these column will be created.', array('@columns' => implode(', ', $uniques)));
|
||||
$items[] = l(t('Download a template'), 'import/' . $this->id . '/template');
|
||||
$form['help']['#markup'] = '<div class="help"><p>' . $output . '</p>' . theme('item_list', array('items' => $items)) . '</div>';
|
||||
$form['help'] = array(
|
||||
'#prefix' => '<div class="help">',
|
||||
'#suffix' => '</div>',
|
||||
'description' => array(
|
||||
'#prefix' => '<p>',
|
||||
'#markup' => $output,
|
||||
'#suffix' => '</p>',
|
||||
),
|
||||
'list' => array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
),
|
||||
);
|
||||
$form['delimiter'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Delimiter'),
|
||||
'#description' => t('The character that delimits fields in the CSV file.'),
|
||||
'#options' => array(
|
||||
',' => ',',
|
||||
';' => ';',
|
||||
'TAB' => 'TAB',
|
||||
),
|
||||
'#options' => $this->getAllDelimiterTypes(),
|
||||
'#default_value' => isset($source_config['delimiter']) ? $source_config['delimiter'] : ',',
|
||||
);
|
||||
$form['no_headers'] = array(
|
||||
@@ -150,6 +177,10 @@ class FeedsCSVParser extends FeedsParser {
|
||||
'#description' => t('Check if the imported CSV file does not start with a header row. If checked, mapping sources must be named \'0\', \'1\', \'2\' etc.'),
|
||||
'#default_value' => isset($source_config['no_headers']) ? $source_config['no_headers'] : 0,
|
||||
);
|
||||
$form['encoding'] = $this->configEncodingForm();
|
||||
if (isset($source_config['encoding'])) {
|
||||
$form['encoding']['#default_value'] = $source_config['encoding'];
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -159,6 +190,7 @@ class FeedsCSVParser extends FeedsParser {
|
||||
public function configDefaults() {
|
||||
return array(
|
||||
'delimiter' => ',',
|
||||
'encoding' => 'UTF-8',
|
||||
'no_headers' => 0,
|
||||
);
|
||||
}
|
||||
@@ -172,11 +204,7 @@ class FeedsCSVParser extends FeedsParser {
|
||||
'#type' => 'select',
|
||||
'#title' => t('Default delimiter'),
|
||||
'#description' => t('Default field delimiter.'),
|
||||
'#options' => array(
|
||||
',' => ',',
|
||||
';' => ';',
|
||||
'TAB' => 'TAB',
|
||||
),
|
||||
'#options' => $this->getAllDelimiterTypes(),
|
||||
'#default_value' => $this->config['delimiter'],
|
||||
);
|
||||
$form['no_headers'] = array(
|
||||
@@ -185,32 +213,162 @@ class FeedsCSVParser extends FeedsParser {
|
||||
'#description' => t('Check if the imported CSV file does not start with a header row. If checked, mapping sources must be named \'0\', \'1\', \'2\' etc.'),
|
||||
'#default_value' => $this->config['no_headers'],
|
||||
);
|
||||
$form['encoding'] = $this->configEncodingForm();
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds configuration field for setting file encoding.
|
||||
*
|
||||
* If the mbstring extension is not available a markup render array
|
||||
* will be returned instead.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*/
|
||||
public function configEncodingForm() {
|
||||
if (extension_loaded('mbstring') && variable_get('feeds_use_mbstring', TRUE)) {
|
||||
// Get the system's list of available encodings.
|
||||
$options = mb_list_encodings();
|
||||
// Make the key/values the same in the array.
|
||||
$options = array_combine($options, $options);
|
||||
// Sort alphabetically not-case sensitive.
|
||||
natcasesort($options);
|
||||
return array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('File encoding'),
|
||||
'#description' => t('Performs character encoding conversion from selected option to UTF-8.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->config['encoding'],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return array(
|
||||
'#markup' => '<em>' . t('PHP mbstring extension must be available for character encoding conversion.') . '</em>',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTemplate() {
|
||||
$mappings = feeds_importer($this->id)->processor->config['mappings'];
|
||||
$sources = $uniques = array();
|
||||
|
||||
foreach ($mappings as $mapping) {
|
||||
if ($mapping['unique']) {
|
||||
$uniques[] = check_plain($mapping['source']);
|
||||
if (in_array($mapping['source'], $uniques) || in_array($mapping['source'], $sources)) {
|
||||
// Skip columns we've already seen.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($mapping['unique'])) {
|
||||
$uniques[] = $mapping['source'];
|
||||
}
|
||||
else {
|
||||
$sources[] = check_plain($mapping['source']);
|
||||
$sources[] = $mapping['source'];
|
||||
}
|
||||
}
|
||||
$sep = ',';
|
||||
|
||||
$sep = $this->getDelimiterChar($this->config);
|
||||
$columns = array();
|
||||
|
||||
foreach (array_merge($uniques, $sources) as $col) {
|
||||
if (strpos($col, $sep) !== FALSE) {
|
||||
$col = '"' . str_replace('"', '""', $col) . '"';
|
||||
}
|
||||
|
||||
$columns[] = $col;
|
||||
}
|
||||
drupal_add_http_header('Cache-Control', 'max-age=60, must-revalidate');
|
||||
drupal_add_http_header('Content-Disposition', 'attachment; filename="' . $this->id . '_template.csv"');
|
||||
drupal_add_http_header('Content-type', 'text/csv; charset=utf-8');
|
||||
|
||||
$template_file_details = $this->getTemplateFileDetails($this->config);
|
||||
|
||||
$filename = "{$this->id}_template.{$template_file_details['extension']}";
|
||||
$cache_control = 'max-age=60, must-revalidate';
|
||||
$content_disposition = 'attachment; filename="' . $filename . '"';
|
||||
$content_type = "{$template_file_details['mime_type']}; charset=utf-8";
|
||||
|
||||
drupal_add_http_header('Cache-Control', $cache_control);
|
||||
drupal_add_http_header('Content-Disposition', $content_disposition);
|
||||
drupal_add_http_header('Content-type', $content_type);
|
||||
|
||||
print implode($sep, $columns);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an associative array of the delimiters supported by this parser.
|
||||
*
|
||||
* The keys represent the value that is persisted into the database, and the
|
||||
* value represents the text that is shown in the admins UI.
|
||||
*
|
||||
* @return array
|
||||
* The associative array of delimiter types to display name.
|
||||
*/
|
||||
protected function getAllDelimiterTypes() {
|
||||
$delimiters = array(
|
||||
',',
|
||||
';',
|
||||
'TAB',
|
||||
'|',
|
||||
'+',
|
||||
);
|
||||
|
||||
return array_combine($delimiters, $delimiters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate delimiter character for the delimiter in the config.
|
||||
*
|
||||
* @param array $config
|
||||
* The configuration for the parser.
|
||||
*
|
||||
* @return string
|
||||
* The delimiter character.
|
||||
*/
|
||||
protected function getDelimiterChar(array $config) {
|
||||
$config_delimiter = $config['delimiter'];
|
||||
|
||||
switch ($config_delimiter) {
|
||||
case 'TAB':
|
||||
$delimiter = "\t";
|
||||
break;
|
||||
|
||||
default:
|
||||
$delimiter = $config_delimiter;
|
||||
break;
|
||||
}
|
||||
|
||||
return $delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets details about the template file, for the delimiter in the config.
|
||||
*
|
||||
* The resulting details indicate the file extension and mime type for the
|
||||
* delimiter type.
|
||||
*
|
||||
* @param array $config
|
||||
* The configuration for the parser.
|
||||
*
|
||||
* @return array
|
||||
* An array with the following information:
|
||||
* - 'extension': The file extension for the template ('tsv', 'csv', etc).
|
||||
* - 'mime-type': The mime type for the template
|
||||
* ('text/tab-separated-values', 'text/csv', etc).
|
||||
*/
|
||||
protected function getTemplateFileDetails(array $config) {
|
||||
switch ($config['delimiter']) {
|
||||
case 'TAB':
|
||||
$extension = 'tsv';
|
||||
$mime_type = 'text/tab-separated-values';
|
||||
break;
|
||||
|
||||
default:
|
||||
$extension = 'csv';
|
||||
$mime_type = 'text/csv';
|
||||
break;
|
||||
}
|
||||
|
||||
return array(
|
||||
'extension' => $extension,
|
||||
'mime_type' => $mime_type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -113,6 +113,13 @@ class FeedsFetcherResult extends FeedsResult {
|
||||
*/
|
||||
abstract class FeedsFetcher extends FeedsPlugin {
|
||||
|
||||
/**
|
||||
* Implements FeedsPlugin::pluginType().
|
||||
*/
|
||||
public function pluginType() {
|
||||
return 'fetcher';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch content from a source and return it.
|
||||
*
|
||||
|
@@ -19,7 +19,7 @@ class FeedsFileFetcherResult extends FeedsFetcherResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides parent::getRaw();
|
||||
* Overrides parent::getRaw().
|
||||
*/
|
||||
public function getRaw() {
|
||||
return $this->sanitizeRaw(file_get_contents($this->file_path));
|
||||
@@ -29,7 +29,7 @@ class FeedsFileFetcherResult extends FeedsFetcherResult {
|
||||
* Overrides parent::getFilePath().
|
||||
*/
|
||||
public function getFilePath() {
|
||||
if (!file_exists($this->file_path)) {
|
||||
if (!is_readable($this->file_path)) {
|
||||
throw new Exception(t('File @filepath is not accessible.', array('@filepath' => $this->file_path)));
|
||||
}
|
||||
return $this->sanitizeFile($this->file_path);
|
||||
@@ -69,25 +69,25 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of files in a directory.
|
||||
* Returns an array of files in a directory.
|
||||
*
|
||||
* @param $dir
|
||||
* @param string $dir
|
||||
* A stream wreapper URI that is a directory.
|
||||
*
|
||||
* @return
|
||||
* An array of stream wrapper URIs pointing to files. The array is empty
|
||||
* if no files could be found. Never contains directories.
|
||||
* @return array
|
||||
* An array of stream wrapper URIs pointing to files. The array is empty if
|
||||
* no files could be found. Never contains directories.
|
||||
*/
|
||||
protected function listFiles($dir) {
|
||||
$dir = file_stream_wrapper_uri_normalize($dir);
|
||||
// Seperate out string into array of extensions. Make sure its regex safe.
|
||||
$config = $this->getConfig();
|
||||
$extensions = array_filter(array_map('preg_quote', explode(' ', $config['allowed_extensions'])));
|
||||
$regex = '/\.(' . implode('|', $extensions) . ')$/';
|
||||
$files = array();
|
||||
if ($items = @scandir($dir)) {
|
||||
foreach ($items as $item) {
|
||||
if (is_file("$dir/$item") && strpos($item, '.') !== 0) {
|
||||
$files[] = "$dir/$item";
|
||||
}
|
||||
}
|
||||
foreach (file_scan_directory($dir, $regex) as $file) {
|
||||
$files[] = $file->uri;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
'#type' => 'file',
|
||||
'#title' => empty($this->config['direct']) ? t('File') : NULL,
|
||||
'#description' => empty($source_config['source']) ? t('Select a file from your local system.') : t('Select a different file from your local system.'),
|
||||
'#theme' => 'feeds_upload',
|
||||
'#theme_wrappers' => array('feeds_upload'),
|
||||
'#file_info' => empty($source_config['fid']) ? NULL : file_load($source_config['fid']),
|
||||
'#size' => 10,
|
||||
);
|
||||
@@ -118,7 +118,7 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
$form['source'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('File'),
|
||||
'#description' => t('Specify a path to a file or a directory. Path must start with @scheme://', array('@scheme' => file_default_scheme())),
|
||||
'#description' => t('Specify a path to a file or a directory. Prefix the path with a scheme. Available schemes: @schemes.', array('@schemes' => implode(', ', $this->config['allowed_schemes']))),
|
||||
'#default_value' => empty($source_config['source']) ? '' : $source_config['source'],
|
||||
);
|
||||
}
|
||||
@@ -126,34 +126,53 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::sourceFormValidate().
|
||||
* Overrides parent::sourceFormValidate().
|
||||
*/
|
||||
public function sourceFormValidate(&$values) {
|
||||
$values['source'] = trim($values['source']);
|
||||
|
||||
$feed_dir = 'public://feeds';
|
||||
file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
|
||||
if (empty($this->config['direct'])) {
|
||||
|
||||
// If there is a file uploaded, save it, otherwise validate input on
|
||||
// file.
|
||||
// @todo: Track usage of file, remove file when removing source.
|
||||
if ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
|
||||
$values['source'] = $file->uri;
|
||||
$values['file'] = $file;
|
||||
$feed_dir = $this->config['directory'];
|
||||
|
||||
if (!file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
if (user_access('administer feeds')) {
|
||||
$plugin_key = feeds_importer($this->id)->config[$this->pluginType()]['plugin_key'];
|
||||
$link = url('admin/structure/feeds/' . $this->id . '/settings/' . $plugin_key);
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed. Please check the upload <a href="@link">settings.</a>', array('@link' => $link)));
|
||||
}
|
||||
else {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed. Please contact your site administrator.'));
|
||||
}
|
||||
watchdog('feeds', 'The upload directory %directory required by a feed could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $feed_dir));
|
||||
}
|
||||
// Validate and save uploaded file.
|
||||
elseif ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
|
||||
$values['source'] = $file->uri;
|
||||
$values['file'] = $file;
|
||||
}
|
||||
elseif (empty($values['source'])) {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('Please upload a file.'));
|
||||
}
|
||||
else {
|
||||
// File present from previous upload. Nothing to validate.
|
||||
}
|
||||
}
|
||||
elseif (empty($values['source'])) {
|
||||
form_set_error('feeds][source', t('Upload a file first.'));
|
||||
}
|
||||
// If a file has not been uploaded and $values['source'] is not empty, make
|
||||
// sure that this file is within Drupal's files directory as otherwise
|
||||
// potentially any file that the web server has access to could be exposed.
|
||||
elseif (strpos($values['source'], file_default_scheme()) !== 0) {
|
||||
form_set_error('feeds][source', t('File needs to reside within the site\'s file directory, its path needs to start with @scheme://.', array('@scheme' => file_default_scheme())));
|
||||
else {
|
||||
// Check if chosen url scheme is allowed.
|
||||
$scheme = file_uri_scheme($values['source']);
|
||||
if (!$scheme || !in_array($scheme, $this->config['allowed_schemes'])) {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t("The file needs to reside within the site's files directory, its path needs to start with scheme://. Available schemes: @schemes.", array('@schemes' => implode(', ', $this->config['allowed_schemes']))));
|
||||
}
|
||||
// Check whether the given path is readable.
|
||||
elseif (!is_readable($values['source'])) {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('The specified file or directory does not exist.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::sourceSave().
|
||||
* Overrides parent::sourceSave().
|
||||
*/
|
||||
public function sourceSave(FeedsSource $source) {
|
||||
$source_config = $source->getConfigFor($this);
|
||||
@@ -176,7 +195,7 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::sourceDelete().
|
||||
* Overrides parent::sourceDelete().
|
||||
*/
|
||||
public function sourceDelete(FeedsSource $source) {
|
||||
$source_config = $source->getConfigFor($this);
|
||||
@@ -186,17 +205,22 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configDefaults().
|
||||
* Overrides parent::configDefaults().
|
||||
*/
|
||||
public function configDefaults() {
|
||||
$schemes = $this->getSchemes();
|
||||
$scheme = in_array('private', $schemes) ? 'private' : 'public';
|
||||
|
||||
return array(
|
||||
'allowed_extensions' => 'txt csv tsv xml opml',
|
||||
'direct' => FALSE,
|
||||
'directory' => $scheme . '://feeds',
|
||||
'allowed_schemes' => $schemes,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configForm().
|
||||
* Overrides parent::configForm().
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$form = array();
|
||||
@@ -214,16 +238,112 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
are already on the server.'),
|
||||
'#default_value' => $this->config['direct'],
|
||||
);
|
||||
$form['directory'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Upload directory'),
|
||||
'#description' => t('Directory where uploaded files get stored. Prefix the path with a scheme. Available schemes: @schemes.', array('@schemes' => implode(', ', $this->getSchemes()))),
|
||||
'#default_value' => $this->config['directory'],
|
||||
'#states' => array(
|
||||
'visible' => array(':input[name="direct"]' => array('checked' => FALSE)),
|
||||
'required' => array(':input[name="direct"]' => array('checked' => FALSE)),
|
||||
),
|
||||
);
|
||||
if ($options = $this->getSchemeOptions()) {
|
||||
$form['allowed_schemes'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Allowed schemes'),
|
||||
'#default_value' => $this->config['allowed_schemes'],
|
||||
'#options' => $options,
|
||||
'#description' => t('Select the schemes you want to allow for direct upload.'),
|
||||
'#states' => array(
|
||||
'visible' => array(':input[name="direct"]' => array('checked' => TRUE)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper. Deletes a file.
|
||||
* Overrides parent::configFormValidate().
|
||||
*
|
||||
* Ensure that the chosen directory is accessible.
|
||||
*/
|
||||
public function configFormValidate(&$values) {
|
||||
|
||||
$values['directory'] = trim($values['directory']);
|
||||
$values['allowed_schemes'] = array_filter($values['allowed_schemes']);
|
||||
|
||||
if (!$values['direct']) {
|
||||
// Ensure that the upload directory field is not empty when not in
|
||||
// direct-mode.
|
||||
if (!$values['directory']) {
|
||||
form_set_error('directory', t('Please specify an upload directory.'));
|
||||
// Do not continue validating the directory if none was specified.
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the URI scheme of the upload directory.
|
||||
$scheme = file_uri_scheme($values['directory']);
|
||||
if (!$scheme || !in_array($scheme, $this->getSchemes())) {
|
||||
form_set_error('directory', t('Please enter a valid scheme into the directory location.'));
|
||||
|
||||
// Return here so that attempts to create the directory below don't
|
||||
// throw warnings.
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the upload directory exists.
|
||||
if (!file_prepare_directory($values['directory'], FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
form_set_error('directory', t('The chosen directory does not exist and attempts to create it failed.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a file.
|
||||
*
|
||||
* @param int $fid
|
||||
* The file id.
|
||||
* @param int $feed_nid
|
||||
* The feed node's id, or 0 if a standalone feed.
|
||||
*
|
||||
* @return bool|array
|
||||
* TRUE for success, FALSE in the event of an error, or an array if the file
|
||||
* is being used by any modules.
|
||||
*
|
||||
* @see file_delete()
|
||||
*/
|
||||
protected function deleteFile($fid, $feed_nid) {
|
||||
if ($file = file_load($fid)) {
|
||||
file_usage_delete($file, 'feeds', get_class($this), $feed_nid);
|
||||
file_delete($file);
|
||||
return file_delete($file);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available schemes.
|
||||
*
|
||||
* @return array
|
||||
* The available schemes.
|
||||
*/
|
||||
protected function getSchemes() {
|
||||
return array_keys(file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available scheme options for use in checkboxes or select list.
|
||||
*
|
||||
* @return array
|
||||
* The available scheme array keyed scheme => description
|
||||
*/
|
||||
protected function getSchemeOptions() {
|
||||
$options = array();
|
||||
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
|
||||
$options[$scheme] = check_plain($scheme . ': ' . $info['description']);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -11,28 +11,70 @@ feeds_include_library('PuSHSubscriber.inc', 'PuSHSubscriber');
|
||||
* Result of FeedsHTTPFetcher::fetch().
|
||||
*/
|
||||
class FeedsHTTPFetcherResult extends FeedsFetcherResult {
|
||||
|
||||
/**
|
||||
* The URL of the feed being fetched.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
protected $file_path;
|
||||
|
||||
/**
|
||||
* The timeout in seconds to wait for a download.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
*
|
||||
* Whether to ignore SSL validation errors.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $acceptInvalidCert;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($url = NULL) {
|
||||
$this->url = $url;
|
||||
parent::__construct('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsFetcherResult::getRaw();
|
||||
*/
|
||||
public function getRaw() {
|
||||
feeds_include_library('http_request.inc', 'http_request');
|
||||
$result = http_request_get($this->url);
|
||||
if (!in_array($result->code, array(200, 201, 202, 203, 204, 205, 206))) {
|
||||
throw new Exception(t('Download of @url failed with code !code.', array('@url' => $this->url, '!code' => $result->code)));
|
||||
if (!isset($this->raw)) {
|
||||
feeds_include_library('http_request.inc', 'http_request');
|
||||
$result = http_request_get($this->url, NULL, NULL, $this->acceptInvalidCert, $this->timeout);
|
||||
if (!in_array($result->code, array(200, 201, 202, 203, 204, 205, 206))) {
|
||||
throw new Exception(t('Download of @url failed with code !code.', array('@url' => $this->url, '!code' => $result->code)));
|
||||
}
|
||||
$this->raw = $result->data;
|
||||
}
|
||||
return $this->sanitizeRaw($result->data);
|
||||
|
||||
return $this->sanitizeRaw($this->raw);
|
||||
}
|
||||
|
||||
public function getTimeout() {
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
public function setTimeout($timeout) {
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accept invalid certificates option.
|
||||
*
|
||||
* @param bool $accept_invalid_cert
|
||||
* Whether to accept invalid certificates.
|
||||
*/
|
||||
public function setAcceptInvalidCert($accept_invalid_cert) {
|
||||
$this->acceptInvalidCert = (bool) $accept_invalid_cert;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +90,11 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
if ($this->config['use_pubsubhubbub'] && ($raw = $this->subscriber($source->feed_nid)->receive())) {
|
||||
return new FeedsFetcherResult($raw);
|
||||
}
|
||||
return new FeedsHTTPFetcherResult($source_config['source']);
|
||||
$fetcher_result = new FeedsHTTPFetcherResult($source_config['source']);
|
||||
// When request_timeout is empty, the global value is used.
|
||||
$fetcher_result->setTimeout($this->config['request_timeout']);
|
||||
$fetcher_result->setAcceptInvalidCert($this->config['accept_invalid_cert']);
|
||||
return $fetcher_result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +141,9 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
'auto_detect_feeds' => FALSE,
|
||||
'use_pubsubhubbub' => FALSE,
|
||||
'designated_hub' => '',
|
||||
'request_timeout' => NULL,
|
||||
'auto_scheme' => 'http',
|
||||
'accept_invalid_cert' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,15 +164,46 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
'#description' => t('Attempt to use a <a href="http://en.wikipedia.org/wiki/PubSubHubbub">PubSubHubbub</a> subscription if available.'),
|
||||
'#default_value' => $this->config['use_pubsubhubbub'],
|
||||
);
|
||||
$form['designated_hub'] = array(
|
||||
$form['advanced'] = array(
|
||||
'#title' => t('Advanced settings'),
|
||||
'#type' => 'fieldset',
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['advanced']['auto_scheme'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Automatically add scheme'),
|
||||
'#description' => t('If the supplied URL does not contain the scheme, use this one automatically. Keep empty to force the user to input the scheme.'),
|
||||
'#default_value' => $this->config['auto_scheme'],
|
||||
);
|
||||
$form['advanced']['designated_hub'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Designated hub'),
|
||||
'#description' => t('Enter the URL of a designated PubSubHubbub hub (e. g. superfeedr.com). If given, this hub will be used instead of the hub specified in the actual feed.'),
|
||||
'#default_value' => $this->config['designated_hub'],
|
||||
'#dependency' => array(
|
||||
'edit-use-pubsubhubbub' => array(1),
|
||||
'#states' => array(
|
||||
'visible' => array(':input[name="use_pubsubhubbub"]' => array('checked' => TRUE)),
|
||||
),
|
||||
);
|
||||
// Per importer override of global http request timeout setting.
|
||||
$form['advanced']['request_timeout'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Request timeout'),
|
||||
'#description' => t('Timeout in seconds to wait for an HTTP get request to finish.</br>' .
|
||||
'<b>Note:</b> this setting will override the global setting.</br>' .
|
||||
'When left empty, the global value is used.'),
|
||||
'#default_value' => $this->config['request_timeout'],
|
||||
'#element_validate' => array('element_validate_integer_positive'),
|
||||
'#maxlength' => 3,
|
||||
'#size'=> 30,
|
||||
);
|
||||
$form['advanced']['accept_invalid_cert'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Accept invalid SSL certificates'),
|
||||
'#description' => t('<strong>IMPORTANT:</strong> This setting will force cURL to completely ignore all SSL errors. This is a <strong>major security risk</strong> and should only be used during development.'),
|
||||
'#default_value' => $this->config['accept_invalid_cert'],
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -149,13 +229,24 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
public function sourceFormValidate(&$values) {
|
||||
$values['source'] = trim($values['source']);
|
||||
|
||||
// Keep a copy for error messages.
|
||||
$original_url = $values['source'];
|
||||
|
||||
$parts = parse_url($values['source']);
|
||||
if (empty($parts['scheme']) && $this->config['auto_scheme']) {
|
||||
$values['source'] = $this->config['auto_scheme'] . '://' . $values['source'];
|
||||
}
|
||||
|
||||
if (!feeds_valid_url($values['source'], TRUE)) {
|
||||
$form_key = 'feeds][' . get_class($this) . '][source';
|
||||
form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $values['source'])));
|
||||
form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $original_url)));
|
||||
}
|
||||
elseif ($this->config['auto_detect_feeds']) {
|
||||
feeds_include_library('http_request.inc', 'http_request');
|
||||
if ($url = http_request_get_common_syndication($values['source'])) {
|
||||
$url = http_request_get_common_syndication($values['source'], array(
|
||||
'accept_invalid_cert' => $this->config['accept_invalid_cert'],
|
||||
));
|
||||
if ($url) {
|
||||
$values['source'] = $url;
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,17 @@
|
||||
* Class definition of FeedsNodeProcessor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option for handling content in Drupal but not in source data (unpublish
|
||||
* instead of skip/delete).
|
||||
*/
|
||||
define('FEEDS_UNPUBLISH_NON_EXISTENT', 'unpublish');
|
||||
|
||||
/**
|
||||
* Creates nodes from feed items.
|
||||
*/
|
||||
class FeedsNodeProcessor extends FeedsProcessor {
|
||||
|
||||
/**
|
||||
* Define entity type.
|
||||
*/
|
||||
@@ -29,11 +36,11 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Creates a new node in memory and returns it.
|
||||
*/
|
||||
protected function newEntity(FeedsSource $source) {
|
||||
$node = new stdClass();
|
||||
$node->type = $this->config['content_type'];
|
||||
$node = parent::newEntity($source);
|
||||
$node->type = $this->bundle();
|
||||
$node->changed = REQUEST_TIME;
|
||||
$node->created = REQUEST_TIME;
|
||||
$node->language = LANGUAGE_NONE;
|
||||
$node->is_new = TRUE;
|
||||
node_object_prepare($node);
|
||||
// Populate properties that are set by node_object_prepare().
|
||||
$node->log = 'Created by FeedsNodeProcessor';
|
||||
@@ -50,14 +57,12 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* @todo Reevaluate the use of node_object_prepare().
|
||||
*/
|
||||
protected function entityLoad(FeedsSource $source, $nid) {
|
||||
if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
|
||||
$node = node_load($nid, NULL, TRUE);
|
||||
}
|
||||
else {
|
||||
// We're replacing the existing node. Only save the absolutely necessary.
|
||||
$node = db_query("SELECT created, nid, vid, type, status FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetchObject();
|
||||
$node = parent::entityLoad($source, $nid);
|
||||
|
||||
if ($this->config['update_existing'] != FEEDS_UPDATE_EXISTING) {
|
||||
$node->uid = $this->config['author'];
|
||||
}
|
||||
|
||||
node_object_prepare($node);
|
||||
|
||||
// Workaround for issue #1247506. See #1245094 for backstory.
|
||||
@@ -87,7 +92,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
|
||||
$author = user_load($entity->uid);
|
||||
|
||||
// If the uid was mapped directly, rather than by email or username, it
|
||||
// If the uid was mapped directly, rather than by email or username, it
|
||||
// could be invalid.
|
||||
if (!$author) {
|
||||
$message = 'User %uid is not a valid user.';
|
||||
@@ -104,20 +109,34 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
if (!$access) {
|
||||
$message = 'User %name is not authorized to %op content type %content_type.';
|
||||
throw new FeedsAccessException(t($message, array('%name' => $author->name, '%op' => $op, '%content_type' => $entity->type)));
|
||||
$message = t('The user %name is not authorized to %op content of type %content_type. To import this item, either the user "@name" (author of the item) must be given the permission to @op content of type @content_type, or the option "Authorize" on the Node processor settings must be turned off.', array(
|
||||
'%name' => $author->name,
|
||||
'%op' => $op,
|
||||
'%content_type' => $entity->type,
|
||||
'@name' => $author->name,
|
||||
'@op' => $op,
|
||||
'@content_type' => $entity->type,
|
||||
));
|
||||
throw new FeedsAccessException($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node.
|
||||
*/
|
||||
protected function entityValidate($entity) {
|
||||
parent::entityValidate($entity);
|
||||
|
||||
if (!isset($entity->uid) || !is_numeric($entity->uid)) {
|
||||
$entity->uid = $this->config['author'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a node.
|
||||
*/
|
||||
public function entitySave($entity) {
|
||||
// If nid is set and a node with that id doesn't exist, flag as new.
|
||||
if (!empty($entity->nid) && !node_load($entity->nid)) {
|
||||
$entity->is_new = TRUE;
|
||||
}
|
||||
node_save($entity);
|
||||
}
|
||||
|
||||
@@ -129,28 +148,12 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement expire().
|
||||
*
|
||||
* @todo: move to processor stage?
|
||||
* Overrides parent::expiryQuery().
|
||||
*/
|
||||
public function expire($time = NULL) {
|
||||
if ($time === NULL) {
|
||||
$time = $this->expiryTime();
|
||||
}
|
||||
if ($time == FEEDS_EXPIRE_NEVER) {
|
||||
return;
|
||||
}
|
||||
$count = $this->getLimit();
|
||||
$nodes = db_query_range("SELECT n.nid FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND n.created < :created", 0, $count, array(':id' => $this->id, ':created' => REQUEST_TIME - $time));
|
||||
$nids = array();
|
||||
foreach ($nodes as $node) {
|
||||
$nids[$node->nid] = $node->nid;
|
||||
}
|
||||
$this->entityDeleteMultiple($nids);
|
||||
if (db_query_range("SELECT 1 FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND n.created < :created", 0, 1, array(':id' => $this->id, ':created' => REQUEST_TIME - $time))->fetchField()) {
|
||||
return FEEDS_BATCH_ACTIVE;
|
||||
}
|
||||
return FEEDS_BATCH_COMPLETE;
|
||||
protected function expiryQuery(FeedsSource $source, $time) {
|
||||
$select = parent::expiryQuery($source, $time);
|
||||
$select->condition('e.created', REQUEST_TIME - $time, '<');
|
||||
return $select;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,10 +167,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Override parent::configDefaults().
|
||||
*/
|
||||
public function configDefaults() {
|
||||
$types = node_type_get_names();
|
||||
$type = isset($types['article']) ? 'article' : key($types);
|
||||
return array(
|
||||
'content_type' => $type,
|
||||
'expire' => FEEDS_EXPIRE_NEVER,
|
||||
'author' => 0,
|
||||
'authorize' => TRUE,
|
||||
@@ -178,16 +178,8 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Override parent::configForm().
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$types = node_type_get_names();
|
||||
array_walk($types, 'check_plain');
|
||||
$form = parent::configForm($form_state);
|
||||
$form['content_type'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Content type'),
|
||||
'#description' => t('Select the content type for the nodes to be created. <strong>Note:</strong> Users with "import !feed_id feeds" permissions will be able to <strong>import</strong> nodes of the content type selected here regardless of the node level permissions. Further, users with "clear !feed_id permissions" will be able to <strong>delete</strong> imported nodes regardless of their node level permissions.', array('!feed_id' => $this->id)),
|
||||
'#options' => $types,
|
||||
'#default_value' => $this->config['content_type'],
|
||||
);
|
||||
|
||||
$author = user_load($this->config['author']);
|
||||
$form['author'] = array(
|
||||
'#type' => 'textfield',
|
||||
@@ -207,14 +199,13 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
'#type' => 'select',
|
||||
'#title' => t('Expire nodes'),
|
||||
'#options' => $period,
|
||||
'#description' => t('Select after how much time nodes should be deleted. The node\'s published date will be used for determining the node\'s age, see Mapping settings.'),
|
||||
'#description' => t("Select after how much time nodes should be deleted. The node's published date will be used for determining the node's age, see Mapping settings."),
|
||||
'#default_value' => $this->config['expire'],
|
||||
);
|
||||
$form['update_existing']['#options'] = array(
|
||||
FEEDS_SKIP_EXISTING => 'Do not update existing nodes',
|
||||
FEEDS_REPLACE_EXISTING => 'Replace existing nodes',
|
||||
FEEDS_UPDATE_EXISTING => 'Update existing nodes (slower than replacing them)',
|
||||
);
|
||||
// Add on the "Unpublish" option for nodes, update wording.
|
||||
if (isset($form['update_non_existent'])) {
|
||||
$form['update_non_existent']['#options'][FEEDS_UNPUBLISH_NON_EXISTENT] = t('Unpublish non-existent nodes');
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -248,10 +239,16 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
case 'created':
|
||||
$target_node->created = feeds_to_unixtime($value, REQUEST_TIME);
|
||||
break;
|
||||
case 'changed':
|
||||
// The 'changed' value will be set on the node in feeds_node_presave().
|
||||
// This is because node_save() always overwrites this value (though
|
||||
// before invoking hook_node_presave()).
|
||||
$target_node->feeds_item->node_changed = feeds_to_unixtime($value, REQUEST_TIME);
|
||||
break;
|
||||
case 'feeds_source':
|
||||
// Get the class of the feed node importer's fetcher and set the source
|
||||
// property. See feeds_node_update() how $node->feeds gets stored.
|
||||
if ($id = feeds_get_importer_id($this->config['content_type'])) {
|
||||
if ($id = feeds_get_importer_id($this->bundle())) {
|
||||
$class = get_class(feeds_importer($id)->fetcher);
|
||||
$target_node->feeds[$class]['source'] = $value;
|
||||
// This effectively suppresses 'import on submission' feature.
|
||||
@@ -279,9 +276,10 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Return available mapping targets.
|
||||
*/
|
||||
public function getMappingTargets() {
|
||||
$type = node_type_get_type($this->config['content_type']);
|
||||
$type = node_type_get_type($this->bundle());
|
||||
|
||||
$targets = parent::getMappingTargets();
|
||||
if ($type->has_title) {
|
||||
if ($type && $type->has_title) {
|
||||
$targets['title'] = array(
|
||||
'name' => t('Title'),
|
||||
'description' => t('The title of the node.'),
|
||||
@@ -313,6 +311,10 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
'name' => t('Published date'),
|
||||
'description' => t('The UNIX time when a node has been published.'),
|
||||
);
|
||||
$targets['changed'] = array(
|
||||
'name' => t('Updated date'),
|
||||
'description' => t('The Unix timestamp when a node has been last updated.'),
|
||||
);
|
||||
$targets['promote'] = array(
|
||||
'name' => t('Promoted to front page'),
|
||||
'description' => t('Boolean value, whether or not node is promoted to front page. (1 = promoted, 0 = not promoted)'),
|
||||
@@ -339,7 +341,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
// If the target content type is a Feed node, expose its source field.
|
||||
if ($id = feeds_get_importer_id($this->config['content_type'])) {
|
||||
if ($id = feeds_get_importer_id($this->bundle())) {
|
||||
$name = feeds_importer($id)->config['name'];
|
||||
$targets['feeds_source'] = array(
|
||||
'name' => t('Feed source'),
|
||||
@@ -348,11 +350,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
// Let other modules expose mapping targets.
|
||||
self::loadMappers();
|
||||
$entity_type = $this->entityType();
|
||||
$bundle = $this->config['content_type'];
|
||||
drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
|
||||
$this->getHookTargets($targets);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
@@ -373,10 +371,10 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
$nid = db_query("SELECT nid FROM {node} WHERE nid = :nid", array(':nid' => $value))->fetchField();
|
||||
break;
|
||||
case 'title':
|
||||
$nid = db_query("SELECT nid FROM {node} WHERE title = :title AND type = :type", array(':title' => $value, ':type' => $this->config['content_type']))->fetchField();
|
||||
$nid = db_query("SELECT nid FROM {node} WHERE title = :title AND type = :type", array(':title' => $value, ':type' => $this->bundle()))->fetchField();
|
||||
break;
|
||||
case 'feeds_source':
|
||||
if ($id = feeds_get_importer_id($this->config['content_type'])) {
|
||||
if ($id = feeds_get_importer_id($this->bundle())) {
|
||||
$nid = db_query("SELECT fs.feed_nid FROM {node} n JOIN {feeds_source} fs ON n.nid = fs.feed_nid WHERE fs.id = :id AND fs.source = :source", array(':id' => $id, ':source' => $value))->fetchField();
|
||||
}
|
||||
break;
|
||||
@@ -388,4 +386,34 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsProcessor::clean().
|
||||
*
|
||||
* Allow unpublish instead of delete.
|
||||
*
|
||||
* @param FeedsState $state
|
||||
* The FeedsState object for the given stage.
|
||||
*/
|
||||
protected function clean(FeedsState $state) {
|
||||
// Delegate to parent if not unpublishing or option not set.
|
||||
if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] != FEEDS_UNPUBLISH_NON_EXISTENT) {
|
||||
return parent::clean($state);
|
||||
}
|
||||
|
||||
$total = count($state->removeList);
|
||||
if ($total) {
|
||||
$nodes = node_load_multiple($state->removeList);
|
||||
foreach ($nodes as &$node) {
|
||||
$this->loadItemInfo($node);
|
||||
// Update the hash value of the feed item to ensure that the item gets
|
||||
// updated in case it reappears in the feed.
|
||||
$node->feeds_item->hash = $this->config['update_non_existent'];
|
||||
node_unpublish_action($node);
|
||||
node_save($node);
|
||||
$state->unpublished++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -53,6 +53,13 @@ class FeedsParserResult extends FeedsResult {
|
||||
*/
|
||||
abstract class FeedsParser extends FeedsPlugin {
|
||||
|
||||
/**
|
||||
* Implements FeedsPlugin::pluginType().
|
||||
*/
|
||||
public function pluginType() {
|
||||
return 'parser';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse content fetched by fetcher.
|
||||
*
|
||||
@@ -112,6 +119,21 @@ abstract class FeedsParser extends FeedsPlugin {
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of mapped sources.
|
||||
*
|
||||
* @return array
|
||||
* List of mapped source names in an array.
|
||||
*/
|
||||
public function getMappingSourceList() {
|
||||
$mappings = feeds_importer($this->id)->processor->config['mappings'];
|
||||
$sources = array();
|
||||
foreach ($mappings as $mapping) {
|
||||
$sources[] = $mapping['source'];
|
||||
}
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element identified by $element_key of the given item.
|
||||
* The element key corresponds to the values in the array returned by
|
||||
@@ -257,7 +279,27 @@ class FeedsGeoTermElement extends FeedsTermElement {
|
||||
* Enclosure element, can be part of the result array.
|
||||
*/
|
||||
class FeedsEnclosure extends FeedsElement {
|
||||
protected $mime_type;
|
||||
|
||||
/**
|
||||
* The mime type of the enclosure.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
protected $mime_type;
|
||||
|
||||
/**
|
||||
* The default list of allowed extensions.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
protected $allowedExtensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
||||
|
||||
/**
|
||||
* The sanitized local file name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $safeFilename;
|
||||
|
||||
/**
|
||||
* Constructor, requires MIME type.
|
||||
@@ -280,6 +322,17 @@ class FeedsEnclosure extends FeedsElement {
|
||||
return $this->mime_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of allowed extensions.
|
||||
*
|
||||
* @param string $extensions
|
||||
* The list of allowed extensions separated by a space.
|
||||
*/
|
||||
public function setAllowedExtensions($extensions) {
|
||||
// Normalize whitespace so that empty extensions are not allowed.
|
||||
$this->allowedExtensions = drupal_strtolower(trim(preg_replace('/\s+/', ' ', $extensions)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method instead of FeedsElement::getValue() when fetching the file
|
||||
* from the URL.
|
||||
@@ -294,20 +347,74 @@ class FeedsEnclosure extends FeedsElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method instead of FeedsElement::getValue() to get the file name
|
||||
* transformed for better local saving (underscores instead of spaces)
|
||||
* Returns the full path to the file URI with a safe file name.
|
||||
*
|
||||
* @return
|
||||
* Value with space characters changed to underscores.
|
||||
* @return string
|
||||
* The safe file URI.
|
||||
*
|
||||
* @see FeedsElement::getValue()
|
||||
* @throws RuntimeException
|
||||
* Thrown if the file extension is invalid.
|
||||
*/
|
||||
public function getLocalValue() {
|
||||
return str_replace(' ', '_', $this->getValue());
|
||||
public function getSanitizedUri() {
|
||||
return drupal_dirname($this->getValue()) . '/' . $this->getSafeFilename();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* Returns the file name transformed for better local saving.
|
||||
*
|
||||
* @return string
|
||||
* Value with space characters changed to underscores.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* Thrown if the file extension is invalid.
|
||||
*/
|
||||
public function getLocalValue() {
|
||||
return str_replace(' ', '_', $this->getSafeFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the safe file name.
|
||||
*
|
||||
* @return string
|
||||
* A filename that is safe to save to the filesystem.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* Thrown if the file extension is invalid.
|
||||
*/
|
||||
protected function getSafeFilename() {
|
||||
if (isset($this->safeFilename)) {
|
||||
return $this->safeFilename;
|
||||
}
|
||||
|
||||
// Strip any query string or fragment from file name.
|
||||
list($filename) = explode('?', $this->getValue());
|
||||
list($filename) = explode('#', $filename);
|
||||
|
||||
$filename = rawurldecode(drupal_basename($filename));
|
||||
|
||||
// Remove leading and trailing whitespace and periods.
|
||||
$filename = trim($filename, " \t\n\r\0\x0B.");
|
||||
|
||||
if (strpos($filename, '.') === FALSE) {
|
||||
$extension = FALSE;
|
||||
}
|
||||
else {
|
||||
$extension = drupal_strtolower(substr($filename, strrpos($filename, '.') + 1));
|
||||
}
|
||||
|
||||
if (!$extension || !in_array($extension, explode(' ', $this->allowedExtensions), TRUE)) {
|
||||
throw new RuntimeException(t('The file @file has an invalid extension.', array('@file' => $filename)));
|
||||
}
|
||||
|
||||
$this->safeFilename = file_munge_filename($filename, $this->allowedExtensions, FALSE);
|
||||
|
||||
return $this->safeFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the content from the file URL.
|
||||
*
|
||||
* @return string
|
||||
* The content of the referenced resource.
|
||||
*/
|
||||
public function getContent() {
|
||||
@@ -333,18 +440,19 @@ class FeedsEnclosure extends FeedsElement {
|
||||
* If file object could not be created.
|
||||
*/
|
||||
public function getFile($destination) {
|
||||
|
||||
$file = NULL;
|
||||
if ($this->getValue()) {
|
||||
// Prepare destination directory.
|
||||
file_prepare_directory($destination, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY);
|
||||
// Copy or save file depending on whether it is remote or local.
|
||||
if (drupal_realpath($this->getValue())) {
|
||||
if (drupal_realpath($this->getSanitizedUri())) {
|
||||
$file = new stdClass();
|
||||
$file->uid = 0;
|
||||
$file->uri = $this->getValue();
|
||||
$file->filemime = $this->mime_type;
|
||||
$file->filename = basename($file->uri);
|
||||
if (dirname($file->uri) != $destination) {
|
||||
$file->uri = $this->getSanitizedUri();
|
||||
$file->filemime = $this->getMIMEType();
|
||||
$file->filename = $this->getSafeFilename();
|
||||
|
||||
if (drupal_dirname($file->uri) !== $destination) {
|
||||
$file = file_copy($file, $destination);
|
||||
}
|
||||
else {
|
||||
@@ -361,15 +469,17 @@ class FeedsEnclosure extends FeedsElement {
|
||||
}
|
||||
}
|
||||
else {
|
||||
$filename = basename($this->getLocalValue());
|
||||
if (module_exists('transliteration')) {
|
||||
require_once drupal_get_path('module', 'transliteration') . '/transliteration.inc';
|
||||
$filename = transliteration_clean_filename($filename);
|
||||
}
|
||||
if (file_uri_target($destination)) {
|
||||
$destination = trim($destination, '/') . '/';
|
||||
}
|
||||
try {
|
||||
$filename = $this->getLocalValue();
|
||||
|
||||
if (module_exists('transliteration')) {
|
||||
require_once drupal_get_path('module', 'transliteration') . '/transliteration.inc';
|
||||
$filename = transliteration_clean_filename($filename);
|
||||
}
|
||||
|
||||
$file = file_save_data($this->getContent(), $destination . $filename);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
@@ -381,8 +491,9 @@ class FeedsEnclosure extends FeedsElement {
|
||||
if (!$file) {
|
||||
throw new Exception(t('Invalid enclosure %enclosure', array('%enclosure' => $this->getValue())));
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,13 +569,13 @@ class FeedsDateTimeElement extends FeedsElement {
|
||||
* Helper method for buildDateField(). Build a FeedsDateTimeElement object
|
||||
* from a standard formatted node.
|
||||
*/
|
||||
protected static function readDateField($entity, $field_name) {
|
||||
protected static function readDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) {
|
||||
$ret = new FeedsDateTimeElement();
|
||||
if (isset($entity->{$field_name}['und'][0]['date']) && $entity->{$field_name}['und'][0]['date'] instanceof FeedsDateTime) {
|
||||
$ret->start = $entity->{$field_name}['und'][0]['date'];
|
||||
if (isset($entity->{$field_name}[$language][$delta]['date']) && $entity->{$field_name}[$language][$delta]['date'] instanceof FeedsDateTime) {
|
||||
$ret->start = $entity->{$field_name}[$language][$delta]['date'];
|
||||
}
|
||||
if (isset($entity->{$field_name}['und'][0]['date2']) && $entity->{$field_name}['und'][0]['date2'] instanceof FeedsDateTime) {
|
||||
$ret->end = $entity->{$field_name}['und'][0]['date2'];
|
||||
if (isset($entity->{$field_name}[$language][$delta]['date2']) && $entity->{$field_name}[$language][$delta]['date2'] instanceof FeedsDateTime) {
|
||||
$ret->end = $entity->{$field_name}[$language][$delta]['date2'];
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
@@ -472,15 +583,17 @@ class FeedsDateTimeElement extends FeedsElement {
|
||||
/**
|
||||
* Build a entity's date field from our object.
|
||||
*
|
||||
* @param $entity
|
||||
* @param object $entity
|
||||
* The entity to build the date field on.
|
||||
* @param $field_name
|
||||
* @param str $field_name
|
||||
* The name of the field to build.
|
||||
* @param int $delta
|
||||
* The delta in the field.
|
||||
*/
|
||||
public function buildDateField($entity, $field_name) {
|
||||
public function buildDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) {
|
||||
$info = field_info_field($field_name);
|
||||
|
||||
$oldfield = FeedsDateTimeElement::readDateField($entity, $field_name);
|
||||
$oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta, $language);
|
||||
// Merge with any preexisting objects on the field; we take precedence.
|
||||
$oldfield = $this->merge($oldfield);
|
||||
$use_start = $oldfield->start;
|
||||
@@ -513,27 +626,27 @@ class FeedsDateTimeElement extends FeedsElement {
|
||||
|
||||
$db_tz = new DateTimeZone($db_tz);
|
||||
if (!isset($entity->{$field_name})) {
|
||||
$entity->{$field_name} = array('und' => array());
|
||||
$entity->{$field_name} = array($language => array());
|
||||
}
|
||||
if ($use_start) {
|
||||
$entity->{$field_name}['und'][0]['timezone'] = $use_start->getTimezone()->getName();
|
||||
$entity->{$field_name}['und'][0]['offset'] = $use_start->getOffset();
|
||||
$entity->{$field_name}[$language][$delta]['timezone'] = $use_start->getTimezone()->getName();
|
||||
$entity->{$field_name}[$language][$delta]['offset'] = $use_start->getOffset();
|
||||
$use_start->setTimezone($db_tz);
|
||||
$entity->{$field_name}['und'][0]['date'] = $use_start;
|
||||
$entity->{$field_name}[$language][$delta]['date'] = $use_start;
|
||||
/**
|
||||
* @todo the date_type_format line could be simplified based upon a patch
|
||||
* DO issue #259308 could affect this, follow up on at some point.
|
||||
* Without this, all granularity info is lost.
|
||||
* $use_start->format(date_type_format($field['type'], $use_start->granularity));
|
||||
*/
|
||||
$entity->{$field_name}['und'][0]['value'] = $use_start->format(date_type_format($info['type']));
|
||||
$entity->{$field_name}[$language][$delta]['value'] = $use_start->format(date_type_format($info['type']));
|
||||
}
|
||||
if ($use_end) {
|
||||
// Don't ever use end to set timezone (for now)
|
||||
$entity->{$field_name}['und'][0]['offset2'] = $use_end->getOffset();
|
||||
$entity->{$field_name}[$language][$delta]['offset2'] = $use_end->getOffset();
|
||||
$use_end->setTimezone($db_tz);
|
||||
$entity->{$field_name}['und'][0]['date2'] = $use_end;
|
||||
$entity->{$field_name}['und'][0]['value2'] = $use_end->format(date_type_format($info['type']));
|
||||
$entity->{$field_name}[$language][$delta]['date2'] = $use_end;
|
||||
$entity->{$field_name}[$language][$delta]['value2'] = $use_end->format(date_type_format($info['type']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,12 +696,17 @@ class FeedsDateTime extends DateTime {
|
||||
* PHP DateTimeZone object, NULL allowed
|
||||
*/
|
||||
public function __construct($time = '', $tz = NULL) {
|
||||
// Assume UNIX timestamp if numeric.
|
||||
if (is_numeric($time)) {
|
||||
// Make sure it's not a simple year
|
||||
if ((is_string($time) && strlen($time) > 4) || is_int($time)) {
|
||||
// Assume UNIX timestamp if it doesn't look like a simple year.
|
||||
if (strlen($time) > 4) {
|
||||
$time = "@" . $time;
|
||||
}
|
||||
// If it's a year, add a default month too, because PHP's date functions
|
||||
// won't parse standalone years after 2000 correctly (see explanation at
|
||||
// http://aaronsaray.com/blog/2007/07/11/helpful-strtotime-reminders/#comment-47).
|
||||
else {
|
||||
$time = 'January ' . $time;
|
||||
}
|
||||
}
|
||||
|
||||
// PHP < 5.3 doesn't like the GMT- notation for parsing timezones.
|
||||
@@ -596,7 +714,7 @@ class FeedsDateTime extends DateTime {
|
||||
$time = str_replace("GMT+", "+", $time);
|
||||
|
||||
// Some PHP 5.2 version's DateTime class chokes on invalid dates.
|
||||
if (!strtotime($time)) {
|
||||
if (!date_create($time)) {
|
||||
$time = 'now';
|
||||
}
|
||||
|
||||
|
@@ -20,13 +20,84 @@ class FeedsResult {}
|
||||
abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInterface {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* The plugin definition.
|
||||
*
|
||||
* Initialize class variables.
|
||||
* @var array
|
||||
*/
|
||||
protected $pluginDefinition;
|
||||
|
||||
/**
|
||||
* Constructs a FeedsPlugin object.
|
||||
*
|
||||
* A copy of FeedsConfigurable::__construct() that doesn't call
|
||||
* configDefaults() so that we avoid circular dependencies.
|
||||
*
|
||||
* @param string $id
|
||||
* The importer id.
|
||||
*/
|
||||
protected function __construct($id) {
|
||||
parent::__construct($id);
|
||||
$this->source_config = $this->sourceDefaults();
|
||||
$this->id = $id;
|
||||
$this->export_type = FEEDS_EXPORT_NONE;
|
||||
$this->disabled = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a FeedsPlugin object.
|
||||
*
|
||||
* Don't use directly, use feeds_plugin() instead.
|
||||
*
|
||||
* @see feeds_plugin()
|
||||
*/
|
||||
public static function instance($class, $id, array $plugin_definition = array()) {
|
||||
if (!strlen($id)) {
|
||||
throw new InvalidArgumentException(t('Empty configuration identifier.'));
|
||||
}
|
||||
|
||||
$instances = &drupal_static(__METHOD__, array());
|
||||
|
||||
if (!isset($instances[$class][$id])) {
|
||||
$instance = new $class($id);
|
||||
|
||||
// The ordering here is important. The plugin definition should be usable
|
||||
// in getConfig().
|
||||
$instance->setPluginDefinition($plugin_definition);
|
||||
$instance->setConfig($instance->configDefaults());
|
||||
$instances[$class][$id] = $instance;
|
||||
}
|
||||
|
||||
return $instances[$class][$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of plugin.
|
||||
*
|
||||
* @return string
|
||||
* One of either 'fetcher', 'parser', or 'processor'.
|
||||
*/
|
||||
abstract public function pluginType();
|
||||
|
||||
/**
|
||||
* Returns the plugin definition.
|
||||
*
|
||||
* @return array
|
||||
* The plugin definition array.
|
||||
*
|
||||
* @see ctools_get_plugins()
|
||||
*/
|
||||
public function pluginDefinition() {
|
||||
return $this->pluginDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plugin definition.
|
||||
*
|
||||
* This is protected since we're only using it in FeedsPlugin::instance().
|
||||
*
|
||||
* @param array $plugin_definition
|
||||
* The plugin definition.
|
||||
*/
|
||||
protected function setPluginDefinition(array $plugin_definition) {
|
||||
$this->pluginDefinition = $plugin_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +161,7 @@ abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInter
|
||||
*
|
||||
* @todo: Use CTools Plugin API.
|
||||
*/
|
||||
protected static function loadMappers() {
|
||||
public static function loadMappers() {
|
||||
static $loaded = FALSE;
|
||||
if (!$loaded) {
|
||||
$path = drupal_get_path('module', 'feeds') . '/mappers';
|
||||
@@ -197,15 +268,102 @@ abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInter
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FeedsConfigurable::dependencies().
|
||||
*/
|
||||
public function dependencies() {
|
||||
$dependencies = parent::dependencies();
|
||||
|
||||
// Find out which module provides this plugin.
|
||||
$plugin_info = $this->pluginDefinition();
|
||||
if (isset($plugin_info['module'])) {
|
||||
$dependencies[$plugin_info['module']] = $plugin_info['module'];
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a plugin is missing.
|
||||
*/
|
||||
class FeedsMissingPlugin extends FeedsPlugin {
|
||||
public function pluginType() {
|
||||
return 'missing';
|
||||
}
|
||||
|
||||
public function save() {}
|
||||
|
||||
/**
|
||||
* Fetcher methods.
|
||||
*/
|
||||
public function fetch(FeedsSource $source) {
|
||||
return new FeedsFetcherResult('');
|
||||
}
|
||||
|
||||
public function clear(FeedsSource $source) {}
|
||||
|
||||
public function request($feed_nid = 0) {
|
||||
drupal_access_denied();
|
||||
}
|
||||
|
||||
public function menuItem() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function subscribe(FeedsSource $source) {}
|
||||
|
||||
public function unsubscribe(FeedsSource $source) {}
|
||||
|
||||
public function importPeriod(FeedsSource $source) {}
|
||||
|
||||
/**
|
||||
* Parser methods.
|
||||
*/
|
||||
public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
|
||||
return new FeedsParserResult();
|
||||
}
|
||||
|
||||
public function getMappingSources() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor methods.
|
||||
*/
|
||||
public function process(FeedsSource $source, FeedsParserResult $parser_result) {}
|
||||
|
||||
public function entityType() {}
|
||||
|
||||
public function bundle() {}
|
||||
|
||||
public function bundleOptions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getMappings() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getMappingTargets() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function expire(FeedsSource $source, $time = NULL) {}
|
||||
|
||||
public function itemCount(FeedsSource $source) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function expiryTime() {
|
||||
return FEEDS_EXPIRE_NEVER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ class FeedsSimplePieEnclosure extends FeedsEnclosure {
|
||||
/**
|
||||
* Serialization helper.
|
||||
*
|
||||
* Handle the simplepie enclosure class seperately ourselves.
|
||||
* Handle the simplepie enclosure class separately ourselves.
|
||||
*/
|
||||
public function __sleep() {
|
||||
$this->_serialized_simplepie_enclosure = serialize($this->simplepie_enclosure);
|
||||
@@ -52,6 +52,7 @@ class FeedsSimplePieEnclosure extends FeedsEnclosure {
|
||||
public function getMIMEType() {
|
||||
return $this->simplepie_enclosure->get_real_type();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,10 +68,6 @@ class FeedsSimplePieParser extends FeedsParser {
|
||||
public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
|
||||
feeds_include_simplepie();
|
||||
|
||||
// Please be quiet SimplePie.
|
||||
$level = error_reporting();
|
||||
error_reporting($level ^ E_DEPRECATED ^ E_STRICT);
|
||||
|
||||
// Initialize SimplePie.
|
||||
$parser = new SimplePie();
|
||||
$parser->set_raw_data($fetcher_result->getRaw());
|
||||
@@ -149,8 +146,7 @@ class FeedsSimplePieParser extends FeedsParser {
|
||||
}
|
||||
// Release parser.
|
||||
unset($parser);
|
||||
// Set error reporting back to its previous value.
|
||||
error_reporting($level);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -237,4 +233,5 @@ class FeedsSimplePieParser extends FeedsParser {
|
||||
$words = array_slice($words, 0, 3);
|
||||
return implode(' ', $words);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
protected function entityInfo() {
|
||||
$info = parent::entityInfo();
|
||||
$info['label plural'] = t('Terms');
|
||||
$info['bundle name'] = t('Vocabulary');
|
||||
return $info;
|
||||
}
|
||||
|
||||
@@ -30,25 +31,35 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
*/
|
||||
protected function newEntity(FeedsSource $source) {
|
||||
$vocabulary = $this->vocabulary();
|
||||
$term = new stdClass();
|
||||
$term = parent::newEntity($source);
|
||||
$term->vid = $vocabulary->vid;
|
||||
$term->vocabulary_machine_name = $vocabulary->machine_name;
|
||||
$term->format = isset($this->config['input_format']) ? $this->config['input_format'] : filter_fallback_format();
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an existing term.
|
||||
* Load an existing entity.
|
||||
*/
|
||||
protected function entityLoad(FeedsSource $source, $tid) {
|
||||
return taxonomy_term_load($tid);
|
||||
protected function entityLoad(FeedsSource $source, $entity_id) {
|
||||
$entity = parent::entityLoad($source, $entity_id);
|
||||
|
||||
// Avoid missing bundle errors when term has been loaded directly from db.
|
||||
if (empty($entity->vocabulary_machine_name) && !empty($entity->vid)) {
|
||||
$vocabulary = taxonomy_vocabulary_load($entity->vid);
|
||||
$entity->vocabulary_machine_name = ($vocabulary) ? $vocabulary->machine_name : NULL;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a term.
|
||||
*/
|
||||
protected function entityValidate($term) {
|
||||
if (empty($term->name)) {
|
||||
parent::entityValidate($term);
|
||||
|
||||
if (drupal_strlen($term->name) == 0) {
|
||||
throw new FeedsValidationException(t('Term name missing.'));
|
||||
}
|
||||
}
|
||||
@@ -90,37 +101,11 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configForm().
|
||||
* Overrides parent::setTargetElement().
|
||||
*
|
||||
* Operate on a target item that is a taxonomy term.
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$options = array(0 => t('Select a vocabulary'));
|
||||
foreach (taxonomy_get_vocabularies() as $vocab) {
|
||||
$options[$vocab->machine_name] = $vocab->name;
|
||||
}
|
||||
$form = parent::configForm($form_state);
|
||||
$form['vocabulary'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Import to vocabulary'),
|
||||
'#description' => t('Choose the vocabulary to import into. <strong>CAUTION:</strong> when deleting terms through the "Delete items" tab, Feeds will delete <em>all</em> terms from this vocabulary.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->config['vocabulary'],
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configFormValidate().
|
||||
*/
|
||||
public function configFormValidate(&$values) {
|
||||
if (empty($values['vocabulary'])) {
|
||||
form_set_error('vocabulary', t('Choose a vocabulary'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override setTargetElement to operate on a target item that is a taxonomy term.
|
||||
*/
|
||||
public function setTargetElement(FeedsSource $source, $target_term, $target_element, $value) {
|
||||
public function setTargetElement(FeedsSource $source, $target_term, $target_element, $value, array $mapping = array()) {
|
||||
switch ($target_element) {
|
||||
case 'parent':
|
||||
if (!empty($value)) {
|
||||
@@ -142,15 +127,26 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
$target_term->parent[] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'parentguid':
|
||||
// value is parent_guid field value
|
||||
$parent_tid = 0;
|
||||
$query = db_select('feeds_item')
|
||||
->fields('feeds_item', array('entity_id'))
|
||||
->condition('entity_type', $this->entityType());
|
||||
$parent_tid = $query->condition('guid', $value)->execute()->fetchField();
|
||||
$target_term->parent[] = ($parent_tid) ? $parent_tid : 0;
|
||||
|
||||
$term_ids = array_keys($query->condition('guid', $value)->execute()->fetchAllAssoc('entity_id'));
|
||||
if (!empty($term_ids)) {
|
||||
$terms = entity_load($this->entityType(), $term_ids);
|
||||
foreach ($terms as $term) {
|
||||
if ($term->vid == $target_term->vid) {
|
||||
$parent_tid = $term->tid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$target_term->parent[] = $parent_tid;
|
||||
break;
|
||||
|
||||
case 'weight':
|
||||
if (!empty($value)) {
|
||||
$weight = intval($value);
|
||||
@@ -160,6 +156,20 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
}
|
||||
$target_term->weight = $weight;
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
if (!empty($mapping['format'])) {
|
||||
$target_term->format = $mapping['format'];
|
||||
}
|
||||
elseif (!empty($this->config['input_format'])) {
|
||||
$target_term->format = $this->config['input_format'];
|
||||
}
|
||||
else {
|
||||
$target_term->format = filter_fallback_format();
|
||||
}
|
||||
$target_term->description = $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::setTargetElement($source, $target_term, $target_element, $value);
|
||||
break;
|
||||
@@ -195,19 +205,13 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
'description' => array(
|
||||
'name' => t('Term description'),
|
||||
'description' => t('Description of the taxonomy term.'),
|
||||
'summary_callbacks' => array('text_feeds_summary_callback'),
|
||||
'form_callbacks' => array('text_feeds_form_callback'),
|
||||
),
|
||||
);
|
||||
|
||||
// Let implementers of hook_feeds_term_processor_targets() add their targets.
|
||||
try {
|
||||
self::loadMappers();
|
||||
$entity_type = $this->entityType();
|
||||
$bundle = $this->vocabulary()->machine_name;
|
||||
drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
$this->getHookTargets($targets);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
@@ -235,11 +239,19 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
* Return vocabulary to map to.
|
||||
*/
|
||||
public function vocabulary() {
|
||||
if (isset($this->config['vocabulary'])) {
|
||||
if ($vocabulary = taxonomy_vocabulary_machine_name_load($this->config['vocabulary'])) {
|
||||
return $vocabulary;
|
||||
}
|
||||
if ($vocabulary = taxonomy_vocabulary_machine_name_load($this->bundle())) {
|
||||
return $vocabulary;
|
||||
}
|
||||
throw new Exception(t('No vocabulary defined for Taxonomy Term processor.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsProcessor::dependencies().
|
||||
*/
|
||||
public function dependencies() {
|
||||
$dependencies = parent::dependencies();
|
||||
$dependencies['taxonomy'] = 'taxonomy';
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,13 +2,21 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* FeedsUserProcessor class.
|
||||
* Contains FeedsUserProcessor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option to block users not found in the feed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
define('FEEDS_BLOCK_NON_EXISTENT', 'block');
|
||||
|
||||
/**
|
||||
* Feeds processor plugin. Create users from feed items.
|
||||
*/
|
||||
class FeedsUserProcessor extends FeedsProcessor {
|
||||
|
||||
/**
|
||||
* Define entity type.
|
||||
*/
|
||||
@@ -29,10 +37,11 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Creates a new user account in memory and returns it.
|
||||
*/
|
||||
protected function newEntity(FeedsSource $source) {
|
||||
$account = new stdClass();
|
||||
$account = parent::newEntity($source);
|
||||
$account->uid = 0;
|
||||
$account->roles = array_filter($this->config['roles']);
|
||||
$account->status = $this->config['status'];
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
@@ -40,8 +49,9 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Loads an existing user.
|
||||
*/
|
||||
protected function entityLoad(FeedsSource $source, $uid) {
|
||||
$user = parent::entityLoad($source, $uid);
|
||||
|
||||
// Copy the password so that we can compare it again at save.
|
||||
$user = user_load($uid);
|
||||
$user->feeds_original_pass = $user->pass;
|
||||
return $user;
|
||||
}
|
||||
@@ -50,6 +60,8 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Validates a user account.
|
||||
*/
|
||||
protected function entityValidate($account) {
|
||||
parent::entityValidate($account);
|
||||
|
||||
if (empty($account->name) || empty($account->mail) || !valid_email_address($account->mail)) {
|
||||
throw new FeedsValidationException(t('User name missing or email not valid.'));
|
||||
}
|
||||
@@ -87,9 +99,7 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Delete multiple user accounts.
|
||||
*/
|
||||
protected function entityDeleteMultiple($uids) {
|
||||
foreach ($uids as $uid) {
|
||||
user_delete($uid);
|
||||
}
|
||||
user_delete_multiple($uids);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,19 +137,13 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
'#options' => $roles,
|
||||
);
|
||||
}
|
||||
// @todo Implement true updating.
|
||||
$form['update_existing'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Replace existing users'),
|
||||
'#description' => t('If an existing user is found for an imported user, replace it. Existing users will be determined using mappings that are a "unique target".'),
|
||||
'#default_value' => $this->config['update_existing'],
|
||||
);
|
||||
$form['defuse_mail'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Defuse e-mail addresses'),
|
||||
'#description' => t('This appends _test to all imported e-mail addresses to ensure they cannot be used as recipients.'),
|
||||
'#default_value' => $this->config['defuse_mail'],
|
||||
);
|
||||
$form['update_non_existent']['#options'][FEEDS_BLOCK_NON_EXISTENT] = t('Block non-existent users');
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -201,11 +205,7 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
// Let other modules expose mapping targets.
|
||||
self::loadMappers();
|
||||
$entity_type = $this->entityType();
|
||||
$bundle = $this->entityType();
|
||||
drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
|
||||
$this->getHookTargets($targets);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
@@ -239,4 +239,35 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsProcessor::clean().
|
||||
*
|
||||
* Block users instead of deleting them.
|
||||
*
|
||||
* @param FeedsState $state
|
||||
* The FeedsState object for the given stage.
|
||||
*/
|
||||
protected function clean(FeedsState $state) {
|
||||
// Delegate to parent if not blocking or option not set.
|
||||
if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] !== FEEDS_BLOCK_NON_EXISTENT) {
|
||||
return parent::clean($state);
|
||||
}
|
||||
|
||||
if (!empty($state->removeList)) {
|
||||
// @see user_user_operations_block().
|
||||
// The following foreach is copied from above function but with an added
|
||||
// counter to count blocked users.
|
||||
foreach (user_load_multiple($state->removeList) as $account) {
|
||||
$this->loadItemInfo($account);
|
||||
$account->feeds_item->hash = $this->config['update_non_existent'];
|
||||
// For efficiency manually save the original account before applying any
|
||||
// changes.
|
||||
$account->original = clone $account;
|
||||
user_save($account, array('status' => 0));
|
||||
$state->blocked++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ class CommonSyndicationParserTestCase extends DrupalWebTestCase {
|
||||
$this->_testRSS10();
|
||||
$this->_testRSS2();
|
||||
$this->_testAtomGeoRSS();
|
||||
$this->_testAtomGeoRSSWithoutAuthor();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +83,14 @@ class CommonSyndicationParserTestCase extends DrupalWebTestCase {
|
||||
$this->assertEqual($feed['items'][3]['geolocations'][0]['lon'], '172.5902');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests parsing an Atom feed without an author.
|
||||
*/
|
||||
protected function _testAtomGeoRSSWithoutAuthor() {
|
||||
$string = $this->readFeed('earthquake-georss-noauthor.atom');
|
||||
$feed = common_syndication_parser_parse($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a feed.
|
||||
*/
|
||||
|
@@ -18,11 +18,11 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
// array of module names to setUp().
|
||||
if (isset($args[0])) {
|
||||
if (is_array($args[0])) {
|
||||
$modules = $args[0];
|
||||
}
|
||||
$modules = $args[0];
|
||||
}
|
||||
else {
|
||||
$modules = $args;
|
||||
}
|
||||
$modules = $args;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$modules = array();
|
||||
@@ -89,6 +89,7 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
$permissions[] = 'administer taxonomy';
|
||||
$permissions[] = 'administer users';
|
||||
$permissions[] = 'administer feeds';
|
||||
$permissions[] = 'administer filters';
|
||||
|
||||
// Create an admin user and log in.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
@@ -188,6 +189,8 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
$this->assertPlugins($id, 'FeedsHTTPFetcher', 'FeedsSyndicationParser', 'FeedsNodeProcessor');
|
||||
// Per default attach to page content type.
|
||||
$this->setSettings($id, NULL, array('content_type' => 'page'));
|
||||
// Per default attached to article content type.
|
||||
$this->setSettings($id, 'FeedsNodeProcessor', array('bundle' => 'article'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,8 +347,13 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
|
||||
// Check whether feed got properly added to scheduler.
|
||||
$this->assertEqual(1, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND id = 0 AND name = 'feeds_source_import' AND last <> 0 AND scheduled = 0", array(':id' => $id))->fetchField());
|
||||
// There must be only one entry for callback 'expire' - no matter what the feed_nid is.
|
||||
$this->assertEqual(0, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND name = 'feeds_importer_expire' AND last <> 0 AND scheduled = 0", array(':id' => $id))->fetchField());
|
||||
// Check expire scheduler.
|
||||
if (feeds_importer($id)->processor->expiryTime() == FEEDS_EXPIRE_NEVER) {
|
||||
$this->assertEqual(0, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND id = 0 AND name = 'feeds_source_expire'", array(':id' => $id))->fetchField());
|
||||
}
|
||||
else {
|
||||
$this->assertEqual(1, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND id = 0 AND name = 'feeds_source_expire'", array(':id' => $id))->fetchField());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,6 +385,50 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
$this->assertEqual($config['processor']['plugin_key'], $processor, 'Correct processor');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalWebTestCase::assertFieldByXPath().
|
||||
*
|
||||
* The core version has a bug, this is the D8 version.
|
||||
*
|
||||
* @todo Remove once https://drupal.org/node/2105617 lands.
|
||||
*/
|
||||
protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
|
||||
$fields = $this->xpath($xpath);
|
||||
|
||||
// If value specified then check array for match.
|
||||
$found = TRUE;
|
||||
if (isset($value)) {
|
||||
$found = FALSE;
|
||||
if ($fields) {
|
||||
foreach ($fields as $field) {
|
||||
if (isset($field['value']) && $field['value'] == $value) {
|
||||
// Input element with correct value.
|
||||
$found = TRUE;
|
||||
}
|
||||
elseif (isset($field->option) || isset($field->optgroup)) {
|
||||
// Select element found.
|
||||
$selected = $this->getSelectedItem($field);
|
||||
if ($selected === FALSE) {
|
||||
// No item selected so use first item.
|
||||
$items = $this->getAllOptions($field);
|
||||
if (!empty($items) && $items[0]['value'] == $value) {
|
||||
$found = TRUE;
|
||||
}
|
||||
}
|
||||
elseif ($selected == $value) {
|
||||
$found = TRUE;
|
||||
}
|
||||
}
|
||||
elseif ((string) $field == $value) {
|
||||
// Text area with correct text.
|
||||
$found = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->assertTrue($fields && $found, $message, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds mappings to a given configuration.
|
||||
*
|
||||
@@ -388,7 +440,7 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
* @param bool $test_mappings
|
||||
* (optional) TRUE to automatically test mapping configs. Defaults to TRUE.
|
||||
*/
|
||||
public function addMappings($id, $mappings, $test_mappings = TRUE) {
|
||||
public function addMappings($id, array $mappings, $test_mappings = TRUE) {
|
||||
|
||||
$path = "admin/structure/feeds/$id/mapping";
|
||||
|
||||
@@ -407,7 +459,7 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
$mapping = array('source' => $mapping['source'], 'target' => $mapping['target']);
|
||||
|
||||
// Add mapping.
|
||||
$this->drupalPost($path, $mapping, t('Add'));
|
||||
$this->drupalPost($path, $mapping, t('Save'));
|
||||
|
||||
// If there are other configuration options, set them.
|
||||
if ($config) {
|
||||
@@ -432,18 +484,18 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
/**
|
||||
* Remove mappings from a given configuration.
|
||||
*
|
||||
* This function mimicks the Javascript behavior in feeds_ui.js
|
||||
*
|
||||
* @param string $id
|
||||
* ID of the importer.
|
||||
* @param array $mappings
|
||||
* An array of mapping arrays. Each mapping array must have a source and
|
||||
* a target key and can have a unique key.
|
||||
* @param bool $test_mappings
|
||||
* (optional) TRUE to automatically test mapping configs. Defaults to TRUE.
|
||||
*/
|
||||
public function removeMappings($id, $mappings, $test_mappings = TRUE) {
|
||||
public function removeMappings($id, array $mappings, $test_mappings = TRUE) {
|
||||
$path = "admin/structure/feeds/$id/mapping";
|
||||
|
||||
$current_mappings = $this->getCurrentMappings($id);
|
||||
$edit = array();
|
||||
|
||||
// Iterate through all mappings and remove via the form.
|
||||
foreach ($mappings as $i => $mapping) {
|
||||
@@ -453,17 +505,11 @@ class FeedsWebTestCase extends DrupalWebTestCase {
|
||||
$this->assertEqual($current_mapping_key, $i, 'Mapping exists before removal.');
|
||||
}
|
||||
|
||||
$remove_mapping = array("remove_flags[$i]" => 1);
|
||||
|
||||
$this->drupalPost($path, $remove_mapping, t('Save'));
|
||||
|
||||
$this->assertText('Your changes have been saved.');
|
||||
|
||||
if ($test_mappings) {
|
||||
$current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
|
||||
$this->assertEqual($current_mapping_key, -1, 'Mapping does not exist after removal.');
|
||||
}
|
||||
$edit["remove_flags[$i]"] = 1;
|
||||
}
|
||||
|
||||
$this->drupalPost($path, $edit, t('Save'));
|
||||
$this->assertText('Your changes have been saved.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,3 +1,3 @@
|
||||
"guid","title","created","alpha","beta","gamma","delta","body"
|
||||
1,"Lorem ipsum",1251936720,"Lorem",42,"4.2",3.14159265,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat."
|
||||
2,"Ut wisi enim ad minim veniam",1251932360,"Ut wisi",32,"1.2",5.62951413,"Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat."
|
||||
"guid","title","created","alpha","beta","gamma","delta","epsilon","body"
|
||||
1,"Lorem ipsum",1251936720,"Lorem",42,"4.2",3.14159265,1,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat."
|
||||
2,"Ut wisi enim ad minim veniam",1251932360,"Ut wisi",32,"1.2",5.62951413,0,"Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat."
|
||||
|
|
3
sites/all/modules/feeds/tests/feeds/content_date.csv
Normal file
3
sites/all/modules/feeds/tests/feeds/content_date.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
"guid","title","created","end"
|
||||
1,"Lorem ipsum",1251936720,1351936720
|
||||
2,"Ut wisi enim ad minim veniam",1251932360,1351932360
|
|
3
sites/all/modules/feeds/tests/feeds/content_empty.csv
Normal file
3
sites/all/modules/feeds/tests/feeds/content_empty.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
"guid","title","created","end","alpha","beta","gamma","delta","epsilon","body","link_title","url"
|
||||
1,"Lorem ipsum",,,,,,,,,,
|
||||
2,"Ut wisi enim ad minim veniam",0,0,"0",0,0,0,0,0,0,0
|
|
3
sites/all/modules/feeds/tests/feeds/content_i18n.csv
Normal file
3
sites/all/modules/feeds/tests/feeds/content_i18n.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
"guid","title","created","alpha","beta","gamma","delta","body","language"
|
||||
1,"Lorem ipsum",1251936720,"Lorem",42,"4.2",3.14159265,"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.","nl"
|
||||
2,"Ut wisi enim ad minim veniam",1251932360,"Ut wisi",32,"1.2",5.62951413,"Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat."
|
Can't render this file because it has a wrong number of fields in line 3.
|
3
sites/all/modules/feeds/tests/feeds/content_link.csv
Normal file
3
sites/all/modules/feeds/tests/feeds/content_link.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
"guid","title","link_title","url"
|
||||
1,"Lorem ipsum","Feeds","https://www.drupal.org/project/feeds"
|
||||
2,"Ut wisi enim ad minim veniam","Framework for expected behavior when importing empty/blank values","https://www.drupal.org/node/1107522"
|
|
@@ -0,0 +1,3 @@
|
||||
"guid","title"
|
||||
1,"Lorem ipsum"
|
||||
2,"Ut wisi enim ad minim veniam"
|
|
263
sites/all/modules/feeds/tests/feeds/developmentseed_missing.rss2
Normal file
263
sites/all/modules/feeds/tests/feeds/developmentseed_missing.rss2
Normal file
@@ -0,0 +1,263 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xml:base="http://developmentseed.org/blog/all" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<channel>
|
||||
<title>Development Seed - Technological Solutions for Progressive Organizations</title>
|
||||
<link>http://developmentseed.org/blog/all</link>
|
||||
<description></description>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title>Managing News Translation Workflow: Two Way Translation Updates</title>
|
||||
<link>http://developmentseed.org/blog/2009/oct/06/open-atrium-translation-workflow-two-way-updating</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>A new translation process for Open Atrium and integration with Localize Drupal</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p>The <a href="http://openatrium.com/">Open Atrium</a> <a href="http://developmentseed.org/blog/2009/jul/16/open-atrium-solving-translation-puzzle">translation infrastructure</a> (and Drupal translations in general) are progressing quickly. For Open Atrium to be well translated we first need Drupal's modules to be translated, so I am splitting efforts at the moment between helping with <a href="http://localize.drupal.org">Localize Drupal</a> and improving <a href="https://translate.openatrium.com">Open Atrium Translate</a>. Already, it is much easier to automatically download your language, get updates from a translation server, protect locally translated strings, and scale the translation system so that translation servers can talk to each other.</p>
|
||||
|
||||
<h1>Automatically download your language</h1>
|
||||
|
||||
<p><img src="http://farm3.static.flickr.com/2496/3984689117_57559c74eb.jpg" alt="Magical translation install" /></p>
|
||||
|
||||
<p>For more than a month now you have been able to install Open Atrium, select one of its 20+ languages, and have the translation automatically downloaded and installed on your site. While this has been working for awhile now, we are still refining the process. One change we've already made is that now translations are downloaded in multiple smaller packages, rather than a single large one.</p>
|
||||
|
||||
<p>Once your translation is installed, you can use tools like the <a href="http://drupal.org/project/l10n_client">Localization client</a>, which comes bundled in the Open Atrium install, to translate page strings and then optionally contribute them back to the localization server, automatically. This flow of translations goes both ways, so your site gets the latest updates from the server just as you can send your latest updates to the server.</p>
|
||||
|
||||
<h1>Two way translation updates</h1>
|
||||
|
||||
<p><em>But what happens with my locally translated strings, which I like more than the ones that come out of the box, when I update from the server?</em></p>
|
||||
|
||||
<p><img src="http://farm3.static.flickr.com/2442/3984689343_e9b7c32718.jpg" alt="Two ways translation updates" /></p>
|
||||
|
||||
<p>In a word, nothing. There has been a major improvement on this front. Now your translations are tracked and won't be overwritten by someone else's translations when you update, unless you choose for them to be. This means that you can contribute your translations, benefit from others contributing theirs, and make the world a better (translated) place, without loosing any custom work that you want. Let the translations flow!</p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/oct/06/open-atrium-translation-workflow-two-way-updating#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/drupal">Drupal</category>
|
||||
<category domain="http://developmentseed.org/tags/localization">localization</category>
|
||||
<category domain="http://developmentseed.org/tags/localization-client">localization client</category>
|
||||
<category domain="http://developmentseed.org/tags/localization-server">localization server</category>
|
||||
<category domain="http://developmentseed.org/tags/open-atrium">open atrium</category>
|
||||
<category domain="http://developmentseed.org/tags/translation">translation</category>
|
||||
<category domain="http://developmentseed.org/tags/translation-server">translation server</category>
|
||||
<category domain="http://developmentseed.org/channel/drupal-planet">Drupal planet</category>
|
||||
<pubDate>Tue, 06 Oct 2009 15:21:48 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">974 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Week in DC Tech: October 5th Edition</title>
|
||||
<link>http://developmentseed.org/blog/2009/oct/05/week-dc-tech-october-5th-edition</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>Drupal, PHP, and Mapping This Week in Washington, DC</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p><img src="http://developmentseed.org/sites/developmentseed.org/files/dctech2_0_0.png" alt="Week in DC Tech" /></p>
|
||||
|
||||
<p>There are some great technology events happening this week in Washington, DC, so if you're looking to talk code, help map the city, or just hear about some neat projects and ideas, you're in luck. Below are the events that caught our eye, and you can find a full list of technology events happening this week at <a href="http://www.dctechevents.com/">DC Tech Events</a>. Have a great week!</p>
|
||||
|
||||
<h1>Wednesday, October 7</h1>
|
||||
|
||||
<p>6:30 pm</p>
|
||||
|
||||
<p><a href="http://drupal.meetup.com/21/calendar/11332695/"><strong>NOVA Drupal Meetup</strong></a>: Are you a Drupal developer, use a Drupal site, or just want to learn more about the open source content management system? Come out for this meetup to meet other Drupal fans and hear how people are using the CMS.</p>
|
||||
|
||||
<p>6:30 pm</p>
|
||||
|
||||
<p><a href="http://groups.google.com/group/washington-dcphp-group/browse_thread/thread/716d4a625287fef5?hl=en"><strong>DC PHP Beverage Subgroup</strong></a>: If you're looking to talk code with PHP developers who share your interest, come out for this casual meetup to chat over beers.</p>
|
||||
|
||||
<p>7:00 pm</p>
|
||||
|
||||
<p><a href="http://hacdc.org/"><strong>Mapping DC Meeting</strong></a>: Come out for this meetup if you want to help create high quality, free maps of Washington, DC. The <a href="http://wiki.openstreetmap.org/wiki/MappingDC">Mapping DC</a> group is doing this, starting with creating a detailed map of the National Zoo.</p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/oct/05/week-dc-tech-october-5th-edition#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/washington-dc">Washington DC</category>
|
||||
<pubDate>Mon, 05 Oct 2009 15:27:40 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">973 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Mapping Innovation at the World Bank with Open Atrium</title>
|
||||
<link>http://developmentseed.org/blog/2009/oct/02/mapping-innovation-world-bank-open-atrium</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>Using map and faceted search features to improve collaboration</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p><a href="http://openatrium.com/">Open Atrium</a> is being used as a base platform for collaboration at the World Bank because of its feature flexibility. Last week the World Bank launched a new Open Atrium site called "Innovate," which is being used to support an organization-wide initiative to better share information about successful projects and approaches to solving problems.</p>
|
||||
|
||||
<p>The core of the site is built around helping World Bank staff discover relevant "innovations" happening around the world and providing a space to discuss them with colleagues in topical discussion groups. To facilitate this workflow we built a custom map-based browser feature that combines custom maps with faceted search, letting users quickly find interesting content. The screenshots below from a staging site with a partial database show what this feature looks like.</p>
|
||||
|
||||
<p><img src="http://farm4.static.flickr.com/3419/3974644312_c992e1afe8.jpg" alt="The map-based browser feature makes custom maps with faceted search" /></p>
|
||||
|
||||
<p>As users apply new facets to their searches, the map results update to reveal global coverage for innovations that meet the search criteria.</p>
|
||||
|
||||
<p><img src="http://farm3.static.flickr.com/2600/3974644162_a44cc3a89a.jpg" alt="Add new facets to the search to further customize the map" /></p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/oct/02/mapping-innovation-world-bank-open-atrium#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/custom-mapping">custom mapping</category>
|
||||
<category domain="http://developmentseed.org/tags/drupal">Drupal</category>
|
||||
<category domain="http://developmentseed.org/tags/faceted-search">faceted search</category>
|
||||
<category domain="http://developmentseed.org/tags/intranet">intranet</category>
|
||||
<category domain="http://developmentseed.org/tags/map-basec-browser">map-basec browser</category>
|
||||
<category domain="http://developmentseed.org/tags/mapbox">mapbox</category>
|
||||
<category domain="http://developmentseed.org/tags/open-atrium">open atrium</category>
|
||||
<category domain="http://developmentseed.org/tags/world-bank">World Bank</category>
|
||||
<category domain="http://developmentseed.org/channel/drupal-planet">Drupal planet</category>
|
||||
<pubDate>Fri, 02 Oct 2009 14:31:04 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">972 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>September GeoDC Meetup Tonight</title>
|
||||
<link>http://developmentseed.org/blog/2009/sep/30/september-geodc-meetup-tonight</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>Presentations on Using Amazon&#8217;s Web Services and OpenStreet Map and an iPhone App that Maps Government Data</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p>Today is the last Wednesday of the month, which means it's time for another <a href="http://geo-dc.ning.com/xn/detail/3537548:Event:1223?xg_source=activity">GeoDC meetup</a>.</p>
|
||||
|
||||
<p><img src="http://farm4.static.flickr.com/3525/3966592859_f7f4cb179c.jpg" alt="September GeoDC Meetup" /></p>
|
||||
|
||||
<p>There will be two short presentations at the meetup. <a href="http://developmentseed.org/team/tom-macwright">Tom MacWright</a> from Development Seed will talk about how using Amazon's web services and <a href="http://www.openstreetmap.org/">OpenStreetMap</a> has helped our mapping team design, render, and host custom maps. Brian Sobel of <a href="http://www.innovationgeo.com/">Innovation Geo</a> will present <a href="http://areyousafedc.com/">Are You Safe</a>, an iPhone App that uses open government data to give users up-to-date and hyper-local information about crime.</p>
|
||||
|
||||
<p>The meetup will run from 7:00 to 9:00 pm at the offices of <a href="http://www.fortiusone.com">Fortius One</a> at 2200 Wilson Blvd, Suite 307 in Arlington, just a <a href="http://maps.google.com/maps?f=q&amp;source=s_q&amp;hl=en&amp;geocode=&amp;q=2200+Wilson+Blvd+%23+307+Arlington,+VA+22201-3324&amp;sll=38.893037,-77.072783&amp;sspn=0.039481,0.087633&amp;ie=UTF8&amp;ll=38.8912,-77.086236&amp;spn=0.009871,0.021908&amp;t=h&amp;z=16&amp;iwloc=A">short walk from the Courthouse metro stop on the orange line</a>. Hope to see you there!</p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/sep/30/september-geodc-meetup-tonight#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/geodc">GeoDC</category>
|
||||
<category domain="http://developmentseed.org/tags/washington-dc">Washington DC</category>
|
||||
<pubDate>Wed, 30 Sep 2009 12:02:53 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">971 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Week in DC Tech: September 28th Edition</title>
|
||||
<link>http://developmentseed.org/blog/2009/sep/28/week-dc-tech-september-28th-edition</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>Healthcare 2.0, iPhone Development, Online Storytelling, and More This Week in Washington, DC</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p><img src="http://developmentseed.org/sites/developmentseed.org/files/dctech2_0_0.png" alt="Week in DC Tech" /></p>
|
||||
|
||||
<p>Looking to geek out this week? There are a bunch of interesting technology events happening in Washington, DC this week, including a look at how social media is impacting healthcare, a screening of online clips from a journalist/filmmaker, and lightning talks on all kinds of geekery by the HacDC folks. Below are the events that caught our eye, and you can find a full list of what's happening this week in technology over at <a href="http://www.dctechevents.com/">DC Tech Events</a>. Have a great week!</p>
|
||||
|
||||
<h1>Tuesday, September 29</h1>
|
||||
|
||||
<p>6:00 pm</p>
|
||||
|
||||
<p><a href="http://www.meetup.com/DC-MD-VA-Health-2-0/calendar/11291017/"><strong>Health 2.0 Meetup</strong></a>: Curious as to how - and if - online technologies are impacting healthcare? At this meetup two speakers - Sanjay Koyani from the U.S. Food and Drug Administration and Taylor Walsh from MetroHealth Media - will talk about what they're seeing and implementing.</p>
|
||||
|
||||
<p>7:00 pm</p>
|
||||
|
||||
<p><a href="http://nscodernightdc.com/"><strong>NSCoderNightDC</strong></a>: Want to build an iphone app, or talk about one that you've already built? Come out for this meetup to talk about mac and iphone development, share the latest news from Apple, and eat some delicious French desserts.</p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/sep/28/week-dc-tech-september-28th-edition#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/washington-dc">Washington DC</category>
|
||||
<pubDate>Mon, 28 Sep 2009 15:33:15 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">970 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Open Data for Microfinance: The New MIXMarket.org</title>
|
||||
<link>http://developmentseed.org/blog/2009/sep/24/open-data-microfinance-new-mixmarketorg</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>Relaunch focuses on rich data visualization and downloadable data</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p>The launch of the new <a href="http://www.mixmarket.org/">MIX Market</a> is a big win for open data in international development, and it vastly improves how rich financial data sets can be accessed. The MIX Market is like a Bloomberg for microfinance, publishing data on more than 1,500 microfinance institutions (MFIs) in more than 190 countries and affecting 80,021,351 people. Additionally, each MFI has as many as 150 financial indicators and in some cases going back as far as 1995. The goal of this tool is simple - to open this information up to help MFIs, researchers, raters, evaluators, and governmental and regulatory agencies better see the marketplace, and that makes for better international development.</p>
|
||||
|
||||
<p>There are profiles for every country that the MIX Market is hosting. Take a look at the country landing page for India, showing how India stacks up to other peer groups and listing out all MFIs, networks, and funders and service providers.</p>
|
||||
|
||||
<p><img src="http://farm4.static.flickr.com/3517/3941870722_390f5aa65d.jpg" alt="Country profiles give a quick overview of the performance of its microfinance institutions." /></p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/sep/24/open-data-microfinance-new-mixmarketorg#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/data-visualization">data visualization</category>
|
||||
<category domain="http://developmentseed.org/tags/graphs">graphs</category>
|
||||
<category domain="http://developmentseed.org/tags/microfinance">microfinance</category>
|
||||
<category domain="http://developmentseed.org/tags/mix-market">MIX Market</category>
|
||||
<category domain="http://developmentseed.org/tags/open-data">open data</category>
|
||||
<category domain="http://developmentseed.org/tags/salesforce">salesforce</category>
|
||||
<category domain="http://developmentseed.org/channel/drupal-planet">Drupal planet</category>
|
||||
<pubDate>Thu, 24 Sep 2009 13:09:10 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">969 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Integrating the Siteminder Access System in an Open Atrium-based Intranet</title>
|
||||
<link>http://developmentseed.org/blog/2009/sep/22/integrating-siteminder-access-system-open-atrium-based-intranet</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>Upgraded Siteminder Module in Drupal allows for better integration with Siteminder </p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p>In <a href="http://developmentseed.org/blog/2009/sep/08/custom-open-atrium-intranet-launches-world-bank">our recent work on the World Bank's Communicate intranet</a>, we needed to integrate the <a href="http://www.ca.com/us/internet-access-control.aspx">Siteminder access system</a> into the <a href="http://openatrium.com/">Open Atrium</a>-based intranet "Communicate" to allow World Bank staff to use the same single sign-on credentials that they use to access all their internal web systems. To do this, we upgraded the Siteminder module for Drupal. You can download the <a href="http://drupal.org/project/siteminder">new module from its Drupal project page</a> and <a href="http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/siteminder/README.txt?revision=1.2&amp;view=markup&amp;pathrev=DRUPAL-6--1-0-ALPHA1">learn more about its API and how to write your own Siteminder plugin in its documentation</a> and from reading the module's code. First, here is a little more background on the changes.</p>
|
||||
|
||||
<p>The Siteminder system, from <a href="http://www.ca.com/us/">Computer Associates</a>, is used by many enterprise-level organizations to authenticate signing on to their web resources. How it works is that you can designate a site - like an Open Atrium powered intranet - to be protected by the Siteminder system. Once a site is protected by Siteminder, all traffic to that site is routed through Siteminder first and then on to the actual site. Siteminder sets certain HTTP headers in the user's request, and Drupal can then examine them to determine credentials. What the Drupal Siteminder module does is map the Siteminder header values to Drupal users and allow a user to login based on the headers they send.</p>
|
||||
|
||||
<p>In addition to authentication, the Siteminder system also stores other information about users. When the Siteminder system sends HTTP headers for authentication, it can also send information about a user - like her name, email address, phone number, and so on. We wanted to be able to pull this information into the intranet too. To achieve this, we re-wrote the Siteminder module in such a way that it's easy to write a plugin module to provide the fields to which you'd like to map this extra Siteminder meta information and to determine how this information is processed and saved. To do this for the World Bank's intranet, we built the Siteminder Profile module, which lets you pick a CCK node type to serve as the target content profile for a user as well as select a few taxonomy vocabularies. Then by using the main module's administrative interface, you can choose which Siteminder headers should get mapped to which CCK fields and vocabularies based on the designated node type and vocabularies you selected in the Siteminder Profile settings page.</p>
|
||||
|
||||
<p>But what happens if a person's information changes in the Siteminder database - for example if they change phone numbers or office buildings? The Siteminder module now has built-in capability and an API to check whether values in users' profiles have changed in the Siteminder system. The Siteminder Profile module uses this API and saves a new version of a user's profile if it detects that a value has changed in the Siteminder system database.</p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/sep/22/integrating-siteminder-access-system-open-atrium-based-intranet#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/authentication">authentication</category>
|
||||
<category domain="http://developmentseed.org/tags/drupal">Drupal</category>
|
||||
<category domain="http://developmentseed.org/tags/open-atrium">open atrium</category>
|
||||
<category domain="http://developmentseed.org/tags/siteminder">siteminder</category>
|
||||
<category domain="http://developmentseed.org/tags/siteminder-module">siteminder module</category>
|
||||
<category domain="http://developmentseed.org/channel/drupal-planet">Drupal planet</category>
|
||||
<pubDate>Tue, 22 Sep 2009 18:02:21 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">964 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Week in DC Tech: September 21 Edition</title>
|
||||
<link>http://developmentseed.org/blog/2009/sep/21/week-dc-tech-september-21-edition</link>
|
||||
<description><div class="field field-type-text field-field-subtitle">
|
||||
<div class="field-items">
|
||||
<div class="field-item odd">
|
||||
<p>PHP, Design, Twitter, and Wikipedia This Week in Washington, DC</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='node-body'><p><img src="http://developmentseed.org/sites/default/files/dctech2_0.png" alt="Week in DC Tech" /></p>
|
||||
|
||||
<p>There's an interesting variety of technology events happening in Washington, DC this week with focuses ranging from using Twitter for advocacy to drinking beers with php developers to discussing designing way outside of the box. Additionally tomorrow is international Car Free Day and there are events happening throughout the city to celebrate it and help you how to rely on cars less. Below are the events that caught our eye, and you can find a full list of the week's technology events at <a href="http://www.dctechevents.com/">DC Tech Events</a>.</p>
|
||||
|
||||
<h2>Tuesday, September 22</h2>
|
||||
|
||||
<p>All day</p>
|
||||
|
||||
<p><a href="http://www.carfreemetrodc.com/"><strong>Car Free Day</strong></a>: Help reduce traffic and improve air quality by leaving your car at home on Tuesday in celebration of Car Free Day. There are also <a href="http://www.carfreemetrodc.com/Information/tabid/57/Default.aspx">free bike repair trainings, yoga classes, and other events</a> happening throughout the day to help you lead a car free lifestyle.</p>
|
||||
|
||||
<p>6:00 - 8:00 pm</p>
|
||||
|
||||
<p><a href="http://www.php.net/cal.php?id=3075"><strong>DC PHP Beverage Subgroup</strong></a>: Come out to talk code with other php developers over a few beers. This is a great opportunity to get to know local php developers in a casual setting while sharing stories about your code.</p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/sep/21/week-dc-tech-september-21-edition#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/washington-dc">Washington DC</category>
|
||||
<pubDate>Mon, 21 Sep 2009 16:03:24 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">968 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title>Peru's Software Freedom Day: Impressions and Photos</title>
|
||||
<link>http://developmentseed.org/blog/2009/sep/21/perus-software-freedom-day-impressions-and-photos</link>
|
||||
<description><div class='node-body'><p>There was a great turn out a <a href="http://www.sfdperu.org/">Software Freedom Day</a> this weekend with 400 people in attendance and a solid 30 presentations. The <a href="http://developmentseed.org/blog/2009/sep/15/preparing-perus-software-freedom-day-talks-drupal-features-and-open-atrium">presentations in the Drupal track</a> were some of the best attended sessions of the day. To get a sense of Drupal's traction down here, "Drupal" was mentioned in many sessions and conversations throughout the day, and not just by the people working directly with Drupal.</p>
|
||||
|
||||
<p><img src="http://farm3.static.flickr.com/2653/3940335249_57ce995a84.jpg" alt="Presenting on Features in Drupal and Managing News" />
|
||||
<em>Presenting on Features in Drupal and Managing News</em></p>
|
||||
|
||||
<p>I had a great time meeting people and learning about the work being done in the different open source communities here in Peru. Software Freedom Day is becoming an annual event in Peru, and there were many discussions on improving the event for next year as well as keeping the energy going to improve the image of open source software in the country. It's great to see the community looking forward like this, and I'm excited to help keep the open source movement growing in Peru.</p>
|
||||
|
||||
<p><a href="http://www.flickr.com/photos/developmentseed/sets/72157622423999830/">More photos from the event here.</a></p></div></description>
|
||||
<comments>http://developmentseed.org/blog/2009/sep/21/perus-software-freedom-day-impressions-and-photos#comments</comments>
|
||||
<category domain="http://developmentseed.org/tags/drupal">Drupal</category>
|
||||
<category domain="http://developmentseed.org/tags/open-source">open source</category>
|
||||
<category domain="http://developmentseed.org/tags/peru">Peru</category>
|
||||
<category domain="http://developmentseed.org/tags/software-freedom-day">software freedom day</category>
|
||||
<pubDate>Mon, 21 Sep 2009 14:22:35 +0000</pubDate>
|
||||
<dc:creator>Development Seed</dc:creator>
|
||||
<guid isPermaLink="false">967 at http://developmentseed.org</guid>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss">
|
||||
<updated>2010-09-07T21:45:39Z</updated>
|
||||
<title>USGS M2.5+ Earthquakes</title>
|
||||
<subtitle>Real-time, worldwide earthquake list for the past day</subtitle>
|
||||
<link rel="self" href="http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml"/>
|
||||
<link href="http://earthquake.usgs.gov/earthquakes/"/>
|
||||
<id>http://earthquake.usgs.gov/</id>
|
||||
<icon>/favicon.ico</icon>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076864</id><title>M 2.6, Central Alaska</title><updated>2010-09-07T21:08:45Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076864.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/65_-150.jpg" alt="64.858°N 150.864°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 21:08:45 UTC<br>Tuesday, September 7, 2010 01:08:45 PM at epicenter</p><p><strong>Depth</strong>: 11.20 km (6.96 mi)</p>]]></summary><georss:point>64.8581 -150.8643</georss:point><georss:elev>-11200</georss:elev><category label="Age" term="Past hour"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axbz</id><title>M 4.9, southern Qinghai, China</title><updated>2010-09-07T20:51:02Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axbz.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/35_95.jpg" alt="33.329°N 96.332°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 20:51:02 UTC<br>Wednesday, September 8, 2010 04:51:02 AM at epicenter</p><p><strong>Depth</strong>: 47.50 km (29.52 mi)</p>]]></summary><georss:point>33.3289 96.3324</georss:point><georss:elev>-47500</georss:elev><category label="Age" term="Past hour"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axbr</id><title>M 5.2, southern East Pacific Rise</title><updated>2010-09-07T19:54:29Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axbr.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/us2010axbr" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-55_-120.jpg" alt="53.198°S 118.068°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 19:54:29 UTC<br>Tuesday, September 7, 2010 11:54:29 AM at epicenter</p><p><strong>Depth</strong>: 15.50 km (9.63 mi)</p>]]></summary><georss:point>-53.1979 -118.0676</georss:point><georss:elev>-15500</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axbp</id><title>M 5.0, South Island of New Zealand</title><updated>2010-09-07T19:49:57Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axbp.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-45_175.jpg" alt="43.437°S 172.590°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 19:49:57 UTC<br>Wednesday, September 8, 2010 07:49:57 AM at epicenter</p><p><strong>Depth</strong>: 1.00 km (0.62 mi)</p>]]></summary><georss:point>-43.4371 172.5902</georss:point><georss:elev>-1000</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076859</id><title>M 3.1, Andreanof Islands, Aleutian Islands, Alaska</title><updated>2010-09-07T19:20:05Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076859.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/ak10076859" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/50_-175.jpg" alt="51.526°N 175.798°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 19:20:05 UTC<br>Tuesday, September 7, 2010 10:20:05 AM at epicenter</p><p><strong>Depth</strong>: 22.20 km (13.79 mi)</p>]]></summary><georss:point>51.5259 -175.7979</georss:point><georss:elev>-22200</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ci:10793957</id><title>M 2.7, Southern California</title><updated>2010-09-07T18:50:42Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ci10793957.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/35_-115.jpg" alt="35.717°N 116.960°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 18:50:42 UTC<br>Tuesday, September 7, 2010 11:50:42 AM at epicenter</p><p><strong>Depth</strong>: 7.80 km (4.85 mi)</p>]]></summary><georss:point>35.7170 -116.9597</georss:point><georss:elev>-7800</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ci:10793909</id><title>M 3.5, Southern California</title><updated>2010-09-07T17:29:13Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ci10793909.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/ci10793909" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/35_-115.jpg" alt="35.727°N 116.957°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 17:29:13 UTC<br>Tuesday, September 7, 2010 10:29:13 AM at epicenter</p><p><strong>Depth</strong>: 4.50 km (2.80 mi)</p>]]></summary><georss:point>35.7273 -116.9567</georss:point><georss:elev>-4500</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076853</id><title>M 3.1, Andreanof Islands, Aleutian Islands, Alaska</title><updated>2010-09-07T17:08:19Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076853.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/ak10076853" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/50_-175.jpg" alt="51.090°N 176.131°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 17:08:19 UTC<br>Tuesday, September 7, 2010 08:08:19 AM at epicenter</p><p><strong>Depth</strong>: 16.50 km (10.25 mi)</p>]]></summary><georss:point>51.0899 -176.1314</georss:point><georss:elev>-16500</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axa9</id><title>M 6.3, Fiji region</title><updated>2010-09-07T16:13:32Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axa9.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-15_-180.jpg" alt="15.869°S 179.261°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 16:13:32 UTC<br>Wednesday, September 8, 2010 04:13:32 AM at epicenter</p><p><strong>Depth</strong>: 10.00 km (6.21 mi)</p>]]></summary><georss:point>-15.8694 -179.2611</georss:point><georss:elev>-10000</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axa7</id><title>M 5.3, Kyrgyzstan</title><updated>2010-09-07T15:41:42Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axa7.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/us2010axa7" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/40_75.jpg" alt="39.476°N 73.825°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 15:41:42 UTC<br>Tuesday, September 7, 2010 09:41:42 PM at epicenter</p><p><strong>Depth</strong>: 39.70 km (24.67 mi)</p>]]></summary><georss:point>39.4759 73.8254</georss:point><georss:elev>-39700</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ci:10793837</id><title>M 2.7, Southern California</title><updated>2010-09-07T13:07:21Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ci10793837.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/35_-115.jpg" alt="35.725°N 116.963°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 13:07:21 UTC<br>Tuesday, September 7, 2010 06:07:21 AM at epicenter</p><p><strong>Depth</strong>: 3.60 km (2.24 mi)</p>]]></summary><georss:point>35.7245 -116.9630</georss:point><georss:elev>-3600</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:nc:71451855</id><title>M 2.5, Northern California</title><updated>2010-09-07T13:06:56Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/nc71451855.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/nc71451855" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/40_-120.jpg" alt="39.210°N 120.067°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 13:06:56 UTC<br>Tuesday, September 7, 2010 06:06:56 AM at epicenter</p><p><strong>Depth</strong>: 8.20 km (5.10 mi)</p>]]></summary><georss:point>39.2102 -120.0667</georss:point><georss:elev>-8200</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axax</id><title>M 5.4, Fiji region</title><updated>2010-09-07T12:49:01Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axax.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/us2010axax" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-15_-175.jpg" alt="14.361°S 176.241°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 12:49:01 UTC<br>Wednesday, September 8, 2010 12:49:01 AM at epicenter</p><p><strong>Depth</strong>: 35.50 km (22.06 mi)</p>]]></summary><georss:point>-14.3605 -176.2406</georss:point><georss:elev>-35500</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axat</id><title>M 5.0, Kuril Islands</title><updated>2010-09-07T11:30:52Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axat.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/us2010axat" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/45_150.jpg" alt="45.858°N 151.311°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 11:30:52 UTC<br>Tuesday, September 7, 2010 11:30:52 PM at epicenter</p><p><strong>Depth</strong>: 30.30 km (18.83 mi)</p>]]></summary><georss:point>45.8582 151.3105</georss:point><georss:elev>-30300</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:mb:25757</id><title>M 2.7, western Montana</title><updated>2010-09-07T10:08:26Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/mb25757.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/45_-110.jpg" alt="44.951°N 111.742°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 10:08:26 UTC<br>Tuesday, September 7, 2010 04:08:26 AM at epicenter</p><p><strong>Depth</strong>: 5.70 km (3.54 mi)</p>]]></summary><georss:point>44.9508 -111.7423</georss:point><georss:elev>-5700</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076821</id><title>M 2.7, Andreanof Islands, Aleutian Islands, Alaska</title><updated>2010-09-07T08:40:35Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076821.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/ak10076821" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/50_-175.jpg" alt="51.201°N 176.194°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 08:40:35 UTC<br>Monday, September 6, 2010 11:40:35 PM at epicenter</p><p><strong>Depth</strong>: 20.10 km (12.49 mi)</p>]]></summary><georss:point>51.2010 -176.1935</georss:point><georss:elev>-20100</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axas</id><title>M 4.9, southwest of Sumatra, Indonesia</title><updated>2010-09-07T07:22:13Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axas.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-5_105.jpg" alt="7.128°S 103.263°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 07:22:13 UTC<br>Tuesday, September 7, 2010 02:22:13 PM at epicenter</p><p><strong>Depth</strong>: 35.00 km (21.75 mi)</p>]]></summary><georss:point>-7.1275 103.2631</georss:point><georss:elev>-35000</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:nc:71451750</id><title>M 3.1, Central California</title><updated>2010-09-07T06:59:25Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/nc71451750.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/nc71451750" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/35_-120.jpg" alt="36.561°N 121.068°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 06:59:25 UTC<br>Monday, September 6, 2010 11:59:25 PM at epicenter</p><p><strong>Depth</strong>: 8.40 km (5.22 mi)</p>]]></summary><georss:point>36.5605 -121.0677</georss:point><georss:elev>-8400</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076797</id><title>M 4.2, Kodiak Island region, Alaska</title><updated>2010-09-07T05:54:04Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076797.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/ak10076797" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/55_-150.jpg" alt="56.980°N 151.666°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 05:54:04 UTC<br>Monday, September 6, 2010 09:54:04 PM at epicenter</p><p><strong>Depth</strong>: 25.00 km (15.53 mi)</p>]]></summary><georss:point>56.9797 -151.6661</georss:point><georss:elev>-25000</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076786</id><title>M 3.4, Andreanof Islands, Aleutian Islands, Alaska</title><updated>2010-09-07T04:43:36Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076786.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/ak10076786" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/50_-175.jpg" alt="51.098°N 176.164°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 04:43:36 UTC<br>Monday, September 6, 2010 07:43:36 PM at epicenter</p><p><strong>Depth</strong>: 16.90 km (10.50 mi)</p>]]></summary><georss:point>51.0975 -176.1635</georss:point><georss:elev>-16900</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:ak:10076776</id><title>M 3.6, Andreanof Islands, Aleutian Islands, Alaska</title><updated>2010-09-07T03:43:43Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10076776.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/50_-175.jpg" alt="51.471°N 176.667°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 03:43:43 UTC<br>Monday, September 6, 2010 06:43:43 PM at epicenter</p><p><strong>Depth</strong>: 39.00 km (24.23 mi)</p>]]></summary><georss:point>51.4706 -176.6674</georss:point><georss:elev>-39000</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axaf</id><title>M 5.3, southern Iran</title><updated>2010-09-07T02:11:07Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axaf.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/us2010axaf" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/25_55.jpg" alt="27.147°N 54.588°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 02:11:07 UTC<br>Tuesday, September 7, 2010 05:41:07 AM at epicenter</p><p><strong>Depth</strong>: 27.90 km (17.34 mi)</p>]]></summary><georss:point>27.1465 54.5877</georss:point><georss:elev>-27900</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axad</id><title>M 4.9, Fiji region</title><updated>2010-09-07T01:42:39Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axad.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-20_-180.jpg" alt="19.657°S 177.699°W" align="left" hspace="20" /><p>Tuesday, September 7, 2010 01:42:39 UTC<br>Tuesday, September 7, 2010 01:42:39 PM at epicenter</p><p><strong>Depth</strong>: 390.40 km (242.58 mi)</p>]]></summary><georss:point>-19.6573 -177.6987</georss:point><georss:elev>-390400</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010axab</id><title>M 5.8, southwest of Sumatra, Indonesia</title><updated>2010-09-07T00:57:26Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010axab.php"/><link rel="related" type="application/cap+xml" href="http://earthquake.usgs.gov/earthquakes/catalogs/cap/us2010axab" /><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-5_105.jpg" alt="6.967°S 103.657°E" align="left" hspace="20" /><p>Tuesday, September 7, 2010 00:57:26 UTC<br>Tuesday, September 7, 2010 07:57:26 AM at epicenter</p><p><strong>Depth</strong>: 34.10 km (21.19 mi)</p>]]></summary><georss:point>-6.9665 103.6573</georss:point><georss:elev>-34100</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
<entry><id>urn:earthquake-usgs-gov:us:2010awby</id><title>M 5.2, North Island of New Zealand</title><updated>2010-09-06T22:48:33Z</updated><link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/us2010awby.php"/><summary type="html"><![CDATA[<img src="http://earthquake.usgs.gov/images/globes/-40_175.jpg" alt="40.143°S 176.657°E" align="left" hspace="20" /><p>Monday, September 6, 2010 22:48:33 UTC<br>Tuesday, September 7, 2010 10:48:33 AM at epicenter</p><p><strong>Depth</strong>: 16.70 km (10.38 mi)</p>]]></summary><georss:point>-40.1430 176.6567</georss:point><georss:elev>-16700</georss:elev><category label="Age" term="Past day"/></entry>
|
||||
</feed>
|
33
sites/all/modules/feeds/tests/feeds/encoding.csv.php
Normal file
33
sites/all/modules/feeds/tests/feeds/encoding.csv.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Result of encoding_{code}.csv file parsed by ParserCSV.inc
|
||||
*/
|
||||
|
||||
// JSON is used here because it supports unicode literals. PHP does not.
|
||||
$json = <<<EOT
|
||||
[
|
||||
[
|
||||
"id",
|
||||
"text"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"\u672c\u65e5\u306f\u3044\u3044\u5929\u6c17"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"\uff71\uff72\uff73\uff74\uff75"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"\u30c6\u30b9\u30c8"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"\u2605"
|
||||
]
|
||||
]
|
||||
EOT;
|
||||
|
||||
$control_result = json_decode($json);
|
@@ -0,0 +1,5 @@
|
||||
id,text
|
||||
1,<EFBFBD>{<7B><><EFBFBD>͂<EFBFBD><CD82><EFBFBD><EFBFBD>V<EFBFBD>C
|
||||
2,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
3,<EFBFBD>e<EFBFBD>X<EFBFBD>g
|
||||
4,<EFBFBD><EFBFBD>
|
|
5
sites/all/modules/feeds/tests/feeds/encoding_SJIS.csv
Normal file
5
sites/all/modules/feeds/tests/feeds/encoding_SJIS.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
id,text
|
||||
1,<EFBFBD>{<7B><><EFBFBD>͂<EFBFBD><CD82><EFBFBD><EFBFBD>V<EFBFBD>C
|
||||
2,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
3,<EFBFBD>e<EFBFBD>X<EFBFBD>g
|
||||
4,<EFBFBD><EFBFBD>
|
|
5
sites/all/modules/feeds/tests/feeds/encoding_UTF-8.csv
Normal file
5
sites/all/modules/feeds/tests/feeds/encoding_UTF-8.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
id,text
|
||||
1,本日はいい天気
|
||||
2,アイウエオ
|
||||
3,テスト
|
||||
4,★
|
|
@@ -0,0 +1,6 @@
|
||||
Title,published,file,GUID,alt,title2
|
||||
"Tubing is awesome",205200720,<?php print $files[0]; ?>,0,,
|
||||
"Jeff vs Tom",428112720,<?php print $files[1]; ?>,1,,
|
||||
"Attersee",1151766000,<?php print $files[2]; ?>,2,,
|
||||
"H Street NE",1256326995,<?php print $files[3]; ?>,3,,
|
||||
"La Fayette Park",1256326995,<?php print $files[4]; ?>,4,,
|
@@ -1,6 +1,6 @@
|
||||
Title,published,file,GUID
|
||||
"Tubing is awesome",205200720,<?php print $files[0]; ?>,0
|
||||
"Jeff vs Tom",428112720,<?php print $files[1]; ?>,1
|
||||
"Attersee",1151766000,<?php print $files[2]; ?>,2
|
||||
"H Street NE",1256326995,<?php print $files[3]; ?>,3
|
||||
"La Fayette Park",1256326995,<?php print $files[4]; ?>,4
|
||||
Title,published,file,GUID,alt,title2
|
||||
"Tubing is awesome",205200720,<?php print $files[0]; ?>,0,Alt text 0,Title text 0
|
||||
"Jeff vs Tom",428112720,<?php print $files[1]; ?>,1,Alt text 1,Title text 1
|
||||
"Attersee",1151766000,<?php print $files[2]; ?>,2,Alt text 2,Title text 2
|
||||
"H Street NE",1256326995,<?php print $files[3]; ?>,3,Alt text 3,Title text 3
|
||||
"La Fayette Park",1256326995,<?php print $files[4]; ?>,4,Alt text 4,Title text 4
|
||||
|
87
sites/all/modules/feeds/tests/feeds/many_nodes_ordered.csv
Normal file
87
sites/all/modules/feeds/tests/feeds/many_nodes_ordered.csv
Normal file
@@ -0,0 +1,87 @@
|
||||
Title,Body,published,GUID
|
||||
"Ut wisi enim ad minim veniam", "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.",205200720,1
|
||||
"Duis autem vel eum iriure dolor", "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.",428112720,2
|
||||
"Nam liber tempor", "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum.",1151766000,3
|
||||
Typi non habent"", "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem.",1256326995,4
|
||||
"Lorem ipsum","Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.",1251936720,5
|
||||
"Investigationes demonstraverunt", "Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius.",946702800,6
|
||||
"Claritas est etiam", "Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum.",438112720,7
|
||||
"Mirum est notare", "Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima.",1151066000,8
|
||||
"Eodem modo typi", "Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.",1212891020,9
|
||||
"Ut quam dolor", "Ut quam dolor, aliquam pretium elementum ac, auctor eu tortor.",1200837000,10
|
||||
"Sed id dignissim lorem", "Donec nec urna mauris. Duis tincidunt",1201000000,11
|
||||
"Aliquam feugiat diam", "Aliquam feugiat diam ac enim egestas ut cursus lacus fermentum.",1210922021,12
|
||||
"Proin et fringilla leo", "Maecenas rhoncus velit quis nibh convallis pharetra.",1201832100,13
|
||||
"Suspendisse potenti", "Integer commodo elit et arcu dapibus at hendrerit nunc dapibus.",1122838020,14
|
||||
"Nunc eu lectus nisi", "Nunc eu lectus nisi, sed vestibulum mauris. Sed tincidunt vehicula sem, eu tincidunt massa ultrices vel.",1200827015,15
|
||||
"Mauris tellus erat", "",1201845210,16
|
||||
"Pellentesque porttitor gravida magna", "Pellentesque porttitor gravida magna, ut lacinia risus suscipit a. Nunc molestie molestie massa non auctor.",1211835320,17
|
||||
"Pellentesque facilisis ultrices", "Pellentesque facilisis ultrices risus non porttitor. Donec dapibus velit in metus consectetur et ullamcorper velit volutpat.",1190835120,18
|
||||
"Sed eros lectus", "Sed eros lectus, mollis vel commodo et, molestie vel urna.",1151825020,19
|
||||
"Morbi consectetur fringilla dolor", "Morbi consectetur fringilla dolor. Morbi eleifend pharetra purus, non facilisis tortor ullamcorper sed.",1201745220,20
|
||||
"Vivamus vitae lectus", "Vivamus vitae lectus ac urna tempus dapibus eu consectetur massa. Donec vitae arcu lectus, non ornare nunc. ",1201825020,21
|
||||
"Fusce est felis", "Fusce est felis, tincidunt eu congue id, placerat nec massa.",1201836001,22
|
||||
"Duis tristique velit", "Duis tristique velit vitae lacus malesuada sed commodo quam commodo.",1201832024,23
|
||||
"In ac felis neque", "Integer dictum sapien eget nunc commodo convallis.",1201830020,24
|
||||
"Proin a mi nulla", "Proin a mi nulla, sodales mattis nunc.",1201822020,25
|
||||
"Pellentesque vitae massa", "Pellentesque vitae massa elementum augue varius suscipit at quis turpis.",1201810020,26
|
||||
"Proin dapibus", "Nam pulvinar urna vel eros aliquam hendrerit.",1201636020,27
|
||||
"Donec", "Nulla pulvinar felis nec nulla pretium id pulvinar risus scelerisque.",1201336020,28
|
||||
"Quisque dictum sagittis purus", "Quisque dictum sagittis purus, nec tristique magna sagittis nec. Mauris tortor nisl, cursus sit amet varius vitae, posuere in ipsum.",1201236020,29
|
||||
"Praesent", "Praesent tincidunt vulputate turpis non faucibus",1201132020,30
|
||||
"Vestibulum", "Vestibulum volutpat interdum elit, quis aliquam leo lobortis non.",1201036020,31
|
||||
"Aliquam", "Aliquam fringilla lobortis mollis.",1201022020,32
|
||||
"Etiam faucibus", "Etiam faucibus, quam vitae sollicitudin sagittis, nisl metus ultricies risus, sed convallis nibh risus ut libero.",1201021020,33
|
||||
"In hac habitasse", "Proin justo sapien, dapibus vel dictum non, vestibulum in neque.",1201010020,34
|
||||
"Mauris risus dolor", "Maecenas rutrum diam suscipit mi feugiat venenatis.",1201005011,35
|
||||
"Pellentesque habitant", "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.",1201001010,36
|
||||
"Duis convallis", "Duis convallis quam non eros suscipit iaculis. Aliquam erat volutpat.",1201000010,37
|
||||
"Proin adipiscing mi vel", "Proin nibh est, gravida ac condimentum viverra, hendrerit sodales tellus.",1200901010,38
|
||||
"Nullam sodales", "Nullam sodales laoreet suscipit.",1200851010,39
|
||||
"Duis ornare pulvinar purus", "Duis ornare pulvinar purus, at pulvinar nulla hendrerit vel.",1200801010,40
|
||||
"Mauris sit amet nibh risus", "Mauris sit amet nibh risus, eget eleifend ante. Donec imperdiet ante quis ligula condimentum consectetur.",1200761010,41
|
||||
"Fusce sit amet mi vitae", "Fusce sit amet mi vitae ipsum tempor rhoncus. Aenean sit amet diam erat, fringilla porttitor felis.",1200751010,42
|
||||
"Cras blandit ornare", "Cras blandit ornare augue in tempor. Curabitur vitae dolor nibh, sed molestie metus.",1200601010,43
|
||||
"Proin at eros", "Proin at eros ut est laoreet tristique. Duis ullamcorper, nisi eu gravida mattis, nibh ligula laoreet lectus, vitae elementum augue sem non nisl.",1200451010,44
|
||||
"Vivamus sem purus", "Vivamus sem purus, viverra eget faucibus et, imperdiet vel massa.",1200551010,45
|
||||
"Ut id nisl", "Morbi suscipit tincidunt ultrices. Suspendisse ornare aliquet elit ac accumsan.",1200121010,46
|
||||
"Sed libero leo", "Phasellus ut sem sit amet justo sagittis placerat.",1200202010,47
|
||||
"Quisque eros nunc", "Quisque eros nunc, iaculis id bibendum ut, imperdiet ut sem. Donec mi quam, ultricies sit amet ornare et, lacinia non libero.",1201222010,48
|
||||
"Vestibulum vulputate", "Vestibulum vulputate, lacus quis fringilla imperdiet, nisi mi consequat lacus, eu sodales nisl nisi venenatis lacus.",1200022010,49
|
||||
"Suspendisse sed", "Suspendisse sed est eget quam imperdiet laoreet.",1202200010,50
|
||||
"Duis a lacus odio", "Duis a lacus odio. Nam luctus sapien id leo vestibulum tristique. Cras eu nisi velit, a aliquet turpis. Maecenas ligula est, ullamcorper vel placerat sit amet, scelerisque et lectus. Nunc velit massa; scelerisque et pretium non, sollicitudin vel tellus? Donec leo turpis, tempus dignissim placerat vel, venenatis a augue. Etiam condimentum porttitor urna, quis scelerisque justo pulvinar eget. Fusce nec nibh non enim mattis posuere. Cras pulvinar erat eget ante gravida congue. Pellentesque ultrices metus ut nunc suscipit id imperdiet nunc ornare. Nam consectetur erat quis massa congue vehicula eget vestibulum turpis. Morbi ac eleifend felis.",1202200010,51
|
||||
"Vivamus tempor", "Vivamus tempor, mi a pulvinar convallis, massa enim pulvinar metus, nec fringilla quam ante eu erat. Cras dignissim, felis non euismod iaculis, est massa posuere diam, et auctor libero ipsum ac ipsum? Nullam iaculis, eros ac sodales sollicitudin; tellus ligula volutpat urna, eget mattis felis metus ac augue.",1202200010,52
|
||||
"Sed molestie", "Sed molestie dolor sit amet neque dictum egestas? Aliquam vitae dui mi. Phasellus fermentum volutpat augue, quis ornare tortor facilisis ac. Suspendisse potenti. Duis facilisis massa at elit pharetra sit amet condimentum est pharetra.",1202200010,53
|
||||
"Donec ut", "Donec ut est ipsum. Pellentesque porttitor eleifend neque non malesuada. Morbi sollicitudin varius dapibus. Morbi sit amet risus leo, eu suscipit urna. Suspendisse velit ligula, suscipit ac rhoncus eu, convallis in eros",1202100010,54
|
||||
"Duis ut dolor sem", "Duis ut dolor sem. Donec convallis, nunc quis pulvinar tempus, leo est blandit lacus, et elementum lectus nisl et purus. Mauris eros eros, iaculis volutpat pretium euismod, porta id arcu. Integer tellus lacus, imperdiet sed vulputate varius, dignissim vel nisi. In porta molestie fermentum.",1202222101,55
|
||||
"Fusce sodales luctus porta", "Fusce sodales luctus porta. Curabitur pellentesque tincidunt tristique. In hac habitasse platea dictumst.",1202123211,56
|
||||
"In egestas lectus a sapien", "In egestas lectus a sapien sollicitudin nec blandit metus scelerisque. Proin et tortor eget risus congue sollicitudin. In auctor interdum turpis porta commodo. Donec faucibus elementum nibh, a egestas nisi tincidunt id.",1202232323,57
|
||||
"Aliquam semper", "Aliquam semper egestas aliquet. Aliquam tristique velit sit amet leo sodales aliquam. Praesent cursus ipsum quis odio aliquet eu eleifend velit aliquam? Maecenas consequat lobortis augue, at venenatis enim hendrerit quis.",1202242452,58
|
||||
"Curabitur tortor", "Curabitur tortor turpis, commodo eu pretium ac, sollicitudin a augue. ",1201341344,59
|
||||
"Duis venenatis", "Duis venenatis lorem vel sapien suscipit consectetur. In vel lectus neque, ut rutrum sapien.",1204564533,60
|
||||
"Phasellus ipsum metus", "Phasellus ipsum metus; suscipit nec malesuada et, fermentum eu nulla. Vivamus id libero in ligula gravida tristique at sed nibh. Cras congue, risus posuere hendrerit pharetra, ante justo eleifend sem, vitae faucibus lacus neque rhoncus urna.",1123452333,61
|
||||
"Nullam porta", "Nullam porta, nisl eu ornare rhoncus, dolor tortor scelerisque justo, non placerat mauris purus vel mauris.",1067356233,62
|
||||
"", "Pellentesque eget ante sit amet turpis vestibulum posuere ut non ipsum. Suspendisse potenti. ",1202122311,63
|
||||
"Pellentesque eget ante sit", "Pellentesque fringilla mi eu diam fermentum condimentum",1200010001,64
|
||||
"Praesent pellentesque quam nec", "Praesent pellentesque quam nec ligula accumsan faucibus. Duis non nisi ante. Mauris eu ullamcorper urna.",1200000000,65
|
||||
"Pellentesque habitant morbi tristique", "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus elit risus, interdum sed iaculis a, vehicula in eros.",1000000000,66
|
||||
"Mauris lobortis luctus risus mattis luctus", "Mauris lobortis luctus risus mattis luctus. Praesent metus mi, euismod quis accumsan vel, placerat id dui. Donec sed erat vel arcu hendrerit hendrerit nec posuere metus! Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.",1202012011,67
|
||||
"Mauris sed felis ut tortor gravida", "Mauris sed felis ut tortor gravida vestibulum vel ac enim!",1202123943,68
|
||||
"Sed vehicula, ipsum non dignissim tristique", "Sed vehicula, ipsum non dignissim tristique, urna dui gravida elit, id tempor nunc eros sit amet ligula.",1202323423,69
|
||||
"Duis consequat sagittis lectus id semper", "Duis consequat sagittis lectus id semper. Phasellus semper ante at leo faucibus venenatis.",1203124344,70
|
||||
"Nunc malesuada convallis neque", "Nunc malesuada convallis neque ac tincidunt. Etiam hendrerit mi at arcu bibendum eget viverra mi fringilla.",1202334234,71
|
||||
"Donec et ante mi?", "Donec et ante mi? Nullam auctor suscipit nibh quis dignissim. Etiam non lorem eros, vel auctor quam.",1205646554,72
|
||||
"Cras consequat", "Cras consequat, ligula quis pulvinar ultrices, dui sem venenatis leo, et dignissim ante mi vitae ipsum.",1202345600,73
|
||||
"Suspendisse convallis tempor adipiscing", "Suspendisse convallis tempor adipiscing. Vivamus ut ullamcorper nulla.",1202203200,74
|
||||
"Vestibulum ante ipsum primis in faucibus orci luctus", "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae.",1202202300,75
|
||||
"Maecenas consectetur quam ac risus iaculis pharetra", "Maecenas consectetur quam ac risus iaculis pharetra. Nam metus velit, mattis ac interdum ac, volutpat et diam.",1202323430,76
|
||||
"Ut odio massa", "Ut odio massa, luctus aliquam pretium non, imperdiet quis risus.",1202200000,77
|
||||
"Curabitur sapien neque", "Curabitur sapien neque, elementum nec iaculis non, commodo nec arcu.",1206000000,78
|
||||
"Duis a arcu felis", "Duis a arcu felis, id tristique lectus. Nullam porta mi vitae erat suscipit vulputate?",1202906458,79
|
||||
"Cras sollicitudin lobortis rutrum", "Cras sollicitudin lobortis rutrum. Nunc vitae nisl nec orci laoreet cursus.",1204534599,80
|
||||
"Cras fringilla commodo posuere", "Cras fringilla commodo posuere. Nam sed justo dolor, vel pharetra odio.",1205345344,81
|
||||
"In fringilla erat et mi consequat convallis", "In fringilla erat et mi consequat convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.",1202455443,82
|
||||
"Aliquam in tellus nibh", "Aliquam in tellus nibh, ut pharetra metus. Fusce tempor, lectus eget ultrices sagittis, ipsum est sagittis urna, vel porttitor magna sem ac diam.",1207566663,83
|
||||
"Nunc gravida, leo sed faucibus viverra", "Nunc gravida, leo sed faucibus viverra, orci sapien mollis tellus, sit amet venenatis odio augue eu nunc.",1203453245,84
|
||||
"Vestibulum tristique sodales arcu", "Vestibulum tristique sodales arcu, nec dignissim mauris rutrum et.",1202230010,85
|
||||
"Praesent porttitor viverra rhoncus", "Praesent porttitor viverra rhoncus! Nulla malesuada elit et diam semper quis vehicula metus euismod.",1209200010,86
|
Can't render this file because it contains an unexpected character in line 5 and column 16.
|
29
sites/all/modules/feeds/tests/feeds/multi-date.xml
Normal file
29
sites/all/modules/feeds/tests/feeds/multi-date.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<items>
|
||||
<item>
|
||||
<title>2 date values</title>
|
||||
<guid>123456789</guid>
|
||||
<date>Wed, 06 Jan 2010 15:05:00 GMT+00:00</date>
|
||||
<date>Thu, 07 Jan 2010 15:08:00 GMT+00:00</date>
|
||||
</item>
|
||||
<item>
|
||||
<title>4 date values</title>
|
||||
<guid>1234567890</guid>
|
||||
<date>Wed, 06 Jan 2010 15:00:00 GMT+00:00</date>
|
||||
<date>Thu, 07 Jan 2010 15:00:00 GMT+00:00</date>
|
||||
<date>Fri, 08 Jan 2010 15:00:00 GMT+00:00</date>
|
||||
<date>Sat, 09 Jan 2010 15:00:00 GMT+00:00</date>
|
||||
</item>
|
||||
<item>
|
||||
<title>Bogus date values</title>
|
||||
<guid>1234567892</guid>
|
||||
<date>Wed, 36 Jan 2010 15:00:00 GMT+00:00</date>
|
||||
<date>Foo, 07 Bar 2010 95:00:00 GMT+00:00</date>
|
||||
<date>This is just pure bogus. No date here!</date>
|
||||
</item>
|
||||
<item>
|
||||
<title>Single date value</title>
|
||||
<guid>1234567894</guid>
|
||||
<date>Wed, 06 Jan 2010 14:00:00 GMT+00:00</date>
|
||||
</item>
|
||||
</items>
|
@@ -0,0 +1,2 @@
|
||||
"guid","title","body","date","datestamp","datetime","image","image_alt","image_title","link","list_boolean","number_decimal","number_float","number_integer","term","text"
|
||||
1,,,,,,,,,,,,,,,
|
|
@@ -0,0 +1 @@
|
||||
"guid","title_en","title_fr","body_en","body_fr","date_en","date_fr","datestamp_en","datestamp_fr","datetime_en","datetime_fr","image_en","image_fr","image_alt_en","image_alt_fr","image_title_en","image_title_fr","link_en","link_fr","list_boolean_en","list_boolean_fr","number_decimal_en","number_decimal_fr","number_float_en","number_float_fr","number_integer_en","number_integer_fr","term_en","term_fr","text_en","text_fr"
|
Can't render this file because it contains an unexpected character in line 1 and column 424.
|
@@ -0,0 +1 @@
|
||||
"guid","title_en","title_fr","body_en","body_fr","date_en","date_fr","datestamp_en","datestamp_fr","datetime_en","datetime_fr","image_en","image_fr","image_alt_en","image_alt_fr","image_title_en","image_title_fr","link_en","link_fr","list_boolean_en","list_boolean_fr","number_decimal_en","number_decimal_fr","number_float_en","number_float_fr","number_integer_en","number_integer_fr","term_en","term_fr","text_en","text_fr"
|
Can't render this file because it contains an unexpected character in line 1 and column 424.
|
2
sites/all/modules/feeds/tests/feeds/multilingual_fr.csv
Normal file
2
sites/all/modules/feeds/tests/feeds/multilingual_fr.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
"guid","title","body","date","datestamp","datetime","image","image_alt","image_title","link","list_boolean","number_decimal","number_float","number_integer","term","text"
|
||||
1,"Teste Feeds Multilingue 1","Ceci est la corps","05-11-1955",-446731187,"1955-11-05 12:00:13","public://images/la fayette.jpeg","La Fayette","la Fayette dans les bois","http://google.fr",1,1.2,5.6295,2000,"Nouvelles","Carottes"
|
|
2
sites/all/modules/feeds/tests/feeds/multilingual_nl.csv
Normal file
2
sites/all/modules/feeds/tests/feeds/multilingual_nl.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
"guid","title","body","date","datestamp","datetime","image","image_alt","image_title","link","list_boolean","number_decimal","number_float","number_integer","term","text"
|
||||
1,"Feeds-meertaligheid 1 testen","Dit is de berichttekst","29-07-1985",491460492,"1985-07-29 4:48:12","public://images/attersee.jpeg","Bij het zien","Bij het zien van de groene vloeistof","http://google.nl",1,30.3,30.2795,30,"Nieuws","Wortelen"
|
|
4
sites/all/modules/feeds/tests/feeds/node_summary.csv
Normal file
4
sites/all/modules/feeds/tests/feeds/node_summary.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
"guid","title","summary","body"
|
||||
1,"Lorem ipsum","Lorem ipsum summary","Lorem ipsum body"
|
||||
2,"Ut wisi enim ad minim veniam","","Ut wisi enim ad minim veniam body"
|
||||
3,"Duis autem vel eum iriure dolor","",""
|
|
10
sites/all/modules/feeds/tests/feeds/nodes_comma.csv
Normal file
10
sites/all/modules/feeds/tests/feeds/nodes_comma.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
Title,Body,published,GUID
|
||||
"Ut wisi enim ad minim veniam", "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.",205200720,2
|
||||
"Duis autem vel eum iriure dolor", "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.",428112720,3
|
||||
"Nam liber tempor", "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum.",1151766000,1
|
||||
Typi non habent"", "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem.",1256326995,4
|
||||
"Lorem ipsum","Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.",1251936720,1
|
||||
"Investigationes demonstraverunt", "Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius.",946702800,5
|
||||
"Claritas est etiam", "Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum.",438112720,6
|
||||
"Mirum est notare", "Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima.",1151066000,7
|
||||
"Eodem modo typi", "Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.",1201936720,8
|
Can't render this file because it contains an unexpected character in line 5 and column 16.
|
10
sites/all/modules/feeds/tests/feeds/nodes_pipe.csv
Normal file
10
sites/all/modules/feeds/tests/feeds/nodes_pipe.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
Title|Body|published|GUID
|
||||
"Ut wisi enim ad minim veniam"| "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat."|205200720|2
|
||||
"Duis autem vel eum iriure dolor"| "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi."|428112720|3
|
||||
"Nam liber tempor"| "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum."|1151766000|1
|
||||
Typi non habent""| "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem."|1256326995|4
|
||||
"Lorem ipsum"|"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat."|1251936720|1
|
||||
"Investigationes demonstraverunt"| "Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius."|946702800|5
|
||||
"Claritas est etiam"| "Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum."|438112720|6
|
||||
"Mirum est notare"| "Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima."|1151066000|7
|
||||
"Eodem modo typi"| "Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum."|1201936720|8
|
Can't render this file because it contains an unexpected character in line 5 and column 16.
|
10
sites/all/modules/feeds/tests/feeds/nodes_plus.csv
Normal file
10
sites/all/modules/feeds/tests/feeds/nodes_plus.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
Title+Body+published+GUID
|
||||
"Ut wisi enim ad minim veniam"+ "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat."+205200720+2
|
||||
"Duis autem vel eum iriure dolor"+ "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi."+428112720+3
|
||||
"Nam liber tempor"+ "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum."+1151766000+1
|
||||
Typi non habent""+ "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem."+1256326995+4
|
||||
"Lorem ipsum"+"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat."+1251936720+1
|
||||
"Investigationes demonstraverunt"+ "Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius."+946702800+5
|
||||
"Claritas est etiam"+ "Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum."+438112720+6
|
||||
"Mirum est notare"+ "Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima."+1151066000+7
|
||||
"Eodem modo typi"+ "Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum."+1201936720+8
|
Can't render this file because it contains an unexpected character in line 2 and column 30.
|
10
sites/all/modules/feeds/tests/feeds/nodes_semicolon.csv
Normal file
10
sites/all/modules/feeds/tests/feeds/nodes_semicolon.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
Title;Body;published;GUID
|
||||
"Ut wisi enim ad minim veniam"; "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.";205200720;2
|
||||
"Duis autem vel eum iriure dolor"; "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.";428112720;3
|
||||
"Nam liber tempor"; "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum.";1151766000;1
|
||||
Typi non habent""; "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem.";1256326995;4
|
||||
"Lorem ipsum";"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.";1251936720;1
|
||||
"Investigationes demonstraverunt"; "Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius.";946702800;5
|
||||
"Claritas est etiam"; "Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum.";438112720;6
|
||||
"Mirum est notare"; "Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima.";1151066000;7
|
||||
"Eodem modo typi"; "Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.";1201936720;8
|
Can't render this file because it contains an unexpected character in line 5 and column 16.
|
10
sites/all/modules/feeds/tests/feeds/nodes_tab.csv
Normal file
10
sites/all/modules/feeds/tests/feeds/nodes_tab.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
Title Body published GUID
|
||||
"Ut wisi enim ad minim veniam" "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat." 205200720 2
|
||||
"Duis autem vel eum iriure dolor" "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi." 428112720 3
|
||||
"Nam liber tempor" "Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum." 1151766000 1
|
||||
Typi non habent"" "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem." 1256326995 4
|
||||
"Lorem ipsum" "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat." 1251936720 1
|
||||
"Investigationes demonstraverunt" "Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius." 946702800 5
|
||||
"Claritas est etiam" "Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum." 438112720 6
|
||||
"Mirum est notare" "Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima." 1151066000 7
|
||||
"Eodem modo typi" "Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum." 1201936720 8
|
Can't render this file because it contains an unexpected character in line 2 and column 33.
|
@@ -0,0 +1,6 @@
|
||||
"guid","title","tags"
|
||||
"1","Lorem Ipsum 1","term1"
|
||||
"2","Lorem Ipsum 2",""
|
||||
"3","Lorem Ipsum 3",
|
||||
"4","Lorem Ipsum 4"," "
|
||||
"5","Lorem Ipsum 5","0"
|
|
3
sites/all/modules/feeds/tests/feeds/terms.csv
Normal file
3
sites/all/modules/feeds/tests/feeds/terms.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
guid,name,parent,parentguid
|
||||
1,Europe,,
|
||||
2,Belgium,Europe,1
|
|
5
sites/all/modules/feeds/tests/feeds/users2.csv
Normal file
5
sites/all/modules/feeds/tests/feeds/users2.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
name,mail,since,password
|
||||
Morticia,morticia@example.com,1244347500,mort
|
||||
Gomez,gomez@example.com,1228572000,gome
|
||||
Wednesday,wednesdayexample.com,1228347137,wedn
|
||||
Pugsley,pugsley@example,1228260225,pugs
|
|
6
sites/all/modules/feeds/tests/feeds/users_updated.csv
Normal file
6
sites/all/modules/feeds/tests/feeds/users_updated.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
name,mail,since,password
|
||||
Morticia,morticia@example.com,1363959643,mort
|
||||
Fester,fester@example.com,1363959643,fest
|
||||
Gomez,gomez@example.com,1363959643,gome
|
||||
Wednesday,wednesdayexample.com,1363959643,wedn
|
||||
Pugsley,pugsley@example,1363959643,pugs
|
|
@@ -38,5 +38,16 @@ class FeedsDateTimeTest extends FeedsWebTestCase {
|
||||
$this->assertTrue(is_numeric($date->format('U')));
|
||||
$date = new FeedsDateTime('12/3/2009 20:00:10');
|
||||
$this->assertTrue(is_numeric($date->format('U')));
|
||||
|
||||
// Check that years above 2000 work correctly.
|
||||
$date1 = new FeedsDateTime(2012);
|
||||
$date2 = new FeedsDateTime('January 2012');
|
||||
$this->assertEqual($date1->format('U'), $date2->format('U'));
|
||||
|
||||
// Check that years before 1902 work correctly.
|
||||
$early_date_string = '01/02/1901';
|
||||
$date = new FeedsDateTime($early_date_string);
|
||||
$this->assertEqual($date->format('m/d/Y'), $early_date_string);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,18 +17,14 @@ class FeedsFileFetcherTestCase extends FeedsWebTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test scheduling on cron.
|
||||
*/
|
||||
public function test() {
|
||||
public function testPublicFiles() {
|
||||
// Set up an importer.
|
||||
$this->createImporterConfiguration('Node import', 'node');
|
||||
// Set and configure plugins and mappings.
|
||||
$edit = array(
|
||||
'content_type' => '',
|
||||
);
|
||||
$this->drupalPost('admin/structure/feeds/node/settings', $edit, 'Save');
|
||||
$this->setSettings('node', NULL, array('content_type' => ''));
|
||||
$this->setPlugin('node', 'FeedsFileFetcher');
|
||||
$this->setPlugin('node', 'FeedsCSVParser');
|
||||
$mappings = array(
|
||||
@@ -40,15 +36,19 @@ class FeedsFileFetcherTestCase extends FeedsWebTestCase {
|
||||
$this->addMappings('node', $mappings);
|
||||
// Straight up upload is covered in other tests, focus on direct mode
|
||||
// and file batching here.
|
||||
$this->setSettings('node', 'FeedsFileFetcher', array('direct' => TRUE));
|
||||
$settings = array(
|
||||
'direct' => TRUE,
|
||||
'directory' => 'public://feeds',
|
||||
);
|
||||
$this->setSettings('node', 'FeedsFileFetcher', $settings);
|
||||
|
||||
// Verify that invalid paths are not accepted.
|
||||
foreach (array('private://', '/tmp/') as $path) {
|
||||
foreach (array('/tmp/') as $path) {
|
||||
$edit = array(
|
||||
'feeds[FeedsFileFetcher][source]' => $path,
|
||||
);
|
||||
$this->drupalPost('import/node', $edit, t('Import'));
|
||||
$this->assertText("File needs to reside within the site's file directory, its path needs to start with public://.");
|
||||
$this->assertText("The file needs to reside within the site's files directory, its path needs to start with scheme://. Available schemes:");
|
||||
$count = db_query("SELECT COUNT(*) FROM {feeds_source} WHERE feed_nid = 0")->fetchField();
|
||||
$this->assertEqual($count, 0);
|
||||
}
|
||||
@@ -67,4 +67,48 @@ class FeedsFileFetcherTestCase extends FeedsWebTestCase {
|
||||
$this->drupalPost('import/node', $edit, t('Import'));
|
||||
$this->assertText('Created 18 nodes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test uploading private files.
|
||||
*/
|
||||
public function testPrivateFiles() {
|
||||
// Set up an importer.
|
||||
$this->createImporterConfiguration('Node import', 'node');
|
||||
// Set and configure plugins and mappings.
|
||||
$edit = array(
|
||||
'content_type' => '',
|
||||
);
|
||||
$this->drupalPost('admin/structure/feeds/node/settings', $edit, 'Save');
|
||||
$this->setPlugin('node', 'FeedsFileFetcher');
|
||||
$this->setPlugin('node', 'FeedsCSVParser');
|
||||
$mappings = array(
|
||||
'0' => array(
|
||||
'source' => 'title',
|
||||
'target' => 'title',
|
||||
),
|
||||
);
|
||||
$this->addMappings('node', $mappings);
|
||||
// Straight up upload is covered in other tests, focus on direct mode
|
||||
// and file batching here.
|
||||
$settings = array(
|
||||
'direct' => TRUE,
|
||||
'directory' => 'private://feeds',
|
||||
);
|
||||
$this->setSettings('node', 'FeedsFileFetcher', $settings);
|
||||
|
||||
// Verify batching through directories.
|
||||
// Copy directory of files.
|
||||
$dir = 'private://batchtest';
|
||||
$this->copyDir($this->absolutePath() . '/tests/feeds/batch', $dir);
|
||||
|
||||
// Ingest directory of files. Set limit to 5 to force processor to batch,
|
||||
// too.
|
||||
variable_set('feeds_process_limit', 5);
|
||||
$edit = array(
|
||||
'feeds[FeedsFileFetcher][source]' => $dir,
|
||||
);
|
||||
$this->drupalPost('import/node', $edit, t('Import'));
|
||||
$this->assertText('Created 18 nodes');
|
||||
}
|
||||
|
||||
}
|
||||
|
47
sites/all/modules/feeds/tests/feeds_fetcher_http.test
Normal file
47
sites/all/modules/feeds/tests/feeds_fetcher_http.test
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains FeedsFileHTTPTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP fetcher test class.
|
||||
*/
|
||||
class FeedsFileHTTPTestCase extends FeedsWebTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Fetcher: HTTP',
|
||||
'description' => 'Tests for file http fetcher plugin.',
|
||||
'group' => 'Feeds',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the Feed URL form.
|
||||
*/
|
||||
public function testFormValidation() {
|
||||
// Set up an importer.
|
||||
$id = drupal_strtolower($this->randomName());
|
||||
$this->createImporterConfiguration($this->randomString(), $id);
|
||||
|
||||
// Check that by default, we add http:// to the front of the URL.
|
||||
$edit = array(
|
||||
'feeds[FeedsHTTPFetcher][source]' => 'example.com',
|
||||
);
|
||||
$this->drupalPost('import/' . $id, $edit, t('Import'));
|
||||
$this->assertText(t('There are no new nodes.'));
|
||||
$this->assertFieldByName('feeds[FeedsHTTPFetcher][source]', 'http://example.com');
|
||||
|
||||
$this->setSettings($id, 'FeedsHTTPFetcher', array('auto_scheme' => 'feed'));
|
||||
$this->drupalPost('import/' . $id, $edit, t('Import'));
|
||||
$this->assertText(t('There are no new nodes.'));
|
||||
$this->assertFieldByName('feeds[FeedsHTTPFetcher][source]', 'feed://example.com');
|
||||
|
||||
$this->setSettings($id, 'FeedsHTTPFetcher', array('auto_scheme' => ''));
|
||||
$this->drupalPost('import/' . $id, $edit, t('Import'));
|
||||
$this->assertText(t('The URL example.com is invalid.'));
|
||||
$this->assertFieldByName('feeds[FeedsHTTPFetcher][source]', 'example.com');
|
||||
}
|
||||
|
||||
}
|
133
sites/all/modules/feeds/tests/feeds_i18n.test
Normal file
133
sites/all/modules/feeds/tests/feeds_i18n.test
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Feedsi18nTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests importing data in a language.
|
||||
*/
|
||||
class Feedsi18nTestCase extends FeedsMapperTestCase {
|
||||
|
||||
/**
|
||||
* The entity type to be tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* The processor being used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $processorName;
|
||||
|
||||
public function setUp($modules = array(), $permissions = array()) {
|
||||
$modules = array_merge($modules, array(
|
||||
'locale',
|
||||
));
|
||||
$permissions = array_merge(array(
|
||||
'administer languages',
|
||||
));
|
||||
parent::setUp($modules, $permissions);
|
||||
|
||||
// Setup other languages.
|
||||
$edit = array(
|
||||
'langcode' => 'nl',
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
$this->assertText(t('The language Dutch has been created and can now be used.'));
|
||||
$edit = array(
|
||||
'langcode' => 'de',
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
$this->assertText(t('The language German has been created and can now be used.'));
|
||||
|
||||
// Include FeedsProcessor.inc to make its constants available.
|
||||
module_load_include('inc', 'feeds', 'plugins/FeedsProcessor');
|
||||
|
||||
// Create and configure importer.
|
||||
$this->createImporterConfiguration('Multilingual term importer', 'i18n');
|
||||
$this->setPlugin('i18n', 'FeedsFileFetcher');
|
||||
$this->setPlugin('i18n', 'FeedsCSVParser');
|
||||
$this->setPlugin('i18n', $this->processorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if entities get the language assigned that is set in the processor.
|
||||
*/
|
||||
public function testImport() {
|
||||
// Import content in German.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the entity's language is in German.
|
||||
$entities = entity_load($this->entityType, array(1, 2));
|
||||
foreach ($entities as $entity) {
|
||||
$this->assertEqual('de', entity_language($this->entityType, $entity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if entities get a different language assigned when the processor's language
|
||||
* is changed.
|
||||
*/
|
||||
public function testChangedLanguageImport() {
|
||||
// Import content in German.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Change processor's language to Dutch.
|
||||
$this->setSettings('i18n', $this->processorName, array('language' => 'nl'));
|
||||
|
||||
// Re-import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the entity's language is now in Dutch.
|
||||
$entities = entity_load($this->entityType, array(1, 2));
|
||||
foreach ($entities as $entity) {
|
||||
$this->assertEqual('nl', entity_language($this->entityType, $entity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if items are imported in LANGUAGE_NONE if the processor's language is disabled.
|
||||
*/
|
||||
public function testDisabledLanguage() {
|
||||
// Disable the German language.
|
||||
$path = 'admin/config/regional/language';
|
||||
$edit = array(
|
||||
'enabled[de]' => FALSE,
|
||||
);
|
||||
$this->drupalPost($path, $edit, t('Save configuration'));
|
||||
|
||||
// Import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the entities have no language assigned.
|
||||
$entities = entity_load($this->entityType, array(1, 2));
|
||||
foreach ($entities as $entity) {
|
||||
$language = entity_language($this->entityType, $entity);
|
||||
$this->assertEqual(LANGUAGE_NONE, $language, format_string('The entity is language neutral (actual: !language).', array('!language' => $language)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if items are imported in LANGUAGE_NONE if the processor's language is removed.
|
||||
*/
|
||||
public function testRemovedLanguage() {
|
||||
// Remove the German language.
|
||||
$path = 'admin/config/regional/language/delete/de';
|
||||
$this->drupalPost($path, array(), t('Delete'));
|
||||
|
||||
// Import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the entities have no language assigned.
|
||||
$entities = entity_load($this->entityType, array(1, 2));
|
||||
foreach ($entities as $entity) {
|
||||
$language = entity_language($this->entityType, $entity);
|
||||
$this->assertEqual(LANGUAGE_NONE, $language, format_string('The entity is language neutral (actual: !language).', array('!language' => $language)));
|
||||
}
|
||||
}
|
||||
}
|
136
sites/all/modules/feeds/tests/feeds_i18n_node.test
Normal file
136
sites/all/modules/feeds/tests/feeds_i18n_node.test
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Feedsi18nNodeTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests importing nodes in a language.
|
||||
*/
|
||||
class Feedsi18nNodeTestCase extends Feedsi18nTestCase {
|
||||
|
||||
/**
|
||||
* Name of created content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $contentType;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Multilingual content',
|
||||
'description' => 'Tests Feeds multilingual support for nodes.',
|
||||
'group' => 'Feeds',
|
||||
'dependencies' => array('locale'),
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp($modules = array(), $permissions = array()) {
|
||||
$this->entityType = 'node';
|
||||
$this->processorName = 'FeedsNodeProcessor';
|
||||
|
||||
parent::setUp($modules, $permissions);
|
||||
|
||||
// Create content type.
|
||||
$this->contentType = $this->createContentType();
|
||||
|
||||
// Configure importer.
|
||||
$this->setSettings('i18n', $this->processorName, array(
|
||||
'bundle' => $this->contentType,
|
||||
'language' => 'de',
|
||||
'update_existing' => FEEDS_UPDATE_EXISTING,
|
||||
'skip_hash_check' => TRUE,
|
||||
));
|
||||
$this->addMappings('i18n', array(
|
||||
0 => array(
|
||||
'source' => 'guid',
|
||||
'target' => 'guid',
|
||||
'unique' => '1',
|
||||
),
|
||||
1 => array(
|
||||
'source' => 'title',
|
||||
'target' => 'title',
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the language setting is available on the processor.
|
||||
*/
|
||||
public function testAvailableProcessorLanguageSetting() {
|
||||
// Check if the language setting is available when the locale module is enabled.
|
||||
$this->drupalGet('admin/structure/feeds/i18n/settings/FeedsNodeProcessor');
|
||||
$this->assertField('language', 'Language field is available on the node processor settings when the locale module is enabled.');
|
||||
|
||||
// Disable the locale module and check if the language setting is no longer available.
|
||||
module_disable(array('locale'));
|
||||
$this->drupalGet('admin/structure/feeds/i18n/settings/FeedsNodeProcessor');
|
||||
$this->assertNoField('language', 'Language field is not available on the node processor settings when the locale module is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests processor language setting in combination with language mapping target.
|
||||
*/
|
||||
public function testWithLanguageMappingTarget() {
|
||||
$this->addMappings('i18n', array(
|
||||
2 => array(
|
||||
'source' => 'language',
|
||||
'target' => 'language',
|
||||
),
|
||||
));
|
||||
|
||||
// Import csv file. The first item has a language specified (Dutch), the second
|
||||
// one has no language specified and should be imported in the processor's language (German).
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content_i18n.csv');
|
||||
|
||||
// The first node should be Dutch.
|
||||
$node = node_load(1);
|
||||
$this->assertEqual('nl', entity_language('node', $node), 'Item 1 has the Dutch language assigned.');
|
||||
|
||||
// The second node should be German.
|
||||
$node = node_load(2);
|
||||
$this->assertEqual('de', entity_language('node', $node), 'Item 2 has the German language assigned.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if nodes get imported in LANGUAGE_NONE when the locale module gets disabled.
|
||||
*/
|
||||
public function testDisabledLocaleModule() {
|
||||
module_disable(array('locale'));
|
||||
// Make sure that entity info is reset.
|
||||
drupal_flush_all_caches();
|
||||
drupal_static_reset();
|
||||
|
||||
// Import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the content has no language assigned.
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$node = node_load($i);
|
||||
$language = entity_language('node', $node);
|
||||
$this->assertEqual(LANGUAGE_NONE, $language, format_string('The node is language neutral (actual: !language).', array('!language' => $language)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if nodes get imported in LANGUAGE_NONE when the locale module gets uninstalled.
|
||||
*/
|
||||
public function testUninstalledLocaleModule() {
|
||||
module_disable(array('locale'));
|
||||
drupal_uninstall_modules(array('locale'));
|
||||
// Make sure that entity info is reset.
|
||||
drupal_flush_all_caches();
|
||||
drupal_static_reset();
|
||||
|
||||
// Import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the content has no language assigned.
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$node = node_load($i);
|
||||
$language = entity_language('node', $node);
|
||||
$this->assertEqual(LANGUAGE_NONE, $language, format_string('The node is language neutral (actual: !language).', array('!language' => $language)));
|
||||
}
|
||||
}
|
||||
}
|
119
sites/all/modules/feeds/tests/feeds_i18n_taxonomy.test
Normal file
119
sites/all/modules/feeds/tests/feeds_i18n_taxonomy.test
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Feedsi18nTaxonomyTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests importing terms in a language.
|
||||
*/
|
||||
class Feedsi18nTaxonomyTestCase extends Feedsi18nTestCase {
|
||||
|
||||
/**
|
||||
* Name of created vocabulary.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $vocabulary;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Multilingual terms',
|
||||
'description' => 'Tests Feeds multilingual support for taxonomy terms.',
|
||||
'group' => 'Feeds',
|
||||
'dependencies' => array('locale', 'i18n_taxonomy'),
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp($modules = array(), $permissions = array()) {
|
||||
$this->entityType = 'taxonomy_term';
|
||||
$this->processorName = 'FeedsTermProcessor';
|
||||
|
||||
$modules = array_merge($modules, array(
|
||||
'i18n_taxonomy',
|
||||
));
|
||||
parent::setUp($modules, $permissions);
|
||||
|
||||
// Create vocabulary.
|
||||
$this->vocabulary = strtolower($this->randomName(8));
|
||||
$edit = array(
|
||||
'name' => $this->vocabulary,
|
||||
'machine_name' => $this->vocabulary,
|
||||
);
|
||||
$this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
|
||||
|
||||
// Configure importer.
|
||||
$this->setSettings('i18n', $this->processorName, array(
|
||||
'bundle' => $this->vocabulary,
|
||||
'language' => 'de',
|
||||
'update_existing' => FEEDS_UPDATE_EXISTING,
|
||||
'skip_hash_check' => TRUE,
|
||||
));
|
||||
$this->addMappings('i18n', array(
|
||||
0 => array(
|
||||
'source' => 'guid',
|
||||
'target' => 'guid',
|
||||
'unique' => '1',
|
||||
),
|
||||
1 => array(
|
||||
'source' => 'title',
|
||||
'target' => 'name',
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the language setting is available on the processor.
|
||||
*/
|
||||
public function testAvailableProcessorLanguageSetting() {
|
||||
// Check if the language setting is available when the i18n_taxonomy module is enabled.
|
||||
$this->drupalGet('admin/structure/feeds/i18n/settings/FeedsTermProcessor');
|
||||
$this->assertField('language', 'Language field is available on the term processor settings when the i18n_taxonomy module is enabled.');
|
||||
|
||||
// Disable the i18n_taxonomy module and check if the language setting is no longer available.
|
||||
module_disable(array('i18n_taxonomy'));
|
||||
$this->drupalGet('admin/structure/feeds/i18n/settings/FeedsTermProcessor');
|
||||
$this->assertNoField('language', 'Language field is not available on the term processor settings when the i18n_taxonomy module is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if terms get imported in LANGUAGE_NONE when the i18n_taxonomy module gets disabled.
|
||||
*/
|
||||
public function testDisabledi18nTaxonomyModule() {
|
||||
module_disable(array('i18n_taxonomy'));
|
||||
// Make sure that entity info is reset.
|
||||
drupal_flush_all_caches();
|
||||
drupal_static_reset();
|
||||
|
||||
// Import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the terms have no language assigned.
|
||||
$entities = entity_load($this->entityType, array(1, 2));
|
||||
foreach ($entities as $entity) {
|
||||
// Terms shouldn't have a language property.
|
||||
$this->assertFalse(isset($entity->language), 'The term does not have a language.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if terms get imported in LANGUAGE_NONE when the i18n_taxonomy module gets uninstalled.
|
||||
*/
|
||||
public function testUninstalledi18nTaxonomyModule() {
|
||||
module_disable(array('i18n_taxonomy'));
|
||||
drupal_uninstall_modules(array('i18n_taxonomy'));
|
||||
// Make sure that entity info is reset.
|
||||
drupal_flush_all_caches();
|
||||
drupal_static_reset();
|
||||
|
||||
// Import content.
|
||||
$this->importFile('i18n', $this->absolutePath() . '/tests/feeds/content.csv');
|
||||
|
||||
// Assert that the terms have no language assigned.
|
||||
$entities = entity_load($this->entityType, array(1, 2));
|
||||
foreach ($entities as $entity) {
|
||||
$this->assertFalse(isset($entity->language), 'The term does not have a language.');
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,11 +2,11 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Helper class with auxiliary functions for feeds mapper module tests.
|
||||
* Contains FeedsMapperTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for implementing Feeds Mapper test cases.
|
||||
* Helper class with auxiliary functions for feeds mapper module tests.
|
||||
*/
|
||||
class FeedsMapperTestCase extends FeedsWebTestCase {
|
||||
|
||||
@@ -19,13 +19,19 @@ class FeedsMapperTestCase extends FeedsWebTestCase {
|
||||
'email' => 'email_textfield',
|
||||
'emimage' => 'emimage_textfields',
|
||||
'emaudio' => 'emaudio_textfields',
|
||||
'filefield' => 'filefield_widget',
|
||||
'image' => 'imagefield_widget',
|
||||
'file' => 'file_generic',
|
||||
'image' => 'image_image',
|
||||
'link_field' => 'link_field',
|
||||
'list_boolean' => 'options_onoff',
|
||||
'list_float' => 'options_select',
|
||||
'list_integer' => 'options_select',
|
||||
'list_text' => 'options_select',
|
||||
'number_float' => 'number',
|
||||
'number_integer' => 'number',
|
||||
'nodereference' => 'nodereference_select',
|
||||
'text' => 'text_textfield',
|
||||
'text_long' => 'text_textarea',
|
||||
'text_with_summary' => 'text_textarea_with_summary',
|
||||
'userreference' => 'userreference_select',
|
||||
);
|
||||
|
||||
@@ -52,6 +58,29 @@ class FeedsMapperTestCase extends FeedsWebTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a form field for the given field with the given value
|
||||
* does not exist in the current form.
|
||||
*
|
||||
* @param $field_name
|
||||
* The name of the field.
|
||||
* @param $value
|
||||
* The (raw) value of the field.
|
||||
* @param $index
|
||||
* The index of the field (for q multi-valued field).
|
||||
*
|
||||
* @see FeedsMapperTestCase::getFormFieldsNames()
|
||||
* @see FeedsMapperTestCase::getFormFieldsValues()
|
||||
*/
|
||||
protected function assertNoNodeFieldValue($field_name, $value, $index = 0) {
|
||||
$names = $this->getFormFieldsNames($field_name, $index);
|
||||
$values = $this->getFormFieldsValues($field_name, $value);
|
||||
foreach ($names as $k => $name) {
|
||||
$value = $values[$k];
|
||||
$this->assertNoFieldByName($name, $value, t('Did not find form field %name for %field_name with the value %value.', array('%name' => $name, '%field_name' => $field_name, '%value' => $value)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form fields names for a given CCK field. Default implementation
|
||||
* provides support for a single form field with the following name pattern
|
||||
@@ -157,4 +186,5 @@ class FeedsMapperTestCase extends FeedsWebTestCase {
|
||||
$field_widgets = FeedsMapperTestCase::$field_widgets;
|
||||
return isset($field_widgets[$field_type]) ? $field_widgets[$field_type] : NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,13 +2,14 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test cases for Feeds mapping configuration form.
|
||||
* Contains FeedsMapperConfigTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for testing basic Feeds ajax mapping configurtaion form behavior.
|
||||
* Test cases for Feeds mapping configuration form.
|
||||
*/
|
||||
class FeedsMapperConfigTestCase extends FeedsMapperTestCase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Mapper: Config',
|
||||
@@ -37,6 +38,8 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase {
|
||||
// Click gear to get form.
|
||||
$this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_0');
|
||||
|
||||
$second_callback_value = $this->randomString();
|
||||
|
||||
// Set some settings.
|
||||
$edit = array(
|
||||
'config[0][settings][checkbox]' => 1,
|
||||
@@ -44,8 +47,10 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase {
|
||||
'config[0][settings][textarea]' => 'Textarea value: Didery dofffffffffffffffffffffffffffffffffffff',
|
||||
'config[0][settings][radios]' => 'option1',
|
||||
'config[0][settings][select]' => 'option4',
|
||||
'config[0][settings][second_value]' => $second_callback_value,
|
||||
);
|
||||
$this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_0');
|
||||
$this->assertText(t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.'));
|
||||
|
||||
// Click Save.
|
||||
$this->drupalPost(NULL, array(), t('Save'));
|
||||
@@ -59,6 +64,7 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase {
|
||||
$this->assertText('Textarea value: Didery dofffffffffffffffffffffffffffffffffffff');
|
||||
$this->assertText('Radios value: Option 1');
|
||||
$this->assertText('Select value: Another One');
|
||||
$this->assertText(t('Second summary: @value', array('@value' => $second_callback_value)));
|
||||
|
||||
// Check that settings are in db.
|
||||
$config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id='syndication'")->fetchField());
|
||||
@@ -69,7 +75,7 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase {
|
||||
$this->assertEqual($settings['textarea'], 'Textarea value: Didery dofffffffffffffffffffffffffffffffffffff');
|
||||
$this->assertEqual($settings['radios'], 'option1');
|
||||
$this->assertEqual($settings['select'], 'option4');
|
||||
|
||||
$this->assertEqual($settings['second_value'], $second_callback_value);
|
||||
|
||||
// Check that form validation works.
|
||||
// Click gear to get form.
|
||||
@@ -112,4 +118,5 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase {
|
||||
$this->assertText('Checkbox inactive.');
|
||||
$this->assertText('Second mapping text');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,16 +2,17 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test case for CCK date field mapper mappers/date.inc.
|
||||
* Contains FeedsMapperDateTestCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for testing Feeds <em>content</em> mapper.
|
||||
* Test case for CCK date field mapper mappers/date.inc.
|
||||
*
|
||||
* @todo: Add test method iCal
|
||||
* @todo: Add test method for end date
|
||||
*/
|
||||
class FeedsMapperDateTestCase extends FeedsMapperTestCase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Mapper: Date',
|
||||
@@ -39,12 +40,33 @@ class FeedsMapperDateTestCase extends FeedsMapperTestCase {
|
||||
//'datetime' => 'datetime', // REMOVED because the field is broken ATM.
|
||||
));
|
||||
|
||||
// Hack to get date fields to not round to every 15 minutes.
|
||||
foreach (array('date', 'datestamp') as $field) {
|
||||
$field = 'field_' . $field;
|
||||
$edit = array(
|
||||
'widget_type' => 'date_select',
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/' . $typename . '/fields/' . $field . '/widget-type', $edit, 'Continue');
|
||||
$edit = array(
|
||||
'instance[widget][settings][increment]' => 1,
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/' . $typename . '/fields/' . $field, $edit, 'Save settings');
|
||||
$edit = array(
|
||||
'widget_type' => 'date_text',
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/' . $typename . '/fields/' . $field . '/widget-type', $edit, 'Continue');
|
||||
}
|
||||
|
||||
// Create and configure importer.
|
||||
$this->createImporterConfiguration('Date RSS', 'daterss');
|
||||
$this->setSettings('daterss', NULL, array('content_type' => '', 'import_period' => FEEDS_SCHEDULE_NEVER));
|
||||
$this->setSettings('daterss', NULL, array(
|
||||
'content_type' => '',
|
||||
'import_period' => FEEDS_SCHEDULE_NEVER,
|
||||
));
|
||||
$this->setPlugin('daterss', 'FeedsFileFetcher');
|
||||
$this->setPlugin('daterss', 'FeedsSyndicationParser');
|
||||
$this->setSettings('daterss', 'FeedsNodeProcessor', array('content_type' => $typename));
|
||||
$this->setSettings('daterss', 'FeedsNodeProcessor', array(
|
||||
'bundle' => $typename,
|
||||
));
|
||||
$this->addMappings('daterss', array(
|
||||
0 => array(
|
||||
'source' => 'title',
|
||||
@@ -81,7 +103,7 @@ class FeedsMapperDateTestCase extends FeedsMapperTestCase {
|
||||
'01/06/2010 - 06:05',
|
||||
'01/06/2010 - 11:26',
|
||||
'01/07/2010 - 00:26',
|
||||
);
|
||||
);
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
$this->drupalGet("node/$i/edit");
|
||||
$this->assertNodeFieldValue('date', $values[$i-1]);
|
||||
@@ -97,4 +119,154 @@ class FeedsMapperDateTestCase extends FeedsMapperTestCase {
|
||||
return parent::getFormFieldsNames($field_name, $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if values are cleared out when an empty value is provided.
|
||||
*/
|
||||
public function testClearOutValues() {
|
||||
// Create content type.
|
||||
$typename = $this->createContentType(array(), array(
|
||||
'date' => 'date',
|
||||
'datestamp' => 'datestamp',
|
||||
'datetime' => 'datetime',
|
||||
));
|
||||
|
||||
// Hack to get date fields to not round to every 15 minutes.
|
||||
foreach (array('date', 'datestamp', 'datetime') as $field) {
|
||||
$field = 'field_' . $field;
|
||||
$edit = array(
|
||||
'widget_type' => 'date_select',
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/' . $typename . '/fields/' . $field . '/widget-type', $edit, 'Continue');
|
||||
$edit = array(
|
||||
'instance[widget][settings][increment]' => 1,
|
||||
'field[settings][enddate_get]' => 1,
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/' . $typename . '/fields/' . $field, $edit, 'Save settings');
|
||||
$edit = array(
|
||||
'widget_type' => 'date_text',
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/' . $typename . '/fields/' . $field . '/widget-type', $edit, 'Continue');
|
||||
}
|
||||
|
||||
// Create and configure importer.
|
||||
$this->createImporterConfiguration('Content CSV', 'csv');
|
||||
$this->setSettings('csv', NULL, array(
|
||||
'content_type' => '',
|
||||
'import_period' => FEEDS_SCHEDULE_NEVER,
|
||||
));
|
||||
$this->setPlugin('csv', 'FeedsFileFetcher');
|
||||
$this->setPlugin('csv', 'FeedsCSVParser');
|
||||
$this->setSettings('csv', 'FeedsNodeProcessor', array(
|
||||
'bundle' => $typename,
|
||||
'update_existing' => 1,
|
||||
));
|
||||
$this->addMappings('csv', array(
|
||||
0 => array(
|
||||
'source' => 'title',
|
||||
'target' => 'title',
|
||||
'unique' => TRUE,
|
||||
),
|
||||
1 => array(
|
||||
'source' => 'created',
|
||||
'target' => 'field_date:start',
|
||||
),
|
||||
2 => array(
|
||||
'source' => 'end',
|
||||
'target' => 'field_date:end',
|
||||
),
|
||||
3 => array(
|
||||
'source' => 'created',
|
||||
'target' => 'field_datestamp:start',
|
||||
),
|
||||
4 => array(
|
||||
'source' => 'end',
|
||||
'target' => 'field_datestamp:end',
|
||||
),
|
||||
5 => array(
|
||||
'source' => 'created',
|
||||
'target' => 'field_datetime:start',
|
||||
),
|
||||
6 => array(
|
||||
'source' => 'end',
|
||||
'target' => 'field_datetime:end',
|
||||
),
|
||||
));
|
||||
|
||||
// Import CSV file.
|
||||
$this->importFile('csv', $this->absolutePath() . '/tests/feeds/content_date.csv');
|
||||
$this->assertText('Created 2 nodes');
|
||||
|
||||
// Check the imported nodes.
|
||||
$date_values = array(
|
||||
1 => array(
|
||||
'created' => '09/03/2009 - 00:12',
|
||||
'end' => '11/03/2012 - 09:58',
|
||||
),
|
||||
2 => array(
|
||||
'created' => '09/02/2009 - 22:59',
|
||||
'end' => '11/03/2012 - 08:46',
|
||||
),
|
||||
);
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$this->drupalGet("node/$i/edit");
|
||||
$this->assertNodeFieldValue('date', $date_values[$i]['created']);
|
||||
$this->assertFieldByName('field_date[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->assertNodeFieldValue('datestamp', $date_values[$i]['created']);
|
||||
$this->assertFieldByName('field_datestamp[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->assertNodeFieldValue('datetime', $date_values[$i]['created']);
|
||||
$this->assertFieldByName('field_datetime[und][0][value2][date]', $date_values[$i]['end']);
|
||||
}
|
||||
|
||||
// Import CSV file with empty values.
|
||||
$this->importFile('csv', $this->absolutePath() . '/tests/feeds/content_empty.csv');
|
||||
$this->assertText('Updated 2 nodes');
|
||||
|
||||
// Check if all values were cleared out for both nodes.
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$this->drupalGet("node/$i/edit");
|
||||
$this->assertNoNodeFieldValue('date', $date_values[$i]['created']);
|
||||
$this->assertNoFieldByName('field_date[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->assertNoNodeFieldValue('datestamp', $date_values[$i]['created']);
|
||||
$this->assertNoFieldByName('field_datestamp[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->assertNoNodeFieldValue('datetime', $date_values[$i]['created']);
|
||||
$this->assertNoFieldByName('field_datetime[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->drupalGet("node/$i");
|
||||
$this->assertNoText('date_label');
|
||||
$this->assertNoText('datestamp_label');
|
||||
$this->assertNoText('datetime_label');
|
||||
}
|
||||
|
||||
// Re-import the first file again and check if the values returned.
|
||||
$this->importFile('csv', $this->absolutePath() . '/tests/feeds/content_date.csv');
|
||||
$this->assertText('Updated 2 nodes');
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$this->drupalGet("node/$i/edit");
|
||||
$this->assertNodeFieldValue('date', $date_values[$i]['created']);
|
||||
$this->assertFieldByName('field_date[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->assertNodeFieldValue('datestamp', $date_values[$i]['created']);
|
||||
$this->assertFieldByName('field_datestamp[und][0][value2][date]', $date_values[$i]['end']);
|
||||
$this->assertNodeFieldValue('datetime', $date_values[$i]['created']);
|
||||
$this->assertFieldByName('field_datetime[und][0][value2][date]', $date_values[$i]['end']);
|
||||
}
|
||||
|
||||
// Import CSV file with non-existent values.
|
||||
$this->importFile('csv', $this->absolutePath() . '/tests/feeds/content_non_existent.csv');
|
||||
$this->assertText('Updated 2 nodes');
|
||||
|
||||
// Check if all values were cleared out for node 1.
|
||||
$this->drupalGet('node/1/edit');
|
||||
$this->assertNoNodeFieldValue('date', $date_values[1]['created']);
|
||||
$this->assertNoFieldByName('field_date[und][0][value2][date]', $date_values[1]['end']);
|
||||
$this->assertNoNodeFieldValue('datestamp', $date_values[1]['created']);
|
||||
$this->assertNoFieldByName('field_datestamp[und][0][value2][date]', $date_values[1]['end']);
|
||||
$this->assertNoNodeFieldValue('datetime', $date_values[1]['created']);
|
||||
$this->assertNoFieldByName('field_datetime[und][0][value2][date]', $date_values[1]['end']);
|
||||
// Check if labels for fields that should be cleared out are not shown.
|
||||
$this->drupalGet('node/1');
|
||||
$this->assertNoText('date_label');
|
||||
$this->assertNoText('datestamp_label');
|
||||
$this->assertNoText('datetime_label');
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user