From c70304c5f25930e3c9cabbb76b7671a0cf7613e1 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa Date: Sat, 27 Feb 2016 13:52:25 +0100 Subject: [PATCH 1/2] Update feeds to 7.x-2.0-beta2 --- .../all/modules/contrib/feeds/CHANGELOG.txt | 261 +++- .../all/modules/contrib/feeds/README.txt | 6 + .../all/modules/contrib/feeds/feeds.api.php | 147 ++- .../all/modules/contrib/feeds/feeds.feeds.inc | 5 + .../all/modules/contrib/feeds/feeds.info | 18 +- .../all/modules/contrib/feeds/feeds.install | 62 +- .../all/modules/contrib/feeds/feeds.module | 32 +- .../all/modules/contrib/feeds/feeds.pages.inc | 2 +- .../feeds/feeds_import/feeds_import.info | 6 +- .../contrib/feeds/feeds_news/feeds_news.info | 6 +- .../contrib/feeds/feeds_ui/feeds_ui.admin.inc | 30 +- .../contrib/feeds/feeds_ui/feeds_ui.css | 2 +- .../contrib/feeds/feeds_ui/feeds_ui.info | 6 +- .../contrib/feeds/feeds_ui/feeds_ui.test | 4 +- .../feeds/includes/FeedsConfigurable.inc | 17 +- .../contrib/feeds/includes/FeedsSource.inc | 69 +- .../contrib/feeds/libraries/ParserCSV.inc | 58 +- .../libraries/common_syndication_parser.inc | 6 +- .../contrib/feeds/libraries/http_request.inc | 46 +- .../modules/contrib/feeds/mappers/date.inc | 4 +- .../feeds/mappers/entity_translation.inc | 83 ++ .../modules/contrib/feeds/mappers/file.inc | 32 +- .../modules/contrib/feeds/mappers/link.inc | 8 +- .../modules/contrib/feeds/mappers/list.inc | 17 +- .../modules/contrib/feeds/mappers/locale.inc | 118 ++ .../modules/contrib/feeds/mappers/number.inc | 8 +- .../contrib/feeds/mappers/taxonomy.inc | 19 +- .../modules/contrib/feeds/mappers/text.inc | 8 +- .../contrib/feeds/plugins/FeedsCSVParser.inc | 189 ++- .../feeds/plugins/FeedsFileFetcher.inc | 14 +- .../feeds/plugins/FeedsNodeProcessor.inc | 15 +- .../contrib/feeds/plugins/FeedsParser.inc | 45 +- .../contrib/feeds/plugins/FeedsPlugin.inc | 2 +- .../contrib/feeds/plugins/FeedsProcessor.inc | 299 +++-- .../feeds/plugins/FeedsSimplePieParser.inc | 2 +- .../feeds/plugins/FeedsTermProcessor.inc | 34 +- .../feeds/plugins/FeedsUserProcessor.inc | 5 +- .../tests/common_syndication_parser.test | 9 + .../contrib/feeds/tests/feeds/content.csv | 6 +- .../feeds/tests/feeds/content_empty.csv | 6 +- .../feeds/tests/feeds/content_i18n.csv | 3 + .../feeds/earthquake-georss-noauthor.atom | 35 + .../feeds/tests/feeds/encoding.csv.php | 33 + .../feeds/tests/feeds/encoding_SJIS-win.csv | 5 + .../feeds/tests/feeds/encoding_SJIS.csv | 5 + .../feeds/tests/feeds/encoding_UTF-8.csv | 5 + .../feeds/tests/feeds/multilingual_empty.csv | 2 + .../feeds/tests/feeds/multilingual_en_fr.csv | 1 + .../tests/feeds/multilingual_en_fr_empty.csv | 1 + .../feeds/tests/feeds/multilingual_fr.csv | 2 + .../feeds/tests/feeds/multilingual_nl.csv | 2 + .../contrib/feeds/tests/feeds/terms.csv | 3 + .../feeds/tests/feeds/users_updated.csv | 6 + .../contrib/feeds/tests/feeds_i18n.test | 133 +++ .../contrib/feeds/tests/feeds_i18n_node.test | 136 +++ .../feeds/tests/feeds_i18n_taxonomy.test | 119 ++ .../contrib/feeds/tests/feeds_mapper.test | 3 + .../feeds/tests/feeds_mapper_list.test | 180 +++ .../feeds_mapper_multilingual_fields.test | 1053 +++++++++++++++++ .../feeds/tests/feeds_mapper_profile.test | 2 +- .../feeds/tests/feeds_mapper_taxonomy.test | 2 + .../contrib/feeds/tests/feeds_parser_csv.test | 100 ++ .../feeds/tests/feeds_processor_node.test | 186 ++- .../feeds/tests/feeds_processor_term.test | 256 ++++ .../contrib/feeds/tests/feeds_tests.info | 9 +- .../contrib/feeds/tests/feeds_tests.module | 22 +- .../contrib/feeds/tests/http_request.test | 305 +++++ .../contrib/feeds/tests/parser_csv.test | 54 + .../contrib/feeds/views/feeds.views.inc | 114 +- .../feeds/views/feeds.views_default.inc | 10 +- 70 files changed, 4118 insertions(+), 375 deletions(-) create mode 100644 www7/sites/all/modules/contrib/feeds/mappers/entity_translation.inc create mode 100644 www7/sites/all/modules/contrib/feeds/mappers/locale.inc create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/content_i18n.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/earthquake-georss-noauthor.atom create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/encoding.csv.php create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/encoding_SJIS-win.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/encoding_SJIS.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/encoding_UTF-8.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/multilingual_empty.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/multilingual_en_fr.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/multilingual_en_fr_empty.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/multilingual_fr.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/multilingual_nl.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/terms.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds/users_updated.csv create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds_i18n.test create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_node.test create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_taxonomy.test create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_list.test create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_multilingual_fields.test create mode 100644 www7/sites/all/modules/contrib/feeds/tests/feeds_parser_csv.test diff --git a/www7/sites/all/modules/contrib/feeds/CHANGELOG.txt b/www7/sites/all/modules/contrib/feeds/CHANGELOG.txt index cee98c059..7ced0112c 100644 --- a/www7/sites/all/modules/contrib/feeds/CHANGELOG.txt +++ b/www7/sites/all/modules/contrib/feeds/CHANGELOG.txt @@ -1,3 +1,262 @@ +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 --------------------------------- @@ -412,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. diff --git a/www7/sites/all/modules/contrib/feeds/README.txt b/www7/sites/all/modules/contrib/feeds/README.txt index ba56e891b..dd112ea4e 100644 --- a/www7/sites/all/modules/contrib/feeds/README.txt +++ b/www7/sites/all/modules/contrib/feeds/README.txt @@ -209,6 +209,12 @@ 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 ======== diff --git a/www7/sites/all/modules/contrib/feeds/feeds.api.php b/www7/sites/all/modules/contrib/feeds/feeds.api.php index 46cf90201..973bdab96 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds.api.php +++ b/www7/sites/all/modules/contrib/feeds/feeds.api.php @@ -227,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', ); } @@ -273,44 +273,104 @@ function my_source_get_source(FeedsSource $source, FeedsParserResult $result, $k * The entity bundle to return targets for. * * @return array - * Array containing the targets to be offered to the user. This function must - * return an array, even an empty one. + * 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($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_callbacks' => array('my_module_summary_callback'), - 'form_callbacks' => array('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('Description of what my third custom node field does.'), + 'description' => t('A field that can be set as an unique target.'), 'callback' => 'my_module_set_target3', - - // Set optional_unique to TRUE and specify unique_callbacks to allow the - // target to be unique. Existing entities can be updated based on unique - // targets. 'optional_unique' => TRUE, 'unique_callbacks' => array('my_module_mapper_unique'), + ); - // Preprocess callbacks are called before the actual callback allowing you - // to prepare values on the entity or mapping array. + // 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; @@ -332,6 +392,7 @@ function hook_feeds_processor_targets($entity_type, $bundle) { * @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'; @@ -362,6 +423,27 @@ function my_module_set_target(FeedsSource $source, $entity, $target, array $valu } } +/** + * Example of the form_callback specified in hook_feeds_processor_targets(). + * + * The arguments are the same that my_module_summary_callback() gets. + * + * @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(array $mapping, $target, array $form, array $form_state) { + return array( + 'my_setting' => array( + '#type' => 'checkbox', + '#title' => t('My setting checkbox'), + '#default_value' => !empty($mapping['my_setting']), + ), + ); +} + /** * Example of the summary_callback specified in hook_feeds_processor_targets(). * @@ -390,27 +472,6 @@ function my_module_summary_callback(array $mapping, $target, array $form, array } } -/** - * Example of the form_callback specified in hook_feeds_processor_targets(). - * - * The arguments are the same that my_module_summary_callback() gets. - * - * @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(array $mapping, $target, array $form, array $form_state) { - return array( - 'my_setting' => array( - '#type' => 'checkbox', - '#title' => t('My setting checkbox'), - '#default_value' => !empty($mapping['my_setting']), - ), - ); -} - /** * Example of the unique_callbacks specified in hook_feeds_processor_targets(). * @@ -449,10 +510,6 @@ function my_module_mapper_unique(FeedsSource $source, $entity_type, $bundle, $ta /** * Example of the preprocess_callbacks specified in hook_feeds_processor_targets(). * - * @param FeedsSource $source - * The Feed source. - * @param object $entity - * The entity being processed. * @param array $target * The full target definition. * @param array &$mapping @@ -460,7 +517,7 @@ function my_module_mapper_unique(FeedsSource $source, $entity_type, $bundle, $ta * * @see hook_feeds_processor_targets() */ -function my_module_preprocess_callback(FeedsSource $source, $entity, array $target, array &$mapping) { +function my_module_preprocess_callback(array $target, array &$mapping) { // Add in default values. $mapping += array('setting_value' => TRUE); } diff --git a/www7/sites/all/modules/contrib/feeds/feeds.feeds.inc b/www7/sites/all/modules/contrib/feeds/feeds.feeds.inc index a51a7a6c5..09ea0a5b6 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds.feeds.inc +++ b/www7/sites/all/modules/contrib/feeds/feeds.feeds.inc @@ -23,6 +23,11 @@ function feeds_feeds_processor_targets($entity_type, $bundle) { 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'); } /** diff --git a/www7/sites/all/modules/contrib/feeds/feeds.info b/www7/sites/all/modules/contrib/feeds/feeds.info index 42c607f4d..24b6a6820 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds.info +++ b/www7/sites/all/modules/contrib/feeds/feeds.info @@ -5,6 +5,12 @@ 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 @@ -38,6 +44,8 @@ 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 @@ -46,6 +54,10 @@ 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 @@ -68,9 +80,9 @@ 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 2015-07-11 -version = "7.x-2.0-beta1" +; Information added by Drupal.org packaging script on 2016-02-21 +version = "7.x-2.0-beta2" core = "7.x" project = "feeds" -datestamp = "1436615941" +datestamp = "1456055647" diff --git a/www7/sites/all/modules/contrib/feeds/feeds.install b/www7/sites/all/modules/contrib/feeds/feeds.install index 683a63e89..ab2695748 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds.install +++ b/www7/sites/all/modules/contrib/feeds/feeds.install @@ -60,6 +60,7 @@ function feeds_requirements($phase) { function feeds_uninstall() { variable_del('http_request_timeout'); variable_del('feeds_reschedule'); + variable_del('feeds_use_mbstring'); } /** @@ -689,65 +690,10 @@ function feeds_update_7209() { DrupalQueue::get('feeds_importer_expire')->deleteQueue(); } -/** - * Fix importer mappings for file and image fields to use :uri convention. - */ + /** + * Does nothing. Update removed. + */ function feeds_update_7211(&$sandbox) { - // If this is the first pass through this update function then set some - // variables. - if (!isset($sandbox['progress'])) { - $importers = feeds_importer_load_all(TRUE); - $sandbox['importers'] = array_keys($importers); - $sandbox['progress'] = 0; - $sandbox['updated_count'] = 0; - $sandbox['max'] = count($sandbox['importers']); - } - - if (empty($sandbox['importers'])) { - return t('No importers to process.'); - } - - // Load a single importer. - $importer = array_pop($sandbox['importers']); - $importer = feeds_importer($importer); - $mappings = $importer->processor->getMappings(); - - foreach ($mappings as $key => $mapping) { - // Act on mappings that do not contain a colon. - if (strpos($mapping['target'], ':') !== FALSE) { - continue; - } - - // Get field data. Check if $info is empty to weed out non-field mappings - // like temporary targets. - if (!$info = field_info_field($mapping['target'])) { - continue; - } - - // Act on file or image fields. - if (in_array($info['type'], array('file', 'image'))) { - // Add ':uri' to fix the mapping. - $mappings[$key]['target'] = $mapping['target'] . ':uri'; - } - } - - // If importer has changed, add the updated config and save it. - if ($importer->processor->getMappings() !== $mappings) { - $importer->processor->addConfig(array('mappings' => $mappings)); - $importer->save(); - $sandbox['updated_count']++; - } - - $sandbox['progress']++; - - // Set the value for finished. If progress == max then finished will be 1, - // signifying we are done. - $sandbox['#finished'] = ($sandbox['progress'] / $sandbox['max']); - - if (empty($sandbox['importers'])) { - $sandbox['#finished'] = 1; - return t('@importers total importers processed, @updated_count with fields that were updated.', array('@importers' => $sandbox['max'], '@updated_count' => $sandbox['updated_count'])); - } } /** diff --git a/www7/sites/all/modules/contrib/feeds/feeds.module b/www7/sites/all/modules/contrib/feeds/feeds.module index f0dd48cff..41122b4f2 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds.module +++ b/www7/sites/all/modules/contrib/feeds/feeds.module @@ -634,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; + } } /** @@ -830,7 +835,14 @@ function _feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bu * Implements hook_flush_caches(). */ function feeds_flush_caches() { - return array('cache_feeds_http'); + // 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(); } /** @@ -864,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. * diff --git a/www7/sites/all/modules/contrib/feeds/feeds.pages.inc b/www7/sites/all/modules/contrib/feeds/feeds.pages.inc index eeeab9097..6eddf47b3 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds.pages.inc +++ b/www7/sites/all/modules/contrib/feeds/feeds.pages.inc @@ -354,7 +354,7 @@ function theme_feeds_upload($variables) { $summary .= '
'; $summary .= '
'; if ($wrapper) { - $summary .= l(check_plain($file->filename), $wrapper->getExternalUrl()); + $summary .= l($file->filename, $wrapper->getExternalUrl()); } else { $summary .= t('URI scheme %scheme not available.', array('%scheme' => file_uri_scheme($uri))); diff --git a/www7/sites/all/modules/contrib/feeds/feeds_import/feeds_import.info b/www7/sites/all/modules/contrib/feeds/feeds_import/feeds_import.info index 4aee1f067..189ca0920 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds_import/feeds_import.info +++ b/www7/sites/all/modules/contrib/feeds/feeds_import/feeds_import.info @@ -10,9 +10,9 @@ features[feeds_importer][] = node features[feeds_importer][] = user files[] = feeds_import.test -; Information added by Drupal.org packaging script on 2015-07-11 -version = "7.x-2.0-beta1" +; Information added by Drupal.org packaging script on 2016-02-21 +version = "7.x-2.0-beta2" core = "7.x" project = "feeds" -datestamp = "1436615941" +datestamp = "1456055647" diff --git a/www7/sites/all/modules/contrib/feeds/feeds_news/feeds_news.info b/www7/sites/all/modules/contrib/feeds/feeds_news/feeds_news.info index de527e61c..34eca86d4 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds_news/feeds_news.info +++ b/www7/sites/all/modules/contrib/feeds/feeds_news/feeds_news.info @@ -19,9 +19,9 @@ features[views_view][] = feeds_defaults_feed_items files[] = feeds_news.module files[] = feeds_news.test -; Information added by Drupal.org packaging script on 2015-07-11 -version = "7.x-2.0-beta1" +; Information added by Drupal.org packaging script on 2016-02-21 +version = "7.x-2.0-beta2" core = "7.x" project = "feeds" -datestamp = "1436615941" +datestamp = "1456055647" diff --git a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.admin.inc b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.admin.inc index f975ffe18..cdfb43c17 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.admin.inc +++ b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.admin.inc @@ -513,6 +513,9 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) { 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']; } @@ -524,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']; } @@ -830,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'] . ' (' . $k . ')'; + if (!empty($v['deprecated'])) { + $result[$k] .= ' - ' . t('DEPRECATED'); + } } elseif (is_array($v)) { $result[$k] = $k; @@ -930,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 = '
'; + $output = '
'; // Build left bar. $output .= '
'; @@ -1017,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'), @@ -1028,16 +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']]) : '' . t('Missing') . ''; + $source = isset($sources[$mapping['source']]) ? check_plain($sources[$mapping['source']]) : check_plain($mapping['source']); + $target = isset($targets[$mapping['target']]) ? check_plain($targets[$mapping['target']]) : '' . t('Missing') . ''; // Add indicator to target if target configuration changed. if (isset($form['#mapping_settings'][$i])) { $target .= '*'; } $rows[] = array( 'data' => array( - check_plain($source), + $source, $target, drupal_render($form['config'][$i]), drupal_render($form['remove_flags'][$i]), diff --git a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.css b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.css index 017c345d9..d089e4106 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.css +++ b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.css @@ -89,7 +89,7 @@ ul.container-actions li { background-image: none; margin: 0; padding: 0; - position: relative; /* Fix for IE 7 compatability mode. */ + position: relative; /* Fix for IE 7 compatibility mode. */ } ul.container-actions .form-item, diff --git a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.info b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.info index a9201aa32..cf3bc30b2 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.info +++ b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.info @@ -7,9 +7,9 @@ configure = admin/structure/feeds files[] = feeds_ui.test -; Information added by Drupal.org packaging script on 2015-07-11 -version = "7.x-2.0-beta1" +; Information added by Drupal.org packaging script on 2016-02-21 +version = "7.x-2.0-beta2" core = "7.x" project = "feeds" -datestamp = "1436615941" +datestamp = "1456055647" diff --git a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.test b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.test index 4b90a6d73..7378b9176 100644 --- a/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.test +++ b/www7/sites/all/modules/contrib/feeds/feeds_ui/feeds_ui.test @@ -122,7 +122,7 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase { $this->createImporterConfiguration($name, $id); $this->setPlugin($id, 'FeedsCSVParser'); $this->setPlugin($id, 'FeedsFileFetcher'); - $this->setPlugin($id, 'FeedsTermProcessor'); + $this->setPlugin($id, 'FeedsUserProcessor'); $this->setSettings($id, 'FeedsFileFetcher', array('allowed_extensions' => 'xml')); $this->setSettings($id, 'FeedsCSVParser', array('delimiter' => '|')); @@ -145,7 +145,7 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase { $importer = feeds_importer($id); $this->assertEqual('FeedsFileFetcher', get_class($importer->fetcher)); $this->assertEqual('FeedsCSVParser', get_class($importer->parser)); - $this->assertEqual('FeedsTermProcessor', get_class($importer->processor)); + $this->assertEqual('FeedsUserProcessor', get_class($importer->processor)); $config = $importer->fetcher->getConfig(); $this->assertEqual('xml', $config['allowed_extensions']); diff --git a/www7/sites/all/modules/contrib/feeds/includes/FeedsConfigurable.inc b/www7/sites/all/modules/contrib/feeds/includes/FeedsConfigurable.inc index d0d8ce169..93ad2eb80 100644 --- a/www7/sites/all/modules/contrib/feeds/includes/FeedsConfigurable.inc +++ b/www7/sites/all/modules/contrib/feeds/includes/FeedsConfigurable.inc @@ -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 { @@ -55,7 +59,9 @@ abstract class FeedsConfigurable { 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); } @@ -137,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') { diff --git a/www7/sites/all/modules/contrib/feeds/includes/FeedsSource.inc b/www7/sites/all/modules/contrib/feeds/includes/FeedsSource.inc index 1fc5209e7..8349b3127 100644 --- a/www7/sites/all/modules/contrib/feeds/includes/FeedsSource.inc +++ b/www7/sites/all/modules/contrib/feeds/includes/FeedsSource.inc @@ -6,7 +6,7 @@ */ /** - * Distinguish exceptions occuring when handling locks. + * Distinguish exceptions occurring when handling locks. */ class FeedsLockException extends Exception {} @@ -134,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; } @@ -197,7 +197,9 @@ class FeedsSource extends FeedsConfigurable { */ 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); } @@ -406,23 +408,80 @@ class FeedsSource extends FeedsConfigurable { // 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. * diff --git a/www7/sites/all/modules/contrib/feeds/libraries/ParserCSV.inc b/www7/sites/all/modules/contrib/feeds/libraries/ParserCSV.inc index 4ddc77a99..6e3307146 100644 --- a/www7/sites/all/modules/contrib/feeds/libraries/ParserCSV.inc +++ b/www7/sites/all/modules/contrib/feeds/libraries/ParserCSV.inc @@ -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; @@ -84,6 +89,9 @@ class ParserCSV { $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; + } } /** @@ -94,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. @@ -197,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)) { @@ -237,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; } @@ -325,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 {} diff --git a/www7/sites/all/modules/contrib/feeds/libraries/common_syndication_parser.inc b/www7/sites/all/modules/contrib/feeds/libraries/common_syndication_parser.inc index d37599f43..2fa1bbd18 100644 --- a/www7/sites/all/modules/contrib/feeds/libraries/common_syndication_parser.inc +++ b/www7/sites/all/modules/contrib/feeds/libraries/common_syndication_parser.inc @@ -165,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}"; } diff --git a/www7/sites/all/modules/contrib/feeds/libraries/http_request.inc b/www7/sites/all/modules/contrib/feeds/libraries/http_request.inc index d7ffaacc0..39cd0ed42 100644 --- a/www7/sites/all/modules/contrib/feeds/libraries/http_request.inc +++ b/www7/sites/all/modules/contrib/feeds/libraries/http_request.inc @@ -49,7 +49,7 @@ function http_request_get_common_syndication($url, $settings = array()) { 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; @@ -196,6 +196,7 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva if ($accept_invalid_cert) { curl_setopt($download, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($download, CURLOPT_SSL_VERIFYHOST, 0); } $header = ''; $data = curl_exec($download); @@ -440,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; - } + $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($cparts); } + $cparts = array_filter(array_reverse($reverse_cparts)); $path = implode("/", $cparts); // Build the prefix to the path. diff --git a/www7/sites/all/modules/contrib/feeds/mappers/date.inc b/www7/sites/all/modules/contrib/feeds/mappers/date.inc index d5ea81f8e..c75f125f0 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/date.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/date.inc @@ -37,7 +37,7 @@ function date_feeds_processor_targets($entity_type, $bundle_name) { /** * Callback for setting date values. */ -function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values) { +function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { list($field_name, $sub_field) = explode(':', $target, 2); $delta = 0; @@ -56,7 +56,7 @@ function date_feeds_set_target(FeedsSource $source, $entity, $target, array $val } } - $value->buildDateField($entity, $field_name, $delta); + $value->buildDateField($entity, $field_name, $delta, $mapping['language']); $delta++; } } diff --git a/www7/sites/all/modules/contrib/feeds/mappers/entity_translation.inc b/www7/sites/all/modules/contrib/feeds/mappers/entity_translation.inc new file mode 100644 index 000000000..cd6051b36 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/mappers/entity_translation.inc @@ -0,0 +1,83 @@ +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); + } + } +} diff --git a/www7/sites/all/modules/contrib/feeds/mappers/file.inc b/www7/sites/all/modules/contrib/feeds/mappers/file.inc index 4f7054637..644cbb2b8 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/file.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/file.inc @@ -23,6 +23,11 @@ function file_feeds_processor_targets($entity_type, $bundle_name) { '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'])), @@ -54,7 +59,9 @@ function file_feeds_processor_targets($entity_type, $bundle_name) { /** * Callback for mapping file fields. */ -function file_feeds_set_target(FeedsSource $source, $entity, $target, array $values) { +function file_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + // Add default of uri for backwards compatibility. list($field_name, $sub_field) = explode(':', $target . ':uri'); $info = field_info_field($field_name); @@ -74,9 +81,14 @@ function file_feeds_set_target(FeedsSource $source, $entity, $target, array $val } } - $entity_type = $source->importer->processor->entityType(); - $bundle = $source->importer->processor->bundle(); - + 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. @@ -90,31 +102,31 @@ function file_feeds_set_target(FeedsSource $source, $entity, $target, array $val } // Populate entity. - $field = isset($entity->$field_name) ? $entity->$field_name : array(LANGUAGE_NONE => array()); + $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array()); $delta = 0; foreach ($values as $v) { if ($info['cardinality'] == $delta) { break; } - if (!isset($field[LANGUAGE_NONE][$delta])) { - $field[LANGUAGE_NONE][$delta] = array(); + if (!isset($field[$language][$delta])) { + $field[$language][$delta] = array(); } switch ($sub_field) { case 'alt': case 'title': case 'description': - $field[LANGUAGE_NONE][$delta][$sub_field] = $v; + $field[$language][$delta][$sub_field] = $v; break; case 'uri': if ($v) { try { $v->setAllowedExtensions($instance_info['settings']['file_extensions']); - $field[LANGUAGE_NONE][$delta] += (array) $v->getFile($destination); + $field[$language][$delta] += (array) $v->getFile($destination); // @todo: Figure out how to properly populate this field. - $field[LANGUAGE_NONE][$delta]['display'] = 1; + $field[$language][$delta]['display'] = 1; } catch (Exception $e) { watchdog('feeds', check_plain($e->getMessage())); diff --git a/www7/sites/all/modules/contrib/feeds/mappers/link.inc b/www7/sites/all/modules/contrib/feeds/mappers/link.inc index e9c363227..1dc19dcb4 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/link.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/link.inc @@ -39,10 +39,12 @@ function link_feeds_processor_targets($entity_type, $bundle_name) { /** * Callback for mapping link fields. */ -function link_feeds_set_target(FeedsSource $source, $entity, $target, array $values) { +function link_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + list($field_name, $column) = explode(':', $target); - $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array()); + $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array()); $delta = 0; foreach ($values as $value) { @@ -51,7 +53,7 @@ function link_feeds_set_target(FeedsSource $source, $entity, $target, array $val } if (is_scalar($value)) { - $field['und'][$delta][$column] = (string) $value; + $field[$language][$delta][$column] = (string) $value; } $delta++; } diff --git a/www7/sites/all/modules/contrib/feeds/mappers/list.inc b/www7/sites/all/modules/contrib/feeds/mappers/list.inc index 8c5dc3627..0b9978d41 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/list.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/list.inc @@ -49,8 +49,10 @@ function list_feeds_processor_targets($entity_type, $bundle_name) { /** * Callback for setting list_boolean fields. */ -function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values) { - $field = isset($entity->$target) ? $entity->$target : array(LANGUAGE_NONE => array()); +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) { @@ -58,7 +60,16 @@ function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, ar $value = $value->getValue(); } - $field[LANGUAGE_NONE][] = array('value' => (int) (bool) $value); + 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; diff --git a/www7/sites/all/modules/contrib/feeds/mappers/locale.inc b/www7/sites/all/modules/contrib/feeds/mappers/locale.inc new file mode 100644 index 000000000..5f235a14a --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/mappers/locale.inc @@ -0,0 +1,118 @@ +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: Error: @error', 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); +} diff --git a/www7/sites/all/modules/contrib/feeds/mappers/number.inc b/www7/sites/all/modules/contrib/feeds/mappers/number.inc index 7f9a0d8e5..406b4f8a1 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/number.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/number.inc @@ -34,9 +34,11 @@ function number_feeds_processor_targets($entity_type, $bundle_name) { /** * Callback for mapping number fields. */ -function number_feeds_set_target(FeedsSource $source, $entity, $target, array $values) { +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()); foreach ($values as $value) { @@ -45,7 +47,7 @@ function number_feeds_set_target(FeedsSource $source, $entity, $target, array $v } if (is_numeric($value)) { - $field['und'][] = array('value' => $value); + $field[$language][] = array('value' => $value); } } diff --git a/www7/sites/all/modules/contrib/feeds/mappers/taxonomy.inc b/www7/sites/all/modules/contrib/feeds/mappers/taxonomy.inc index 6fbdec691..a8a500624 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/taxonomy.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/taxonomy.inc @@ -85,6 +85,8 @@ function taxonomy_feeds_processor_targets($entity_type, $bundle_name) { * Callback for mapping taxonomy terms. */ function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array $terms, array $mapping) { + $language = $mapping['language']; + // Add in default values. $mapping += array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME, @@ -118,10 +120,14 @@ function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array ->range(0, 1); - $field = isset($entity->$target) ? $entity->$target : array('und' => array()); + $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['und']); + $delta = count($field[$language]); // Iterate over all values. foreach ($terms as $term) { @@ -159,6 +165,13 @@ function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array '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. @@ -181,7 +194,7 @@ function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array } if ($tid && isset($cache['allowed_values'][$target][$tid])) { - $field['und'][] = array('tid' => $tid); + $field[$language][] = array('tid' => $tid); $delta++; } } diff --git a/www7/sites/all/modules/contrib/feeds/mappers/text.inc b/www7/sites/all/modules/contrib/feeds/mappers/text.inc index aa9c2e1fb..1cf76bf9b 100644 --- a/www7/sites/all/modules/contrib/feeds/mappers/text.inc +++ b/www7/sites/all/modules/contrib/feeds/mappers/text.inc @@ -49,6 +49,8 @@ function text_feeds_processor_targets($entity_type, $bundle_name) { * Callback for mapping text fields. */ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + list($field_name, $column) = explode(':', $target . ':value'); if ($column === 'value' && isset($source->importer->processor->config['input_format'])) { @@ -59,7 +61,7 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val ); } - $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array()); + $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array()); // Iterate over all values. $delta = 0; @@ -71,10 +73,10 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val if (is_scalar($value) && strlen($value)) { - $field['und'][$delta][$column] = (string) $value; + $field[$language][$delta][$column] = (string) $value; if (isset($mapping['format'])) { - $field['und'][$delta]['format'] = $mapping['format']; + $field[$language][$delta]['format'] = $mapping['format']; } } diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsCSVParser.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsCSVParser.inc index 04d79b676..f9caeaf84 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsCSVParser.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsCSVParser.inc @@ -20,8 +20,12 @@ 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'])) { @@ -101,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'], ); } @@ -123,16 +135,21 @@ 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 (strpos($mapping['source'], ',') !== FALSE) { + $sources[] = '"' . $mapping['source'] . '"'; + } + else { + $sources[] = $mapping['source']; + } if (!empty($mapping['unique'])) { - $uniques[] = check_plain($mapping['source']); + $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), 'Column !columns is mandatory and considered unique: only one item per !columns value will be created.', 'Columns !columns 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 @columns is mandatory and considered unique: only one item per @columns value will be created.', 'Columns @columns 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'] = array( '#prefix' => '
', @@ -151,13 +168,7 @@ class FeedsCSVParser extends FeedsParser { '#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( @@ -166,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; } @@ -175,6 +190,7 @@ class FeedsCSVParser extends FeedsParser { public function configDefaults() { return array( 'delimiter' => ',', + 'encoding' => 'UTF-8', 'no_headers' => 0, ); } @@ -188,13 +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( @@ -203,37 +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' => '' . t('PHP mbstring extension must be available for character encoding conversion.') . '', + ); + } + } + public function getTemplate() { $mappings = feeds_importer($this->id)->processor->config['mappings']; $sources = $uniques = array(); + foreach ($mappings as $mapping) { - if (in_array(check_plain($mapping['source']), $uniques) || in_array(check_plain($mapping['source']), $sources)) { + if (in_array($mapping['source'], $uniques) || in_array($mapping['source'], $sources)) { // Skip columns we've already seen. continue; } if (!empty($mapping['unique'])) { - $uniques[] = check_plain($mapping['source']); + $uniques[] = $mapping['source']; } else { - $sources[] = check_plain($mapping['source']); + $sources[] = $mapping['source']; } } - $sep = $this->config['delimiter']; + + $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, + ); } } diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsFileFetcher.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsFileFetcher.inc index 6f1b31076..a3b415c90 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsFileFetcher.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsFileFetcher.inc @@ -79,15 +79,15 @@ class FeedsFileFetcher extends FeedsFetcher { * 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; } diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsNodeProcessor.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsNodeProcessor.inc index 2cfdf3061..0087d2db7 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsNodeProcessor.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsNodeProcessor.inc @@ -36,11 +36,10 @@ class FeedsNodeProcessor extends FeedsProcessor { * Creates a new node in memory and returns it. */ protected function newEntity(FeedsSource $source) { - $node = new stdClass(); + $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(). @@ -127,6 +126,8 @@ class FeedsNodeProcessor extends FeedsProcessor { * Validates a node. */ protected function entityValidate($entity) { + parent::entityValidate($entity); + if (!isset($entity->uid) || !is_numeric($entity->uid)) { $entity->uid = $this->config['author']; } @@ -238,6 +239,12 @@ 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. @@ -304,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)'), diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsParser.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsParser.inc index 1e1d32900..412888e74 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsParser.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsParser.inc @@ -119,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 @@ -554,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, $delta = 0) { + protected static function readDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) { $ret = new FeedsDateTimeElement(); - if (isset($entity->{$field_name}['und'][$delta]['date']) && $entity->{$field_name}['und'][$delta]['date'] instanceof FeedsDateTime) { - $ret->start = $entity->{$field_name}['und'][$delta]['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'][$delta]['date2']) && $entity->{$field_name}['und'][$delta]['date2'] instanceof FeedsDateTime) { - $ret->end = $entity->{$field_name}['und'][$delta]['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; } @@ -575,10 +590,10 @@ class FeedsDateTimeElement extends FeedsElement { * @param int $delta * The delta in the field. */ - public function buildDateField($entity, $field_name, $delta = 0) { + public function buildDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) { $info = field_info_field($field_name); - $oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta); + $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; @@ -611,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'][$delta]['timezone'] = $use_start->getTimezone()->getName(); - $entity->{$field_name}['und'][$delta]['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'][$delta]['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'][$delta]['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'][$delta]['offset2'] = $use_end->getOffset(); + $entity->{$field_name}[$language][$delta]['offset2'] = $use_end->getOffset(); $use_end->setTimezone($db_tz); - $entity->{$field_name}['und'][$delta]['date2'] = $use_end; - $entity->{$field_name}['und'][$delta]['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'])); } } } diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsPlugin.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsPlugin.inc index 2629fb9e7..65115f85c 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsPlugin.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsPlugin.inc @@ -53,7 +53,7 @@ abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInter throw new InvalidArgumentException(t('Empty configuration identifier.')); } - static $instances = array(); + $instances = &drupal_static(__METHOD__, array()); if (!isset($instances[$class][$id])) { $instance = new $class($id); diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsProcessor.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsProcessor.inc index e8b4b49e9..a77a284e0 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsProcessor.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsProcessor.inc @@ -5,6 +5,10 @@ * Contains FeedsProcessor and related classes. */ +// Insert mode for new items. +define('FEEDS_SKIP_NEW', 0); +define('FEEDS_INSERT_NEW', 1); + // Update mode for existing items. define('FEEDS_SKIP_EXISTING', 0); define('FEEDS_REPLACE_EXISTING', 1); @@ -82,13 +86,22 @@ abstract class FeedsProcessor extends FeedsPlugin { /** * Create a new entity. * - * @param $source + * @param FeedsSource $source * The feeds source that spawns this entity. * - * @return + * @return object * A new entity object. */ - protected abstract function newEntity(FeedsSource $source); + protected function newEntity(FeedsSource $source) { + $entity = new stdClass(); + + $info = $this->entityInfo(); + if (!empty($info['entity keys']['language'])) { + $entity->{$info['entity keys']['language']} = $this->entityLanguage(); + } + + return $entity; + } /** * Load an existing entity. @@ -105,28 +118,46 @@ abstract class FeedsProcessor extends FeedsPlugin { * existing ids first. */ protected function entityLoad(FeedsSource $source, $entity_id) { + $info = $this->entityInfo(); + if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) { $entities = entity_load($this->entityType(), array($entity_id)); - return reset($entities); + $entity = reset($entities); + } + else { + $args = array(':entity_id' => $entity_id); + $table = db_escape_table($info['base table']); + $key = db_escape_field($info['entity keys']['id']); + $entity = db_query("SELECT * FROM {" . $table . "} WHERE $key = :entity_id", $args)->fetchObject(); } - $info = $this->entityInfo(); - - $args = array(':entity_id' => $entity_id); - - $table = db_escape_table($info['base table']); - $key = db_escape_field($info['entity keys']['id']); + if ($entity && !empty($info['entity keys']['language'])) { + $entity->{$info['entity keys']['language']} = $this->entityLanguage(); + } - return db_query("SELECT * FROM {" . $table . "} WHERE $key = :entity_id", $args)->fetchObject(); + return $entity; } /** - * Validate an entity. + * Validates an entity. * * @throws FeedsValidationException $e - * If validation fails. + * Thrown if validation fails. */ - protected function entityValidate($entity) {} + protected function entityValidate($entity) { + $info = $this->entityInfo(); + if (empty($info['entity keys']['language'])) { + return; + } + + // Ensure that a valid language is always set. + $key = $info['entity keys']['language']; + $languages = language_list('enabled'); + + if (empty($entity->$key) || !isset($languages[1][$entity->$key])) { + $entity->$key = $this->entityLanguage(); + } + } /** * Access check for saving an enity. @@ -162,7 +193,38 @@ abstract class FeedsProcessor extends FeedsPlugin { * 'label plural' ... the plural label of an entity type. */ protected function entityInfo() { - return entity_get_info($this->entityType()); + $info = entity_get_info($this->entityType()); + + // Entity module has defined the plural label in "plural label" instead of + // "label plural". So if "plural label" is defined, this will have priority + // over "label plural". + if (isset($info['plural label'])) { + $info['label plural'] = $info['plural label']; + } + + return $info; + } + + /** + * Returns the current language for entities. + * + * This checks if the configuration value is valid. + * + * @return string + * The current language code. + */ + protected function entityLanguage() { + if (!module_exists('locale')) { + // language_list() may return languages even if the locale module is + // disabled. See https://www.drupal.org/node/173227 why. + // When the locale module is disabled, there are no selectable languages + // in the UI, so the content should be imported in LANGUAGE_NONE. + return LANGUAGE_NONE; + } + + $languages = language_list('enabled'); + + return isset($languages[1][$this->config['language']]) ? $this->config['language'] : LANGUAGE_NONE; } /** @@ -183,6 +245,9 @@ abstract class FeedsProcessor extends FeedsPlugin { $this->initEntitiesToBeRemoved($source, $state); } + $skip_new = $this->config['insert_new'] == FEEDS_SKIP_NEW; + $skip_existing = $this->config['update_existing'] == FEEDS_SKIP_EXISTING; + while ($item = $parser_result->shiftItem()) { // Check if this item already exists. @@ -191,24 +256,22 @@ abstract class FeedsProcessor extends FeedsPlugin { if ($entity_id) { unset($state->removeList[$entity_id]); } - $skip_existing = $this->config['update_existing'] == FEEDS_SKIP_EXISTING; module_invoke_all('feeds_before_update', $source, $item, $entity_id); - // If it exists, and we are not updating, pass onto the next item. - if ($entity_id && $skip_existing) { + // If it exists, and we are not updating, or if it does not exist, and we + // are not inserting, pass onto the next item. + if (($entity_id && $skip_existing) || (!$entity_id && $skip_new)) { continue; } try { - $hash = $this->hash($item); - $changed = ($hash !== $this->getHash($entity_id)); - $force_update = $this->config['skip_hash_check']; + $changed = $hash !== $this->getHash($entity_id); // Do not proceed if the item exists, has not changed, and we're not // forcing the update. - if ($entity_id && !$changed && !$force_update) { + if ($entity_id && !$changed && !$this->config['skip_hash_check']) { continue; } @@ -357,8 +420,10 @@ abstract class FeedsProcessor extends FeedsPlugin { } /** - * Initialize the array of entities to remove with all existing entities - * previously imported from the source. + * Initializes the list of entities to remove. + * + * This populates $state->removeList with all existing entities previously + * imported from the source. * * @param FeedsSource $source * Source information about this import. @@ -367,33 +432,27 @@ abstract class FeedsProcessor extends FeedsPlugin { */ protected function initEntitiesToBeRemoved(FeedsSource $source, FeedsState $state) { $state->removeList = array(); + // We fill it only if needed. - if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) { + if ($this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) { return; } - // Build base select statement. - $info = $this->entityInfo(); - $id_key = db_escape_field($info['entity keys']['id']); - $select = db_select($info['base table'], 'e'); - $select->addField('e', $info['entity keys']['id'], 'entity_id'); - $select->join( - 'feeds_item', - 'fi', - 'e.' . $id_key . ' = fi.entity_id AND fi.entity_type = :entity_type', array( - ':entity_type' => $this->entityType(), - )); - $select->condition('fi.id', $this->id); - $select->condition('fi.feed_nid', $source->feed_nid); - // No need to remove item again if same method of removal was already used. - $select->condition('fi.hash', $this->config['update_non_existent'], '<>'); - $entities = $select->execute(); - // If not found on process, existing entities will be deleted. - foreach ($entities as $entity) { - // Obviously, items which are still included in the source feed will be - // removed from this array when processed. - $state->removeList[$entity->entity_id] = $entity->entity_id; + // Get the full list of entities for this source. + $entity_ids = db_select('feeds_item') + ->fields('feeds_item', array('entity_id')) + ->condition('entity_type', $this->entityType()) + ->condition('id', $this->id) + ->condition('feed_nid', $source->feed_nid) + ->condition('hash', $this->config['update_non_existent'], '<>') + ->execute() + ->fetchCol(); + + if (!$entity_ids) { + return; } + + $state->removeList = array_combine($entity_ids, $entity_ids); } /** @@ -406,12 +465,11 @@ abstract class FeedsProcessor extends FeedsPlugin { */ protected function clean(FeedsState $state) { // We clean only if needed. - if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) { + if ($this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) { return; } - $total = count($state->removeList); - if ($total) { + if ($total = count($state->removeList)) { $this->entityDeleteMultiple($state->removeList); $state->deleted += $total; } @@ -432,35 +490,25 @@ abstract class FeedsProcessor extends FeedsPlugin { $state = $source->state(FEEDS_PROCESS_CLEAR); // Build base select statement. - $info = $this->entityInfo(); - $select = db_select($info['base table'], 'e'); - $select->addField('e', $info['entity keys']['id'], 'entity_id'); - $select->join( - 'feeds_item', - 'fi', - "e.{$info['entity keys']['id']} = fi.entity_id AND fi.entity_type = '{$this->entityType()}'"); - $select->condition('fi.id', $this->id); - $select->condition('fi.feed_nid', $source->feed_nid); + $select = db_select('feeds_item') + ->fields('feeds_item', array('entity_id')) + ->condition('entity_type', $this->entityType()) + ->condition('id', $this->id) + ->condition('feed_nid', $source->feed_nid); // If there is no total, query it. if (!$state->total) { - $state->total = $select->countQuery() - ->execute() - ->fetchField(); + $state->total = $select->countQuery()->execute()->fetchField(); } // Delete a batch of entities. - $entities = $select->range(0, $this->getLimit())->execute(); - $entity_ids = array(); - foreach ($entities as $entity) { - $entity_ids[$entity->entity_id] = $entity->entity_id; - } + $entity_ids = $select->range(0, $this->getLimit())->execute()->fetchCol(); $this->entityDeleteMultiple($entity_ids); - // Report progress, take into account that we may not have deleted as - // many items as we have counted at first. - if (count($entity_ids)) { - $state->deleted += count($entity_ids); + // Report progress, take into account that we may not have deleted as many + // items as we have counted at first. + if ($deleted_count = count($entity_ids)) { + $state->deleted += $deleted_count; $state->progress($state->total, $state->deleted); } else { @@ -469,6 +517,8 @@ abstract class FeedsProcessor extends FeedsPlugin { // Report results when done. if ($source->progressClearing() == FEEDS_BATCH_COMPLETE) { + $info = $this->entityInfo(); + if ($state->deleted) { $message = format_plural( $state->deleted, @@ -572,16 +622,12 @@ abstract class FeedsProcessor extends FeedsPlugin { protected function expiryQuery(FeedsSource $source, $time) { // Build base select statement. $info = $this->entityInfo(); - $id_key = db_escape_field($info['entity keys']['id']); + $id_key = $info['entity keys']['id']; $select = db_select($info['base table'], 'e'); - $select->addField('e', $info['entity keys']['id'], 'entity_id'); - $select->join( - 'feeds_item', - 'fi', - "e.$id_key = fi.entity_id AND fi.entity_type = :entity_type", array( - ':entity_type' => $this->entityType(), - )); + $select->addField('e', $id_key); + $select->join('feeds_item', 'fi', "e.$id_key = fi.entity_id"); + $select->condition('fi.entity_type', $this->entityType()); $select->condition('fi.id', $this->id); $select->condition('fi.feed_nid', $source->feed_nid); @@ -659,6 +705,8 @@ abstract class FeedsProcessor extends FeedsPlugin { */ protected function map(FeedsSource $source, FeedsParserResult $result, $target_item = NULL) { $targets = $this->getCachedTargets(); + // Get fields for the entity type we are mapping to. + $fields = field_info_instances($this->entityType(), $this->bundle()); if (empty($target_item)) { $target_item = array(); @@ -667,12 +715,24 @@ abstract class FeedsProcessor extends FeedsPlugin { // Many mappers add to existing fields rather than replacing them. Hence we // need to clear target elements of each item before mapping in case we are // mapping on a prepopulated item such as an existing node. - foreach ($this->config['mappings'] as $mapping) { + foreach ($this->getMappings() as $mapping) { if (isset($targets[$mapping['target']]['real_target'])) { - $target_item->{$targets[$mapping['target']]['real_target']} = NULL; + $target_name = $targets[$mapping['target']]['real_target']; + } + else { + $target_name = $mapping['target']; + } + + // If the target is a field empty the value for the targeted language + // only. + // In all other cases, just empty the target completely. + if (isset($fields[$target_name])) { + // Empty the target for the specified language. + $target_item->{$target_name}[$mapping['language']] = array(); } else { - $target_item->{$mapping['target']} = NULL; + // Empty the whole target. + $target_item->{$target_name} = NULL; } } @@ -680,7 +740,7 @@ abstract class FeedsProcessor extends FeedsPlugin { // the parser's getSourceElement() method to retrieve the value of the // source element and pass it to the processor's setTargetElement() to stick // it on the right place of the target item. - foreach ($this->config['mappings'] as $mapping) { + foreach ($this->getMappings() as $mapping) { $value = $this->getSourceValue($source, $result, $mapping['source']); $this->mapToTarget($source, $mapping['target'], $target_item, $value, $mapping); @@ -727,12 +787,6 @@ abstract class FeedsProcessor extends FeedsPlugin { protected function mapToTarget(FeedsSource $source, $target, &$target_item, $value, array $mapping) { $targets = $this->getCachedTargets(); - if (isset($targets[$target]['preprocess_callbacks'])) { - foreach ($targets[$target]['preprocess_callbacks'] as $callback) { - call_user_func_array($callback, array($source, $target_item, $target, &$mapping)); - } - } - // Map the source element's value to the target. // If the mapping specifies a callback method, use the callback instead of // setTargetElement(). @@ -770,11 +824,13 @@ abstract class FeedsProcessor extends FeedsPlugin { } return array( 'mappings' => array(), + 'insert_new' => FEEDS_INSERT_NEW, 'update_existing' => FEEDS_SKIP_EXISTING, 'update_non_existent' => FEEDS_SKIP_NON_EXISTENT, 'input_format' => NULL, 'skip_hash_check' => FALSE, 'bundle' => $bundle, + 'language' => LANGUAGE_NONE, ); } @@ -801,8 +857,29 @@ abstract class FeedsProcessor extends FeedsPlugin { ); } + if (module_exists('locale') && !empty($info['entity keys']['language'])) { + $form['language'] = array( + '#type' => 'select', + '#options' => array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name'), + '#title' => t('Language'), + '#required' => TRUE, + '#default_value' => $this->config['language'], + ); + } + $tokens = array('@entities' => strtolower($info['label plural'])); + $form['insert_new'] = array( + '#type' => 'radios', + '#title' => t('Insert new @entities', $tokens), + '#description' => t('New @entities will be determined using mappings that are a "unique target".', $tokens), + '#options' => array( + FEEDS_INSERT_NEW => t('Insert new @entities', $tokens), + FEEDS_SKIP_NEW => t('Do not insert new @entities', $tokens), + ), + '#default_value' => $this->config['insert_new'], + ); + $form['update_existing'] = array( '#type' => 'radios', '#title' => t('Update existing @entities', $tokens), @@ -853,7 +930,38 @@ abstract class FeedsProcessor extends FeedsPlugin { * Get mappings. */ public function getMappings() { - return isset($this->config['mappings']) ? $this->config['mappings'] : array(); + $cache = &drupal_static('FeedsProcessor::getMappings', array()); + + if (!isset($cache[$this->id])) { + $mappings = $this->config['mappings']; + $targets = $this->getCachedTargets(); + $languages = language_list('enabled'); + + foreach ($mappings as &$mapping) { + + if (isset($targets[$mapping['target']]['preprocess_callbacks'])) { + foreach ($targets[$mapping['target']]['preprocess_callbacks'] as $callback) { + call_user_func_array($callback, array($targets[$mapping['target']], &$mapping)); + } + } + + // Ensure there's always a language set. + if (empty($mapping['language'])) { + $mapping['language'] = LANGUAGE_NONE; + } + else { + // Check if the configured language is available. If not, fallback to + // LANGUAGE_NONE. + if (!isset($languages[1][$mapping['language']])) { + $mapping['language'] = LANGUAGE_NONE; + } + } + } + + $cache[$this->id] = $mappings; + } + + return $cache[$this->id]; } /** @@ -994,7 +1102,7 @@ abstract class FeedsProcessor extends FeedsPlugin { protected function uniqueTargets(FeedsSource $source, FeedsParserResult $result) { $parser = feeds_importer($this->id)->parser; $targets = array(); - foreach ($this->config['mappings'] as $mapping) { + foreach ($this->getMappings() as $mapping) { if (!empty($mapping['unique'])) { // Invoke the parser's getSourceElement to retrieve the value for this // mapping's source. @@ -1052,12 +1160,13 @@ abstract class FeedsProcessor extends FeedsPlugin { * Include mappings as a change in mappings may have an affect on the item * produced. * - * @return Always returns a hash, even with empty, NULL, FALSE: - * Empty arrays return 40cd750bba9870f18aada2478b24840a - * Empty/NULL/FALSE strings return d41d8cd98f00b204e9800998ecf8427e + * @return string + * A hash is always returned, even when the item is empty, NULL or FALSE. */ protected function hash($item) { - return hash('md5', serialize($item) . serialize($this->config['mappings'])); + $sources = feeds_importer($this->id)->parser->getMappingSourceList(); + $mapped_item = array_intersect_key($item, array_flip($sources)); + return hash('md5', serialize($mapped_item) . serialize($this->getMappings())); } /** @@ -1108,7 +1217,7 @@ abstract class FeedsProcessor extends FeedsPlugin { } /** - * Creates a log entry for when an exception occured during import. + * Creates a log entry for when an exception occurred during import. * * @param Exception $e * The exception that was throwned during processing the item. diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsSimplePieParser.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsSimplePieParser.inc index 495b2a1a0..018d98ad5 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsSimplePieParser.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsSimplePieParser.inc @@ -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); diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsTermProcessor.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsTermProcessor.inc index f69819f30..19ec5e5d4 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsTermProcessor.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsTermProcessor.inc @@ -31,16 +31,34 @@ 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; + return $term; } + /** + * Load an existing entity. + */ + 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) { + parent::entityValidate($term); + if (drupal_strlen($term->name) == 0) { throw new FeedsValidationException(t('Term name missing.')); } @@ -112,11 +130,21 @@ class FeedsTermProcessor extends FeedsProcessor { 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': diff --git a/www7/sites/all/modules/contrib/feeds/plugins/FeedsUserProcessor.inc b/www7/sites/all/modules/contrib/feeds/plugins/FeedsUserProcessor.inc index 778c72bed..47d26e5cd 100644 --- a/www7/sites/all/modules/contrib/feeds/plugins/FeedsUserProcessor.inc +++ b/www7/sites/all/modules/contrib/feeds/plugins/FeedsUserProcessor.inc @@ -37,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; } @@ -59,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.')); } diff --git a/www7/sites/all/modules/contrib/feeds/tests/common_syndication_parser.test b/www7/sites/all/modules/contrib/feeds/tests/common_syndication_parser.test index d61baad4b..d8eabb4ca 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/common_syndication_parser.test +++ b/www7/sites/all/modules/contrib/feeds/tests/common_syndication_parser.test @@ -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. */ diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds/content.csv b/www7/sites/all/modules/contrib/feeds/tests/feeds/content.csv index 1e68bdf86..be5d69d77 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds/content.csv +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds/content.csv @@ -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." diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds/content_empty.csv b/www7/sites/all/modules/contrib/feeds/tests/feeds/content_empty.csv index 5b08023a6..f1afa75cb 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds/content_empty.csv +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds/content_empty.csv @@ -1,3 +1,3 @@ -"guid","title","created","end","alpha","beta","gamma","delta","body","link_title","url" -1,"Lorem ipsum",,,,,,,,, -2,"Ut wisi enim ad minim veniam",0,0,"0",0,0,0,0,0,0 +"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 diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds/content_i18n.csv b/www7/sites/all/modules/contrib/feeds/tests/feeds/content_i18n.csv new file mode 100644 index 000000000..130935812 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds/content_i18n.csv @@ -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." diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds/earthquake-georss-noauthor.atom b/www7/sites/all/modules/contrib/feeds/tests/feeds/earthquake-georss-noauthor.atom new file mode 100644 index 000000000..b7884ae3c --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds/earthquake-georss-noauthor.atom @@ -0,0 +1,35 @@ + + + 2010-09-07T21:45:39Z + USGS M2.5+ Earthquakes + Real-time, worldwide earthquake list for the past day + + + http://earthquake.usgs.gov/ + /favicon.ico + urn:earthquake-usgs-gov:ak:10076864M 2.6, Central Alaska2010-09-07T21:08:45Z

Tuesday, September 7, 2010 21:08:45 UTC
Tuesday, September 7, 2010 01:08:45 PM at epicenter

Depth: 11.20 km (6.96 mi)

]]>
64.8581 -150.8643-11200
+ urn:earthquake-usgs-gov:us:2010axbzM 4.9, southern Qinghai, China2010-09-07T20:51:02Z

Tuesday, September 7, 2010 20:51:02 UTC
Wednesday, September 8, 2010 04:51:02 AM at epicenter

Depth: 47.50 km (29.52 mi)

]]>
33.3289 96.3324-47500
+ urn:earthquake-usgs-gov:us:2010axbrM 5.2, southern East Pacific Rise2010-09-07T19:54:29Z

Tuesday, September 7, 2010 19:54:29 UTC
Tuesday, September 7, 2010 11:54:29 AM at epicenter

Depth: 15.50 km (9.63 mi)

]]>
-53.1979 -118.0676-15500
+ urn:earthquake-usgs-gov:us:2010axbpM 5.0, South Island of New Zealand2010-09-07T19:49:57Z

Tuesday, September 7, 2010 19:49:57 UTC
Wednesday, September 8, 2010 07:49:57 AM at epicenter

Depth: 1.00 km (0.62 mi)

]]>
-43.4371 172.5902-1000
+ urn:earthquake-usgs-gov:ak:10076859M 3.1, Andreanof Islands, Aleutian Islands, Alaska2010-09-07T19:20:05Z

Tuesday, September 7, 2010 19:20:05 UTC
Tuesday, September 7, 2010 10:20:05 AM at epicenter

Depth: 22.20 km (13.79 mi)

]]>
51.5259 -175.7979-22200
+ urn:earthquake-usgs-gov:ci:10793957M 2.7, Southern California2010-09-07T18:50:42Z

Tuesday, September 7, 2010 18:50:42 UTC
Tuesday, September 7, 2010 11:50:42 AM at epicenter

Depth: 7.80 km (4.85 mi)

]]>
35.7170 -116.9597-7800
+ urn:earthquake-usgs-gov:ci:10793909M 3.5, Southern California2010-09-07T17:29:13Z

Tuesday, September 7, 2010 17:29:13 UTC
Tuesday, September 7, 2010 10:29:13 AM at epicenter

Depth: 4.50 km (2.80 mi)

]]>
35.7273 -116.9567-4500
+ urn:earthquake-usgs-gov:ak:10076853M 3.1, Andreanof Islands, Aleutian Islands, Alaska2010-09-07T17:08:19Z

Tuesday, September 7, 2010 17:08:19 UTC
Tuesday, September 7, 2010 08:08:19 AM at epicenter

Depth: 16.50 km (10.25 mi)

]]>
51.0899 -176.1314-16500
+ urn:earthquake-usgs-gov:us:2010axa9M 6.3, Fiji region2010-09-07T16:13:32Z

Tuesday, September 7, 2010 16:13:32 UTC
Wednesday, September 8, 2010 04:13:32 AM at epicenter

Depth: 10.00 km (6.21 mi)

]]>
-15.8694 -179.2611-10000
+ urn:earthquake-usgs-gov:us:2010axa7M 5.3, Kyrgyzstan2010-09-07T15:41:42Z

Tuesday, September 7, 2010 15:41:42 UTC
Tuesday, September 7, 2010 09:41:42 PM at epicenter

Depth: 39.70 km (24.67 mi)

]]>
39.4759 73.8254-39700
+ urn:earthquake-usgs-gov:ci:10793837M 2.7, Southern California2010-09-07T13:07:21Z

Tuesday, September 7, 2010 13:07:21 UTC
Tuesday, September 7, 2010 06:07:21 AM at epicenter

Depth: 3.60 km (2.24 mi)

]]>
35.7245 -116.9630-3600
+ urn:earthquake-usgs-gov:nc:71451855M 2.5, Northern California2010-09-07T13:06:56Z

Tuesday, September 7, 2010 13:06:56 UTC
Tuesday, September 7, 2010 06:06:56 AM at epicenter

Depth: 8.20 km (5.10 mi)

]]>
39.2102 -120.0667-8200
+ urn:earthquake-usgs-gov:us:2010axaxM 5.4, Fiji region2010-09-07T12:49:01Z

Tuesday, September 7, 2010 12:49:01 UTC
Wednesday, September 8, 2010 12:49:01 AM at epicenter

Depth: 35.50 km (22.06 mi)

]]>
-14.3605 -176.2406-35500
+ urn:earthquake-usgs-gov:us:2010axatM 5.0, Kuril Islands2010-09-07T11:30:52Z

Tuesday, September 7, 2010 11:30:52 UTC
Tuesday, September 7, 2010 11:30:52 PM at epicenter

Depth: 30.30 km (18.83 mi)

]]>
45.8582 151.3105-30300
+ urn:earthquake-usgs-gov:mb:25757M 2.7, western Montana2010-09-07T10:08:26Z

Tuesday, September 7, 2010 10:08:26 UTC
Tuesday, September 7, 2010 04:08:26 AM at epicenter

Depth: 5.70 km (3.54 mi)

]]>
44.9508 -111.7423-5700
+ urn:earthquake-usgs-gov:ak:10076821M 2.7, Andreanof Islands, Aleutian Islands, Alaska2010-09-07T08:40:35Z

Tuesday, September 7, 2010 08:40:35 UTC
Monday, September 6, 2010 11:40:35 PM at epicenter

Depth: 20.10 km (12.49 mi)

]]>
51.2010 -176.1935-20100
+ urn:earthquake-usgs-gov:us:2010axasM 4.9, southwest of Sumatra, Indonesia2010-09-07T07:22:13Z

Tuesday, September 7, 2010 07:22:13 UTC
Tuesday, September 7, 2010 02:22:13 PM at epicenter

Depth: 35.00 km (21.75 mi)

]]>
-7.1275 103.2631-35000
+ urn:earthquake-usgs-gov:nc:71451750M 3.1, Central California2010-09-07T06:59:25Z

Tuesday, September 7, 2010 06:59:25 UTC
Monday, September 6, 2010 11:59:25 PM at epicenter

Depth: 8.40 km (5.22 mi)

]]>
36.5605 -121.0677-8400
+ urn:earthquake-usgs-gov:ak:10076797M 4.2, Kodiak Island region, Alaska2010-09-07T05:54:04Z

Tuesday, September 7, 2010 05:54:04 UTC
Monday, September 6, 2010 09:54:04 PM at epicenter

Depth: 25.00 km (15.53 mi)

]]>
56.9797 -151.6661-25000
+ urn:earthquake-usgs-gov:ak:10076786M 3.4, Andreanof Islands, Aleutian Islands, Alaska2010-09-07T04:43:36Z

Tuesday, September 7, 2010 04:43:36 UTC
Monday, September 6, 2010 07:43:36 PM at epicenter

Depth: 16.90 km (10.50 mi)

]]>
51.0975 -176.1635-16900
+ urn:earthquake-usgs-gov:ak:10076776M 3.6, Andreanof Islands, Aleutian Islands, Alaska2010-09-07T03:43:43Z

Tuesday, September 7, 2010 03:43:43 UTC
Monday, September 6, 2010 06:43:43 PM at epicenter

Depth: 39.00 km (24.23 mi)

]]>
51.4706 -176.6674-39000
+ urn:earthquake-usgs-gov:us:2010axafM 5.3, southern Iran2010-09-07T02:11:07Z

Tuesday, September 7, 2010 02:11:07 UTC
Tuesday, September 7, 2010 05:41:07 AM at epicenter

Depth: 27.90 km (17.34 mi)

]]>
27.1465 54.5877-27900
+ urn:earthquake-usgs-gov:us:2010axadM 4.9, Fiji region2010-09-07T01:42:39Z

Tuesday, September 7, 2010 01:42:39 UTC
Tuesday, September 7, 2010 01:42:39 PM at epicenter

Depth: 390.40 km (242.58 mi)

]]>
-19.6573 -177.6987-390400
+ urn:earthquake-usgs-gov:us:2010axabM 5.8, southwest of Sumatra, Indonesia2010-09-07T00:57:26Z

Tuesday, September 7, 2010 00:57:26 UTC
Tuesday, September 7, 2010 07:57:26 AM at epicenter

Depth: 34.10 km (21.19 mi)

]]>
-6.9665 103.6573-34100
+ urn:earthquake-usgs-gov:us:2010awbyM 5.2, North Island of New Zealand2010-09-06T22:48:33Z

Monday, September 6, 2010 22:48:33 UTC
Tuesday, September 7, 2010 10:48:33 AM at epicenter

Depth: 16.70 km (10.38 mi)

]]>
-40.1430 176.6567-16700
+
diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds/encoding.csv.php b/www7/sites/all/modules/contrib/feeds/tests/feeds/encoding.csv.php new file mode 100644 index 000000000..05f300c3d --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds/encoding.csv.php @@ -0,0 +1,33 @@ + '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))); + } + } +} diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_node.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_node.test new file mode 100644 index 000000000..00300eb05 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_node.test @@ -0,0 +1,136 @@ + '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))); + } + } +} diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_taxonomy.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_taxonomy.test new file mode 100644 index 000000000..1b6920936 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_i18n_taxonomy.test @@ -0,0 +1,119 @@ + '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.'); + } + } +} diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper.test index 0cd0d3d7c..87efbdb28 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper.test +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper.test @@ -22,6 +22,9 @@ class FeedsMapperTestCase extends FeedsWebTestCase { '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', diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_list.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_list.test new file mode 100644 index 000000000..279fdce20 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_list.test @@ -0,0 +1,180 @@ + 'Mapper: List and Boolean', + 'description' => 'Test Feeds Mapper support for List and Boolean fields.', + 'group' => 'Feeds', + 'dependencies' => array('list'), + ); + } + + public function setUp() { + parent::setUp(array('list')); + } + + /** + * Tests if values are cleared out when an empty value is provided. + */ + public function testClearOutValues() { + // Create content type. + $typename = $this->createContentType(array(), array( + 'alpha' => array( + 'type' => 'list_text', + 'settings' => array( + 'field[settings][allowed_values]' => "0\nLorem\nUt wisi", + ), + ), + 'beta' => array( + 'type' => 'list_integer', + 'settings' => array( + 'field[settings][allowed_values]' => "0\n42\n32", + ), + ), + 'delta' => array( + 'type' => 'list_float', + 'settings' => array( + 'field[settings][allowed_values]' => "0\n3.14159\n5.62951", + ), + ), + 'epsilon' => 'list_boolean', + )); + + // 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( + array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + array( + 'source' => 'title', + 'target' => 'title', + ), + array( + 'source' => 'alpha', + 'target' => 'field_alpha', + ), + array( + 'source' => 'beta', + 'target' => 'field_beta', + ), + array( + 'source' => 'delta', + 'target' => 'field_delta', + ), + array( + 'source' => 'epsilon', + 'target' => 'field_epsilon', + ), + )); + + // Import CSV file. + $this->importFile('csv', $this->absolutePath() . '/tests/feeds/content.csv'); + $this->assertText('Created 2 nodes'); + + // Check the two imported nodes. + $this->drupalGet('node/1/edit'); + $this->assertOptionSelected('edit-field-alpha-und', 'Lorem'); + $this->assertOptionSelected('edit-field-beta-und', '42'); + $this->assertOptionSelected('edit-field-delta-und', '3.14159'); + $this->assertFieldChecked('edit-field-epsilon-und'); + $this->drupalGet('node/2/edit'); + $this->assertOptionSelected('edit-field-alpha-und', 'Ut wisi'); + $this->assertOptionSelected('edit-field-beta-und', '32'); + $this->assertOptionSelected('edit-field-delta-und', '5.62951'); + $this->assertNoFieldChecked('edit-field-epsilon-und'); + + // 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 node 1. + $this->drupalGet('node/1/edit'); + $this->assertNoOptionSelected('edit-field-alpha-und', 'Lorem'); + $this->assertNoOptionSelected('edit-field-beta-und', '42'); + $this->assertNoOptionSelected('edit-field-delta-und', '3.14159'); + $this->assertNoFieldChecked('edit-field-epsilon-und'); + // Check if labels for fields that should be cleared out are not shown. + $this->drupalGet('node/1'); + $this->assertNoText('alpha_list_text_label'); + $this->assertNoText('beta_list_integer_label'); + $this->assertNoText('delta_list_float_label'); + $this->assertNoText('epsilon_list_boolean_label'); + // Load node 1 and check if the boolean field does *not* have a value. + $node = node_load(1, NULL, TRUE); + $this->assertTrue(empty($node->field_epsilon[LANGUAGE_NONE]), 'The field field_epsilon is empty.'); + + // Check if zero's didn't cleared out values for node 2. + $this->drupalGet('node/2/edit'); + $this->assertOptionSelected('edit-field-alpha-und', '0'); + $this->assertOptionSelected('edit-field-beta-und', '0'); + $this->assertOptionSelected('edit-field-delta-und', '0'); + $this->assertNoFieldChecked('edit-field-epsilon-und'); + // Check if labels for fields of node 2 are still shown. + $this->drupalGet('node/2'); + $this->assertText('alpha_list_text_label'); + $this->assertText('beta_list_integer_label'); + $this->assertText('delta_list_float_label'); + $this->assertText('epsilon_list_boolean_label'); + // Load node 2 and check if the boolean field *does* have a value. + $node = node_load(2, NULL, TRUE); + $this->assertEqual('0', $node->field_epsilon[LANGUAGE_NONE][0]['value']); + + // Re-import the first file again. + $this->importFile('csv', $this->absolutePath() . '/tests/feeds/content.csv'); + $this->assertText('Updated 2 nodes'); + + // Check if the two imported nodes have content again. + $this->drupalGet('node/1/edit'); + $this->assertOptionSelected('edit-field-alpha-und', 'Lorem'); + $this->assertOptionSelected('edit-field-beta-und', '42'); + $this->assertOptionSelected('edit-field-delta-und', '3.14159'); + $this->assertFieldChecked('edit-field-epsilon-und'); + $this->drupalGet('node/2/edit'); + $this->assertOptionSelected('edit-field-alpha-und', 'Ut wisi'); + $this->assertOptionSelected('edit-field-beta-und', '32'); + $this->assertOptionSelected('edit-field-delta-und', '5.62951'); + $this->assertNoFieldChecked('edit-field-epsilon-und'); + + // 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->assertNoOptionSelected('edit-field-alpha-und', 'Lorem'); + $this->assertNoOptionSelected('edit-field-beta-und', '42'); + $this->assertNoOptionSelected('edit-field-delta-und', '3.14159'); + $this->assertNoFieldChecked('edit-field-epsilon-und'); + // Check if labels for fields that should be cleared out are not shown. + $this->drupalGet('node/1'); + $this->assertNoText('alpha_list_text_label'); + $this->assertNoText('beta_list_integer_label'); + $this->assertNoText('delta_list_float_label'); + $this->assertNoText('epsilon_list_boolean_label'); + // Load node 1 and check if the boolean field does *not* have a value. + $node = node_load(1, NULL, TRUE); + $this->assertTrue(empty($node->field_epsilon[LANGUAGE_NONE]), 'The field field_epsilon is empty.'); + } +} diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_multilingual_fields.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_multilingual_fields.test new file mode 100644 index 000000000..bee9c96f1 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_multilingual_fields.test @@ -0,0 +1,1053 @@ + 'Mapper: Multilingual fields', + 'description' => 'Tests Feeds multilingual support.', + 'group' => 'Feeds', + 'dependencies' => array('date', 'entity_translation', 'i18n_taxonomy', 'link'), + ); + } + + public function setUp() { + $modules = array( + 'locale', + 'entity_translation', + 'date', + 'link', + 'list', + 'number', + ); + + $permissions = array( + 'administer entity translation', + 'translate any entity', + 'administer languages', + ); + + parent::setUp($modules, $permissions); + + // Include FeedsProcessor.inc so processor related constants are available. + module_load_include('inc', 'feeds', 'plugins/FeedsProcessor'); + + // Add French language. + $this->addLanguage('fr', 'French'); + + // Add Categories vocabulary. + $edit = array( + 'name' => 'Categories', + 'machine_name' => 'categories', + ); + $this->drupalPost('admin/structure/taxonomy/add', $edit, 'Save'); + + // Create content type. + $this->fields = array( + 'date' => array( + 'type' => 'date', + 'settings' => array( + 'field[settings][granularity][hour]' => FALSE, + 'field[settings][granularity][minute]' => FALSE, + 'field[settings][tz_handling]' => 'none', + ), + ), + 'datestamp' => array( + 'type' => 'datestamp', + 'settings' => array( + 'field[settings][granularity][second]' => TRUE, + 'field[settings][tz_handling]' => 'utc', + ), + ), + 'datetime' => array( + 'type' => 'datetime', + 'settings' => array( + 'field[settings][granularity][second]' => TRUE, + 'field[settings][tz_handling]' => 'utc', + ), + ), + 'image' => array( + 'type' => 'image', + 'instance_settings' => array( + 'instance[settings][alt_field]' => 1, + 'instance[settings][title_field]' => 1, + ), + ), + 'link' => 'link_field', + 'list_boolean' => 'list_boolean', + 'number_integer' => 'number_integer', + 'number_decimal' => 'number_decimal', + 'number_float' => 'number_float', + 'text' => 'text', + ); + $this->contentType = $this->createContentType(array(), $this->fields); + + // Create term reference field. + $field = array( + 'field_name' => 'field_category', + 'type' => 'taxonomy_term_reference', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => 'categories', + 'parent' => 0, + ), + ), + ), + ); + field_create_field($field); + + // Add term reference field to article bundle. + $this->instance = array( + 'field_name' => 'field_category', + 'bundle' => $this->contentType, + 'entity_type' => 'node', + 'widget' => array( + 'type' => 'taxonomy_autocomplete', + ), + 'display' => array( + 'default' => array( + 'type' => 'taxonomy_term_reference_link', + ), + ), + ); + field_create_instance($this->instance); + + // Make content type and fields multilingual. + $field_names = array( + 'body', + 'field_category', + ); + foreach ($this->fields as $field_name => $field_type) { + $field_names[] = 'field_' . $field_name; + } + $this->setupMultilingual($this->contentType, $field_names); + + // Copy directory of source files, CSV file expects them in public://images. + $this->copyDir($this->absolutePath() . '/tests/feeds/assets', 'public://images'); + + // Create an importer configuration with basic mapping. + $this->createImporterConfiguration('Test multilingual fields import from CSV', 'node'); + $this->setPlugin('node', 'FeedsCSVParser'); + $this->setPlugin('node', 'FeedsFileFetcher'); + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'bundle' => $this->contentType, + 'language' => 'en', + )); + + // Add language neutral mappings. + $this->addMappings('node', array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => 1, + ), + 1 => array( + 'source' => 'title', + 'target' => 'title', + ), + )); + } + + /** + * Tests multilingual mappings to translatable fields (entity translation). + */ + public function testMultilingualFieldMappings() { + // Add English mappers. + $index = 2; + $mappings = $this->getMappingsInLanguage('en', $index); + // Append "_en" to each source name. + foreach ($mappings as &$mapping) { + $mapping['source'] .= '_en'; + } + $this->addMappings('node', $mappings); + $index += count($mappings); + + // Add French mappers. + $mappings = $this->getMappingsInLanguage('fr', $index); + // Append "_fr" to each source name. + foreach ($mappings as &$mapping) { + $mapping['source'] .= '_fr'; + } + $this->addMappings('node', $mappings); + + // Import file that has items with both English and French field values. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_en_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Load node. + $node = node_load(1, NULL, TRUE); + + // Inspect availability of English values. + $english = $this->getEnglishValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['en'][0]['tid'], + ), + ); + foreach ($english as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The English field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + + // Inspect availability of French values. + $french = $this->getFrenchValues($node) + array( + 'field_category' => array( + 'expected' => 2, + 'actual' => $node->field_category['fr'][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The French field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if values of fields in other languages are kept when not importing + * in that language. + */ + public function testChangedLanguageImport() { + // Add Dutch language. + $this->addLanguage('nl', 'Dutch'); + + // Import an item first in the Dutch language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'nl', + )); + $mappings = $this->getMappingsInLanguage('nl', 2); + $this->addMappings('node', $mappings); + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_nl.csv'); + $this->assertText(t('Created 1 node')); + + // Assert that Dutch values were created. + $node = node_load(1, NULL, TRUE); + $dutch = $this->getDutchValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['nl'][0]['tid'], + ), + ); + foreach ($dutch as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The Dutch field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + + // Set import to update existing nodes. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'update_existing' => FEEDS_UPDATE_EXISTING, + )); + + // Change mappers language to French. + $path = 'admin/structure/feeds/node/mapping'; + foreach ($mappings as $i => $mapping) { + $this->drupalPostAJAX($path, array(), 'mapping_settings_edit_' . $i); + $edit = array("config[$i][settings][field_language]" => 'fr'); + $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_' . $i); + $this->drupalPost(NULL, array(), t('Save')); + } + // Import French item. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Updated 1 node')); + + // Assert that French values were created. + $node = node_load(1, NULL, TRUE); + $french = $this->getFrenchValues($node) + array( + 'field_category' => array( + 'expected' => 2, + 'actual' => $node->field_category['fr'][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The French field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + + // Assert that Dutch values still exist. + $dutch = $this->getDutchValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['nl'][0]['tid'], + ), + ); + foreach ($dutch as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The Dutch field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if values of fields in other languages are kept when not importing + * in that language for nodes that were not created by Feeds. + */ + public function testChangedLanguageImportForExistingNode() { + // Add Dutch language. + $this->addLanguage('nl', 'Dutch'); + + // Date settings. + foreach (array('datestamp', 'datetime') as $field) { + $field = 'field_' . $field; + $edit = array( + 'field[settings][granularity][second]' => 1, + ); + $this->drupalPost('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field . '/field-settings', $edit, 'Save field settings'); + } + + // Hack to get date fields to not round to every 15 seconds. + foreach (array('date', 'datestamp', 'datetime') as $field) { + $field = 'field_' . $field; + $edit = array( + 'widget_type' => 'date_select', + ); + $this->drupalPost('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field . '/widget-type', $edit, 'Continue'); + $edit = array( + 'instance[widget][settings][increment]' => 1, + 'field[settings][enddate_get]' => 1, + ); + $this->drupalPost('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field, $edit, 'Save settings'); + $edit = array( + 'widget_type' => 'date_text', + ); + $this->drupalPost('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field . '/widget-type', $edit, 'Continue'); + } + + // Create a node with Dutch values. + $edit = array( + 'title' => 'Teste Feeds Multilingue 1', + 'body[und][0][value]' => 'Dit is de berichttekst', + 'field_date[und][0][value][date]' => '07/29/1985', + 'field_datestamp[und][0][value][date]' => '07/29/1985 - 04:48:12', + 'field_datetime[und][0][value][date]' => '07/29/1985 - 04:48:12', + 'field_link[und][0][url]' => 'http://google.nl', + 'field_list_boolean[und]' => '1', + 'field_number_decimal[und][0][value]' => '30.3', + 'field_number_float[und][0][value]' => '30.2795', + 'field_number_integer[und][0][value]' => '30', + 'field_text[und][0][value]' => 'Wortelen', + 'files[field_image_und_0]' => drupal_realpath('public://images/attersee.jpeg'), + 'field_category[und]' => 'Nieuws', + 'language' => 'nl', + ); + $this->drupalPost('node/add/' . $this->contentType, $edit, t('Save')); + // Add alt/title to the image. + $edit = array( + 'field_image[nl][0][alt]' => 'Bij het zien', + 'field_image[nl][0][title]' => 'Bij het zien van de groene vloeistof', + ); + $this->drupalPost('node/1/edit/nl', $edit, t('Save')); + $this->drupalGet('node/1/edit/nl'); + + // Assert that the Dutch values were put in as expected. + $node = node_load(1, NULL, TRUE); + $dutch = $this->getDutchValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['nl'][0]['tid'], + ), + ); + foreach ($dutch as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The Dutch field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + + // Change unique target from guid (0) to title (1). + $path = 'admin/structure/feeds/node/mapping'; + $this->drupalPostAJAX($path, array(), 'mapping_settings_edit_0'); + $edit = array("config[0][settings][unique]" => FALSE); + $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_0'); + $this->drupalPost(NULL, array(), t('Save')); + $this->drupalPostAJAX($path, array(), 'mapping_settings_edit_1'); + $edit = array("config[1][settings][unique]" => 1); + $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_1'); + $this->drupalPost(NULL, array(), t('Save')); + + // Update this item with Feeds. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'update_existing' => FEEDS_UPDATE_EXISTING, + )); + $this->addMappings('node', $this->getMappingsInLanguage('fr')); + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Updated 1 node')); + + // Assert that French values were created. + $node = node_load(1, NULL, TRUE); + $french = $this->getFrenchValues($node) + array( + 'field_category' => array( + 'expected' => 2, + 'actual' => $node->field_category['fr'][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The French field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + + // Assert that Dutch values still exist. + $dutch = $this->getDutchValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['nl'][0]['tid'], + ), + ); + foreach ($dutch as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The Dutch field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if fields still are imported in their language when the + * entity_translation module gets disabled. + * + * The entity_translation module is mainly an UI module for configuring field + * language and disabling that module should not have effect on importing + * values in a specific language for fields. + */ + public function testWithDisabledEntityTranslationModule() { + module_disable(array('entity_translation')); + // Make sure that entity info is reset. + drupal_flush_all_caches(); + drupal_static_reset(); + + // Configure importer to import in French language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'fr', + )); + $this->addMappings('node', $this->getMappingsInLanguage('fr')); + + // Import content. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Assert that the fields were all created in French. + $node = node_load(1, NULL, TRUE); + $french = $this->getFrenchValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['fr'][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if fields are still imported in their language when the + * entity_translation module gets uninstalled. + * + * @see testWithDisabledEntityTranslationModule() + */ + public function testWithUninstalledEntityTranslationModule() { + module_disable(array('entity_translation')); + drupal_uninstall_modules(array('entity_translation')); + // Make sure that entity info is reset. + drupal_flush_all_caches(); + drupal_static_reset(); + + // Configure importer to import in French language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'fr', + )); + $this->addMappings('node', $this->getMappingsInLanguage('fr')); + + // Import content. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Assert that the fields were all created in French. + $node = node_load(1, NULL, TRUE); + $french = $this->getFrenchValues($node) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category['fr'][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if fields are imported in LANGUAGE_NONE if the field's language gets + * disabled after configuring. + */ + public function testDisabledLanguage() { + // Configure importer to import in French language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'fr', + )); + $this->addMappings('node', $this->getMappingsInLanguage('fr')); + + // Now disable the French language. + $path = 'admin/config/regional/language'; + $edit = array( + 'enabled[fr]' => FALSE, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + // Reset static cache to update the available languages. + drupal_static_reset(); + + // Ensure no error messages are shown on the mappings page. + $this->drupalGet('admin/structure/feeds/node/mapping'); + + // Import content. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Assert that the fields were all created in LANGUAGE_NONE. + $node = node_load(1, NULL, TRUE); + $french = $this->getFrenchValues($node, LANGUAGE_NONE) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category[LANGUAGE_NONE][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if fields are imported in LANGUAGE_NONE if the field's language gets + * removed after configuring. + */ + public function testRemovedLanguage() { + // Configure importer to import in French language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'fr', + )); + $this->addMappings('node', $this->getMappingsInLanguage('fr')); + + // Now remove the French language. + $path = 'admin/config/regional/language/delete/fr'; + $this->drupalPost($path, array(), t('Delete')); + // Reset static cache to update the available languages. + drupal_static_reset(); + + // Import content. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Assert that the fields were all created in LANGUAGE_NONE. + $node = node_load(1, NULL, TRUE); + $french = $this->getFrenchValues($node, LANGUAGE_NONE) + array( + 'field_category' => array( + 'expected' => 1, + 'actual' => $node->field_category[LANGUAGE_NONE][0]['tid'], + ), + ); + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if autocreated terms are in the language that was set on the target configuration + * in case the taxonomy is multilingual. + */ + public function testAutocreatedTermLanguage() { + module_enable(array('i18n_taxonomy')); + // Make sure that entity info is reset. + drupal_flush_all_caches(); + drupal_static_reset(); + + // Enable multilingual taxonomy. + $edit = array('i18n_mode' => 4); + $this->drupalPost('admin/structure/taxonomy/categories/edit', $edit, 'Save'); + + // Configure importer to import in French language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'fr', + )); + $this->addMappings('node', array( + 2 => array( + 'source' => 'term', + 'target' => 'field_category', + 'autocreate' => TRUE, + 'field_language' => 'fr', + ), + )); + + // Import French item. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Assert that the created term is in the French language. + $term = taxonomy_term_load(1); + $this->assertEqual('fr', entity_language('taxonomy_term', $term)); + } + + /** + * Tests if values are cleared out when an empty value or no value is + * provided. + */ + public function testClearOutValues() { + // Set to update existing nodes. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'update_existing' => FEEDS_UPDATE_EXISTING, + )); + + // Add English mappers. + $index = 2; + $mappings = $this->getMappingsInLanguage('en', $index); + // Append "_en" to each source name. + foreach ($mappings as &$mapping) { + $mapping['source'] .= '_en'; + } + $this->addMappings('node', $mappings); + $index += count($mappings); + + // Add French mappers. + $mappings = $this->getMappingsInLanguage('fr', $index); + // Append "_fr" to each source name. + foreach ($mappings as &$mapping) { + $mapping['source'] .= '_fr'; + } + $this->addMappings('node', $mappings); + + // Import file that has items with both English and French field values. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_en_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Now import a file where the French remained, but the English values were + // removed. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_en_fr_empty.csv'); + $this->assertText(t('Updated 1 node')); + + // Load node. + $node = node_load(1, NULL, TRUE); + + // Check that the English values are gone, but the French values are still + // there. + $fields = array( + 'body', + 'field_date', + 'field_datestamp', + 'field_datetime', + 'field_image', + 'field_link', + 'field_list_boolean', + 'field_number_decimal', + 'field_number_float', + 'field_number_integer', + 'field_category', + 'field_text', + ); + foreach ($fields as $field_name) { + $this->assertTrue(empty($node->{$field_name}['en']), format_string('The field %field is empty.', array('%field' => $field_name))); + } + + // Inspect availability of French values. + $french = $this->getFrenchValues($node) + array( + 'field_category' => array( + 'expected' => 2, + 'actual' => $node->field_category['fr'][0]['tid'], + ), + ); + // Since the image was placed on the node again, its file name is now + // "la fayette_0.jpeg." + $french['field_image']['expected'] = 'la fayette_0.jpeg'; + foreach ($french as $field_name => $value) { + $this->assertEqual($value['expected'], $value['actual'], format_string('The French field %field has the expected value (actual: @actual).', array('%field' => $field_name, '@actual' => $value['actual']))); + } + } + + /** + * Tests if values are cleared out when an empty value is provided for a + * language that got disabled. + */ + public function testClearOutValuesWithDisabledLanguage() { + // Set to update existing nodes. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'update_existing' => FEEDS_UPDATE_EXISTING, + )); + + // Configure importer to import in French language. + $this->setSettings('node', 'FeedsNodeProcessor', array( + 'language' => 'fr', + )); + $this->addMappings('node', $this->getMappingsInLanguage('fr')); + + // Now disable the French language. + $path = 'admin/config/regional/language'; + $edit = array( + 'enabled[fr]' => FALSE, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + + // Ensure no error messages are shown on the mappings page. + $this->drupalGet('admin/structure/feeds/node/mapping'); + + // Import content. Since the French language was disabled, the content + // should be imported as LANGUAGE_NONE. + // @see ::testDisabledLanguage() + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_fr.csv'); + $this->assertText(t('Created 1 node')); + + // Now import a file with empty values. + $this->importFile('node', $this->absolutePath() . '/tests/feeds/multilingual_empty.csv'); + $this->assertText(t('Updated 1 node')); + + // Load node. + $node = node_load(1, NULL, TRUE); + + // Check that the values in LANGUAGE_NONE are gone. + $fields = array( + 'body', + 'field_date', + 'field_datestamp', + 'field_datetime', + 'field_image', + 'field_link', + 'field_list_boolean', + 'field_number_decimal', + 'field_number_float', + 'field_number_integer', + 'field_category', + 'field_text', + ); + foreach ($fields as $field_name) { + $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE]), format_string('The field %field is empty.', array('%field' => $field_name))); + } + } + + /** + * Adds a language to test with. + * + * @param string $langcode + * The language's langcode. + * @param string $label + * The language human readable name. + */ + protected function addLanguage($langcode, $label) { + $edit = array( + 'langcode' => $langcode, + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText(format_string('The language @language has been created and can now be used.', array('@language' => $label))); + } + + /** + * Sets given content type and fields to be translatable. + * + * @param string $typename + * The machine name of the node type. + * @param array $field_names + * The fields to enable multilingual support for. + */ + protected function setupMultilingual($typename, array $field_names) { + // Enable entity field translation for content type. + $edit = array( + 'language_content_type' => 4, + 'entity_translation_hide_translation_links' => 1, + 'entity_translation_node_metadata' => 0, + ); + $this->drupalPost('admin/structure/types/manage/' . $typename, $edit, t('Save content type')); + + // Enable field translation on fields. + $edit = array( + 'field[translatable]' => 1, + ); + foreach ($field_names as $field_name) { + $this->drupalPost("admin/structure/types/manage/{$typename}/fields/{$field_name}", $edit, t('Save settings')); + } + + // Reset static cache so that all languages are available when + // field_available_languages() is called during node_load(). + drupal_static_reset(); + } + + /** + * Adds mappings for each field in specified language. + * + * @param string $langcode + * The code of the desired language. + * @param int $start + * The index number to start the array with. This must be + * specified in order to add mappings to the right index when + * calling FeedsWebTestCase::addMappings(). + */ + protected function getMappingsInLanguage($langcode, $start = 2) { + $mappings = array( + $start => array( + 'source' => 'body', + 'target' => 'body', + ), + array( + 'source' => 'date', + 'target' => 'field_date:start', + ), + array( + 'source' => 'datestamp', + 'target' => 'field_datestamp:start', + ), + array( + 'source' => 'datetime', + 'target' => 'field_datetime:start', + ), + array( + 'source' => 'image', + 'target' => 'field_image:uri', + ), + array( + 'source' => 'image_alt', + 'target' => 'field_image:alt', + ), + array( + 'source' => 'image_title', + 'target' => 'field_image:title', + ), + array( + 'source' => 'link', + 'target' => 'field_link:url', + ), + array( + 'source' => 'list_boolean', + 'target' => 'field_list_boolean', + ), + array( + 'source' => 'number_decimal', + 'target' => 'field_number_decimal', + ), + array( + 'source' => 'number_float', + 'target' => 'field_number_float', + ), + array( + 'source' => 'number_integer', + 'target' => 'field_number_integer', + ), + array( + 'source' => 'term', + 'target' => 'field_category', + 'autocreate' => TRUE, + ), + array( + 'source' => 'text', + 'target' => 'field_text', + ), + ); + foreach ($mappings as &$mapping) { + $mapping['field_language'] = $langcode; + } + return $mappings; + } + + /** + * Returns expected and actual values of given node for the Dutch language. + * + * @param object $node + * The multilingual node. + * @param string $langcode + * The used language code. + * + * @return array + * The expected and actual Dutch values. + */ + protected function getDutchValues($node, $langcode = 'nl') { + return array( + 'body' => array( + 'expected' => 'Dit is de berichttekst', + 'actual' => $node->body[$langcode][0]['value'], + ), + 'field_date' => array( + 'expected' => '1985-07-29T00:00:00', + 'actual' => $node->field_date[$langcode][0]['value'], + ), + 'field_datestamp' => array( + 'expected' => '491460492', + 'actual' => $node->field_datestamp[$langcode][0]['value'], + ), + 'field_datetime' => array( + 'expected' => '1985-07-29 04:48:12', + 'actual' => $node->field_datetime[$langcode][0]['value'], + ), + 'field_image' => array( + 'expected' => 'attersee.jpeg', + 'actual' => $node->field_image[$langcode][0]['filename'], + ), + 'field_image:alt' => array( + 'expected' => 'Bij het zien', + 'actual' => $node->field_image[$langcode][0]['alt'], + ), + 'field_image:title' => array( + 'expected' => 'Bij het zien van de groene vloeistof', + 'actual' => $node->field_image[$langcode][0]['title'], + ), + 'field_link' => array( + 'expected' => 'http://google.nl', + 'actual' => $node->field_link[$langcode][0]['url'], + ), + 'field_list_boolean' => array( + 'expected' => '1', + 'actual' => $node->field_list_boolean[$langcode][0]['value'], + ), + 'field_number_decimal' => array( + 'expected' => 30.3, + 'actual' => $node->field_number_decimal[$langcode][0]['value'], + ), + 'field_number_float' => array( + 'expected' => 30.2795, + 'actual' => $node->field_number_float[$langcode][0]['value'], + ), + 'field_number_integer' => array( + 'expected' => 30, + 'actual' => $node->field_number_integer[$langcode][0]['value'], + ), + 'field_text' => array( + 'expected' => 'Wortelen', + 'actual' => $node->field_text[$langcode][0]['value'], + ), + ); + } + + /** + * Returns expected and actual values of given node for the English language. + * + * @param object $node + * The multilingual node. + * @param string $langcode + * The used language code. + * + * @return array + * The expected and actual English values. + */ + protected function getEnglishValues($node, $langcode = 'en') { + return array( + 'body' => array( + 'expected' => 'This is the body', + 'actual' => $node->body[$langcode][0]['value'], + ), + 'field_date' => array( + 'expected' => '2015-10-21T00:00:00', + 'actual' => $node->field_date[$langcode][0]['value'], + ), + 'field_datestamp' => array( + 'expected' => '1445470140', + 'actual' => $node->field_datestamp[$langcode][0]['value'], + ), + 'field_datetime' => array( + 'expected' => '2015-10-21 23:29:00', + 'actual' => $node->field_datetime[$langcode][0]['value'], + ), + 'field_image' => array( + 'expected' => 'foosball.jpeg', + 'actual' => $node->field_image[$langcode][0]['filename'], + ), + 'field_image:alt' => array( + 'expected' => 'Foosball', + 'actual' => $node->field_image[$langcode][0]['alt'], + ), + 'field_image:title' => array( + 'expected' => 'Foosball played by two guys', + 'actual' => $node->field_image[$langcode][0]['title'], + ), + 'field_link' => array( + 'expected' => 'http://google.ca', + 'actual' => $node->field_link[$langcode][0]['url'], + ), + 'field_list_boolean' => array( + 'expected' => '0', + 'actual' => $node->field_list_boolean[$langcode][0]['value'], + ), + 'field_number_decimal' => array( + 'expected' => 4.2, + 'actual' => $node->field_number_decimal[$langcode][0]['value'], + ), + 'field_number_float' => array( + 'expected' => 3.1416, + 'actual' => $node->field_number_float[$langcode][0]['value'], + ), + 'field_number_integer' => array( + 'expected' => 1000, + 'actual' => $node->field_number_integer[$langcode][0]['value'], + ), + 'field_text' => array( + 'expected' => 'Carrots', + 'actual' => $node->field_text[$langcode][0]['value'], + ), + ); + } + + /** + * Returns expected and actual values of given node for the French language. + * + * @param object $node + * The multilingual node. + * @param string $langcode + * The used language code. + * + * @return array + * The expected and actual French values. + */ + protected function getFrenchValues($node, $langcode = 'fr') { + return array( + 'body' => array( + 'expected' => 'Ceci est la corps', + 'actual' => $node->body[$langcode][0]['value'], + ), + 'field_date' => array( + 'expected' => '1955-11-05T00:00:00', + 'actual' => $node->field_date[$langcode][0]['value'], + ), + 'field_datestamp' => array( + 'expected' => '-446731187', + 'actual' => $node->field_datestamp[$langcode][0]['value'], + ), + 'field_datetime' => array( + 'expected' => '1955-11-05 12:00:13', + 'actual' => $node->field_datetime[$langcode][0]['value'], + ), + 'field_image' => array( + 'expected' => 'la fayette.jpeg', + 'actual' => $node->field_image[$langcode][0]['filename'], + ), + 'field_image:alt' => array( + 'expected' => 'La Fayette', + 'actual' => $node->field_image[$langcode][0]['alt'], + ), + 'field_image:title' => array( + 'expected' => 'la Fayette dans les bois', + 'actual' => $node->field_image[$langcode][0]['title'], + ), + 'field_link' => array( + 'expected' => 'http://google.fr', + 'actual' => $node->field_link[$langcode][0]['url'], + ), + 'field_list_boolean' => array( + 'expected' => '1', + 'actual' => $node->field_list_boolean[$langcode][0]['value'], + ), + 'field_number_decimal' => array( + 'expected' => 1.2, + 'actual' => $node->field_number_decimal[$langcode][0]['value'], + ), + 'field_number_float' => array( + 'expected' => 5.6295, + 'actual' => $node->field_number_float[$langcode][0]['value'], + ), + 'field_number_integer' => array( + 'expected' => 2000, + 'actual' => $node->field_number_integer[$langcode][0]['value'], + ), + 'field_text' => array( + 'expected' => 'Carottes', + 'actual' => $node->field_text[$langcode][0]['value'], + ), + ); + } +} diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_profile.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_profile.test index b0d9efdb5..1b3988822 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_profile.test +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_profile.test @@ -24,7 +24,7 @@ class FeedsMapperProfileTestCase extends FeedsMapperTestCase { } /** - * Basic test loading a doulbe entry CSV file. + * Basic test loading a double entry CSV file. */ public function test() { // Create profile fields. diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_taxonomy.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_taxonomy.test index ed557513c..de83191d2 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_taxonomy.test +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_mapper_taxonomy.test @@ -234,6 +234,7 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase { $target = 'field_tags'; $mapping = array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_ID, + 'language' => LANGUAGE_NONE, ); $source = FeedsSource::instance('tmp', 0); @@ -284,6 +285,7 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase { $target = 'field_tags'; $mapping = array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_GUID, + 'language' => LANGUAGE_NONE, ); $source = FeedsSource::instance('tmp', 0); diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_parser_csv.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_parser_csv.test new file mode 100644 index 000000000..581d80245 --- /dev/null +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_parser_csv.test @@ -0,0 +1,100 @@ + 'CSV parser functional tests', + 'description' => 'Tests the CSV parser using the UI.', + 'group' => 'Feeds', + ); + } + + /** + * Tests parsing a CSV when the mbstring extension is not available. + */ + public function testMbstringExtensionDisabled() { + // Set "feeds_use_mbstring" to FALSE to emulate that the mbstring extension + // is not loaded. + variable_set('feeds_use_mbstring', FALSE); + + // Remove items after parsing because in < PHP 5.4 processing items with + // encoding issues leads to test failures because check_plain() can only + // handle UTF-8 encoded strings. + // @see feeds_tests_feeds_after_parse() + variable_set('feeds_tests_feeds_after_parse_empty_items', TRUE); + + // Create node type. + $node_type = $this->drupalCreateContentType(); + + // Create and configure importer. + $this->createImporterConfiguration('Content CSV', 'csv'); + $this->setPlugin('csv', 'FeedsFileFetcher'); + $this->setPlugin('csv', 'FeedsCSVParser'); + $this->setSettings('csv', 'FeedsNodeProcessor', array('bundle' => $node_type->type)); + $this->addMappings('csv', array( + 0 => array( + 'source' => 'id', + 'target' => 'guid', + ), + 1 => array( + 'source' => 'text', + 'target' => 'title', + ), + )); + + // Ensure that on the CSV parser settings page a message is shown about that + // the mbstring extension is not available. + $this->drupalGet('admin/structure/feeds/csv/settings/FeedsCSVParser'); + $this->assertNoField('encoding'); + $this->assertText('PHP mbstring extension must be available for character encoding conversion.'); + + // Try to import a CSV file that is not UTF-8 encoded. No encoding warning + // should be shown, but import should fail. + $this->importFile('csv', $this->absolutePath() . '/tests/feeds/encoding_SJIS.csv'); + $this->assertNoText('Source file is not in UTF-8 encoding.'); + } + + /** + * Tests an encoding failure during parsing a CSV. + */ + public function testEncodingFailure() { + // Create node type. + $node_type = $this->drupalCreateContentType(); + + // Create and configure importer. + $this->createImporterConfiguration('Content CSV', 'csv'); + $this->setPlugin('csv', 'FeedsFileFetcher'); + $this->setPlugin('csv', 'FeedsCSVParser'); + $this->setSettings('csv', 'FeedsNodeProcessor', array('bundle' => $node_type->type)); + $this->addMappings('csv', array( + 0 => array( + 'source' => 'id', + 'target' => 'guid', + ), + 1 => array( + 'source' => 'text', + 'target' => 'title', + ), + )); + + // Ensure that on the CSV parser settings page a setting for encoding is + // shown. + $this->drupalGet('admin/structure/feeds/csv/settings/FeedsCSVParser'); + $this->assertField('encoding'); + $this->assertNoText('PHP mbstring extension must be available for character encoding conversion.'); + + // Try to import a CSV file that is not UTF-8 encoded. Import should be + // halted and an encoding warning should be shown. + $this->importFile('csv', $this->absolutePath() . '/tests/feeds/encoding_SJIS.csv'); + $this->assertNoText('Failed importing 4 nodes.'); + $this->assertText('Source file is not in UTF-8 encoding.'); + } +} diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_node.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_node.test index 2c9714274..25ec79a11 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_node.test +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_node.test @@ -12,8 +12,8 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { public static function getInfo() { return array( - 'name' => 'RSS import to nodes', - 'description' => 'Tests a feed configuration that is attached to a content type, uses HTTP fetcher, common syndication parser and a node processor. Repeats the same test for an importer configuration that is not attached to a content type and for a configuration that is attached to a content type and uses the file fetcher.', + 'name' => 'Processor: Node', + 'description' => 'Tests for the node processor.', 'group' => 'Feeds', ); } @@ -577,10 +577,188 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { // The feed should still be scheduled because it is being processed. // @see https://drupal.org/node/2275893 - feeds_source('syndication', 0)->scheduleImport(); - $this->cronRun(); $this->assertEqual(86, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); } + /** + * Tests skip new items. + */ + public function testSkipNewItems() { + // Include FeedsProcessor.inc so processor related constants are available. + module_load_include('inc', 'feeds', 'plugins/FeedsProcessor'); + + // Attach to standalone importer. + $this->setSettings('syndication', NULL, array('content_type' => '')); + // Set that new items should not be imported. + $this->setSettings('syndication', 'FeedsNodeProcessor', array( + 'insert_new' => FEEDS_SKIP_NEW, + 'update_existing' => FEEDS_SKIP_EXISTING, + )); + + // Make title unique target. + $this->removeMappings('syndication', $this->getCurrentMappings('syndication')); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'description', + 'target' => 'body', + ), + 2 => array( + 'source' => 'timestamp', + 'target' => 'created', + ), + )); + + // Do a first import, no nodes should be created. + $edit = array( + 'feeds[FeedsHTTPFetcher][source]' => $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2', + ); + $this->drupalPost('import/syndication', $edit, 'Import'); + $this->assertText('There are no new nodes'); + + // Now create two nodes with titles that are present in the source + // "developmentseed.rss2". + $this->drupalCreateNode(array( + 'type' => 'article', + 'title' => 'Open Atrium Translation Workflow: Two Way Translation Updates', + )); + $this->drupalCreateNode(array( + 'type' => 'article', + 'title' => 'Week in DC Tech: October 5th Edition', + )); + + // Import again. Since the processor is set to not update as well, nothing + // should be imported. + $this->drupalPost('import/syndication', array(), 'Import'); + $this->assertText('There are no new nodes'); + + // Now set importer to update existing. + $this->setSettings('syndication', 'FeedsNodeProcessor', array( + 'update_existing' => FEEDS_UPDATE_EXISTING, + )); + // And import again. Two nodes should be updated. + $this->drupalPost('import/syndication', array(), 'Import'); + $this->assertText('Updated 2 nodes.'); + + // Change "insert_new" setting to insert new items to verify if changing the + // setting later has the effect that new items will be imported as yet. + $this->setSettings('syndication', 'FeedsNodeProcessor', array( + 'insert_new' => FEEDS_INSERT_NEW, + )); + // Import. Eight nodes should be created. No nodes should be updated. + $this->drupalPost('import/syndication', array(), 'Import'); + $this->assertText('Created 8 nodes.'); + $this->assertNoText('Updated 2 nodes.'); + } + + /** + * Tests if the target "changed" works as expected. + */ + public function testChangedTarget() { + // 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->addMappings('csv', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + // Borrow the timestamp value from the "created" column in the csv. + 1 => array( + 'source' => 'created', + 'target' => 'changed', + ), + )); + + // Import CSV file. + $this->importFile('csv', $this->absolutePath() . '/tests/feeds/content.csv'); + $this->assertText('Created 2 nodes'); + + // Assert changed date of nodes. + $expected_values = array( + 1 => array( + 'changed' => 1251936720, + ), + 2 => array( + 'changed' => 1251932360, + ), + ); + for ($i = 1; $i <= 2; $i++) { + $node = node_load($i); + $this->assertEqual($expected_values[$i]['changed'], $node->changed); + } + } + + /** + * Tests the FeedsSource::pushImport() method. + */ + public function testPushImport() { + // Attach to standalone importer. + $this->setSettings('syndication', NULL, array('content_type' => '')); + + $raw = file_get_contents(dirname(__FILE__) . '/feeds/developmentseed.rss2'); + feeds_source('syndication', 0)->pushImport(new FeedsFetcherResult($raw)); + $this->assertEqual(10, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + } + + /** + * Tests the FeedsSource::pushImport() method with a CSV file. + */ + public function testPushImportWithCSV() { + // Attach to standalone importer and configure. + $this->setSettings('syndication', NULL, array('content_type' => '')); + $this->setPlugin('syndication', 'FeedsCSVParser'); + $this->removeMappings('syndication', $this->getCurrentMappings('syndication')); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + )); + + $raw = file_get_contents($this->absolutePath() . '/tests/feeds/many_nodes.csv'); + feeds_source('syndication', 0)->pushImport(new FeedsFetcherResult($raw)); + $this->assertEqual(86, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + } + + /** + * Tests if target item is not updated when only non-mapped data on the source changed. + */ + public function testIrrelevantUpdate() { + // Include FeedsProcessor.inc so processor related constants are available. + module_load_include('inc', 'feeds', 'plugins/FeedsProcessor'); + + // Attach to standalone importer and configure. + $this->setSettings('syndication', NULL, array('content_type' => '')); + $this->setPlugin('syndication', 'FeedsFileFetcher'); + $this->setPlugin('syndication', 'FeedsCSVParser'); + $this->removeMappings('syndication', $this->getCurrentMappings('syndication')); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'name', + 'target' => 'title', + 'unique' => TRUE, + ), + )); + + // Import file. + $this->importFile('syndication', $this->absolutePath() . '/tests/feeds/users.csv'); + $this->assertText('Created 5 nodes'); + + // Ensure that no nodes are updated when only non-mapped columns changed. + $this->setSettings('syndication', 'FeedsNodeProcessor', array( + 'skip_hash_check' => FALSE, + 'update_existing' => FEEDS_UPDATE_EXISTING, + )); + $this->importFile('syndication', $this->absolutePath() . '/tests/feeds/users_updated.csv'); + $this->assertText('There are no new nodes.'); + } + } diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_term.test b/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_term.test index 3e9c5cd03..5d40f8b9b 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_term.test +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_processor_term.test @@ -120,4 +120,260 @@ class FeedsCSVtoTermsTest extends FeedsWebTestCase { $this->assertText(t('No vocabulary defined for Taxonomy Term processor.')); } + /** + * Tests that terms mapped to their parent by GUID are from the same vocabulary. + */ + public function testParentTargetByGUID() { + // Create an other vocabulary. + $vocabulary1 = 'addams'; + $vocabulary2 = strtolower($this->randomName()); + $edit = array( + 'name' => $this->randomString(), + 'machine_name' => $vocabulary2, + ); + $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save')); + + // Add mappings for the first importer. + $this->addMappings('term_import', + array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'name', + 'target' => 'name', + ), + 2 => array( + 'source' => 'parentguid', + 'target' => 'parentguid', + ), + ) + ); + + // Create a second importer. + $this->createImporterConfiguration('Term import 2', 'term_import2'); + $this->setSettings('term_import2', NULL, array('content_type' => '')); + + // Set and configure plugins and mappings. + $this->setPlugin('term_import2', 'FeedsFileFetcher'); + $this->setPlugin('term_import2', 'FeedsCSVParser'); + $this->setPlugin('term_import2', 'FeedsTermProcessor'); + $this->setSettings('term_import2', 'FeedsTermProcessor', array('bundle' => $vocabulary2)); + + // Add mappings for the second importer. + $this->addMappings('term_import2', + array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'name', + 'target' => 'name', + ), + 2 => array( + 'source' => 'parentguid', + 'target' => 'parentguid', + ), + ) + ); + + $values = array( + 1 => 'Europe', + 2 => 'Belgium', + ); + + // Import file using the first importer. + $this->importFile('term_import', $this->absolutePath() . '/tests/feeds/terms.csv'); + $this->assertText('Created 2 terms.'); + + // Assert that two terms were created in the first vocabulary. + $terms = entity_load('taxonomy_term', array_keys($values)); + foreach ($terms as $tid => $term) { + $this->assertEqual($values[$tid], $term->name); + $this->assertEqual($vocabulary1, $term->vocabulary_machine_name); + } + // Assert that the second term's parent is the first term. + $parents = taxonomy_get_parents($terms[2]->tid); + $message = format_string('The term @term is correctly linked to its parent.', array('@term' => $terms[2]->name)); + if (!empty($parents)) { + $parent = current($parents); + $this->assertEqual(1, $parent->tid, $message); + } + else { + $this->fail($message); + } + + $values = array( + 3 => 'Europe', + 4 => 'Belgium', + ); + + // Now import the file using the second importer. + $this->importFile('term_import2', $this->absolutePath() . '/tests/feeds/terms.csv'); + $this->assertText('Created 2 terms.'); + + // Assert that two terms were created in the second vocabulary. + $terms = entity_load('taxonomy_term', array_keys($values)); + foreach ($terms as $tid => $term) { + $this->assertEqual($values[$tid], $term->name); + $this->assertEqual($vocabulary2, $term->vocabulary_machine_name); + } + // Assert that the second term's parent is the first term. + $parents = taxonomy_get_parents($terms[4]->tid); + $message = format_string('The term @term is correctly linked to its parent.', array('@term' => $terms[4]->name)); + if (!empty($parents)) { + $parent = current($parents); + $this->assertEqual(3, $parent->tid, $message); + } + else { + $this->fail($message); + } + } + + /** + * Tests that terms mapped to their parent by GUID are from the same vocabulary. + */ + public function testParentTargetByName() { + // Create an other vocabulary. + $vocabulary1 = 'addams'; + $vocabulary2 = strtolower($this->randomName()); + $edit = array( + 'name' => $this->randomString(), + 'machine_name' => $vocabulary2, + ); + $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save')); + + // Add mappings for the first importer. + $this->addMappings('term_import', + array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'name', + 'target' => 'name', + ), + 2 => array( + 'source' => 'parent', + 'target' => 'parent', + ), + ) + ); + + // Create a second importer. + $this->createImporterConfiguration('Term import 2', 'term_import2'); + $this->setSettings('term_import2', NULL, array('content_type' => '')); + + // Set and configure plugins and mappings. + $this->setPlugin('term_import2', 'FeedsFileFetcher'); + $this->setPlugin('term_import2', 'FeedsCSVParser'); + $this->setPlugin('term_import2', 'FeedsTermProcessor'); + $this->setSettings('term_import2', 'FeedsTermProcessor', array('bundle' => $vocabulary2)); + + // Add mappings for the second importer. + $this->addMappings('term_import2', + array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'name', + 'target' => 'name', + ), + 2 => array( + 'source' => 'parent', + 'target' => 'parent', + ), + ) + ); + + $values = array( + 1 => 'Europe', + 2 => 'Belgium', + ); + + // Import file using the first importer. + $this->importFile('term_import', $this->absolutePath() . '/tests/feeds/terms.csv'); + $this->assertText('Created 2 terms.'); + + // Assert that two terms were created in the first vocabulary. + $terms = entity_load('taxonomy_term', array_keys($values)); + foreach ($terms as $tid => $term) { + $this->assertEqual($values[$tid], $term->name); + $this->assertEqual($vocabulary1, $term->vocabulary_machine_name); + } + // Assert that the second term's parent is the first term. + $parents = taxonomy_get_parents($terms[2]->tid); + $message = format_string('The term @term is correctly linked to its parent.', array('@term' => $terms[2]->name)); + if (!empty($parents)) { + $parent = current($parents); + $this->assertEqual(1, $parent->tid, $message); + } + else { + $this->fail($message); + } + + $values = array( + 3 => 'Europe', + 4 => 'Belgium', + ); + + // Now import the file using the second importer. + $this->importFile('term_import2', $this->absolutePath() . '/tests/feeds/terms.csv'); + $this->assertText('Created 2 terms.'); + + // Assert that two terms were created in the second vocabulary. + $terms = entity_load('taxonomy_term', array_keys($values)); + foreach ($terms as $tid => $term) { + $this->assertEqual($values[$tid], $term->name); + $this->assertEqual($vocabulary2, $term->vocabulary_machine_name); + } + // Assert that the second term's parent is the first term. + $parents = taxonomy_get_parents($terms[4]->tid); + $message = format_string('The term @term is correctly linked to its parent.', array('@term' => $terms[4]->name)); + if (!empty($parents)) { + $parent = current($parents); + $this->assertEqual(3, $parent->tid, $message); + } + else { + $this->fail($message); + } + } + + /** + * Test replacing terms on subsequent imports. + */ + public function testReplaceTerms() { + $mappings = array( + 0 => array( + 'source' => 'name', + 'target' => 'name', + 'unique' => 1, + ), + ); + $this->addMappings('term_import', $mappings); + + // Configure the processor to "Replace existing terms". + $this->setSettings('term_import', 'FeedsTermProcessor', array( + 'skip_hash_check' => TRUE, + 'update_existing' => 1, + )); + + // Import first time. + $this->importFile('term_import', $this->absolutePath() . '/tests/feeds/users.csv'); + $this->assertText('Created 5 terms'); + + // Import again to replace terms. + $this->importFile('term_import', $this->absolutePath() . '/tests/feeds/users.csv'); + $this->assertText('Updated 5 terms.'); + } + } diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.info b/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.info index c9ffc1e28..dcf1e0dde 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.info +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.info @@ -3,14 +3,11 @@ description = "Support module for Feeds related testing." package = Testing version = VERSION core = 7.x -test_dependencies[] = date -test_dependencies[] = feeds_xpathparser -test_dependencies[] = link hidden = TRUE -; Information added by Drupal.org packaging script on 2015-07-11 -version = "7.x-2.0-beta1" +; Information added by Drupal.org packaging script on 2016-02-21 +version = "7.x-2.0-beta2" core = "7.x" project = "feeds" -datestamp = "1436615941" +datestamp = "1456055647" diff --git a/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.module b/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.module index fa894622a..79df5875d 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.module +++ b/www7/sites/all/modules/contrib/feeds/tests/feeds_tests.module @@ -209,7 +209,7 @@ function feeds_tests_feeds_processor_targets_alter(array &$targets, $entity_type * * @see feeds_tests_feeds_processor_targets() */ -function feeds_tests_preprocess_callback(FeedsSource $source, $target_item, $target, array &$mapping) { +function feeds_tests_preprocess_callback(array $target, array &$mapping) { $mapping['required_value'] = TRUE; } @@ -350,6 +350,24 @@ function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $ } } +/** + * Implements hook_feeds_after_parse(). + * + * Empties the list of items to import in case the test says that there are + * items in there with encoding issues. These items can not be processed during + * tests without having a test failure because in < PHP 5.4 that would produce + * the following warning: + * htmlspecialchars(): Invalid multibyte sequence in argument + * + * @see FeedsCSVParserTestCase::testMbstringExtensionDisabled() + */ +function feeds_tests_feeds_after_parse(FeedsSource $source, FeedsParserResult $result) { + if (variable_get('feeds_tests_feeds_after_parse_empty_items', FALSE)) { + // Remove all items. No items will be processed. + $result->items = array(); + } +} + /** * Helper class to ensure callbacks can be objects. */ @@ -360,7 +378,7 @@ class FeedsTestsPreprocess { * * @see feeds_tests_feeds_processor_targets() */ - public static function callback(FeedsSource $source, $target_item, $target, array &$mapping) { + public static function callback(array $target, array &$mapping) { $mapping['required_value'] = TRUE; } diff --git a/www7/sites/all/modules/contrib/feeds/tests/http_request.test b/www7/sites/all/modules/contrib/feeds/tests/http_request.test index de6b1e1d5..8bfab6a70 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/http_request.test +++ b/www7/sites/all/modules/contrib/feeds/tests/http_request.test @@ -51,4 +51,309 @@ EOF; $this->assertEqual($links[0], 'http://example.com/rss.xml'); } + /** + * Tests http_request_create_absolute_url(). + */ + public function testHTTPRequestCreateAbsoluteUrl() { + $test_urls = array( + // Rels that do not start with "/". + array( + 'rel' => 'h', + 'base' => 'http://www', + 'expected' => 'http://www/h', + ), + array( + 'rel' => 'h', + 'base' => 'http://www/', + 'expected' => 'http://www/h', + ), + array( + 'rel' => 'h', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h', + ), + array( + 'rel' => 'h', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/a/h', + ), + + array( + 'rel' => 'h/j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => 'h/j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => 'h/j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => 'h/j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/a/h/j', + ), + array( + 'rel' => 'h/j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/a/b/h/j', + ), + + // Rels that start with "/". + array( + 'rel' => '/h', + 'base' => 'http://www', + 'expected' => 'http://www/h', + ), + array( + 'rel' => '/h', + 'base' => 'http://www/', + 'expected' => 'http://www/h', + ), + array( + 'rel' => '/h', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h', + ), + array( + 'rel' => '/h', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/h', + ), + + array( + 'rel' => '/h/j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/h/j', + ), + + // Rels that contain ".". + array( + 'rel' => './h', + 'base' => 'http://www', + 'expected' => 'http://www/h', + ), + array( + 'rel' => './h', + 'base' => 'http://www/', + 'expected' => 'http://www/h', + ), + array( + 'rel' => './h', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h', + ), + array( + 'rel' => './h', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/a/h', + ), + + array( + 'rel' => './h/j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => './h/j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => './h/j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => './h/j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/a/h/j', + ), + array( + 'rel' => './h/j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/a/b/h/j', + ), + + array( + 'rel' => 'h/./j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => 'h/./j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => 'h/./j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => 'h/./j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/a/h/j', + ), + array( + 'rel' => 'h/./j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/a/b/h/j', + ), + + array( + 'rel' => '/h/./j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/./j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/./j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/./j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '/h/./j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/h/j', + ), + + // Rels that starts with "../". + array( + 'rel' => '../h/j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../h/j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../h/j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../h/j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../h/j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/a/h/j', + ), + array( + 'rel' => '../h/j', + 'base' => 'http://www/a/b/c/', + 'expected' => 'http://www/a/b/h/j', + ), + + // Rels that start with "../../". + array( + 'rel' => '../../h/j', + 'base' => 'http://www', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../../h/j', + 'base' => 'http://www/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../../h/j', + 'base' => 'http://www/?c;d=e#f', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../../h/j', + 'base' => 'http://www/a/b', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../../h/j', + 'base' => 'http://www/a/b/', + 'expected' => 'http://www/h/j', + ), + array( + 'rel' => '../../h/j', + 'base' => 'http://www/a/b/c/', + 'expected' => 'http://www/a/h/j', + ), + array( + 'rel' => '../../h/j', + 'base' => 'http://www/a/b/c/d', + 'expected' => 'http://www/a/h/j', + ), + + // Crazy rels. + array( + 'rel' => 'h/../../j/./k', + 'base' => 'http://www/a/b/c/', + 'expected' => 'http://www/a/b/j/k', + ), + array( + 'rel' => 'h/../../j/./k', + 'base' => 'http://www/a/b/c/d', + 'expected' => 'http://www/a/b/j/k', + ), + array( + 'rel' => '../../../', + 'base' => 'http://www/a/b/c/', + 'expected' => 'http://www/', + ), + array( + 'rel' => 'h/j/k/../../', + 'base' => 'http://www/a/b/c/', + 'expected' => 'http://www/a/b/c/h', + ), + ); + + foreach ($test_urls as $test_url) { + $result_url = http_request_create_absolute_url($test_url['rel'], $test_url['base']); + $this->assertEqual($test_url['expected'], $result_url, format_string('Creating an absolute URL from base @base and rel @rel resulted into @expected (actual: @actual).', array( + '@actual' => var_export($result_url, TRUE), + '@expected' => var_export($test_url['expected'], TRUE), + '@rel' => var_export($test_url['rel'], TRUE), + '@base' => var_export($test_url['base'], TRUE), + ))); + } + + } + } diff --git a/www7/sites/all/modules/contrib/feeds/tests/parser_csv.test b/www7/sites/all/modules/contrib/feeds/tests/parser_csv.test index 2905b3983..c5cb32411 100644 --- a/www7/sites/all/modules/contrib/feeds/tests/parser_csv.test +++ b/www7/sites/all/modules/contrib/feeds/tests/parser_csv.test @@ -32,6 +32,8 @@ class ParserCSVTest extends DrupalWebTestCase { $this->_testSimple(); $this->_testBatching(); + $this->_testEncodingConversion(); + $this->_testEncodingConversionFailure(); } /** @@ -53,6 +55,51 @@ class ParserCSVTest extends DrupalWebTestCase { } } + /** + * Simple test of encoding conversion prior to parsing. + */ + protected function _testEncodingConversion() { + // Pull in the $control_result array. + include $this->absolutePath() . '/tests/feeds/encoding.csv.php'; + + $encodings = $this->getEncodings(); + foreach ($encodings as $encoding) { + $file = $this->absolutePath() . "/tests/feeds/encoding_{$encoding}.csv"; + $iterator = new ParserCSVIterator($file); + $parser = new ParserCSV(); + $parser->setDelimiter(','); + $parser->setEncoding($encoding); + $rows = $parser->parse($iterator); + $this->assertFalse($parser->lastLinePos(), format_string('CSV reports all lines parsed, with encoding: %encoding', array('%encoding' => $encoding))); + $this->assertEqual(md5(serialize($rows)), md5(serialize($control_result)), 'Converted and parsed result matches control result.'); + } + } + + /** + * Simple test of failed encoding conversion prior to parsing. + */ + protected function _testEncodingConversionFailure() { + // Pull in the $control_result array. + include $this->absolutePath() . '/tests/feeds/encoding.csv.php'; + + $encodings = $this->getEncodings(); + foreach ($encodings as $encoding) { + $file = $this->absolutePath() . "/tests/feeds/encoding_{$encoding}.csv"; + $iterator = new ParserCSVIterator($file); + $parser = new ParserCSV(); + $parser->setDelimiter(','); + // Attempt to read file as UTF-8. + $parser->setEncoding('UTF-8'); + try { + $rows = $parser->parse($iterator); + $this->fail('Incorrect conversion attempt throws exception.'); + } + catch (ParserCSVEncodingException $e) { + $this->assertNotNull($e->getMessage(), 'Incorrect conversion attempt throws exception.'); + } + } + } + /** * Test batching. */ @@ -100,4 +147,11 @@ class ParserCSVTest extends DrupalWebTestCase { 'tab' => "\t", ); } + + static function getEncodings() { + return array( + 'SJIS-win', + 'SJIS', + ); + } } diff --git a/www7/sites/all/modules/contrib/feeds/views/feeds.views.inc b/www7/sites/all/modules/contrib/feeds/views/feeds.views.inc index f754beb28..7c89b60c0 100644 --- a/www7/sites/all/modules/contrib/feeds/views/feeds.views.inc +++ b/www7/sites/all/modules/contrib/feeds/views/feeds.views.inc @@ -55,6 +55,23 @@ function feeds_views_data() { 'click sortable' => FALSE, ), ); + $data['feeds_source']['imported'] = array( + 'title' => t('Imported date'), + 'help' => t('The date the source was imported last.'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_date', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_date', + ), + 'sort' => array( + 'handler' => 'views_handler_sort_date', + ), + ); $data['feeds_source']['table']['join'] = array( 'node' => array( 'left_field' => 'nid', @@ -189,13 +206,31 @@ function feeds_views_data() { 'group' => 'Feeds log', 'base' => array( 'field' => array('flid'), - 'title' => 'Feeds log', - 'help' => 'Logs events during importing, clearing, expiry.', + 'title' => t('Feeds log'), + 'help' => t('Logs events during importing, clearing, expiry.'), + ), + ); + $data['feeds_log']['flid'] = array( + 'title' => t('Log id'), + 'help' => t('The id of the log message.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + 'numeric' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', ), ); $data['feeds_log']['id'] = array( - 'title' => 'Importer id', - 'help' => 'The id of an importer.', + 'title' => t('Importer id'), + 'help' => t('The id of an importer.'), 'field' => array( 'handler' => 'views_handler_field', 'click sortable' => TRUE, @@ -203,15 +238,15 @@ function feeds_views_data() { 'filter' => array( 'handler' => 'views_handler_filter_string', 'allow empty' => TRUE, - 'help' => 'Filter on an importer id.', + 'help' => t('Filter on an importer id.'), ), 'argument' => array( 'handler' => 'feeds_views_handler_argument_importer_id', - 'help' => 'Filter on an importer id.', + 'help' => t('Filter on an importer id.'), ), 'sort' => array( 'handler' => 'views_handler_sort', - 'help' => 'Sort by importer id.', + 'help' => t('Sort by importer id.'), ), 'relationship' => array( 'title' => t('Importer'), @@ -223,15 +258,15 @@ function feeds_views_data() { ); $data['feeds_log']['importer_name'] = array( 'real field' => 'id', - 'title' => 'Importer name', - 'help' => 'The human readable name of an importer.', + 'title' => t('Importer name'), + 'help' => t('The human readable name of an importer.'), 'field' => array( 'handler' => 'feeds_views_handler_field_importer_name', ), ); $data['feeds_log']['feed_nid'] = array( - 'title' => 'Feed node id', - 'help' => 'Contains the node id of a feed node if the feed\'s configuration is attached to a content type, otherwise contains 0.', + 'title' => t('Feed node id'), + 'help' => t('Contains the node id of a feed node if the feed\'s configuration is attached to a content type, otherwise contains 0.'), 'field' => array( 'handler' => 'views_handler_field_numeric', 'click sortable' => TRUE, @@ -239,17 +274,17 @@ function feeds_views_data() { 'filter' => array( 'handler' => 'views_handler_filter_numeric', 'allow empty' => TRUE, - 'help' => 'Filter on a Feeds Source\'s feed_nid field.', + 'help' => t('Filter on a Feeds Source\'s feed_nid field.'), ), 'argument' => array( 'handler' => 'views_handler_argument_numeric', 'numeric' => TRUE, 'validate type' => 'nid', - 'help' => 'Argument on a Feeds Source\'s feed_nid field.', + 'help' => t('Argument on a Feeds Source\'s feed_nid field.'), ), 'sort' => array( 'handler' => 'views_handler_sort', - 'help' => 'Sort Feeds Source\'s feed_nid field.', + 'help' => t('Sort Feeds Source\'s feed_nid field.'), ), 'relationship' => array( 'title' => t('Feed node'), @@ -272,6 +307,12 @@ function feeds_views_data() { 'filter' => array( 'handler' => 'views_handler_filter_date', ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), ); $data['feeds_log']['request_time'] = array( 'title' => t('Request time'), @@ -286,10 +327,33 @@ function feeds_views_data() { 'filter' => array( 'handler' => 'views_handler_filter_date', ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + $data['feeds_log']['type'] = array( + 'title' => t('Type'), + 'help' => t('Type of log message.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), ); $data['feeds_log']['message'] = array( - 'title' => 'Log message', - 'help' => 'The message logged by the event.', + 'title' => t('Log message'), + 'help' => t('The message logged by the event.'), 'field' => array( 'handler' => 'feeds_views_handler_field_log_message', 'click sortable' => FALSE, @@ -297,10 +361,16 @@ function feeds_views_data() { 'variables', ), ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), ); $data['feeds_log']['severity'] = array( - 'title' => 'Severity', - 'help' => 'The severity of the event logged.', + 'title' => t('Severity'), + 'help' => t('The severity of the event logged.'), 'field' => array( 'handler' => 'feeds_views_handler_field_severity', 'click sortable' => FALSE, @@ -308,7 +378,13 @@ function feeds_views_data() { 'filter' => array( 'handler' => 'feeds_views_handler_filter_severity', 'allow empty' => TRUE, - 'help' => 'Filter on the severity of a log message.', + 'help' => t('Filter on the severity of a log message.'), + ), + 'argument' => array( + 'handler' => 'views_handler_argument_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', ), ); $data['feeds_log']['table']['join'] = array( diff --git a/www7/sites/all/modules/contrib/feeds/views/feeds.views_default.inc b/www7/sites/all/modules/contrib/feeds/views/feeds.views_default.inc index fb208d3c2..d7710e22e 100644 --- a/www7/sites/all/modules/contrib/feeds/views/feeds.views_default.inc +++ b/www7/sites/all/modules/contrib/feeds/views/feeds.views_default.inc @@ -110,11 +110,11 @@ function feeds_views_default_views() { $handler->display->display_options['fields']['severity']['id'] = 'severity'; $handler->display->display_options['fields']['severity']['table'] = 'feeds_log'; $handler->display->display_options['fields']['severity']['field'] = 'severity'; - /* Sort criterion: Feeds log: Log time */ - $handler->display->display_options['sorts']['log_time']['id'] = 'log_time'; - $handler->display->display_options['sorts']['log_time']['table'] = 'feeds_log'; - $handler->display->display_options['sorts']['log_time']['field'] = 'log_time'; - $handler->display->display_options['sorts']['log_time']['order'] = 'DESC'; + /* Sort criterion: Feeds log: Log id */ + $handler->display->display_options['sorts']['flid']['id'] = 'flid'; + $handler->display->display_options['sorts']['flid']['table'] = 'feeds_log'; + $handler->display->display_options['sorts']['flid']['field'] = 'flid'; + $handler->display->display_options['sorts']['flid']['order'] = 'DESC'; /* Contextual filter: Feeds log: Importer id */ $handler->display->display_options['arguments']['id']['id'] = 'id'; $handler->display->display_options['arguments']['id']['table'] = 'feeds_log'; From ae8639fe78984c0ff1ff6d9378098615442161c7 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa Date: Sat, 27 Feb 2016 13:53:16 +0100 Subject: [PATCH 2/2] Update Core to 7.43 --- www7/includes/bootstrap.inc | 2 +- www7/includes/common.inc | 37 ++--- www7/includes/path.inc | 3 +- www7/includes/xmlrpcs.inc | 8 + www7/modules/aggregator/aggregator.info | 6 +- .../aggregator/tests/aggregator_test.info | 6 +- www7/modules/block/block.info | 6 +- www7/modules/block/tests/block_test.info | 6 +- .../block_test_theme/block_test_theme.info | 6 +- www7/modules/blog/blog.info | 6 +- www7/modules/book/book.info | 6 +- www7/modules/color/color.info | 6 +- www7/modules/comment/comment.info | 6 +- www7/modules/contact/contact.info | 6 +- www7/modules/contextual/contextual.info | 6 +- www7/modules/dashboard/dashboard.info | 6 +- www7/modules/dblog/dblog.info | 6 +- www7/modules/field/field.info | 6 +- .../field_sql_storage/field_sql_storage.info | 6 +- www7/modules/field/modules/list/list.info | 6 +- .../field/modules/list/tests/list_test.info | 6 +- www7/modules/field/modules/number/number.info | 6 +- .../field/modules/options/options.info | 6 +- www7/modules/field/modules/text/text.info | 6 +- www7/modules/field/tests/field_test.info | 6 +- www7/modules/field_ui/field_ui.info | 6 +- www7/modules/file/file.info | 6 +- www7/modules/file/file.module | 15 +- www7/modules/file/tests/file.test | 138 ++++++++++++++++++ www7/modules/file/tests/file_module_test.info | 6 +- www7/modules/filter/filter.info | 6 +- www7/modules/forum/forum.info | 6 +- www7/modules/help/help.info | 6 +- www7/modules/image/image.info | 6 +- .../image/tests/image_module_test.info | 6 +- www7/modules/locale/locale.info | 6 +- www7/modules/locale/tests/locale_test.info | 6 +- www7/modules/menu/menu.info | 6 +- www7/modules/node/node.info | 6 +- www7/modules/node/tests/node_access_test.info | 6 +- www7/modules/node/tests/node_test.info | 6 +- .../node/tests/node_test_exception.info | 6 +- www7/modules/openid/openid.info | 6 +- www7/modules/openid/tests/openid_test.info | 6 +- www7/modules/overlay/overlay.info | 6 +- www7/modules/path/path.info | 6 +- www7/modules/php/php.info | 6 +- www7/modules/poll/poll.info | 6 +- www7/modules/profile/profile.info | 6 +- www7/modules/rdf/rdf.info | 6 +- www7/modules/rdf/tests/rdf_test.info | 6 +- www7/modules/search/search.info | 6 +- .../search/tests/search_embedded_form.info | 6 +- .../search/tests/search_extra_type.info | 6 +- .../search/tests/search_node_tags.info | 6 +- www7/modules/shortcut/shortcut.info | 6 +- www7/modules/simpletest/simpletest.info | 6 +- .../simpletest/tests/actions_loop_test.info | 6 +- .../simpletest/tests/ajax_forms_test.info | 6 +- www7/modules/simpletest/tests/ajax_test.info | 6 +- www7/modules/simpletest/tests/batch_test.info | 6 +- .../modules/simpletest/tests/boot_test_1.info | 6 +- .../modules/simpletest/tests/boot_test_2.info | 6 +- www7/modules/simpletest/tests/common.test | 68 +++++++++ .../modules/simpletest/tests/common_test.info | 6 +- .../simpletest/tests/common_test.module | 9 ++ .../tests/common_test_cron_helper.info | 6 +- .../simpletest/tests/database_test.info | 6 +- .../drupal_autoload_test.info | 6 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- .../simpletest/tests/entity_cache_test.info | 6 +- .../tests/entity_cache_test_dependency.info | 6 +- .../tests/entity_crud_hook_test.info | 6 +- .../tests/entity_query_access_test.info | 6 +- www7/modules/simpletest/tests/error_test.info | 6 +- www7/modules/simpletest/tests/file_test.info | 6 +- .../modules/simpletest/tests/filter_test.info | 6 +- www7/modules/simpletest/tests/form_test.info | 6 +- www7/modules/simpletest/tests/image_test.info | 6 +- www7/modules/simpletest/tests/menu_test.info | 6 +- .../modules/simpletest/tests/module_test.info | 6 +- www7/modules/simpletest/tests/path_test.info | 6 +- .../tests/psr_0_test/psr_0_test.info | 6 +- .../tests/psr_4_test/psr_4_test.info | 6 +- .../simpletest/tests/requirements1_test.info | 6 +- .../simpletest/tests/requirements2_test.info | 6 +- .../simpletest/tests/session_test.info | 6 +- .../tests/system_dependencies_test.info | 6 +- ...atible_core_version_dependencies_test.info | 6 +- ...system_incompatible_core_version_test.info | 6 +- ...ible_module_version_dependencies_test.info | 6 +- ...stem_incompatible_module_version_test.info | 6 +- .../tests/system_project_namespace_test.info | 6 +- .../modules/simpletest/tests/system_test.info | 6 +- .../simpletest/tests/taxonomy_test.info | 6 +- www7/modules/simpletest/tests/theme_test.info | 6 +- .../themes/test_basetheme/test_basetheme.info | 6 +- .../themes/test_subtheme/test_subtheme.info | 6 +- .../tests/themes/test_theme/test_theme.info | 6 +- .../simpletest/tests/update_script_test.info | 6 +- .../simpletest/tests/update_test_1.info | 6 +- .../simpletest/tests/update_test_2.info | 6 +- .../simpletest/tests/update_test_3.info | 6 +- .../simpletest/tests/url_alter_test.info | 6 +- www7/modules/simpletest/tests/xmlrpc.test | 34 +++++ .../modules/simpletest/tests/xmlrpc_test.info | 6 +- www7/modules/statistics/statistics.info | 6 +- www7/modules/syslog/syslog.info | 6 +- www7/modules/system/system.admin.inc | 8 +- www7/modules/system/system.info | 6 +- www7/modules/system/system.js | 2 +- www7/modules/system/system.test | 16 ++ .../modules/system/tests/cron_queue_test.info | 6 +- .../system/tests/system_cron_test.info | 6 +- www7/modules/taxonomy/taxonomy.info | 6 +- www7/modules/toolbar/toolbar.info | 6 +- www7/modules/tracker/tracker.info | 6 +- .../translation/tests/translation_test.info | 6 +- www7/modules/translation/translation.info | 6 +- www7/modules/trigger/tests/trigger_test.info | 6 +- www7/modules/trigger/trigger.info | 6 +- .../modules/update/tests/aaa_update_test.info | 6 +- .../modules/update/tests/bbb_update_test.info | 6 +- .../modules/update/tests/ccc_update_test.info | 6 +- .../update_test_basetheme.info | 6 +- .../update_test_subtheme.info | 6 +- www7/modules/update/tests/update_test.info | 6 +- www7/modules/update/update.info | 6 +- www7/modules/user/tests/user_form_test.info | 6 +- www7/modules/user/user.info | 6 +- www7/modules/user/user.module | 16 +- www7/profiles/minimal/minimal.info | 6 +- www7/profiles/standard/standard.info | 6 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- www7/profiles/testing/testing.info | 6 +- www7/themes/bartik/bartik.info | 6 +- www7/themes/garland/garland.info | 6 +- www7/themes/seven/seven.info | 6 +- www7/themes/stark/stark.info | 6 +- 141 files changed, 708 insertions(+), 416 deletions(-) diff --git a/www7/includes/bootstrap.inc b/www7/includes/bootstrap.inc index af88ffe4e..0428bd362 100644 --- a/www7/includes/bootstrap.inc +++ b/www7/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.42'); +define('VERSION', '7.43'); /** * Core API compatibility. diff --git a/www7/includes/common.inc b/www7/includes/common.inc index 34fa9b962..c6303efad 100644 --- a/www7/includes/common.inc +++ b/www7/includes/common.inc @@ -688,6 +688,13 @@ function drupal_goto($path = '', array $options = array(), $http_response_code = $options['fragment'] = $destination['fragment']; } + // In some cases modules call drupal_goto(current_path()). We need to ensure + // that such a redirect is not to an external URL. + if ($path === current_path() && empty($options['external']) && url_is_external($path)) { + // Force url() to generate a non-external URL. + $options['external'] = FALSE; + } + drupal_alter('drupal_goto', $path, $options, $http_response_code); // The 'Location' HTTP header must be absolute. @@ -2220,20 +2227,8 @@ function url($path = NULL, array $options = array()) { 'prefix' => '' ); - // A duplicate of the code from url_is_external() to avoid needing another - // function call, since performance inside url() is critical. if (!isset($options['external'])) { - // Return an external link if $path contains an allowed absolute URL. Avoid - // calling drupal_strip_dangerous_protocols() if there is any slash (/), - // hash (#) or question_mark (?) before the colon (:) occurrence - if any - - // as this would clearly mean it is not a URL. If the path starts with 2 - // slashes then it is always considered an external URL without an explicit - // protocol part. - $colonpos = strpos($path, ':'); - $options['external'] = (strpos($path, '//') === 0) - || ($colonpos !== FALSE - && !preg_match('![/?#]!', substr($path, 0, $colonpos)) - && drupal_strip_dangerous_protocols($path) == $path); + $options['external'] = url_is_external($path); } // Preserve the original path before altering or aliasing. @@ -2353,12 +2348,18 @@ function url($path = NULL, array $options = array()) { */ function url_is_external($path) { $colonpos = strpos($path, ':'); - // Avoid calling drupal_strip_dangerous_protocols() if there is any slash (/), - // hash (#) or question_mark (?) before the colon (:) occurrence - if any - as - // this would clearly mean it is not a URL. If the path starts with 2 slashes - // then it is always considered an external URL without an explicit protocol - // part. + // Some browsers treat \ as / so normalize to forward slashes. + $path = str_replace('\\', '/', $path); + // If the path starts with 2 slashes then it is always considered an external + // URL without an explicit protocol part. return (strpos($path, '//') === 0) + // Leading control characters may be ignored or mishandled by browsers, so + // assume such a path may lead to an external location. The \p{C} character + // class matches all UTF-8 control, unassigned, and private characters. + || (preg_match('/^\p{C}/u', $path) !== 0) + // Avoid calling drupal_strip_dangerous_protocols() if there is any slash + // (/), hash (#) or question_mark (?) before the colon (:) occurrence - if + // any - as this would clearly mean it is not a URL. || ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path); diff --git a/www7/includes/path.inc b/www7/includes/path.inc index 2e3571114..6bd48d306 100644 --- a/www7/includes/path.inc +++ b/www7/includes/path.inc @@ -347,7 +347,8 @@ function drupal_match_path($path, $patterns) { * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. * * @return - * The current Drupal URL path. + * The current Drupal URL path. The path is untrusted user input and must be + * treated as such. * * @see request_path() */ diff --git a/www7/includes/xmlrpcs.inc b/www7/includes/xmlrpcs.inc index 8655c05b0..c334de159 100644 --- a/www7/includes/xmlrpcs.inc +++ b/www7/includes/xmlrpcs.inc @@ -264,6 +264,10 @@ function xmlrpc_server_call($xmlrpc_server, $methodname, $args) { */ function xmlrpc_server_multicall($methodcalls) { // See http://www.xmlrpc.com/discuss/msgReader$1208 + // To avoid multicall expansion attacks, limit the number of duplicate method + // calls allowed with a default of 1. Set to -1 for unlimited. + $duplicate_method_limit = variable_get('xmlrpc_multicall_duplicate_method_limit', 1); + $method_count = array(); $return = array(); $xmlrpc_server = xmlrpc_server_get(); foreach ($methodcalls as $call) { @@ -273,10 +277,14 @@ function xmlrpc_server_multicall($methodcalls) { $ok = FALSE; } $method = $call['methodName']; + $method_count[$method] = isset($method_count[$method]) ? $method_count[$method] + 1 : 1; $params = $call['params']; if ($method == 'system.multicall') { $result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.')); } + elseif ($duplicate_method_limit > 0 && $method_count[$method] > $duplicate_method_limit) { + $result = xmlrpc_error(-156579, t('Too many duplicate method calls in system.multicall.')); + } elseif ($ok) { $result = xmlrpc_server_call($xmlrpc_server, $method, $params); } diff --git a/www7/modules/aggregator/aggregator.info b/www7/modules/aggregator/aggregator.info index 3321f8fe0..c181417ab 100644 --- a/www7/modules/aggregator/aggregator.info +++ b/www7/modules/aggregator/aggregator.info @@ -7,8 +7,8 @@ files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/aggregator/tests/aggregator_test.info b/www7/modules/aggregator/tests/aggregator_test.info index 669a18e09..4579b77e6 100644 --- a/www7/modules/aggregator/tests/aggregator_test.info +++ b/www7/modules/aggregator/tests/aggregator_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/block/block.info b/www7/modules/block/block.info index 711ee5f3b..ad206a88a 100644 --- a/www7/modules/block/block.info +++ b/www7/modules/block/block.info @@ -6,8 +6,8 @@ core = 7.x files[] = block.test configure = admin/structure/block -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/block/tests/block_test.info b/www7/modules/block/tests/block_test.info index 2b211043e..1e7e4e92d 100644 --- a/www7/modules/block/tests/block_test.info +++ b/www7/modules/block/tests/block_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info b/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info index 57e5cf755..e49025ab4 100644 --- a/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info +++ b/www7/modules/block/tests/themes/block_test_theme/block_test_theme.info @@ -13,8 +13,8 @@ regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/blog/blog.info b/www7/modules/blog/blog.info index 156edfd71..13294f93d 100644 --- a/www7/modules/blog/blog.info +++ b/www7/modules/blog/blog.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = blog.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/book/book.info b/www7/modules/book/book.info index f0a8fc1ab..fa5b99ba8 100644 --- a/www7/modules/book/book.info +++ b/www7/modules/book/book.info @@ -7,8 +7,8 @@ files[] = book.test configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/color/color.info b/www7/modules/color/color.info index 77f214376..47ae442ed 100644 --- a/www7/modules/color/color.info +++ b/www7/modules/color/color.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = color.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/comment/comment.info b/www7/modules/comment/comment.info index 194e20355..fa206f98b 100644 --- a/www7/modules/comment/comment.info +++ b/www7/modules/comment/comment.info @@ -9,8 +9,8 @@ files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/contact/contact.info b/www7/modules/contact/contact.info index 7d946e818..f8d1a97de 100644 --- a/www7/modules/contact/contact.info +++ b/www7/modules/contact/contact.info @@ -6,8 +6,8 @@ core = 7.x files[] = contact.test configure = admin/structure/contact -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/contextual/contextual.info b/www7/modules/contextual/contextual.info index 0b9010b68..8e82e2d3c 100644 --- a/www7/modules/contextual/contextual.info +++ b/www7/modules/contextual/contextual.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = contextual.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/dashboard/dashboard.info b/www7/modules/dashboard/dashboard.info index 46062944c..55b454efe 100644 --- a/www7/modules/dashboard/dashboard.info +++ b/www7/modules/dashboard/dashboard.info @@ -7,8 +7,8 @@ files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/dblog/dblog.info b/www7/modules/dblog/dblog.info index d813e26ba..89de733fc 100644 --- a/www7/modules/dblog/dblog.info +++ b/www7/modules/dblog/dblog.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = dblog.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/field.info b/www7/modules/field/field.info index 06a388ef7..11015a757 100644 --- a/www7/modules/field/field.info +++ b/www7/modules/field/field.info @@ -11,8 +11,8 @@ dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/modules/field_sql_storage/field_sql_storage.info b/www7/modules/field/modules/field_sql_storage/field_sql_storage.info index a49c06d50..0828c20a7 100644 --- a/www7/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/www7/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,8 +7,8 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/modules/list/list.info b/www7/modules/field/modules/list/list.info index 420602417..7b0c9c239 100644 --- a/www7/modules/field/modules/list/list.info +++ b/www7/modules/field/modules/list/list.info @@ -7,8 +7,8 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/modules/list/tests/list_test.info b/www7/modules/field/modules/list/tests/list_test.info index 41c460d6a..9b3e4d6b1 100644 --- a/www7/modules/field/modules/list/tests/list_test.info +++ b/www7/modules/field/modules/list/tests/list_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/modules/number/number.info b/www7/modules/field/modules/number/number.info index 320a638e5..52553fd7a 100644 --- a/www7/modules/field/modules/number/number.info +++ b/www7/modules/field/modules/number/number.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/modules/options/options.info b/www7/modules/field/modules/options/options.info index 81c55adf7..f07ea90b6 100644 --- a/www7/modules/field/modules/options/options.info +++ b/www7/modules/field/modules/options/options.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/modules/text/text.info b/www7/modules/field/modules/text/text.info index a8b6aac7c..b3cf7cf0e 100644 --- a/www7/modules/field/modules/text/text.info +++ b/www7/modules/field/modules/text/text.info @@ -7,8 +7,8 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field/tests/field_test.info b/www7/modules/field/tests/field_test.info index 78957a8f0..8bfe17195 100644 --- a/www7/modules/field/tests/field_test.info +++ b/www7/modules/field/tests/field_test.info @@ -6,8 +6,8 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/field_ui/field_ui.info b/www7/modules/field_ui/field_ui.info index 77d4f1068..ef904d2e2 100644 --- a/www7/modules/field_ui/field_ui.info +++ b/www7/modules/field_ui/field_ui.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = field_ui.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/file/file.info b/www7/modules/file/file.info index bf8450cd4..aebd7f92e 100644 --- a/www7/modules/file/file.info +++ b/www7/modules/file/file.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = field files[] = tests/file.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/file/file.module b/www7/modules/file/file.module index fbf8b81ec..9e091af03 100644 --- a/www7/modules/file/file.module +++ b/www7/modules/file/file.module @@ -529,14 +529,19 @@ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) // publicly accessible, with no download restrictions; for security // reasons all other schemes must go through the file_download_access() // check. - if (in_array(file_uri_scheme($file->uri), variable_get('file_public_schema', array('public'))) || file_download_access($file->uri)) { - $fid = $file->fid; + if (!in_array(file_uri_scheme($file->uri), variable_get('file_public_schema', array('public'))) && !file_download_access($file->uri)) { + $force_default = TRUE; } - // If the current user doesn't have access, don't let the file be - // changed. - else { + // Temporary files that belong to other users should never be allowed. + // Since file ownership can't be determined for anonymous users, they + // are not allowed to reuse temporary files at all. + elseif ($file->status != FILE_STATUS_PERMANENT && (!$GLOBALS['user']->uid || $file->uid != $GLOBALS['user']->uid)) { $force_default = TRUE; } + // If all checks pass, allow the file to be changed. + else { + $fid = $file->fid; + } } } } diff --git a/www7/modules/file/tests/file.test b/www7/modules/file/tests/file.test index 80433954b..6d7cb4bc4 100644 --- a/www7/modules/file/tests/file.test +++ b/www7/modules/file/tests/file.test @@ -218,6 +218,30 @@ class FileFieldTestCase extends DrupalWebTestCase { $message = isset($message) ? $message : format_string('File %file is permanent.', array('%file' => $file->uri)); $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message); } + + /** + * Creates a temporary file, for a specific user. + * + * @param string $data + * A string containing the contents of the file. + * @param int $uid + * The user ID of the file owner. + * + * @return object + * A file object, or FALSE on error. + */ + function createTemporaryFile($data, $uid = NULL) { + $file = file_save_data($data, NULL, NULL); + + if ($file) { + $file->uid = isset($uid) ? $uid : $this->admin_user->uid; + // Change the file status to be temporary. + $file->status = NULL; + return file_save($file); + } + + return $file; + } } /** @@ -526,6 +550,120 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { } } + /** + * Tests exploiting the temporary file removal of another user using fid. + */ + function testTemporaryFileRemovalExploit() { + // Create a victim user. + $victim_user = $this->drupalCreateUser(); + + // Create an attacker user. + $attacker_user = $this->drupalCreateUser(array( + 'access content', + 'create page content', + 'edit any page content', + )); + + // Log in as the attacker user. + $this->drupalLogin($attacker_user); + + // Perform tests using the newly created users. + $this->doTestTemporaryFileRemovalExploit($victim_user->uid, $attacker_user->uid); + } + + /** + * Tests exploiting the temporary file removal for anonymous users using fid. + */ + public function testTemporaryFileRemovalExploitAnonymous() { + // Set up an anonymous victim user. + $victim_uid = 0; + + // Set up an anonymous attacker user. + $attacker_uid = 0; + + // Set up permissions for anonymous attacker user. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access content' => TRUE, + 'create page content' => TRUE, + 'edit any page content' => TRUE, + )); + + // In order to simulate being the anonymous attacker user, we need to log + // out here since setUp() has logged in the admin. + $this->drupalLogout(); + + // Perform tests using the newly set up users. + $this->doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid); + } + + /** + * Helper for testing exploiting the temporary file removal using fid. + * + * @param int $victim_uid + * The victim user ID. + * @param int $attacker_uid + * The attacker user ID. + */ + protected function doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid) { + // Use 'page' instead of 'article', so that the 'article' image field does + // not conflict with this test. If in the future the 'page' type gets its + // own default file or image field, this test can be made more robust by + // using a custom node type. + $type_name = 'page'; + $field_name = 'test_file_field'; + $this->createFileField($field_name, $type_name); + + $test_file = $this->getTestFile('text'); + foreach (array('nojs', 'js') as $type) { + // Create a temporary file owned by the anonymous victim user. This will be + // as if they had uploaded the file, but not saved the node they were + // editing or creating. + $victim_tmp_file = $this->createTemporaryFile('some text', $victim_uid); + $victim_tmp_file = file_load($victim_tmp_file->fid); + $this->assertTrue($victim_tmp_file->status != FILE_STATUS_PERMANENT, 'New file saved to disk is temporary.'); + $this->assertFalse(empty($victim_tmp_file->fid), 'New file has a fid'); + $this->assertEqual($victim_uid, $victim_tmp_file->uid, 'New file belongs to the victim user'); + + // Have attacker create a new node with a different uploaded file and + // ensure it got uploaded successfully. + // @todo Can we test AJAX? See https://www.drupal.org/node/2538260 + $edit = array( + 'title' => $type . '-title', + ); + + // Attach a file to a node. + $langcode = LANGUAGE_NONE; + $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($test_file->uri); + $this->drupalPost("node/add/$type_name", $edit, 'Save'); + $node = $this->drupalGetNodeByTitle($edit['title']); + $node_file = file_load($node->{$field_name}[$langcode][0]['fid']); + $this->assertFileExists($node_file, 'New file saved to disk on node creation.'); + $this->assertEqual($attacker_uid, $node_file->uid, 'New file belongs to the attacker.'); + + // Ensure the file can be downloaded. + $this->drupalGet(file_create_url($node_file->uri)); + $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.'); + + // "Click" the remove button (emulating either a nojs or js submission). + // In this POST request, the attacker "guesses" the fid of the victim's + // temporary file and uses that to remove this file. + $this->drupalGet('node/' . $node->nid . '/edit'); + switch ($type) { + case 'nojs': + $this->drupalPost(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), 'Remove'); + break; + case 'js': + $button = $this->xpath('//input[@type="submit" and @value="Remove"]'); + $this->drupalPostAJAX(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), array((string) $button[0]['name'] => (string) $button[0]['value'])); + break; + } + + // The victim's temporary file should not be removed by the attacker's + // POST request. + $this->assertFileExists($victim_tmp_file); + } + } + /** * Tests upload and remove buttons for multiple multi-valued File fields. */ diff --git a/www7/modules/file/tests/file_module_test.info b/www7/modules/file/tests/file_module_test.info index 2d24e263e..a9675621a 100644 --- a/www7/modules/file/tests/file_module_test.info +++ b/www7/modules/file/tests/file_module_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/filter/filter.info b/www7/modules/filter/filter.info index 3e18bda48..7584d6ed5 100644 --- a/www7/modules/filter/filter.info +++ b/www7/modules/filter/filter.info @@ -7,8 +7,8 @@ files[] = filter.test required = TRUE configure = admin/config/content/formats -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/forum/forum.info b/www7/modules/forum/forum.info index 2ac207909..2f10e3954 100644 --- a/www7/modules/forum/forum.info +++ b/www7/modules/forum/forum.info @@ -9,8 +9,8 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/help/help.info b/www7/modules/help/help.info index 84dd5ef0c..accbca800 100644 --- a/www7/modules/help/help.info +++ b/www7/modules/help/help.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/image/image.info b/www7/modules/image/image.info index 53d1146ae..b6bd514d5 100644 --- a/www7/modules/image/image.info +++ b/www7/modules/image/image.info @@ -7,8 +7,8 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/image/tests/image_module_test.info b/www7/modules/image/tests/image_module_test.info index 84ad7f568..85a47e218 100644 --- a/www7/modules/image/tests/image_module_test.info +++ b/www7/modules/image/tests/image_module_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/locale/locale.info b/www7/modules/locale/locale.info index dde39f34c..b2208f7ae 100644 --- a/www7/modules/locale/locale.info +++ b/www7/modules/locale/locale.info @@ -6,8 +6,8 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/locale/tests/locale_test.info b/www7/modules/locale/tests/locale_test.info index bc4018ef0..23493fe30 100644 --- a/www7/modules/locale/tests/locale_test.info +++ b/www7/modules/locale/tests/locale_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/menu/menu.info b/www7/modules/menu/menu.info index bcdecf32a..b135e395d 100644 --- a/www7/modules/menu/menu.info +++ b/www7/modules/menu/menu.info @@ -6,8 +6,8 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/node/node.info b/www7/modules/node/node.info index 2bd6d48f6..58f56696c 100644 --- a/www7/modules/node/node.info +++ b/www7/modules/node/node.info @@ -9,8 +9,8 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/node/tests/node_access_test.info b/www7/modules/node/tests/node_access_test.info index 6cc8a0d6d..faa0c5b3d 100644 --- a/www7/modules/node/tests/node_access_test.info +++ b/www7/modules/node/tests/node_access_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/node/tests/node_test.info b/www7/modules/node/tests/node_test.info index 281afb84c..fe528f7a6 100644 --- a/www7/modules/node/tests/node_test.info +++ b/www7/modules/node/tests/node_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/node/tests/node_test_exception.info b/www7/modules/node/tests/node_test_exception.info index 0cf4a67b6..59941f03d 100644 --- a/www7/modules/node/tests/node_test_exception.info +++ b/www7/modules/node/tests/node_test_exception.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/openid/openid.info b/www7/modules/openid/openid.info index d1d72e738..8f2659254 100644 --- a/www7/modules/openid/openid.info +++ b/www7/modules/openid/openid.info @@ -5,8 +5,8 @@ package = Core core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/openid/tests/openid_test.info b/www7/modules/openid/tests/openid_test.info index d6de7b3f1..c123b9520 100644 --- a/www7/modules/openid/tests/openid_test.info +++ b/www7/modules/openid/tests/openid_test.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/overlay/overlay.info b/www7/modules/overlay/overlay.info index 395fdb189..d415a0d54 100644 --- a/www7/modules/overlay/overlay.info +++ b/www7/modules/overlay/overlay.info @@ -4,8 +4,8 @@ package = Core version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/path/path.info b/www7/modules/path/path.info index b51d3028c..7ef6ad34b 100644 --- a/www7/modules/path/path.info +++ b/www7/modules/path/path.info @@ -6,8 +6,8 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/php/php.info b/www7/modules/php/php.info index 78ec54a19..fb788c624 100644 --- a/www7/modules/php/php.info +++ b/www7/modules/php/php.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/poll/poll.info b/www7/modules/poll/poll.info index 8502b2a9e..8d9d91afd 100644 --- a/www7/modules/poll/poll.info +++ b/www7/modules/poll/poll.info @@ -6,8 +6,8 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/profile/profile.info b/www7/modules/profile/profile.info index c1d27ab6e..db6e7b1b9 100644 --- a/www7/modules/profile/profile.info +++ b/www7/modules/profile/profile.info @@ -11,8 +11,8 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/rdf/rdf.info b/www7/modules/rdf/rdf.info index 1c39cdb2f..a58be4632 100644 --- a/www7/modules/rdf/rdf.info +++ b/www7/modules/rdf/rdf.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/rdf/tests/rdf_test.info b/www7/modules/rdf/tests/rdf_test.info index e21368606..836ddf636 100644 --- a/www7/modules/rdf/tests/rdf_test.info +++ b/www7/modules/rdf/tests/rdf_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/search/search.info b/www7/modules/search/search.info index 787389b3a..33fed95d3 100644 --- a/www7/modules/search/search.info +++ b/www7/modules/search/search.info @@ -8,8 +8,8 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/search/tests/search_embedded_form.info b/www7/modules/search/tests/search_embedded_form.info index e4960dc94..d8b237ffe 100644 --- a/www7/modules/search/tests/search_embedded_form.info +++ b/www7/modules/search/tests/search_embedded_form.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/search/tests/search_extra_type.info b/www7/modules/search/tests/search_extra_type.info index 34a547d4d..306cc8cbb 100644 --- a/www7/modules/search/tests/search_extra_type.info +++ b/www7/modules/search/tests/search_extra_type.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/search/tests/search_node_tags.info b/www7/modules/search/tests/search_node_tags.info index 1668e8726..78b1f3974 100644 --- a/www7/modules/search/tests/search_node_tags.info +++ b/www7/modules/search/tests/search_node_tags.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/shortcut/shortcut.info b/www7/modules/shortcut/shortcut.info index b42db9cbc..d6aa11c01 100644 --- a/www7/modules/shortcut/shortcut.info +++ b/www7/modules/shortcut/shortcut.info @@ -6,8 +6,8 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/simpletest.info b/www7/modules/simpletest/simpletest.info index d037b8eeb..1063ed66b 100644 --- a/www7/modules/simpletest/simpletest.info +++ b/www7/modules/simpletest/simpletest.info @@ -57,8 +57,8 @@ files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/actions_loop_test.info b/www7/modules/simpletest/tests/actions_loop_test.info index a29d18a9e..8054ae3e7 100644 --- a/www7/modules/simpletest/tests/actions_loop_test.info +++ b/www7/modules/simpletest/tests/actions_loop_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/ajax_forms_test.info b/www7/modules/simpletest/tests/ajax_forms_test.info index 124fa1332..9a036b552 100644 --- a/www7/modules/simpletest/tests/ajax_forms_test.info +++ b/www7/modules/simpletest/tests/ajax_forms_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/ajax_test.info b/www7/modules/simpletest/tests/ajax_test.info index c94233f69..a987a3fa6 100644 --- a/www7/modules/simpletest/tests/ajax_test.info +++ b/www7/modules/simpletest/tests/ajax_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/batch_test.info b/www7/modules/simpletest/tests/batch_test.info index ca6096e5e..04db1c8c3 100644 --- a/www7/modules/simpletest/tests/batch_test.info +++ b/www7/modules/simpletest/tests/batch_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/boot_test_1.info b/www7/modules/simpletest/tests/boot_test_1.info index e549d125c..16c7bc486 100644 --- a/www7/modules/simpletest/tests/boot_test_1.info +++ b/www7/modules/simpletest/tests/boot_test_1.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/boot_test_2.info b/www7/modules/simpletest/tests/boot_test_2.info index 8004acc73..64fb360a5 100644 --- a/www7/modules/simpletest/tests/boot_test_2.info +++ b/www7/modules/simpletest/tests/boot_test_2.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/common.test b/www7/modules/simpletest/tests/common.test index bf8557619..92aefe48f 100644 --- a/www7/modules/simpletest/tests/common.test +++ b/www7/modules/simpletest/tests/common.test @@ -372,6 +372,65 @@ class CommonURLUnitTest extends DrupalWebTestCase { } } +/** + * Tests url_is_external(). + */ +class UrlIsExternalUnitTest extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'External URL checking', + 'description' => 'Performs tests on url_is_external().', + 'group' => 'System', + ); + } + + /** + * Tests if each URL is external or not. + */ + function testUrlIsExternal() { + foreach ($this->examples() as $path => $expected) { + $this->assertIdentical(url_is_external($path), $expected, $path); + } + } + + /** + * Provides data for testUrlIsExternal(). + * + * @return array + * An array of test data, keyed by a path, with the expected value where + * TRUE is external, and FALSE is not external. + */ + protected function examples() { + return array( + // Simple external URLs. + 'http://example.com' => TRUE, + 'https://example.com' => TRUE, + 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE, + '//drupal.org' => TRUE, + // Some browsers ignore or strip leading control characters. + "\x00//www.example.com" => TRUE, + "\x08//www.example.com" => TRUE, + "\x1F//www.example.com" => TRUE, + "\n//www.example.com" => TRUE, + // JSON supports decoding directly from UTF-8 code points. + json_decode('"\u00AD"') . "//www.example.com" => TRUE, + json_decode('"\u200E"') . "//www.example.com" => TRUE, + json_decode('"\uE0020"') . "//www.example.com" => TRUE, + json_decode('"\uE000"') . "//www.example.com" => TRUE, + // Backslashes should be normalized to forward. + '\\\\example.com' => TRUE, + // Local URLs. + 'node' => FALSE, + '/system/ajax' => FALSE, + '?q=foo:bar' => FALSE, + 'node/edit:me' => FALSE, + '/drupal.org' => FALSE, + '' => FALSE, + ); + } +} + /** * Tests for check_plain(), filter_xss(), format_string(), and check_url(). */ @@ -1256,6 +1315,15 @@ class DrupalGotoTest extends DrupalWebTestCase { $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); + // Test that calling drupal_goto() on the current path is not dangerous. + variable_set('common_test_redirect_current_path', TRUE); + $this->drupalGet('', array('query' => array('q' => 'http://www.example.com/'))); + $headers = $this->drupalGetHeaders(TRUE); + list(, $status) = explode(' ', $headers[0][':status'], 3); + $this->assertEqual($status, 302, 'Expected response code was sent.'); + $this->assertNotEqual($this->getUrl(), 'http://www.example.com/', 'Drupal goto did not redirect to external URL.'); + $this->assertTrue(strpos($this->getUrl(), url('', array('absolute' => TRUE))) === 0, 'Drupal redirected to itself.'); + variable_del('common_test_redirect_current_path'); // Test that drupal_goto() respects ?destination=xxx. Use an complicated URL // to test that the path is encoded and decoded properly. $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123'; diff --git a/www7/modules/simpletest/tests/common_test.info b/www7/modules/simpletest/tests/common_test.info index 989a95897..e484a8c24 100644 --- a/www7/modules/simpletest/tests/common_test.info +++ b/www7/modules/simpletest/tests/common_test.info @@ -7,8 +7,8 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/common_test.module b/www7/modules/simpletest/tests/common_test.module index 674a49446..2eb8cd5d2 100644 --- a/www7/modules/simpletest/tests/common_test.module +++ b/www7/modules/simpletest/tests/common_test.module @@ -92,6 +92,15 @@ function common_test_drupal_goto_alter(&$path, &$options, &$http_response_code) } } +/** + * Implements hook_init(). + */ +function common_test_init() { + if (variable_get('common_test_redirect_current_path', FALSE)) { + drupal_goto(current_path()); + } +} + /** * Print destination query parameter. */ diff --git a/www7/modules/simpletest/tests/common_test_cron_helper.info b/www7/modules/simpletest/tests/common_test_cron_helper.info index 3f641479a..a11c2fc5e 100644 --- a/www7/modules/simpletest/tests/common_test_cron_helper.info +++ b/www7/modules/simpletest/tests/common_test_cron_helper.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/database_test.info b/www7/modules/simpletest/tests/database_test.info index a049a7d4c..c8c2aa80a 100644 --- a/www7/modules/simpletest/tests/database_test.info +++ b/www7/modules/simpletest/tests/database_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info index e398d8c70..520e8d597 100644 --- a/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info +++ b/www7/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info @@ -7,8 +7,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index a61cfb1b8..613d911a5 100644 --- a/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/www7/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index da0731759..e417f4bf6 100644 --- a/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/www7/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/entity_cache_test.info b/www7/modules/simpletest/tests/entity_cache_test.info index 96eeba3a4..ebb4b089c 100644 --- a/www7/modules/simpletest/tests/entity_cache_test.info +++ b/www7/modules/simpletest/tests/entity_cache_test.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/entity_cache_test_dependency.info b/www7/modules/simpletest/tests/entity_cache_test_dependency.info index e42de23d6..835e4ca62 100644 --- a/www7/modules/simpletest/tests/entity_cache_test_dependency.info +++ b/www7/modules/simpletest/tests/entity_cache_test_dependency.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/entity_crud_hook_test.info b/www7/modules/simpletest/tests/entity_crud_hook_test.info index c9766a1af..d7969f185 100644 --- a/www7/modules/simpletest/tests/entity_crud_hook_test.info +++ b/www7/modules/simpletest/tests/entity_crud_hook_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/entity_query_access_test.info b/www7/modules/simpletest/tests/entity_query_access_test.info index 6c770caf9..03c2dcf1d 100644 --- a/www7/modules/simpletest/tests/entity_query_access_test.info +++ b/www7/modules/simpletest/tests/entity_query_access_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/error_test.info b/www7/modules/simpletest/tests/error_test.info index 6622fcef7..08ff6765c 100644 --- a/www7/modules/simpletest/tests/error_test.info +++ b/www7/modules/simpletest/tests/error_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/file_test.info b/www7/modules/simpletest/tests/file_test.info index 53f5591c5..4f6598ab6 100644 --- a/www7/modules/simpletest/tests/file_test.info +++ b/www7/modules/simpletest/tests/file_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/filter_test.info b/www7/modules/simpletest/tests/filter_test.info index ee5164e02..b57f9795d 100644 --- a/www7/modules/simpletest/tests/filter_test.info +++ b/www7/modules/simpletest/tests/filter_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/form_test.info b/www7/modules/simpletest/tests/form_test.info index 34337e52d..21f621b45 100644 --- a/www7/modules/simpletest/tests/form_test.info +++ b/www7/modules/simpletest/tests/form_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/image_test.info b/www7/modules/simpletest/tests/image_test.info index f06e90a27..6ad1785fb 100644 --- a/www7/modules/simpletest/tests/image_test.info +++ b/www7/modules/simpletest/tests/image_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/menu_test.info b/www7/modules/simpletest/tests/menu_test.info index b630707fe..88007a1e4 100644 --- a/www7/modules/simpletest/tests/menu_test.info +++ b/www7/modules/simpletest/tests/menu_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/module_test.info b/www7/modules/simpletest/tests/module_test.info index d821d9380..08c2c7f1f 100644 --- a/www7/modules/simpletest/tests/module_test.info +++ b/www7/modules/simpletest/tests/module_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/path_test.info b/www7/modules/simpletest/tests/path_test.info index d660a4737..7c3d6c931 100644 --- a/www7/modules/simpletest/tests/path_test.info +++ b/www7/modules/simpletest/tests/path_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info b/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info index b9553696c..e17791ac0 100644 --- a/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info +++ b/www7/modules/simpletest/tests/psr_0_test/psr_0_test.info @@ -5,8 +5,8 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info b/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info index f61246846..520049612 100644 --- a/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info +++ b/www7/modules/simpletest/tests/psr_4_test/psr_4_test.info @@ -5,8 +5,8 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/requirements1_test.info b/www7/modules/simpletest/tests/requirements1_test.info index d7d6284ad..95ac379bf 100644 --- a/www7/modules/simpletest/tests/requirements1_test.info +++ b/www7/modules/simpletest/tests/requirements1_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/requirements2_test.info b/www7/modules/simpletest/tests/requirements2_test.info index 19b86f560..ab6015ab1 100644 --- a/www7/modules/simpletest/tests/requirements2_test.info +++ b/www7/modules/simpletest/tests/requirements2_test.info @@ -7,8 +7,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/session_test.info b/www7/modules/simpletest/tests/session_test.info index e06f6903b..fe8594ac7 100644 --- a/www7/modules/simpletest/tests/session_test.info +++ b/www7/modules/simpletest/tests/session_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_dependencies_test.info b/www7/modules/simpletest/tests/system_dependencies_test.info index 12087764e..6bc9979f2 100644 --- a/www7/modules/simpletest/tests/system_dependencies_test.info +++ b/www7/modules/simpletest/tests/system_dependencies_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = _missing_dependency -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info index c1f6d5277..0cdbda706 100644 --- a/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_incompatible_core_version_test.info b/www7/modules/simpletest/tests/system_incompatible_core_version_test.info index ee3c97242..7c36e4965 100644 --- a/www7/modules/simpletest/tests/system_incompatible_core_version_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_core_version_test.info @@ -5,8 +5,8 @@ version = VERSION core = 5.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info index bd9bd820f..5c7c43ab6 100644 --- a/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info @@ -7,8 +7,8 @@ hidden = TRUE ; system_incompatible_module_version_test declares version 1.0 dependencies[] = system_incompatible_module_version_test (>2.0) -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_incompatible_module_version_test.info b/www7/modules/simpletest/tests/system_incompatible_module_version_test.info index ec3d0c47c..0bdef7cd0 100644 --- a/www7/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/www7/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,8 +5,8 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_project_namespace_test.info b/www7/modules/simpletest/tests/system_project_namespace_test.info index 663a1b17d..2bfeb881c 100644 --- a/www7/modules/simpletest/tests/system_project_namespace_test.info +++ b/www7/modules/simpletest/tests/system_project_namespace_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = drupal:filter -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/system_test.info b/www7/modules/simpletest/tests/system_test.info index a89e1fcbc..3c7da63b1 100644 --- a/www7/modules/simpletest/tests/system_test.info +++ b/www7/modules/simpletest/tests/system_test.info @@ -6,8 +6,8 @@ core = 7.x files[] = system_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/taxonomy_test.info b/www7/modules/simpletest/tests/taxonomy_test.info index fa48c188d..1e2d72218 100644 --- a/www7/modules/simpletest/tests/taxonomy_test.info +++ b/www7/modules/simpletest/tests/taxonomy_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/theme_test.info b/www7/modules/simpletest/tests/theme_test.info index e94630efb..adb04d761 100644 --- a/www7/modules/simpletest/tests/theme_test.info +++ b/www7/modules/simpletest/tests/theme_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index 178eb02bd..b82f86a42 100644 --- a/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/www7/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,8 +6,8 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index 414ec5e2a..5059593b4 100644 --- a/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/www7/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,8 +6,8 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/themes/test_theme/test_theme.info b/www7/modules/simpletest/tests/themes/test_theme/test_theme.info index c1aee297d..98050c406 100644 --- a/www7/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/www7/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,8 +17,8 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/update_script_test.info b/www7/modules/simpletest/tests/update_script_test.info index 15de6d50b..fb5d12bbf 100644 --- a/www7/modules/simpletest/tests/update_script_test.info +++ b/www7/modules/simpletest/tests/update_script_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/update_test_1.info b/www7/modules/simpletest/tests/update_test_1.info index ebe765c21..4dddc4934 100644 --- a/www7/modules/simpletest/tests/update_test_1.info +++ b/www7/modules/simpletest/tests/update_test_1.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/update_test_2.info b/www7/modules/simpletest/tests/update_test_2.info index ebe765c21..4dddc4934 100644 --- a/www7/modules/simpletest/tests/update_test_2.info +++ b/www7/modules/simpletest/tests/update_test_2.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/update_test_3.info b/www7/modules/simpletest/tests/update_test_3.info index ebe765c21..4dddc4934 100644 --- a/www7/modules/simpletest/tests/update_test_3.info +++ b/www7/modules/simpletest/tests/update_test_3.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/url_alter_test.info b/www7/modules/simpletest/tests/url_alter_test.info index b5e7f28fa..641b673f2 100644 --- a/www7/modules/simpletest/tests/url_alter_test.info +++ b/www7/modules/simpletest/tests/url_alter_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/simpletest/tests/xmlrpc.test b/www7/modules/simpletest/tests/xmlrpc.test index 1a9ef2349..bb74f059b 100644 --- a/www7/modules/simpletest/tests/xmlrpc.test +++ b/www7/modules/simpletest/tests/xmlrpc.test @@ -246,4 +246,38 @@ class XMLRPCMessagesTestCase extends DrupalWebTestCase { $this->assertEqual($removed, 'system.methodSignature', 'Hiding builting system.methodSignature with hook_xmlrpc_alter works'); } + /** + * Test limits on system.multicall that can prevent brute-force attacks. + */ + function testMulticallLimit() { + $url = url(NULL, array('absolute' => TRUE)) . 'xmlrpc.php'; + $multicall_args = array(); + $num_method_calls = 10; + for ($i = 0; $i < $num_method_calls; $i++) { + $struct = array('i' => $i); + $multicall_args[] = array('methodName' => 'validator1.echoStructTest', 'params' => array($struct)); + } + // Test limits of 1, 5, 9, 13. + for ($limit = 1; $limit < $num_method_calls + 4; $limit += 4) { + variable_set('xmlrpc_multicall_duplicate_method_limit', $limit); + $results = xmlrpc($url, array('system.multicall' => array($multicall_args))); + $this->assertEqual($num_method_calls, count($results)); + for ($i = 0; $i < min($limit, $num_method_calls); $i++) { + $x = array_shift($results); + $this->assertTrue(empty($x->is_error), "Result $i is not an error"); + $this->assertEqual($multicall_args[$i]['params'][0], $x); + } + for (; $i < $num_method_calls; $i++) { + $x = array_shift($results); + $this->assertFalse(empty($x->is_error), "Result $i is an error"); + $this->assertEqual(-156579, $x->code); + } + } + variable_set('xmlrpc_multicall_duplicate_method_limit', -1); + $results = xmlrpc($url, array('system.multicall' => array($multicall_args))); + $this->assertEqual($num_method_calls, count($results)); + foreach ($results as $i => $x) { + $this->assertTrue(empty($x->is_error), "Result $i is not an error"); + } + } } diff --git a/www7/modules/simpletest/tests/xmlrpc_test.info b/www7/modules/simpletest/tests/xmlrpc_test.info index f6cd6fa85..857b1a512 100644 --- a/www7/modules/simpletest/tests/xmlrpc_test.info +++ b/www7/modules/simpletest/tests/xmlrpc_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/statistics/statistics.info b/www7/modules/statistics/statistics.info index abb4d2d88..25a6ecc20 100644 --- a/www7/modules/statistics/statistics.info +++ b/www7/modules/statistics/statistics.info @@ -6,8 +6,8 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/syslog/syslog.info b/www7/modules/syslog/syslog.info index 1c0ccc736..39afcb724 100644 --- a/www7/modules/syslog/syslog.info +++ b/www7/modules/syslog/syslog.info @@ -6,8 +6,8 @@ core = 7.x files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/system/system.admin.inc b/www7/modules/system/system.admin.inc index 0f525c6cf..16c40d4d4 100644 --- a/www7/modules/system/system.admin.inc +++ b/www7/modules/system/system.admin.inc @@ -2202,6 +2202,11 @@ function system_add_date_format_type_form_submit($form, &$form_state) { * Return the date for a given format string via Ajax. */ function system_date_time_lookup() { + // This callback is protected with a CSRF token because user input from the + // query string is reflected in the output. + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'admin/config/regional/date-time/formats/lookup')) { + return MENU_ACCESS_DENIED; + } $result = format_date(REQUEST_TIME, 'custom', $_GET['format']); drupal_json_output($result); } @@ -2875,13 +2880,14 @@ function system_date_time_formats() { * Allow users to add additional date formats. */ function system_configure_date_formats_form($form, &$form_state, $dfid = 0) { + $ajax_path = 'admin/config/regional/date-time/formats/lookup'; $js_settings = array( 'type' => 'setting', 'data' => array( 'dateTime' => array( 'date-format' => array( 'text' => t('Displayed as'), - 'lookup' => url('admin/config/regional/date-time/formats/lookup'), + 'lookup' => url($ajax_path, array('query' => array('token' => drupal_get_token($ajax_path)))), ), ), ), diff --git a/www7/modules/system/system.info b/www7/modules/system/system.info index 6db2732e1..850f7cee5 100644 --- a/www7/modules/system/system.info +++ b/www7/modules/system/system.info @@ -12,8 +12,8 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/system/system.js b/www7/modules/system/system.js index 910fb5d3d..c0e76d38e 100644 --- a/www7/modules/system/system.js +++ b/www7/modules/system/system.js @@ -105,7 +105,7 @@ Drupal.behaviors.dateTime = { // Attach keyup handler to custom format inputs. $('input' + source, context).once('date-time').keyup(function () { var input = $(this); - var url = fieldSettings.lookup + (/\?q=/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val()); + var url = fieldSettings.lookup + (/\?/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val()); $.getJSON(url, function (data) { $(suffix).empty().append(' ' + fieldSettings.text + ': ' + data + ''); }); diff --git a/www7/modules/system/system.test b/www7/modules/system/system.test index bc764dde5..95b43538b 100644 --- a/www7/modules/system/system.test +++ b/www7/modules/system/system.test @@ -1350,7 +1350,23 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.'); $this->assertText(t('Custom date format updated.'), 'Custom date format successfully updated.'); + // Check that ajax callback is protected by CSRF token. + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('format' => 'Y m d'))); + $this->assertResponse(403, 'Access denied with no token'); + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => 'invalid', 'format' => 'Y m d'))); + $this->assertResponse(403, 'Access denied with invalid token'); + $this->drupalGet('admin/config/regional/date-time/formats'); + $this->clickLink(t('edit')); + $settings = $this->drupalGetSettings(); + $lookup_url = $settings['dateTime']['date-format']['lookup']; + preg_match('/token=([^&]+)/', $lookup_url, $matches); + $this->assertFalse(empty($matches[1]), 'Found token value'); + $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => $matches[1], 'format' => 'Y m d'))); + $this->assertResponse(200, 'Access allowed with valid token'); + $this->assertText(format_date(time(), 'custom', 'Y m d')); + // Delete custom date format. + $this->drupalGet('admin/config/regional/date-time/formats'); $this->clickLink(t('delete')); $this->drupalPost($this->getUrl(), array(), t('Remove')); $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.'); diff --git a/www7/modules/system/tests/cron_queue_test.info b/www7/modules/system/tests/cron_queue_test.info index 7d2d32e6e..0042f8541 100644 --- a/www7/modules/system/tests/cron_queue_test.info +++ b/www7/modules/system/tests/cron_queue_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/system/tests/system_cron_test.info b/www7/modules/system/tests/system_cron_test.info index fdfe81d22..8a82231a1 100644 --- a/www7/modules/system/tests/system_cron_test.info +++ b/www7/modules/system/tests/system_cron_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/taxonomy/taxonomy.info b/www7/modules/taxonomy/taxonomy.info index 3bc80c0ff..725335a1e 100644 --- a/www7/modules/taxonomy/taxonomy.info +++ b/www7/modules/taxonomy/taxonomy.info @@ -8,8 +8,8 @@ files[] = taxonomy.module files[] = taxonomy.test configure = admin/structure/taxonomy -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/toolbar/toolbar.info b/www7/modules/toolbar/toolbar.info index 5260d55f0..7edd8eda9 100644 --- a/www7/modules/toolbar/toolbar.info +++ b/www7/modules/toolbar/toolbar.info @@ -4,8 +4,8 @@ core = 7.x package = Core version = VERSION -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/tracker/tracker.info b/www7/modules/tracker/tracker.info index 077de4127..2917302b8 100644 --- a/www7/modules/tracker/tracker.info +++ b/www7/modules/tracker/tracker.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/translation/tests/translation_test.info b/www7/modules/translation/tests/translation_test.info index 89382cc74..692b4ad89 100644 --- a/www7/modules/translation/tests/translation_test.info +++ b/www7/modules/translation/tests/translation_test.info @@ -5,8 +5,8 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/translation/translation.info b/www7/modules/translation/translation.info index e37aa2e18..2f38d2ba0 100644 --- a/www7/modules/translation/translation.info +++ b/www7/modules/translation/translation.info @@ -6,8 +6,8 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/trigger/tests/trigger_test.info b/www7/modules/trigger/tests/trigger_test.info index 514532b7a..0fe583a39 100644 --- a/www7/modules/trigger/tests/trigger_test.info +++ b/www7/modules/trigger/tests/trigger_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/trigger/trigger.info b/www7/modules/trigger/trigger.info index 1240601c6..85175379b 100644 --- a/www7/modules/trigger/trigger.info +++ b/www7/modules/trigger/trigger.info @@ -6,8 +6,8 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/tests/aaa_update_test.info b/www7/modules/update/tests/aaa_update_test.info index 79ead47d3..23756e025 100644 --- a/www7/modules/update/tests/aaa_update_test.info +++ b/www7/modules/update/tests/aaa_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/tests/bbb_update_test.info b/www7/modules/update/tests/bbb_update_test.info index 049374b01..3fa1422dc 100644 --- a/www7/modules/update/tests/bbb_update_test.info +++ b/www7/modules/update/tests/bbb_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/tests/ccc_update_test.info b/www7/modules/update/tests/ccc_update_test.info index ed756bf43..50327549a 100644 --- a/www7/modules/update/tests/ccc_update_test.info +++ b/www7/modules/update/tests/ccc_update_test.info @@ -4,8 +4,8 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 50297e449..427e9e335 100644 --- a/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/www7/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,8 +3,8 @@ description = Test theme which acts as a base theme for other test subthemes. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index 2a5dcc269..3e6b0ba38 100644 --- a/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/www7/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,8 +4,8 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/tests/update_test.info b/www7/modules/update/tests/update_test.info index 4c45d3ee5..1c868d133 100644 --- a/www7/modules/update/tests/update_test.info +++ b/www7/modules/update/tests/update_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/update/update.info b/www7/modules/update/update.info index 19184787b..f55538f7d 100644 --- a/www7/modules/update/update.info +++ b/www7/modules/update/update.info @@ -6,8 +6,8 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/user/tests/user_form_test.info b/www7/modules/user/tests/user_form_test.info index 63857d83d..3f4862d84 100644 --- a/www7/modules/user/tests/user_form_test.info +++ b/www7/modules/user/tests/user_form_test.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/user/user.info b/www7/modules/user/user.info index fff7f0e11..03be9cd57 100644 --- a/www7/modules/user/user.info +++ b/www7/modules/user/user.info @@ -9,8 +9,8 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/modules/user/user.module b/www7/modules/user/user.module index c33aa0982..d38de69b1 100644 --- a/www7/modules/user/user.module +++ b/www7/modules/user/user.module @@ -1308,10 +1308,12 @@ function user_user_presave(&$edit, $account, $category) { elseif (!empty($edit['picture_delete'])) { $edit['picture'] = NULL; } - // Prepare user roles. - if (isset($edit['roles'])) { - $edit['roles'] = array_filter($edit['roles']); - } + } + + // Filter out roles with empty values to avoid granting extra roles when + // processing custom form submissions. + if (isset($edit['roles'])) { + $edit['roles'] = array_filter($edit['roles']); } // Move account cancellation information into $user->data. @@ -2227,7 +2229,11 @@ function user_login_final_validate($form, &$form_state) { } } else { - form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name'])))))); + // Use $form_state['input']['name'] here to guarantee that we send + // exactly what the user typed in. $form_state['values']['name'] may have + // been modified by validation handlers that ran earlier than this one. + $query = isset($form_state['input']['name']) ? array('name' => $form_state['input']['name']) : array(); + form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password', array('query' => $query))))); watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name'])); } } diff --git a/www7/profiles/minimal/minimal.info b/www7/profiles/minimal/minimal.info index a23c13692..f52b0f963 100644 --- a/www7/profiles/minimal/minimal.info +++ b/www7/profiles/minimal/minimal.info @@ -5,8 +5,8 @@ core = 7.x dependencies[] = block dependencies[] = dblog -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/profiles/standard/standard.info b/www7/profiles/standard/standard.info index ddf241690..9840da320 100644 --- a/www7/profiles/standard/standard.info +++ b/www7/profiles/standard/standard.info @@ -24,8 +24,8 @@ dependencies[] = field_ui dependencies[] = file dependencies[] = rdf -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index d77cf003a..1a6c00839 100644 --- a/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/www7/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -6,8 +6,8 @@ core = 7.x hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 939bad947..d6353f5d4 100644 --- a/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/www7/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -8,8 +8,8 @@ version = VERSION core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/profiles/testing/testing.info b/www7/profiles/testing/testing.info index 622cbcd32..4342ddab7 100644 --- a/www7/profiles/testing/testing.info +++ b/www7/profiles/testing/testing.info @@ -4,8 +4,8 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/themes/bartik/bartik.info b/www7/themes/bartik/bartik.info index c677e10c3..01533c0db 100644 --- a/www7/themes/bartik/bartik.info +++ b/www7/themes/bartik/bartik.info @@ -34,8 +34,8 @@ regions[footer] = Footer settings[shortcut_module_link] = 0 -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/themes/garland/garland.info b/www7/themes/garland/garland.info index 6eb61b47c..a4b410a98 100644 --- a/www7/themes/garland/garland.info +++ b/www7/themes/garland/garland.info @@ -7,8 +7,8 @@ stylesheets[all][] = style.css stylesheets[print][] = print.css settings[garland_width] = fluid -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/themes/seven/seven.info b/www7/themes/seven/seven.info index 22bfa80ac..3866ed2ed 100644 --- a/www7/themes/seven/seven.info +++ b/www7/themes/seven/seven.info @@ -13,8 +13,8 @@ regions[page_bottom] = Page bottom regions[sidebar_first] = First sidebar regions_hidden[] = sidebar_first -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506" diff --git a/www7/themes/stark/stark.info b/www7/themes/stark/stark.info index 3a4e44f3d..4b2f7db54 100644 --- a/www7/themes/stark/stark.info +++ b/www7/themes/stark/stark.info @@ -5,8 +5,8 @@ version = VERSION core = 7.x stylesheets[all][] = layout.css -; Information added by Drupal.org packaging script on 2016-02-03 -version = "7.42" +; Information added by Drupal.org packaging script on 2016-02-24 +version = "7.43" project = "drupal" -datestamp = "1454517955" +datestamp = "1456343506"