diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 04cff864..fa4f2091 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,261 @@ +Drupal 7.60, 2018-10-18 +------------------------ +- Fixed security issues. See SA-CORE-2018-006. + +Drupal 7.59, 2018-04-25 +----------------------- +- Fixed security issues (remote code execution). See SA-CORE-2018-004. + +Drupal 7.58, 2018-03-28 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-002. + +Drupal 7.57, 2018-02-21 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-001. + +Drupal 7.56, 2017-06-21 +----------------------- +- Fixed security issues (access bypass). See SA-CORE-2017-003. + +Drupal 7.55, 2017-06-07 +----------------------- +- Fixed incompatibility with PHP versions 7.0.19 and 7.1.5 due to duplicate + DATE_RFC7231 definition. +- Made Drupal core pass all automated tests on PHP 7.1. +- Allowed services such as Let's Encrypt to work with Drupal on Apache, by + making Drupal's .htaccess file allow access to the .well-known directory + defined by RFC 5785. +- Made new Drupal sites work correctly on Apache 2.4 when the mod_access_compat + Apache module is disabled. +- Fixed Drupal's URL-generating functions to always encode '[' and ']' so that + the URLs will pass HTML5 validation. +- Various additional bug fixes. +- Various API documentation improvements. +- Additional automated test coverage. + +Drupal 7.54, 2017-02-01 +----------------------- +- Modules are now able to define theme engines (API addition: + https://www.drupal.org/node/2826480). +- Logging of searches can now be disabled (new option in the administrative + interface). +- Added menu tree render structure to (pre-)process hooks for theme_menu_tree() + (API addition: https://www.drupal.org/node/2827134). +- Added new function for determining whether an HTTPS request is being served + (API addition: https://www.drupal.org/node/2824590). +- Fixed incorrect default value for short and medium date formats on the date + type configuration page. +- File validation error message is now removed after subsequent upload of valid + file. +- Numerous bug fixes. +- Numerous API documentation improvements. +- Additional performance improvements. +- Additional automated test coverage. + +Drupal 7.53, 2016-12-07 +----------------------- +- Fixed drag and drop support on newer Chrome/IE 11+ versions after 7.51 update + when jQuery is updated to 1.7-1.11.0. + +Drupal 7.52, 2016-11-16 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-005. + +Drupal 7.51, 2016-10-05 +----------------------- +- The Update module now also checks for updates to a disabled theme that is + used as an admin theme. +- Exceptions thrown in dblog_watchdog() are now caught and ignored. +- Clarified the warning that appears when modules are missing or have moved. +- Log messages are now XSS filtered on display. +- Draggable tables now work on touch screen devices. +- Added a setting for allowing double underscores in CSS identifiers + (https://www.drupal.org/node/2810369). +- If a user navigates away from a page while an Ajax request is running they + will no longer get an error message saying "An Ajax HTTP request terminated + abnormally". +- The system_region_list() API function now takes an optional third parameter + which allows region name translations to be skipped when they are not needed + (API addition: https://www.drupal.org/node/2810365). +- Numerous performance improvements. +- Numerous bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.50, 2016-07-07 +----------------------- +- Added a new "administer fields" permission for trusted users, which is + required in addition to other permissions to use the field UI + (https://www.drupal.org/node/2483307). +- Added clickjacking protection to Drupal core by setting the X-Frame-Options + header to SAMEORIGIN by default (https://www.drupal.org/node/2735873). +- Added support for full UTF-8 (emojis, Asian symbols, mathematical symbols) on + MySQL and other database drivers when the site and database are configured to + allow it (https://www.drupal.org/node/2761183). +- Improved performance by avoiding a re-scan of directories when a file is + missing; instead, trigger a PHP warning (minor API change: + https://www.drupal.org/node/2581445). +- Made it possible to use any PHP callable in Ajax form callbacks, form API + form-building functions, and form API wrapper callbacks (API addition: + https://www.drupal.org/node/2761169). +- Fixed that following a password reset link while logged in leaves users unable + to change their password (minor user interface change: + https://www.drupal.org/node/2759023). +- Implemented various fixes for automated test failures on PHP 5.4+ and PHP 7. + Drupal core automated tests now pass in these environments. +- Improved support for PHP 7 by fixing various problems. +- Fixed various bugs with PHP 5.5+ imagerotate(), including when incorrect + color indices are passed in. +- Fixed a regression introduced in Drupal 7.43 that allowed files uploaded by + anonymous users to be lost after form validation errors, and that also caused + regressions with certain contributed modules. +- Fixed a regression introduced in Drupal 7.36 which caused the default value + of hidden textarea fields to be ignored. +- Fixed robots.txt to allow search engines to access CSS, JavaScript and image + files. +- Changed wording on the Update Manager settings page to clarify that the + option to check for disabled module updates also applies to uninstalled + modules (administrative-facing translatable string change). +- Changed the help text when editing menu links and configuring URL redirect + actions so that it does not reference "Drupal" or the drupal.org website + (administrative-facing translatable string change). +- Fixed the locale safety check that is used to ensure that translations are + safe to allow for tokens in the href/src attributes of translated strings. +- Fixed that URL generation only works on port 80 when using domain based + language negotation. +- Made method="get" forms work inside the administrative overlay. The fix adds + a new hidden field to these forms when they appear inside the overlay (minor + data structure change). +- Increased maxlength of menu link title input fields in the node form and + menu link form from 128 to 255 characters. +- Removed meaningless post-check=0 and pre-check=0 cache control headers from + Drupal HTTP responses. +- Added a .editorconfig file to auto-configure editors that support it. +- Added --directory option to run-tests.sh for easier test discovery of all + tests within a project. +- Made run-tests.sh exit with a failure code when there are test fails or + problems running the script. +- Fixed that cookies from previous tests are still present when a new test + starts in DrupalWebTestCase. +- Improved performance of queries on the {authmap} database table. +- Fixed handling of missing files and functions inside the registry. +- Fixed Ajax handling for tableselect form elements that use checkboxes. +- Fixed a bug which caused ip_address() to return nothing when the client IP + address and proxy IP address are the same. +- Added a new option to format_xml_elements() to allow for already encoded + values. +- Changed the {history} table's node ID field to be an unsigned integer, to + match the same field in the {node} table and to prevent errors with very + large node IDs. +- Added an explicit page callback to the "admin/people/create" menu item in the + User module (minor data structure change). Previously this automatically + inherited the page callback from the parent "admin/people" menu item, which + broke contributed modules that override the "admin/people" page. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.44, 2016-06-15 +----------------------- +- Fixed security issues (privilege escalation). See SA-CORE-2016-002. + +Drupal 7.43, 2016-02-24 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-001. + +Drupal 7.42, 2016-02-03 +----------------------- +- Stopped invoking hook_flush_caches() on every cron run, since some modules + use that hook for expensive operations that are only needed on cache clears. +- Changed the default .htaccess and web.config to block Composer-related files. +- Added static caching to module_load_include() to improve performance. +- Fixed double-encoding bugs in select field widgets provided by the Options + module. The fix deprecates the 'strip_tags' property on option widgets and + replaces it with a new 'strip_tags_and_unescape' property (minor data + structure change). +- Improved MySQL 5.7 support by changing the MySQL database driver to stop + using the ANSI SQL mode alias, which has different meanings for different + MySQL versions. +- Fixed a regression introduced in Drupal 7.39 which prevented autocomplete + functionality from working on servers that are not configured to + automatically recognize index.php. +- Updated the Archive_Tar PEAR package to the latest 1.4.0 release, to fix bugs + with tar file handling on various operating systems. +- Fixed fatal errors on node preview when a field is displayed in the node + teaser but hidden in the full node view. The fix removes a + field_attach_prepare_view() call from the node_preview() function since it is + redundant with one in the node preview theme layer. +- Improved the description of the "Trimmed" format option on text fields + (translatable string change, and minor UI and data structure change). +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. + +Drupal 7.41, 2015-10-21 +----------------------- +- Fixed security issues (open redirect). See SA-CORE-2015-004. + +Drupal 7.40, 2015-10-14 +----------------------- +- Made Drupal's code for parsing .info files run much faster and use much less + memory. +- Prevented drupal_http_request() from returning an error when it receives a + 201 through 206 HTTP status code. +- Added support for autoloading traits via the registry on sites running PHP + 5.4 or higher. +- Allowed the user-picture.tpl.php theme template to have HTML classes besides + the default "user-picture" class printed in it (markup change). +- Fixed the URL text filter to convert e-mail addresses with plus signs into + mailto: links. +- Added alternate text to file icons displayed by the File module, to improve + accessibility (string change, and minor API addition to theme_file_icon()). +- Changed one-time login link failure messages to be displayed as errors or + warnings as appropriate, rather than as regular status messages (minor UI + change and data structure change). +- Changed the default settings.php configuration to exclude private files from + the "404_fast_paths" behavior. +- Changed the page that displays filter tips for a particular text format, for + example filter/tips/full_html, to return "page not found" or "access denied" + if the format does not exist or the user does not have access to it. This + change adds a new menu item to the Filter module's hook_menu() entry (minor + data structure change). +- Added a new hook, hook_block_cid_parts_alter(), to allow modules to alter the + cache keys used for caching a particular block. +- Made drupal_set_message() display and return messages when "0" is passed in + as the message to set. +- Fixed non-functional "Files displayed by default" setting on file fields. +- The "worker callback" provided in hook_cron_queue_info() and the "finished" + callback specified during batch processing can now be any PHP callable + instead of just functions. +- Prevented drupal_set_time_limit() from decreasing the time limit in the case + where the PHP maximum execution time is already unlimited. +- Changed the default thousand marker for numeric fields from a space ("1 000") + to nothing ("1000") (minor UI change: https://www.drupal.org/node/1388376). +- Prevented malformed theme .info files (without a "name" key) from causing + exceptions during menu rebuilds. If an .info file without a "name" key is + found in a module or theme directory, Drupal will now use the module or + theme's machine name as the display name instead. +- Made the format column in the {date_format_locale} database table + case-sensitive, to match the equivalent column in the {date_formats} table. +- Fixed a bug in the Statistics module that caused JavaScript files attached to + a node while it is being viewed to be omitted from the page. +- Added an optional 'project:' prefix that can be added to dependencies in a + module's .info file to indicate which project the dependency resides in (API + addition: https://www.drupal.org/node/2299747). +- Fixed various bugs that occurred after hooks were invoked early in the Drupal + bootstrap and that caused module_implements() and drupal_alter() to cache an + incomplete set of hook implementations for later use. +- Set the X-Content-Type-Options header to "nosniff" when possible, to prevent + certain web browsers from picking an unsafe MIME type. +- Prevented the database API from executing multiple queries at once on MySQL, + if the site's PHP version is new enough to do so. This is a secondary defense + against SQL injection (API change: https://www.drupal.org/node/2463973). +- Fixed a bug in the Drupal 6 to Drupal 7 upgrade path which caused the upgrade + to fail when there were multiple file records pointing to the same file. +- Numerous small bug fixes. +- Numerous API documentation improvements. +- Additional automated test coverage. Drupal 7.39, 2015-08-19 ----------------------- @@ -86,11 +344,11 @@ Drupal 7.36, 2015-04-01 - Additional automated test coverage. Drupal 7.35, 2015-03-18 ----------------------- +----------------------- - Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-001. Drupal 7.34, 2014-11-19 ----------------------- +----------------------- - Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-006. Drupal 7.33, 2014-11-07 @@ -159,11 +417,11 @@ Drupal 7.33, 2014-11-07 - Additional automated test coverage. Drupal 7.32, 2014-10-15 ----------------------- +----------------------- - Fixed security issues (SQL injection). See SA-CORE-2014-005. Drupal 7.31, 2014-08-06 ----------------------- +----------------------- - Fixed security issues (denial of service). See SA-CORE-2014-004. Drupal 7.30, 2014-07-24 @@ -178,7 +436,7 @@ Drupal 7.30, 2014-07-24 - Additional automated test coverage. Drupal 7.29, 2014-07-16 ----------------------- +----------------------- - Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-003. Drupal 7.28, 2014-05-08 @@ -224,11 +482,11 @@ Drupal 7.28, 2014-05-08 - Additional automated test coverage. Drupal 7.27, 2014-04-16 ----------------------- +----------------------- - Fixed security issues (information disclosure). See SA-CORE-2014-002. Drupal 7.26, 2014-01-15 ----------------------- +----------------------- - Fixed security issues (multiple vulnerabilities). See SA-CORE-2014-001. Drupal 7.25, 2014-01-02 @@ -294,7 +552,7 @@ Drupal 7.25, 2014-01-02 - Additional automated test coverage. Drupal 7.24, 2013-11-20 ----------------------- +----------------------- - Fixed security issues (multiple vulnerabilities), see SA-CORE-2013-003. Drupal 7.23, 2013-08-07 @@ -548,8 +806,8 @@ Drupal 7.15, 2012-08-01 - Numerous API documentation improvements. - Additional automated test coverage. -Drupal 7.14 2012-05-02 ----------------------- +Drupal 7.14, 2012-05-02 +----------------------- - Fixed "integrity constraint" fatal errors when rebuilding registry. - Fixed custom logo and favicon functionality referencing incorrect paths. - Fixed DB Case Sensitivity: Allow BINARY attribute in MySQL. @@ -597,12 +855,12 @@ Drupal 7.14 2012-05-02 - system_update_7061() converts filepaths too aggressively. - Trigger upgrade path: Node triggers removed when upgrading to 7-x from 6.25. -Drupal 7.13 2012-05-02 ----------------------- +Drupal 7.13, 2012-05-02 +----------------------- - Fixed security issues (Multiple vulnerabilities), see SA-CORE-2012-002. Drupal 7.12, 2012-02-01 ----------------------- +----------------------- - Fixed bug preventing custom menus from receiving an active trail. - Fixed hook_field_delete() no longer invoked during field_purge_data(). - Fixed bug causing entity info cache to not be cleared with the rest of caches. @@ -636,11 +894,11 @@ Drupal 7.12, 2012-02-01 cache. Drupal 7.11, 2012-02-01 ----------------------- +----------------------- - Fixed security issues (Multiple vulnerabilities), see SA-CORE-2012-001. Drupal 7.10, 2011-12-05 ----------------------- +----------------------- - Fixed Content-Language HTTP header to not cause issues with Drush 5.x. - Reduce memory usage of theme registry (performance). - Fixed PECL upload progress bar for FileField @@ -993,7 +1251,7 @@ Drupal 7.0, 2011-01-05 requests. Drupal 6.23-dev, xxxx-xx-xx (development release) ------------------------ +--------------------------- Drupal 6.22, 2011-05-25 ----------------------- @@ -1003,25 +1261,25 @@ Drupal 6.22, 2011-05-25 - Fixed a variety of other bugs. Drupal 6.21, 2011-05-25 ----------------------- +----------------------- - Fixed security issues (Cross site scripting), see SA-CORE-2011-001. Drupal 6.20, 2010-12-15 ----------------------- +----------------------- - Fixed a variety of small bugs, improved code documentation. Drupal 6.19, 2010-08-11 ----------------------- +----------------------- - Fixed a variety of small bugs, improved code documentation. Drupal 6.18, 2010-08-11 ----------------------- +----------------------- - Fixed security issues (OpenID authentication bypass, File download access bypass, Comment unpublishing bypass, Actions cross site scripting), see SA-CORE-2010-002. Drupal 6.17, 2010-06-02 ----------------------- +----------------------- - Improved PostgreSQL compatibility - Better PHP 5.3 and PHP 4 compatibility - Better browser compatibility of CSS and JS aggregation @@ -1030,7 +1288,7 @@ Drupal 6.17, 2010-06-02 - Fixed a variety of other bugs. Drupal 6.16, 2010-03-03 ----------------------- +----------------------- - Fixed security issues (Installation cross site scripting, Open redirection, Locale module cross site scripting, Blocked user session regeneration), see SA-CORE-2010-001. @@ -1042,12 +1300,12 @@ Drupal 6.16, 2010-03-03 - Fixed a variety of other bugs. Drupal 6.15, 2009-12-16 ----------------------- +----------------------- - Fixed security issues (Cross site scripting), see SA-CORE-2009-009. - Fixed a variety of other bugs. Drupal 6.14, 2009-09-16 ----------------------- +----------------------- - Fixed security issues (OpenID association cross site request forgeries, OpenID impersonation and File upload), see SA-CORE-2009-008. - Changed the system modules page to not run all cache rebuilds; use the @@ -1056,18 +1314,18 @@ Drupal 6.14, 2009-09-16 - Fixed a variety of small bugs. Drupal 6.13, 2009-07-01 ----------------------- +----------------------- - Fixed security issues (Cross site scripting, Input format access bypass and Password leakage in URL), see SA-CORE-2009-007. - Fixed a variety of small bugs. Drupal 6.12, 2009-05-13 ----------------------- +----------------------- - Fixed security issues (Cross site scripting), see SA-CORE-2009-006. - Fixed a variety of small bugs. Drupal 6.11, 2009-04-29 ----------------------- +----------------------- - Fixed security issues (Cross site scripting and limited information disclosure), see SA-CORE-2009-005 - Fixed performance issues with the menu router cache, the update @@ -1075,7 +1333,7 @@ Drupal 6.11, 2009-04-29 - Fixed a variety of small bugs. Drupal 6.10, 2009-02-25 ----------------------- +----------------------- - Fixed a security issue, (Local file inclusion on Windows), see SA-CORE-2009-003 - Fixed node_feed() so custom fields can show up in RSS feeds. @@ -1471,7 +1729,7 @@ Drupal 4.7.9, 2007-12-05 - fixed a security issue (SQL injection), see SA-2007-031 Drupal 4.7.8, 2007-10-17 ----------------------- +------------------------ - fixed a security issue (HTTP response splitting), see SA-2007-024 - fixed a security issue (Cross site scripting via uploads), see SA-2007-026 - fixed a security issue (API handling of unpublished comment), see SA-2007-030 @@ -1584,7 +1842,7 @@ Drupal 4.6.11, 2007-01-05 - Fixed security issue (DoS), see SA-2007-002 Drupal 4.6.10, 2006-10-18 ------------------------- +------------------------- - Fixed security issue (XSS), see SA-2006-024 - Fixed security issue (CSRF), see SA-2006-025 - Fixed security issue (Form action attribute injection), see SA-2006-026 diff --git a/INSTALL.txt b/INSTALL.txt index 6f02c05a..e00c8bad 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -23,7 +23,7 @@ Drupal requires: - Percona Server 5.1.70 (or greater) (http://www.percona.com/). Percona Server is a backwards-compatible replacement for MySQL. - PostgreSQL 8.3 (or greater) (http://www.postgresql.org/). - - SQLite 3.4.2 (or greater) (http://www.sqlite.org/). + - SQLite 3.3.7 (or greater) (http://www.sqlite.org/). For more detailed information about Drupal requirements, including a list of PHP extensions and configurations that are required, see "System requirements" diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index f5cf6f89..5603a432 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -1,7 +1,8 @@ Drupal core is built and maintained by the Drupal project community. Everyone is encouraged to submit issues and changes (patches) to improve Drupal, and to -contribute in other ways -- see http://drupal.org/contribute to find out how. +contribute in other ways -- see https://www.drupal.org/contribute to find out +how. Branch maintainers ------------------ @@ -9,154 +10,154 @@ Branch maintainers The Drupal Core branch maintainers oversee the development of Drupal as a whole. The branch maintainers for Drupal 7 are: -- Dries Buytaert 'dries' http://drupal.org/user/1 -- Angela Byron 'webchick' http://drupal.org/user/24967 -- David Rothstein 'David_Rothstein' http://drupal.org/user/124982 +- Dries Buytaert 'dries' https://www.drupal.org/u/dries +- Angela Byron 'webchick' https://www.drupal.org/u/webchick +- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx +- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein +- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0 Component maintainers --------------------- The Drupal Core component maintainers oversee the development of Drupal -subsystems. See http://drupal.org/contribute/core-maintainers for more +subsystems. See https://www.drupal.org/contribute/core-maintainers for more information on their responsibilities, and to find out how to become a component maintainer. Current component maintainers for Drupal 7: Ajax system -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Earl Miles 'merlinofchaos' http://drupal.org/user/26979 +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos Base system -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman Batch system -- Yves Chedemois 'yched' http://drupal.org/user/39567 +- Yves Chedemois 'yched' https://www.drupal.org/u/yched Cache system -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch Cron system -- Derek Wright 'dww' http://drupal.org/user/46549 +- Derek Wright 'dww' https://www.drupal.org/u/dww Database system -- Larry Garfield 'Crell' http://drupal.org/user/26398 +- Larry Garfield 'Crell' https://www.drupal.org/u/crell - MySQL driver - - Larry Garfield 'Crell' http://drupal.org/user/26398 - - David Strauss 'David Strauss' http://drupal.org/user/93254 + - Larry Garfield 'Crell' https://www.drupal.org/u/crell + - David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss - PostgreSQL driver - - Damien Tournoud 'DamZ' http://drupal.org/user/22211 - - Josh Waihi 'fiasco' http://drupal.org/user/188162 + - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud + - Josh Waihi 'fiasco' https://www.drupal.org/u/josh-waihi - Sqlite driver - - Damien Tournoud 'DamZ' http://drupal.org/user/22211 + - Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud Database update system -- Ashok Modi 'BTMash' http://drupal.org/user/60422 +- Ashok Modi 'BTMash' https://www.drupal.org/u/btmash Entity system -- Wolfgang Ziegler 'fago' http://drupal.org/user/16747 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 -- Franz Heinzmann 'Frando' http://drupal.org/user/21850 +- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch +- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando File system -- Andrew Morton 'drewish' http://drupal.org/user/34869 -- Aaron Winborn 'aaron' http://drupal.org/user/33420 +- Andrew Morton 'drewish' https://www.drupal.org/u/drewish +- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron Form system -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Wolfgang Ziegler 'fago' http://drupal.org/user/16747 -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 -- Franz Heinzmann 'Frando' http://drupal.org/user/21850 +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun +- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando Image system -- Andrew Morton 'drewish' http://drupal.org/user/34869 -- Nathan Haug 'quicksketch' http://drupal.org/user/35821 +- Andrew Morton 'drewish' https://www.drupal.org/u/drewish +- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch Install system -- David Rothstein 'David_Rothstein' http://drupal.org/user/124982 +- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein JavaScript -- Théodore Biadala 'nod_' http://drupal.org/user/598310 -- Steve De Jonghe 'seutje' http://drupal.org/user/264148 -- Jesse Renée Beach 'jessebeach' http://drupal.org/user/748566 +- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_ +- Steve De Jonghe 'seutje' https://www.drupal.org/u/seutje Language system -- Francesco Placella 'plach' http://drupal.org/user/183211 -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Francesco Placella 'plach' https://www.drupal.org/u/plach +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Lock system -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud Mail system - ? Markup -- Jacine Luisi 'Jacine' http://drupal.org/user/88931 -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Jacine Luisi 'Jacine' https://www.drupal.org/u/jacine +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Menu system -- Peter Wolanin 'pwolanin' http://drupal.org/user/49851 +- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin Path system -- Dave Reid 'davereid' http://drupal.org/user/53892 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch Render system -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Franz Heinzmann 'Frando' http://drupal.org/user/21850 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando Theme system -- Earl Miles 'merlinofchaos' http://drupal.org/user/26979 -- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040 -- Joon Park 'dvessel' http://drupal.org/user/56782 -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos +- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia +- Joon Park 'dvessel' https://www.drupal.org/u/dvessel +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin Token system -- Dave Reid 'davereid' http://drupal.org/user/53892 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid XML-RPC system -- Frederic G. Marand 'fgm' http://drupal.org/user/27985 +- Frederic G. Marand 'fgm' https://www.drupal.org/u/fgm Topic coordinators ------------------ Accessibility -- Everett Zufelt 'Everett Zufelt' http://drupal.org/user/406552 -- Brandon Bowersox-Johnson 'bowersox' http://drupal.org/user/186415 +- Everett Zufelt 'Everett Zufelt' https://www.drupal.org/u/everett-zufelt +- Brandon Bowersox-Johnson 'bowersox' https://www.drupal.org/u/bowersox Documentation -- Jennifer Hodgdon 'jhodgdon' http://drupal.org/user/155601 +- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon Translations -- Gerhard Killesreiter 'killes' http://drupal.org/user/83 +- Gerhard Killesreiter 'killes' https://www.drupal.org/u/gerhard-killesreiter User experience and usability -- Roy Scholten 'yoroy' http://drupal.org/user/41502 -- Bojhan Somers 'Bojhan' http://drupal.org/user/87969 +- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy +- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan Node Access -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- Ken Rickard 'agentrickard' http://drupal.org/user/20975 -- Jess Myrbo 'xjm' http://drupal.org/user/65776 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard Security team ----------------- -To report a security issue, see: https://drupal.org/security-team/report-issue +To report a security issue, see: https://www.drupal.org/security-team/report-issue The Drupal security team provides Security Advisories for vulnerabilities, assists developers in resolving security issues, and provides security -documentation. See http://drupal.org/security-team for more information. The -security team lead is: +documentation. See https://www.drupal.org/security-team for more information. +The security team lead is: -- Michael Hess 'mlhess' https://drupal.org/user/102818 +- Michael Hess 'mlhess' https://www.drupal.org/u/mlhess Module maintainers @@ -166,142 +167,141 @@ Aggregator module - ? Block module -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin Blog module - ? Book module -- Peter Wolanin 'pwolanin' http://drupal.org/user/49851 +- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin Color module - ? Comment module -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch Contact module -- Dave Reid 'davereid' http://drupal.org/user/53892 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid Contextual module -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Dashboard module - ? Database logging module -- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063 +- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey Field module -- Yves Chedemois 'yched' http://drupal.org/user/39567 -- Barry Jaspan 'bjaspan' http://drupal.org/user/46413 +- Yves Chedemois 'yched' https://www.drupal.org/u/yched +- Barry Jaspan 'bjaspan' https://www.drupal.org/u/bjaspan Field UI module -- Yves Chedemois 'yched' http://drupal.org/user/39567 +- Yves Chedemois 'yched' https://www.drupal.org/u/yched File module -- Aaron Winborn 'aaron' http://drupal.org/user/33420 +- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron Filter module -- Daniel F. Kudwien 'sun' http://drupal.org/user/54136 +- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun Forum module -- Lee Rowlands 'larowlan' http://drupal.org/user/395439 +- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan Help module - ? Image module -- Nathan Haug 'quicksketch' http://drupal.org/user/35821 +- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch Locale module -- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166 +- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy Menu module - ? Node module -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- David Strauss 'David Strauss' http://drupal.org/user/93254 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss OpenID module -- Vojtech Kusy 'wojtha' http://drupal.org/user/56154 -- Christian Schmidt 'c960657' http://drupal.org/user/216078 -- Damien Tournoud 'DamZ' http://drupal.org/user/22211 +- Vojtech Kusy 'wojtha' https://www.drupal.org/u/wojtha +- Christian Schmidt 'c960657' https://www.drupal.org/u/c960657 +- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud Overlay module -- Katherine Senzee 'ksenzee' http://drupal.org/user/139855 +- Katherine Senzee 'ksenzee' https://www.drupal.org/u/ksenzee Path module -- Dave Reid 'davereid' http://drupal.org/user/53892 +- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid PHP module - ? Poll module -- Andrei Mateescu 'amateescu' http://drupal.org/user/729614 +- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu Profile module - ? RDF module -- Stéphane Corlosquet 'scor' http://drupal.org/user/52142 +- Stéphane Corlosquet 'scor' https://www.drupal.org/u/scor Search module -- Doug Green 'douggreen' http://drupal.org/user/29191 +- Doug Green 'douggreen' https://www.drupal.org/u/douggreen Shortcut module -- David Rothstein 'David_Rothstein' http://drupal.org/user/124982 +- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein Simpletest module -- Jimmy Berry 'boombatower' http://drupal.org/user/214218 +- Jimmy Berry 'boombatower' https://www.drupal.org/u/boombatower Statistics module -- Tim Millwood 'timmillwood' http://drupal.org/user/227849 +- Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood Syslog module -- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063 +- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey System module - ? Taxonomy module -- Jess Myrbo 'xjm' http://drupal.org/user/65776 -- Nathaniel Catchpole 'catch' http://drupal.org/user/35733 -- Benjamin Doherty 'bangpound' http://drupal.org/user/100456 +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch +- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound Toolbar module - ? Tracker module -- David Strauss 'David Strauss' http://drupal.org/user/93254 +- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss Translation module -- Francesco Placella 'plach' http://drupal.org/user/183211 +- Francesco Placella 'plach' https://www.drupal.org/u/plach Trigger module - ? Update module -- Derek Wright 'dww' http://drupal.org/user/46549 +- Derek Wright 'dww' https://www.drupal.org/u/dww User module -- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23 -- David Strauss 'David Strauss' http://drupal.org/user/93254 +- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman +- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss Theme maintainers ----------------- Bartik theme -- Jen Simmons 'jensimmons' http://drupal.org/user/140882 -- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393 +- Jen Simmons 'jensimmons' https://www.drupal.org/u/jensimmons +- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz Garland theme -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin Seven theme -- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393 +- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz Stark theme -- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095 +- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin diff --git a/PATCHES.txt b/PATCHES.txt index dc278579..229f703b 100644 --- a/PATCHES.txt +++ b/PATCHES.txt @@ -5,4 +5,4 @@ The following patches have been applied to this project: - http://drupal.org/files/issues/drupal-7.x-allow_profile_change_sys_req-1772316-28.patch - http://drupal.org/files/1275902-15-entity_uri_callback-D7.patch -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file +This file was automatically generated by Drush Make (http://drupal.org/project/drush). diff --git a/UPGRADE.txt b/UPGRADE.txt index e870ff0f..ae733ca1 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -64,6 +64,9 @@ following the instructions in the INTRODUCTION section at the top of this file: Sometimes an update includes changes to default.settings.php (this will be noted in the release notes). If that's the case, follow these steps: + - Locate your settings.php file in the /sites/* directory. (Typically + sites/default.) + - Make a backup copy of your settings.php file, with a different file name. - Make a copy of the new default.settings.php file, and name the copy @@ -74,6 +77,13 @@ following the instructions in the INTRODUCTION section at the top of this file: database information, and you will also want to copy in any other customizations you have added. + You can find the release notes for your version at + https://www.drupal.org/project/drupal. At bottom of the project page under + "Downloads" use the link for your version of Drupal to view the release + notes. If your version is not listed, use the 'View all releases' link. From + this page you can scroll down or use the filter to find your version and its + release notes. + 4. Download the latest Drupal 7.x release from http://drupal.org to a directory outside of your web root. Extract the archive and copy the files into your Drupal directory. diff --git a/includes/ajax.inc b/includes/ajax.inc index 50e8e28a..f059209b 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -394,7 +394,7 @@ function ajax_form_callback() { if (!empty($form_state['triggering_element'])) { $callback = $form_state['triggering_element']['#ajax']['callback']; } - if (!empty($callback) && function_exists($callback)) { + if (!empty($callback) && is_callable($callback)) { $result = $callback($form, $form_state); if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) { diff --git a/includes/batch.inc b/includes/batch.inc index 061acd4c..e89ab8de 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -460,10 +460,10 @@ function _batch_finished() { if (isset($batch_set['file']) && is_file($batch_set['file'])) { include_once DRUPAL_ROOT . '/' . $batch_set['file']; } - if (function_exists($batch_set['finished'])) { + if (is_callable($batch_set['finished'])) { $queue = _batch_queue($batch_set); $operations = $queue->getAllItems(); - $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000)); + call_user_func($batch_set['finished'], $batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000)); } } } diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 5cf78dcf..2d8b820f 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.39'); +define('VERSION', '7.60'); /** * Core API compatibility. @@ -254,8 +254,13 @@ define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' * http://tools.ietf.org/html/rfc7231#section-7.1.1.1 * * Example: Sun, 06 Nov 1994 08:49:37 GMT + * + * This constant was introduced in PHP 7.0.19 and PHP 7.1.5 but needs to be + * defined by Drupal for earlier PHP versions. */ -define('DATE_RFC7231', 'D, d M Y H:i:s \G\M\T'); +if (!defined('DATE_RFC7231')) { + define('DATE_RFC7231', 'D, d M Y H:i:s \G\M\T'); +} /** * Provides a caching wrapper to be used in place of large array structures. @@ -718,6 +723,16 @@ function drupal_valid_http_host($host) { && preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host); } +/** + * Checks whether an HTTPS request is being served. + * + * @return bool + * TRUE if the request is HTTPS, FALSE otherwise. + */ +function drupal_is_https() { + return isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; +} + /** * Sets the base URL, cookie domain, and session name from configuration. */ @@ -731,7 +746,7 @@ function drupal_settings_initialize() { if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php'; } - $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; + $is_https = drupal_is_https(); if (isset($base_url)) { // Parse fixed base URL from settings.php. @@ -828,14 +843,21 @@ function drupal_settings_initialize() { * @param $filename * The filename of the item if it is to be set explicitly rather * than by consulting the database. + * @param bool $trigger_error + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. This defaults to TRUE, but can be set to FALSE by calling code that + * merely wants to check whether an item exists in the filesystem. * * @return * The filename of the requested item or NULL if the item is not found. */ -function drupal_get_filename($type, $name, $filename = NULL) { +function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) { + // The $files static variable will hold the locations of all requested files. + // We can be sure that any file listed in this static variable actually + // exists as all additions have gone through a file_exists() check. // The location of files will not change during the request, so do not use // drupal_static(). - static $files = array(), $dirs = array(); + static $files = array(); // Profiles are a special case: they have a fixed location and naming. if ($type == 'profile') { @@ -847,64 +869,296 @@ function drupal_get_filename($type, $name, $filename = NULL) { } if (!empty($filename) && file_exists($filename)) { + // Prime the static cache with the provided filename. $files[$type][$name] = $filename; } elseif (isset($files[$type][$name])) { - // nothing + // This item had already been found earlier in the request, either through + // priming of the static cache (for example, in system_list()), through a + // lookup in the {system} table, or through a file scan (cached or not). Do + // nothing. } - // Verify that we have an active database connection, before querying - // the database. This is required because this function is called both - // before we have a database connection (i.e. during installation) and - // when a database connection fails. else { + // Look for the filename listed in the {system} table. Verify that we have + // an active database connection before doing so, since this function is + // called both before we have a database connection (i.e. during + // installation) and when a database connection fails. + $database_unavailable = TRUE; try { if (function_exists('db_query')) { $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField(); if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) { $files[$type][$name] = $file; } + $database_unavailable = FALSE; } } catch (Exception $e) { // The database table may not exist because Drupal is not yet installed, - // or the database might be down. We have a fallback for this case so we - // hide the error completely. + // the database might be down, or we may have done a non-database cache + // flush while $conf['page_cache_without_database'] = TRUE and + // $conf['page_cache_invoke_hooks'] = TRUE. We have a fallback for these + // cases so we hide the error completely. } - // Fallback to searching the filesystem if the database could not find the - // file or the file returned by the database is not found. + // Fall back to searching the filesystem if the database could not find the + // file or the file does not exist at the path returned by the database. if (!isset($files[$type][$name])) { - // We have a consistent directory naming: modules, themes... - $dir = $type . 's'; - if ($type == 'theme_engine') { - $dir = 'themes/engines'; - $extension = 'engine'; - } - elseif ($type == 'theme') { - $extension = 'info'; - } - else { - $extension = $type; - } + $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable); + } + } - if (!isset($dirs[$dir][$extension])) { - $dirs[$dir][$extension] = TRUE; - if (!function_exists('drupal_system_listing')) { - require_once DRUPAL_ROOT . '/includes/common.inc'; - } - // Scan the appropriate directories for all files with the requested - // extension, not just the file we are currently looking for. This - // prevents unnecessary scans from being repeated when this function is - // called more than once in the same page request. - $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0); - foreach ($matches as $matched_name => $file) { - $files[$type][$matched_name] = $file->uri; + if (isset($files[$type][$name])) { + return $files[$type][$name]; + } +} + +/** + * Performs a cached file system scan as a fallback when searching for a file. + * + * This function looks for the requested file by triggering a file scan, + * caching the new location if the file has moved and caching the miss + * if the file is missing. If a file had been marked as missing in a previous + * file scan, or if it has been marked as moved and is still in the last known + * location, no new file scan will be performed. + * + * @param string $type + * The type of the item (theme, theme_engine, module, profile). + * @param string $name + * The name of the item for which the filename is requested. + * @param bool $trigger_error + * Whether to trigger an error when a file is missing or has unexpectedly + * moved. + * @param bool $database_unavailable + * Whether this function is being called because the Drupal database could + * not be queried for the file's location. + * + * @return + * The filename of the requested item or NULL if the item is not found. + * + * @see drupal_get_filename() + */ +function _drupal_get_filename_fallback($type, $name, $trigger_error, $database_unavailable) { + $file_scans = &_drupal_file_scan_cache(); + $filename = NULL; + + // If the cache indicates that the item is missing, or we can verify that the + // item exists in the location the cache says it exists in, use that. + if (isset($file_scans[$type][$name]) && ($file_scans[$type][$name] === FALSE || file_exists($file_scans[$type][$name]))) { + $filename = $file_scans[$type][$name]; + } + // Otherwise, perform a new file scan to find the item. + else { + $filename = _drupal_get_filename_perform_file_scan($type, $name); + // Update the static cache, and mark the persistent cache for updating at + // the end of the page request. See drupal_file_scan_write_cache(). + $file_scans[$type][$name] = $filename; + $file_scans['#write_cache'] = TRUE; + } + + // If requested, trigger a user-level warning about the missing or + // unexpectedly moved file. If the database was unavailable, do not trigger a + // warning in the latter case, though, since if the {system} table could not + // be queried there is no way to know if the location found here was + // "unexpected" or not. + if ($trigger_error) { + $error_type = $filename === FALSE ? 'missing' : 'moved'; + if ($error_type == 'missing' || !$database_unavailable) { + _drupal_get_filename_fallback_trigger_error($type, $name, $error_type); + } + } + + // The cache stores FALSE for files that aren't found (to be able to + // distinguish them from files that have not yet been searched for), but + // drupal_get_filename() expects NULL for these instead, so convert to NULL + // before returning. + if ($filename === FALSE) { + $filename = NULL; + } + return $filename; +} + +/** + * Returns the current list of cached file system scan results. + * + * @return + * An associative array tracking the most recent file scan results for all + * files that have had scans performed. The keys are the type and name of the + * item that was searched for, and the values can be either: + * - Boolean FALSE if the item was not found in the file system. + * - A string pointing to the location where the item was found. + */ +function &_drupal_file_scan_cache() { + $file_scans = &drupal_static(__FUNCTION__, array()); + + // The file scan results are stored in a persistent cache (in addition to the + // static cache) but because this function can be called before the + // persistent cache is available, we must merge any items that were found + // earlier in the page request into the results from the persistent cache. + if (!isset($file_scans['#cache_merge_done'])) { + try { + if (function_exists('cache_get')) { + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + if (!empty($cache->data)) { + // File scan results from the current request should take precedence + // over the results from the persistent cache, since they are newer. + $file_scans = drupal_array_merge_deep($cache->data, $file_scans); } + // Set a flag to indicate that the persistent cache does not need to be + // merged again. + $file_scans['#cache_merge_done'] = TRUE; } } + catch (Exception $e) { + // Hide the error. + } } - if (isset($files[$type][$name])) { - return $files[$type][$name]; + return $file_scans; +} + +/** + * Performs a file system scan to search for a system resource. + * + * @param $type + * The type of the item (theme, theme_engine, module, profile). + * @param $name + * The name of the item for which the filename is requested. + * + * @return + * The filename of the requested item or FALSE if the item is not found. + * + * @see drupal_get_filename() + * @see _drupal_get_filename_fallback() + */ +function _drupal_get_filename_perform_file_scan($type, $name) { + // The location of files will not change during the request, so do not use + // drupal_static(). + static $dirs = array(), $files = array(); + + // We have a consistent directory naming: modules, themes... + $dir = $type . 's'; + if ($type == 'theme_engine') { + $dir = 'themes/engines'; + $extension = 'engine'; + } + elseif ($type == 'theme') { + $extension = 'info'; + } + else { + $extension = $type; + } + + // Check if we had already scanned this directory/extension combination. + if (!isset($dirs[$dir][$extension])) { + // Log that we have now scanned this directory/extension combination + // into a static variable so as to prevent unnecessary file scans. + $dirs[$dir][$extension] = TRUE; + if (!function_exists('drupal_system_listing')) { + require_once DRUPAL_ROOT . '/includes/common.inc'; + } + // Scan the appropriate directories for all files with the requested + // extension, not just the file we are currently looking for. This + // prevents unnecessary scans from being repeated when this function is + // called more than once in the same page request. + $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0); + foreach ($matches as $matched_name => $file) { + // Log the locations found in the file scan into a static variable. + $files[$type][$matched_name] = $file->uri; + } + } + + // Return the results of the file system scan, or FALSE to indicate the file + // was not found. + return isset($files[$type][$name]) ? $files[$type][$name] : FALSE; +} + +/** + * Triggers a user-level warning for missing or unexpectedly moved files. + * + * @param $type + * The type of the item (theme, theme_engine, module, profile). + * @param $name + * The name of the item for which the filename is requested. + * @param $error_type + * The type of the error ('missing' or 'moved'). + * + * @see drupal_get_filename() + * @see _drupal_get_filename_fallback() + */ +function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) { + // Hide messages due to known bugs that will appear on a lot of sites. + // @todo Remove this in https://www.drupal.org/node/2383823 + if (empty($name)) { + return; + } + + // Make sure we only show any missing or moved file errors only once per + // request. + static $errors_triggered = array(); + if (empty($errors_triggered[$type][$name][$error_type])) { + // Use _drupal_trigger_error_with_delayed_logging() here since these are + // triggered during low-level operations that cannot necessarily be + // interrupted by a watchdog() call. + if ($error_type == 'missing') { + _drupal_trigger_error_with_delayed_logging(format_string('The following @type is missing from the file system: %name. For information about how to fix this, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + } + elseif ($error_type == 'moved') { + _drupal_trigger_error_with_delayed_logging(format_string('The following @type has moved within the file system: %name. In order to fix this, clear caches or put the @type back in its original location. For more information, see the documentation page.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING); + } + $errors_triggered[$type][$name][$error_type] = TRUE; + } +} + +/** + * Invokes trigger_error() with logging delayed until the end of the request. + * + * This is an alternative to PHP's trigger_error() function which can be used + * during low-level Drupal core operations that need to avoid being interrupted + * by a watchdog() call. + * + * Normally, Drupal's error handler calls watchdog() in response to a + * trigger_error() call. However, this invokes hook_watchdog() which can run + * arbitrary code. If the trigger_error() happens in the middle of an + * operation such as a rebuild operation which should not be interrupted by + * arbitrary code, that could potentially break or trigger the rebuild again. + * This function protects against that by delaying the watchdog() call until + * the end of the current page request. + * + * This is an internal function which should only be called by low-level Drupal + * core functions. It may be removed in a future Drupal 7 release. + * + * @param string $error_msg + * The error message to trigger. As with trigger_error() itself, this is + * limited to 1024 bytes; additional characters beyond that will be removed. + * @param int $error_type + * (optional) The type of error. This should be one of the E_USER family of + * constants. As with trigger_error() itself, this defaults to E_USER_NOTICE + * if not provided. + * + * @see _drupal_log_error() + */ +function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_USER_NOTICE) { + $delay_logging = &drupal_static(__FUNCTION__, FALSE); + $delay_logging = TRUE; + trigger_error($error_msg, $error_type); + $delay_logging = FALSE; +} + +/** + * Writes the file scan cache to the persistent cache. + * + * This cache stores all files marked as missing or moved after a file scan + * to prevent unnecessary file scans in subsequent requests. This cache is + * cleared in system_list_reset() (i.e. after a module/theme rebuild). + */ +function drupal_file_scan_write_cache() { + // Only write to the persistent cache if requested, and if we know that any + // data previously in the cache was successfully loaded and merged in by + // _drupal_file_scan_cache(). + $file_scans = &_drupal_file_scan_cache(); + if (isset($file_scans['#write_cache']) && isset($file_scans['#cache_merge_done'])) { + unset($file_scans['#write_cache']); + cache_set('_drupal_file_scan_cache', $file_scans, 'cache_bootstrap'); } } @@ -1055,7 +1309,7 @@ function drupal_page_get_cache($check_only = FALSE) { * Determines the cacheability of the current page. * * @param $allow_caching - * Set to FALSE if you want to prevent this page to get cached. + * Set to FALSE if you want to prevent this page from being cached. * * @return * TRUE if the current page can be cached, FALSE otherwise. @@ -1261,7 +1515,11 @@ function drupal_page_header() { $default_headers = array( 'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT', - 'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0', + 'Cache-Control' => 'no-cache, must-revalidate', + // Prevent browsers from sniffing a response and picking a MIME type + // different from the declared content-type, since that can lead to + // XSS and other vulnerabilities. + 'X-Content-Type-Options' => 'nosniff', ); drupal_send_headers($default_headers); } @@ -1435,6 +1693,23 @@ function drupal_unpack($obj, $field = 'data') { * available to code that needs localization. See st() and get_t() for * alternatives. * + * @section sec_context String context + * Matching source strings are normally only translated once, and the same + * translation is used everywhere that has a matching string. However, in some + * cases, a certain English source string needs to have multiple translations. + * One example of this is the string "May", which could be used as either a + * full month name or a 3-letter abbreviated month. In other languages where + * the month name for May has more than 3 letters, you would need to provide + * two different translations (one for the full name and one abbreviated), and + * the correct form would need to be chosen, depending on how "May" is being + * used. To facilitate this, the "May" string should be provided with two + * different contexts in the $options parameter when calling t(). For example: + * @code + * t('May', array(), array('context' => 'Long month name') + * t('May', array(), array('context' => 'Abbreviated month name') + * @endcode + * See https://localize.drupal.org/node/2109 for more information. + * * @param $string * A string containing the English string to translate. * @param $args @@ -1445,8 +1720,9 @@ function drupal_unpack($obj, $field = 'data') { * An associative array of additional options, with the following elements: * - 'langcode' (defaults to the current language): The language code to * translate to a language other than what is used to display the page. - * - 'context' (defaults to the empty context): The context the source string - * belongs to. + * - 'context' (defaults to the empty context): A string giving the context + * that the source string belongs to. See @ref sec_context above for more + * information. * * @return * The translated string. @@ -1776,7 +2052,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO * @see theme_status_messages() */ function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { - if ($message) { + if ($message || $message === '0' || $message === 0) { if (!isset($_SESSION['messages'][$type])) { $_SESSION['messages'][$type] = array(); } @@ -2468,6 +2744,9 @@ function _drupal_bootstrap_database() { // the install or upgrade process. spl_autoload_register('drupal_autoload_class'); spl_autoload_register('drupal_autoload_interface'); + if (version_compare(PHP_VERSION, '5.4') >= 0) { + spl_autoload_register('drupal_autoload_trait'); + } } /** @@ -2788,10 +3067,14 @@ function language_list($field = 'language') { } /** - * Returns the default language used on the site + * Returns the default language, as an object, or one of its properties. * * @param $property - * Optional property of the language object to return + * (optional) The property of the language object to return. + * + * @return + * Either the language object for the default language used on the site, + * or the property of that object named in the $property parameter. */ function language_default($property = NULL) { $language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '')); @@ -2943,8 +3226,15 @@ function ip_address() { // Eliminate all trusted IPs. $untrusted = array_diff($forwarded, $reverse_proxy_addresses); - // The right-most IP is the most specific we can trust. - $ip_address = array_pop($untrusted); + if (!empty($untrusted)) { + // The right-most IP is the most specific we can trust. + $ip_address = array_pop($untrusted); + } + else { + // All IP addresses in the forwarded array are configured proxy IPs + // (and thus trusted). We take the leftmost IP. + $ip_address = array_shift($forwarded); + } } } } @@ -2961,7 +3251,9 @@ function ip_address() { * Gets the schema definition of a table, or the whole database schema. * * The returned schema will include any modifications made by any - * module that implements hook_schema_alter(). + * module that implements hook_schema_alter(). To get the schema without + * modifications, use drupal_get_schema_unprocessed(). + * * * @param $table * The name of the table. If not given, the schema of all tables is returned. @@ -3116,6 +3408,22 @@ function drupal_autoload_class($class) { return _registry_check_code('class', $class); } +/** + * Confirms that a trait is available. + * + * This function is rarely called directly. Instead, it is registered as an + * spl_autoload() handler, and PHP calls it for us when necessary. + * + * @param string $trait + * The name of the trait to check or load. + * + * @return bool + * TRUE if the trait is currently available, FALSE otherwise. + */ +function drupal_autoload_trait($trait) { + return _registry_check_code('trait', $trait); +} + /** * Checks for a resource in the registry. * @@ -3134,7 +3442,7 @@ function drupal_autoload_class($class) { function _registry_check_code($type, $name = NULL) { static $lookup_cache, $cache_update_needed; - if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name)) { + if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name) || $type == 'trait' && trait_exists($name)) { return TRUE; } @@ -3167,7 +3475,7 @@ function _registry_check_code($type, $name = NULL) { $cache_key = $type[0] . $name; if (isset($lookup_cache[$cache_key])) { if ($lookup_cache[$cache_key]) { - require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; + include_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; } return (bool) $lookup_cache[$cache_key]; } @@ -3192,7 +3500,7 @@ function _registry_check_code($type, $name = NULL) { $lookup_cache[$cache_key] = $file; if ($file) { - require_once DRUPAL_ROOT . '/' . $file; + include_once DRUPAL_ROOT . '/' . $file; return TRUE; } else { diff --git a/includes/cache.inc b/includes/cache.inc index 207bf661..945dd663 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -14,6 +14,7 @@ * * @param $bin * The cache bin for which the cache object should be returned. + * * @return DrupalCacheInterface * The cache object associated with the specified bin. * @@ -121,7 +122,12 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * the administrator panel. * - cache_path: Stores the system paths that have an alias. * @param $expire - * (optional) One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use one of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next @@ -261,7 +267,12 @@ interface DrupalCacheInterface { * 1MB in size to be stored by default. When caching large arrays or * similar, take care to ensure $data does not exceed this size. * @param $expire - * (optional) One of the following values: + * (optional) Controls the maximum lifetime of this cache entry. Note that + * caches might be subject to clearing at any time, so this setting does not + * guarantee a minimum lifetime. With this in mind, the cache should not be + * used for data that must be kept during a cache clear, like sessions. + * + * Use one of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next diff --git a/includes/common.inc b/includes/common.inc index 472485f7..a9855658 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -488,7 +488,7 @@ function drupal_http_build_query(array $query, $parent = '') { $params = array(); foreach ($query as $key => $value) { - $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key)); + $key = $parent ? $parent . rawurlencode('[' . $key . ']') : rawurlencode($key); // Recurse into children. if (is_array($value)) { @@ -690,6 +690,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. @@ -755,7 +762,8 @@ function drupal_access_denied() { * - headers: An array containing request headers to send as name/value pairs. * - method: A string containing the request method. Defaults to 'GET'. * - data: A string containing the request body, formatted as - * 'param=value¶m=value&...'. Defaults to NULL. + * 'param=value¶m=value&...'; to generate this, use http_build_query(). + * Defaults to NULL. * - max_redirects: An integer representing how many times a redirect * may be followed. Defaults to 3. * - timeout: A float representing the maximum number of seconds the function @@ -780,6 +788,8 @@ function drupal_access_denied() { * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for * easy access the array keys are returned in lower case. * - data: A string containing the response body that was received. + * + * @see http_build_query() */ function drupal_http_request($url, array $options = array()) { // Allow an alternate HTTP client library to replace Drupal's default @@ -1059,6 +1069,12 @@ function drupal_http_request($url, array $options = array()) { switch ($code) { case 200: // OK + case 201: // Created + case 202: // Accepted + case 203: // Non-Authoritative Information + case 204: // No Content + case 205: // Reset Content + case 206: // Partial Content case 304: // Not modified break; case 301: // Moved permanently @@ -1756,9 +1772,15 @@ function format_rss_item($title, $link, $description, $args = array()) { * - 'key': element name * - 'value': element contents * - 'attributes': associative array of element attributes + * - 'encoded': TRUE if 'value' is already encoded * * In both cases, 'value' can be a simple string, or it can be another array * with the same format as $array itself for nesting. + * + * If 'encoded' is TRUE it is up to the caller to ensure that 'value' is either + * entity-encoded or CDATA-escaped. Using this option is not recommended when + * working with untrusted user input, since failing to escape the data + * correctly has security implications. */ function format_xml_elements($array) { $output = ''; @@ -1771,7 +1793,7 @@ function format_xml_elements($array) { } if (isset($value['value']) && $value['value'] != '') { - $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '\n"; + $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : (!empty($value['encoded']) ? $value['value'] : check_plain($value['value']))) . '\n"; } else { $output .= " />\n"; @@ -2216,20 +2238,11 @@ 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. + // Determine whether this is an external link, but ensure that the current + // path is always treated as internal by default (to prevent external link + // injection vulnerabilities). 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'] = $path === $_GET['q'] ? FALSE : url_is_external($path); } // Preserve the original path before altering or aliasing. @@ -2299,7 +2312,10 @@ function url($path = NULL, array $options = array()) { $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; $alias = drupal_get_path_alias($original_path, $language); if ($alias != $original_path) { - $path = $alias; + // Strip leading slashes from internal path aliases to prevent them + // becoming external URLs without protocol. /example.com should not be + // turned into //example.com. + $path = ltrim($alias, '/'); } } @@ -2349,12 +2365,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); @@ -2639,6 +2661,15 @@ function drupal_deliver_html_page($page_callback_result) { global $language; drupal_add_http_header('Content-Language', $language->language); + // By default, do not allow the site to be rendered in an iframe on another + // domain, but provide a variable to override this. If the code running for + // this page request already set the X-Frame-Options header earlier, don't + // overwrite it here. + $frame_options = variable_get('x_frame_options', 'SAMEORIGIN'); + if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) { + drupal_add_http_header('X-Frame-Options', $frame_options); + } + // Menu status constants are integers; page content is a string or array. if (is_int($page_callback_result)) { // @todo: Break these up into separate functions? @@ -2753,6 +2784,7 @@ function drupal_page_footer() { _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); drupal_cache_system_paths(); module_implements_write_cache(); + drupal_file_scan_write_cache(); system_run_automated_cron(); } @@ -2814,11 +2846,11 @@ function drupal_map_assoc($array, $function = NULL) { * into script execution a call such as set_time_limit(20) is made, the * script will run for a total of 45 seconds before timing out. * - * It also means that it is possible to decrease the total time limit if - * the sum of the new time limit and the current time spent running the - * script is inferior to the original time limit. It is inherent to the way - * set_time_limit() works, it should rather be called with an appropriate - * value every time you need to allocate a certain amount of time + * If the current time limit is not unlimited it is possible to decrease the + * total time limit if the sum of the new time limit and the current time spent + * running the script is inferior to the original time limit. It is inherent to + * the way set_time_limit() works, it should rather be called with an + * appropriate value every time you need to allocate a certain amount of time * to execute a task than only once at the beginning of the script. * * Before calling set_time_limit(), we check if this function is available @@ -2835,7 +2867,11 @@ function drupal_map_assoc($array, $function = NULL) { */ function drupal_set_time_limit($time_limit) { if (function_exists('set_time_limit')) { - @set_time_limit($time_limit); + $current = ini_get('max_execution_time'); + // Do not set time limit if it is currently unlimited. + if ($current != 0) { + @set_time_limit($time_limit); + } } } @@ -3016,6 +3052,13 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { */ function drupal_add_css($data = NULL, $options = NULL) { $css = &drupal_static(__FUNCTION__, array()); + $count = &drupal_static(__FUNCTION__ . '_count', 0); + + // If the $css variable has been reset with drupal_static_reset(), there is + // no longer any CSS being tracked, so set the counter back to 0 also. + if (count($css) === 0) { + $count = 0; + } // Construct the options, taking the defaults into consideration. if (isset($options)) { @@ -3051,7 +3094,8 @@ function drupal_add_css($data = NULL, $options = NULL) { } // Always add a tiny value to the weight, to conserve the insertion order. - $options['weight'] += count($css) / 1000; + $options['weight'] += $count / 1000; + $count++; // Add the data to the CSS array depending on the type. switch ($options['type']) { @@ -3864,6 +3908,21 @@ function drupal_delete_file_if_stale($uri) { * The cleaned identifier. */ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['allow_css_double_underscores'] = &drupal_static(__FUNCTION__ . ':allow_css_double_underscores'); + } + $allow_css_double_underscores = &$drupal_static_fast['allow_css_double_underscores']; + if (!isset($allow_css_double_underscores)) { + $allow_css_double_underscores = variable_get('allow_css_double_underscores', FALSE); + } + + // Preserve BEM-style double-underscores depending on custom setting. + if ($allow_css_double_underscores) { + $filter['__'] = '__'; + } + // By default, we filter using Drupal's coding standards. $identifier = strtr($identifier, $filter); @@ -3935,7 +3994,11 @@ function drupal_html_id($id) { // be merged with content already on the base page. The HTML IDs must be // unique for the fully merged content. Therefore, initialize $seen_ids to // take into account IDs that are already in use on the base page. - $seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); + static $drupal_static_fast; + if (!isset($drupal_static_fast['seen_ids_init'])) { + $drupal_static_fast['seen_ids_init'] = &drupal_static(__FUNCTION__ . ':init'); + } + $seen_ids_init = &$drupal_static_fast['seen_ids_init']; if (!isset($seen_ids_init)) { // Ideally, Drupal would provide an API to persist state information about // prior page requests in the database, and we'd be able to add this @@ -3980,7 +4043,10 @@ function drupal_html_id($id) { } } } - $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init); + if (!isset($drupal_static_fast['seen_ids'])) { + $drupal_static_fast['seen_ids'] = &drupal_static(__FUNCTION__, $seen_ids_init); + } + $seen_ids = &$drupal_static_fast['seen_ids']; $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); @@ -5358,6 +5424,11 @@ function _drupal_bootstrap_full() { fix_gpc_magic(); // Load all enabled modules module_load_all(); + // Reset drupal_alter() and module_implements() static caches as these + // include implementations for vital modules only when called early on + // in the bootstrap. + drupal_static_reset('drupal_alter'); + drupal_static_reset('module_implements'); // Make sure all stream wrappers are registered. file_get_stream_wrappers(); // Ensure mt_rand is reseeded, to prevent random values from one page load @@ -5454,8 +5525,8 @@ function drupal_page_set_cache() { * * Do not call this function from a test. Use $this->cronRun() instead. * - * @return - * TRUE if cron ran successfully. + * @return bool + * TRUE if cron ran successfully and FALSE if cron is already running. */ function drupal_cron_run() { // Allow execution to continue even if the request gets canceled. @@ -5517,12 +5588,12 @@ function drupal_cron_run() { // Do not run if queue wants to skip. continue; } - $function = $info['worker callback']; + $callback = $info['worker callback']; $end = time() + (isset($info['time']) ? $info['time'] : 15); $queue = DrupalQueue::get($queue_name); while (time() < $end && ($item = $queue->claimItem())) { try { - $function($item->data); + call_user_func($callback, $item->data); $queue->deleteItem($item); } catch (Exception $e) { @@ -7229,7 +7300,8 @@ function drupal_uninstall_schema($module) { * specification of a schema, as it was defined in a module's * hook_schema(). No additional default values will be set, * hook_schema_alter() is not invoked and these unprocessed - * definitions won't be cached. + * definitions won't be cached. To retrieve the schema after + * hook_schema_alter() has been invoked use drupal_get_schema(). * * This function can be used to retrieve a schema specification in * hook_schema(), so it allows you to derive your tables from existing @@ -7302,6 +7374,7 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU */ function drupal_schema_field_types($table) { $table_schema = drupal_get_schema($table); + $field_types = array(); foreach ($table_schema['fields'] as $field_name => $field_info) { $field_types[$field_name] = isset($field_info['type']) ? $field_info['type'] : NULL; } @@ -7509,7 +7582,16 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { * Information stored in a module .info file: * - name: The real name of the module for display purposes. * - description: A brief description of the module. - * - dependencies: An array of shortnames of other modules this module requires. + * - dependencies: An array of dependency strings. Each is in the form + * 'project:module (versions)'; with the following meanings: + * - project: (optional) Project shortname, recommended to ensure uniqueness, + * if the module is part of a project hosted on drupal.org. If omitted, + * also omit the : that follows. The project name is currently ignored by + * Drupal core but is used for automated testing. + * - module: (required) Module shortname within the project. + * - (versions): Optional version information, consisting of one or more + * comma-separated operator/value pairs or simply version numbers, which + * can contain "x" as a wildcard. Examples: (>=7.22, <7.28), (7.x-3.x). * - package: The name of the package of modules this module belongs to. * * See forum.info for an example of a module .info file. @@ -7589,7 +7671,6 @@ function drupal_parse_info_file($filename) { */ function drupal_parse_info_format($data) { $info = array(); - $constants = get_defined_constants(); if (preg_match_all(' @^\s* # Start at the beginning of a line, ignoring leading whitespace @@ -7629,8 +7710,8 @@ function drupal_parse_info_format($data) { } // Handle PHP constants. - if (isset($constants[$value])) { - $value = $constants[$value]; + if (preg_match('/^\w+$/i', $value) && defined($value)) { + $value = constant($value); } // Insert actual value. @@ -7794,7 +7875,12 @@ function debug($data, $label = NULL, $print_r = FALSE) { * Parses a dependency for comparison by drupal_check_incompatibility(). * * @param $dependency - * A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'. + * A dependency string, which specifies a module dependency, and optionally + * the project it comes from and versions that are supported. Supported + * formats include: + * - 'module' + * - 'project:module' + * - 'project:module (>=version, version)' * * @return * An associative array with three keys: @@ -7809,6 +7895,12 @@ function debug($data, $label = NULL, $print_r = FALSE) { * @see drupal_check_incompatibility() */ function drupal_parse_dependency($dependency) { + $value = array(); + // Split out the optional project name. + if (strpos($dependency, ':')) { + list($project_name, $dependency) = explode(':', $dependency); + $value['project'] = $project_name; + } // We use named subpatterns and support every op that version_compare // supports. Also, op is optional and defaults to equals. $p_op = '(?P!=|==|=|<|<=|>|>=|<>)?'; @@ -7817,7 +7909,6 @@ function drupal_parse_dependency($dependency) { $p_major = '(?P\d+)'; // By setting the minor version to x, branches can be matched. $p_minor = '(?P(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; - $value = array(); $parts = explode('(', $dependency, 2); $value['name'] = trim($parts[0]); if (isset($parts[1])) { diff --git a/includes/database/database.inc b/includes/database/database.inc index 3d776b57..6879f699 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -296,6 +296,20 @@ abstract class DatabaseConnection extends PDO { */ protected $prefixReplace = array(); + /** + * List of escaped database, table, and field names, keyed by unescaped names. + * + * @var array + */ + protected $escapedNames = array(); + + /** + * List of escaped aliases names, keyed by unescaped aliases. + * + * @var array + */ + protected $escapedAliases = array(); + function __construct($dsn, $username, $password, $driver_options = array()) { // Initialize and prepare the connection prefix. $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); @@ -656,7 +670,7 @@ abstract class DatabaseConnection extends PDO { * @return DatabaseStatementInterface * This method will return one of: the executed statement, the number of * rows affected by the query (not the number matched), or the generated - * insert IT of the last query, depending on the value of + * insert ID of the last query, depending on the value of * $options['return']. Typically that value will be set by default or a * query builder and should not be set by a user. If there is an error, * this method will return NULL and may throw an exception if @@ -919,11 +933,14 @@ abstract class DatabaseConnection extends PDO { * For some database drivers, it may also wrap the table name in * database-specific escape characters. * - * @return + * @return string * The sanitized table name string. */ public function escapeTable($table) { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); + if (!isset($this->escapedNames[$table])) { + $this->escapedNames[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table); + } + return $this->escapedNames[$table]; } /** @@ -933,11 +950,14 @@ abstract class DatabaseConnection extends PDO { * For some database drivers, it may also wrap the field name in * database-specific escape characters. * - * @return + * @return string * The sanitized field name string. */ public function escapeField($field) { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $field); + if (!isset($this->escapedNames[$field])) { + $this->escapedNames[$field] = preg_replace('/[^A-Za-z0-9_.]+/', '', $field); + } + return $this->escapedNames[$field]; } /** @@ -948,11 +968,14 @@ abstract class DatabaseConnection extends PDO { * DatabaseConnection::escapeTable(), this doesn't allow the period (".") * because that is not allowed in aliases. * - * @return + * @return string * The sanitized field name string. */ public function escapeAlias($field) { - return preg_replace('/[^A-Za-z0-9_]+/', '', $field); + if (!isset($this->escapedAliases[$field])) { + $this->escapedAliases[$field] = preg_replace('/[^A-Za-z0-9_]+/', '', $field); + } + return $this->escapedAliases[$field]; } /** @@ -1313,6 +1336,39 @@ abstract class DatabaseConnection extends PDO { * also larger than the $existing_id if one was passed in. */ abstract public function nextId($existing_id = 0); + + /** + * Checks whether utf8mb4 support is configurable in settings.php. + * + * @return bool + */ + public function utf8mb4IsConfigurable() { + // Since 4 byte UTF-8 is not supported by default, there is nothing to + // configure. + return FALSE; + } + + /** + * Checks whether utf8mb4 support is currently active. + * + * @return bool + */ + public function utf8mb4IsActive() { + // Since 4 byte UTF-8 is not supported by default, there is nothing to + // activate. + return FALSE; + } + + /** + * Checks whether utf8mb4 support is available on the current database system. + * + * @return bool + */ + public function utf8mb4IsSupported() { + // By default we assume that the database backend may not support 4 byte + // UTF-8. + return FALSE; + } } /** diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 0b84f271..356e039f 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -28,6 +28,12 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->connectionOptions = $connection_options; + $charset = 'utf8'; + // Check if the charset is overridden to utf8mb4 in settings.php. + if ($this->utf8mb4IsActive()) { + $charset = 'utf8mb4'; + } + // The DSN should use either a socket or a host/port. if (isset($connection_options['unix_socket'])) { $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket']; @@ -39,7 +45,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { // Character set is added to dsn to ensure PDO uses the proper character // set when escaping. This has security implications. See // https://www.drupal.org/node/1201452 for further discussion. - $dsn .= ';charset=utf8'; + $dsn .= ';charset=' . $charset; $dsn .= ';dbname=' . $connection_options['database']; // Allow PDO options to be overridden. $connection_options += array( @@ -51,6 +57,11 @@ class DatabaseConnection_mysql extends DatabaseConnection { // Because MySQL's prepared statements skip the query cache, because it's dumb. PDO::ATTR_EMULATE_PREPARES => TRUE, ); + if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) { + // An added connection option in PHP 5.5.21+ to optionally limit SQL to a + // single statement like mysqli. + $connection_options['pdo'] += array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE); + } parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); @@ -58,10 +69,10 @@ class DatabaseConnection_mysql extends DatabaseConnection { // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci' // for UTF-8. if (!empty($connection_options['collation'])) { - $this->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']); + $this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']); } else { - $this->exec('SET NAMES utf8'); + $this->exec('SET NAMES ' . $charset); } // Set MySQL init_commands if not already defined. Default Drupal's MySQL @@ -76,10 +87,12 @@ class DatabaseConnection_mysql extends DatabaseConnection { 'init_commands' => array(), ); $connection_options['init_commands'] += array( - 'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", + 'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", ); - // Set connection options. - $this->exec(implode('; ', $connection_options['init_commands'])); + // Execute initial commands. + foreach ($connection_options['init_commands'] as $sql) { + $this->exec($sql); + } } public function __destruct() { @@ -199,6 +212,42 @@ class DatabaseConnection_mysql extends DatabaseConnection { } } } + + public function utf8mb4IsConfigurable() { + return TRUE; + } + + public function utf8mb4IsActive() { + return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4'; + } + + public function utf8mb4IsSupported() { + // Ensure that the MySQL driver supports utf8mb4 encoding. + $version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION); + if (strpos($version, 'mysqlnd') !== FALSE) { + // The mysqlnd driver supports utf8mb4 starting at version 5.0.9. + $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version); + if (version_compare($version, '5.0.9', '<')) { + return FALSE; + } + } + else { + // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3. + if (version_compare($version, '5.5.3', '<')) { + return FALSE; + } + } + + // Ensure that the MySQL server supports large prefixes and utf8mb4. + try { + $this->query("CREATE TABLE {drupal_utf8mb4_test} (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB"); + } + catch (Exception $e) { + return FALSE; + } + $this->query("DROP TABLE {drupal_utf8mb4_test}"); + return TRUE; + } } diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc index 2a2722e6..9ba1c733 100644 --- a/includes/database/mysql/schema.inc +++ b/includes/database/mysql/schema.inc @@ -39,8 +39,8 @@ class DatabaseSchema_mysql extends DatabaseSchema { $info['table'] = substr($table, ++$pos); } else { - $db_info = Database::getConnectionInfo(); - $info['database'] = $db_info[$this->connection->getTarget()]['database']; + $db_info = $this->connection->getConnectionOptions(); + $info['database'] = $db_info['database']; $info['table'] = $table; } return $info; @@ -81,7 +81,8 @@ class DatabaseSchema_mysql extends DatabaseSchema { // Provide defaults if needed. $table += array( 'mysql_engine' => 'InnoDB', - 'mysql_character_set' => 'utf8', + // Allow the default charset to be overridden in settings.php. + 'mysql_character_set' => $this->connection->utf8mb4IsActive() ? 'utf8mb4' : 'utf8', ); $sql = "CREATE TABLE {" . $name . "} (\n"; @@ -109,6 +110,13 @@ class DatabaseSchema_mysql extends DatabaseSchema { $sql .= ' COLLATE ' . $info['collation']; } + // The row format needs to be either DYNAMIC or COMPRESSED in order to allow + // for the innodb_large_prefix setting to take effect, see + // https://dev.mysql.com/doc/refman/5.6/en/create-table.html + if ($this->connection->utf8mb4IsActive()) { + $sql .= ' ROW_FORMAT=DYNAMIC'; + } + // Add table comment. if (!empty($table['description'])) { $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE); diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index 67b49fed..fb3d0ab5 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -11,7 +11,7 @@ */ /** - * The name by which to obtain a lock for retrive the next insert id. + * The name by which to obtain a lock for retrieving the next insert id. */ define('POSTGRESQL_NEXTID_LOCK', 1000); @@ -55,7 +55,7 @@ class DatabaseConnection_pgsql extends DatabaseConnection { $connection_options['pdo'] += array( // Prepared statements are most effective for performance when queries // are recycled (used several times). However, if they are not re-used, - // prepared statements become ineffecient. Since most of Drupal's + // prepared statements become inefficient. Since most of Drupal's // prepared queries are not re-used, it should be faster to emulate // the preparation than to actually ready statements for re-use. If in // doubt, reset to FALSE and measure performance. @@ -175,14 +175,14 @@ class DatabaseConnection_pgsql extends DatabaseConnection { } /** - * Retrive a the next id in a sequence. + * Retrieve the next id in a sequence. * * PostgreSQL has built in sequences. We'll use these instead of inserting * and updating a sequences table. */ public function nextId($existing = 0) { - // Retrive the name of the sequence. This information cannot be cached + // Retrieve the name of the sequence. This information cannot be cached // because the prefix may change, for example, like it does in simpletests. $sequence_name = $this->makeSequenceName('sequences', 'value'); @@ -194,7 +194,7 @@ class DatabaseConnection_pgsql extends DatabaseConnection { } // PostgreSQL advisory locks are simply locks to be used by an - // application such as Drupal. This will prevent other Drupal proccesses + // application such as Drupal. This will prevent other Drupal processes // from altering the sequence while we are. $this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")"); @@ -209,13 +209,21 @@ class DatabaseConnection_pgsql extends DatabaseConnection { // Reset the sequence to a higher value than the existing id. $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1)); - // Retrive the next id. We know this will be as high as we want it. + // Retrieve the next id. We know this will be as high as we want it. $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")"); return $id; } + + public function utf8mb4IsActive() { + return TRUE; + } + + public function utf8mb4IsSupported() { + return TRUE; + } } /** diff --git a/includes/database/pgsql/install.inc b/includes/database/pgsql/install.inc index c77f4ea7..122031ee 100644 --- a/includes/database/pgsql/install.inc +++ b/includes/database/pgsql/install.inc @@ -165,7 +165,7 @@ class DatabaseTasks_pgsql extends DatabaseTasks { LANGUAGE \'sql\'' ); - // Using || to concatenate in Drupal is not recommeneded because there are + // Using || to concatenate in Drupal is not recommended because there are // database drivers for Drupal that do not support the syntax, however // they do support CONCAT(item1, item2) which we can replicate in // PostgreSQL. PostgreSQL requires the function to be defined for each diff --git a/includes/database/pgsql/select.inc b/includes/database/pgsql/select.inc index f6a83db7..5b8736d6 100644 --- a/includes/database/pgsql/select.inc +++ b/includes/database/pgsql/select.inc @@ -80,7 +80,7 @@ class SelectQuery_pgsql extends SelectQuery { } // If a table loads all fields, it can not be added again. It would - // result in an ambigious alias error because that field would be loaded + // result in an ambiguous alias error because that field would be loaded // twice: Once through table_alias.* and once directly. If the field // actually belongs to a different table, it must be added manually. foreach ($this->tables as $table) { @@ -90,7 +90,7 @@ class SelectQuery_pgsql extends SelectQuery { } // If $field contains an characters which are not allowed in a field name - // it is considered an expression, these can't be handeld automatically + // it is considered an expression, these can't be handled automatically // either. if ($this->connection->escapeField($field) != $field) { return $return; diff --git a/includes/database/query.inc b/includes/database/query.inc index c9c5a832..048c8a26 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -845,8 +845,8 @@ class DeleteQuery extends Query implements QueryConditionInterface { /** * Executes the DELETE query. * - * @return - * The return value is dependent on the database connection. + * @return int + * The number of rows affected by the delete query. */ public function execute() { $values = array(); @@ -1242,7 +1242,7 @@ class UpdateQuery extends Query implements QueryConditionInterface { * MergeQuery::updateFields() and MergeQuery::insertFields() needs to be called * instead. MergeQuery::fields() can also be called which calls both of these * methods as the common case is to use the same column-value pairs for both - * INSERT and UPDATE. However, this is not mandatory. Another convinient + * INSERT and UPDATE. However, this is not mandatory. Another convenient * wrapper is MergeQuery::key() which adds the same column-value pairs to the * condition and the INSERT query part. * diff --git a/includes/database/schema.inc b/includes/database/schema.inc index 1fc92954..31862db3 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -92,7 +92,8 @@ require_once dirname(__FILE__) . '/query.inc'; * specification). Each specification is an array containing the name of * the referenced table ('table'), and an array of column mappings * ('columns'). Column mappings are defined by key pairs ('source_column' => - * 'referenced_column'). + * 'referenced_column'). This key is for documentation purposes only; foreign + * keys are not created in the database, nor are they enforced by Drupal. * - 'indexes': An associative array of indexes ('indexname' => * specification). Each specification is an array of one or more * key column specifiers (see below) that form an index on the @@ -144,6 +145,8 @@ require_once dirname(__FILE__) . '/query.inc'; * 'unique keys' => array( * 'vid' => array('vid'), * ), + * // For documentation purposes only; foreign keys are not created in the + * // database. * 'foreign keys' => array( * 'node_revision' => array( * 'table' => 'node_revision', @@ -161,6 +164,9 @@ require_once dirname(__FILE__) . '/query.inc'; * @see drupal_install_schema() */ +/** + * Base class for database schema definitions. + */ abstract class DatabaseSchema implements QueryPlaceholderInterface { protected $connection; @@ -288,7 +294,7 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { $info = $this->connection->getConnectionOptions(); - // Retrive the table name and schema + // Retrieve the table name and schema $table_info = $this->getPrefixInfo($table_name, $add_prefix); $condition = new DatabaseCondition('AND'); diff --git a/includes/database/select.inc b/includes/database/select.inc index 3abd205c..8d84460e 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1231,6 +1231,21 @@ class SelectQuery extends Query implements SelectQueryInterface { // Modules may alter all queries or only those having a particular tag. if (isset($this->alterTags)) { + // Many contrib modules assume that query tags used for access-checking + // purposes follow the pattern $entity_type . '_access'. But this is + // not the case for taxonomy terms, since core used to add term_access + // instead of taxonomy_term_access to its queries. Provide backwards + // compatibility by adding both tags here instead of attempting to fix + // all contrib modules in a coordinated effort. + // TODO: + // - Extract this mechanism into a hook as part of a public (non-security) + // issue. + // - Emit E_USER_DEPRECATED if term_access is used. + // https://www.drupal.org/node/2575081 + $term_access_tags = array('term_access' => 1, 'taxonomy_term_access' => 1); + if (array_intersect_key($this->alterTags, $term_access_tags)) { + $this->alterTags += $term_access_tags; + } $hooks = array('query'); foreach ($this->alterTags as $tag => $value) { $hooks[] = 'query_' . $tag; diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 8a5ba8c9..589a1728 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -378,6 +378,14 @@ class DatabaseConnection_sqlite extends DatabaseConnection { } } + public function utf8mb4IsActive() { + return TRUE; + } + + public function utf8mb4IsSupported() { + return TRUE; + } + } /** diff --git a/includes/database/sqlite/install.inc b/includes/database/sqlite/install.inc index 62cbac38..10884e2f 100644 --- a/includes/database/sqlite/install.inc +++ b/includes/database/sqlite/install.inc @@ -14,8 +14,6 @@ class DatabaseTasks_sqlite extends DatabaseTasks { /** * Minimum engine version. - * - * @todo: consider upping to 3.6.8 in Drupal 8 to get SAVEPOINT support. */ public function minimumVersion() { return '3.3.7'; diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index 1c6289bd..c9c028bb 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -99,16 +99,15 @@ class UpdateQuery_sqlite extends UpdateQuery { /** * SQLite specific implementation of DeleteQuery. - * - * When the WHERE is omitted from a DELETE statement and the table being deleted - * has no triggers, SQLite uses an optimization to erase the entire table content - * without having to visit each row of the table individually. - * - * Prior to SQLite 3.6.5, SQLite does not return the actual number of rows deleted - * by that optimized "truncate" optimization. */ class DeleteQuery_sqlite extends DeleteQuery { public function execute() { + // When the WHERE is omitted from a DELETE statement and the table being + // deleted has no triggers, SQLite uses an optimization to erase the entire + // table content without having to visit each row of the table individually. + // Prior to SQLite 3.6.5, SQLite does not return the actual number of rows + // deleted by that optimized "truncate" optimization. But we want to return + // the number of rows affected, so we calculate it directly. if (!count($this->condition)) { $total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField(); parent::execute(); diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc index df19d2fa..281d8fc6 100644 --- a/includes/database/sqlite/schema.inc +++ b/includes/database/sqlite/schema.inc @@ -244,7 +244,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema { // database. So the syntax '...RENAME TO database.table' would fail. // So we must determine the full table name here rather than surrounding // the table with curly braces incase the db_prefix contains a reference - // to a database outside of our existsing database. + // to a database outside of our existing database. $info = $this->getPrefixInfo($new_name); $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']); diff --git a/includes/date.inc b/includes/date.inc index 01ab131b..9e68fee4 100644 --- a/includes/date.inc +++ b/includes/date.inc @@ -12,11 +12,6 @@ function system_default_date_formats() { $formats = array(); // Short date formats. - $formats[] = array( - 'type' => 'short', - 'format' => 'Y-m-d H:i', - 'locales' => array(), - ); $formats[] = array( 'type' => 'short', 'format' => 'm/d/Y - H:i', @@ -37,6 +32,11 @@ function system_default_date_formats() { 'format' => 'd.m.Y - H:i', 'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'), ); + $formats[] = array( + 'type' => 'short', + 'format' => 'Y-m-d H:i', + 'locales' => array(), + ); $formats[] = array( 'type' => 'short', 'format' => 'm/d/Y - g:ia', @@ -84,11 +84,6 @@ function system_default_date_formats() { ); // Medium date formats. - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, Y-m-d H:i', - 'locales' => array(), - ); $formats[] = array( 'type' => 'medium', 'format' => 'D, m/d/Y - H:i', @@ -104,6 +99,11 @@ function system_default_date_formats() { 'format' => 'D, Y/m/d - H:i', 'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'), ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, Y-m-d H:i', + 'locales' => array(), + ); $formats[] = array( 'type' => 'medium', 'format' => 'F j, Y - H:i', diff --git a/includes/entity.inc b/includes/entity.inc index 62359a94..e80ce3b8 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -446,7 +446,7 @@ class EntityFieldQueryException extends Exception {} * * This class allows finding entities based on entity properties (for example, * node->changed), field values, and generic entity meta data (bundle, - * entity type, entity id, and revision ID). It is not possible to query across + * entity type, entity ID, and revision ID). It is not possible to query across * multiple entity types. For example, there is no facility to find published * nodes written by users created in the last hour, as this would require * querying both node->status and user->created. @@ -688,14 +688,36 @@ class EntityFieldQuery { * @param $field * Either a field name or a field array. * @param $column - * The column that should hold the value to be matched. + * The column that should hold the value to be matched, defined in the + * hook_field_schema() of this field. If this is omitted then all of the + * other parameters are ignored, except $field, and this call will just be + * adding a condition that says that the field has a value, rather than + * testing the value itself. * @param $value - * The value to test the column value against. + * The value to test the column value against. In most cases, this is a + * scalar. For more complex options, it is an array. The meaning of each + * element in the array is dependent on $operator. * @param $operator - * The operator to be used to test the given value. + * The operator to be used to test the given value. The possible values are: + * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * The operator can be omitted, and will default to 'IN' if the value is an + * array, or to '=' otherwise. * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same - * $delta_group. + * $delta_group. For example, let's presume a multivalue field which has + * two columns, 'color' and 'shape', and for entity ID 1, there are two + * values: red/square and blue/circle. Entity ID 1 does not have values + * corresponding to 'red circle'; however if you pass 'red' and 'circle' as + * conditions, it will appear in the results -- by default queries will run + * against any combination of deltas. By passing the conditions with the + * same $delta_group it will ensure that only values attached to the same + * delta are matched, and entity 1 would then be excluded from the results. * @param $language_group * An arbitrary identifier: conditions in the same group must have the same * $language_group. @@ -770,9 +792,11 @@ class EntityFieldQuery { * @param $field * Either a field name or a field array. * @param $column - * A column defined in the hook_field_schema() of this field. If this is - * omitted then the query will find only entities that have data in this - * field, using the entity and property conditions if there are any. + * The column that should hold the value to be matched, defined in the + * hook_field_schema() of this field. If this is omitted then all of the + * other parameters are ignored, except $field, and this call will just be + * adding a condition that says that the field has a value, rather than + * testing the value itself. * @param $value * The value to test the column value against. In most cases, this is a * scalar. For more complex options, it is an array. The meaning of each @@ -791,10 +815,10 @@ class EntityFieldQuery { * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same * $delta_group. For example, let's presume a multivalue field which has - * two columns, 'color' and 'shape', and for entity id 1, there are two + * two columns, 'color' and 'shape', and for entity ID 1, there are two * values: red/square and blue/circle. Entity ID 1 does not have values * corresponding to 'red circle', however if you pass 'red' and 'circle' as - * conditions, it will appear in the results - by default queries will run + * conditions, it will appear in the results -- by default queries will run * against any combination of deltas. By passing the conditions with the * same $delta_group it will ensure that only values attached to the same * delta are matched, and entity 1 would then be excluded from the results. diff --git a/includes/errors.inc b/includes/errors.inc index a9b7b5bd..3548d1fd 100644 --- a/includes/errors.inc +++ b/includes/errors.inc @@ -66,7 +66,7 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c _drupal_log_error(array( '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', // The standard PHP error handler considers that the error messages - // are HTML. We mimick this behavior here. + // are HTML. We mimic this behavior here. '!message' => filter_xss_admin($message), '%function' => $caller['function'], '%file' => $caller['file'], @@ -114,7 +114,7 @@ function _drupal_decode_exception($exception) { return array( '%type' => get_class($exception), // The standard PHP exception handler considers that the exception message - // is plain-text. We mimick this behavior here. + // is plain-text. We mimic this behavior here. '!message' => check_plain($message), '%function' => $caller['function'], '%file' => $caller['file'], @@ -199,7 +199,16 @@ function _drupal_log_error($error, $fatal = FALSE) { $number++; } - watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + // Log the error immediately, unless this is a non-fatal error which has been + // triggered via drupal_trigger_error_with_delayed_logging(); in that case + // trigger it in a shutdown function. Fatal errors are always triggered + // immediately since for a fatal error the page request will end here anyway. + if (!$fatal && drupal_static('_drupal_trigger_error_with_delayed_logging')) { + drupal_register_shutdown_function('watchdog', 'php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + } + else { + watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + } if ($fatal) { drupal_add_http_header('Status', '500 Service unavailable (with message)'); @@ -224,7 +233,7 @@ function _drupal_log_error($error, $fatal = FALSE) { } else { // Display the message if the current error reporting level allows this type - // of message to be displayed, and unconditionnaly in update.php. + // of message to be displayed, and unconditionally in update.php. if (error_displayable($error)) { $class = 'error'; diff --git a/includes/file.inc b/includes/file.inc index d3ac87ea..fa7f5eb5 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -273,7 +273,9 @@ function file_default_scheme() { * The normalized URI. */ function file_stream_wrapper_uri_normalize($uri) { - $scheme = file_uri_scheme($uri); + // Inline file_uri_scheme() function call for performance reasons. + $position = strpos($uri, '://'); + $scheme = $position ? substr($uri, 0, $position) : FALSE; if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { $target = file_uri_target($uri); @@ -533,7 +535,18 @@ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 EOF; if ($private) { - $lines = "Deny from all\n\n" . $lines; + $lines = << + Require all denied + + +# Deny all requests from Apache 2.0-2.2. + + Deny from all + +EOF + . "\n\n" . $lines; } return $lines; @@ -887,7 +900,6 @@ function file_valid_uri($uri) { */ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { $original_source = $source; - $original_destination = $destination; // Assert that the source file actually exists. if (!file_exists($source)) { @@ -1602,6 +1614,20 @@ function file_save_upload($form_field_name, $validators = array(), $destination // If we made it this far it's safe to record this file in the database. if ($file = file_save($file)) { + // Track non-public files in the session if they were uploaded by an + // anonymous user. This allows modules such as the File module to only + // grant view access to the specific anonymous user who uploaded the file. + // See file_file_download(). + // The 'file_public_schema' variable is used to allow other publicly + // accessible file schemes to be treated the same as the public:// scheme + // provided by Drupal core and to avoid adding unnecessary data to the + // session (and the resulting bypass of the page cache) in those cases. For + // security reasons, only schemes that are completely publicly accessible, + // with no download restrictions, should be added to this variable. See + // file_managed_file_value(). + if (!$user->uid && !in_array($destination_scheme, variable_get('file_public_schema', array('public')))) { + $_SESSION['anonymous_allowed_file_ids'][$file->fid] = $file->fid; + } // Add file to the cache. $upload_cache[$form_field_name] = $file; return $file; @@ -1785,7 +1811,7 @@ function file_validate_is_image(stdClass $file) { /** * Verifies that image dimensions are within the specified maximum and minimum. * - * Non-image files will be ignored. If a image toolkit is available the image + * Non-image files will be ignored. If an image toolkit is available the image * will be scaled to fit within the desired maximum dimensions. * * @param $file @@ -2022,7 +2048,7 @@ function file_download() { * * @see file_transfer() * @see file_download_access() - * @see hook_file_downlaod() + * @see hook_file_download() */ function file_download_headers($uri) { // Let other modules provide headers and control access to the file. @@ -2551,7 +2577,6 @@ function file_directory_temp() { * An associative array of headers, as expected by file_transfer(). */ function file_get_content_headers($file) { - $name = mime_header_encode($file->filename); $type = mime_header_encode($file->filemime); return array( diff --git a/includes/form.inc b/includes/form.inc index f7671bed..e749239e 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -105,7 +105,8 @@ * generate the same form (or very similar forms) using different $form_ids * can implement hook_forms(), which maps different $form_id values to the * proper form constructor function. Examples may be found in node_forms(), - * and search_forms(). + * and search_forms(). hook_forms() can also be used to define forms in + * classes. * @param ... * Any additional arguments are passed on to the functions called by * drupal_get_form(), including the unique form constructor function. For @@ -809,7 +810,7 @@ function drupal_retrieve_form($form_id, &$form_state) { } if (isset($form_definition['callback'])) { $callback = $form_definition['callback']; - $form_state['build_info']['base_form_id'] = $callback; + $form_state['build_info']['base_form_id'] = isset($form_definition['base_form_id']) ? $form_definition['base_form_id'] : $callback; } // In case $form_state['wrapper_callback'] is not defined already, we also // allow hook_forms() to define one. @@ -830,7 +831,7 @@ function drupal_retrieve_form($form_id, &$form_state) { // the actual form builder function ($callback) expects. This allows for // pre-populating a form with common elements for certain forms, such as // back/next/save buttons in multi-step form wizards. See drupal_build_form(). - if (isset($form_state['wrapper_callback']) && function_exists($form_state['wrapper_callback'])) { + if (isset($form_state['wrapper_callback']) && is_callable($form_state['wrapper_callback'])) { $form = call_user_func_array($form_state['wrapper_callback'], $args); // Put the prepopulated $form into $args. $args[0] = $form; @@ -1175,7 +1176,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) { // If the session token was set by drupal_prepare_form(), ensure that it // matches the current user's session. This is duplicate to code in // form_builder() but left to protect any custom form handling code. - if (isset($form['#token'])) { + if (!empty($form['#token'])) { if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) { _drupal_invalid_token_set_form_error(); // Stop here and don't run any further validation handlers, because they @@ -1836,7 +1837,7 @@ function form_builder($form_id, &$element, &$form_state) { // If the session token was set by drupal_prepare_form(), ensure that it // matches the current user's session. $form_state['invalid_token'] = FALSE; - if (isset($element['#token'])) { + if (!empty($element['#token'])) { if (empty($form_state['input']['form_token']) || !drupal_valid_token($form_state['input']['form_token'], $element['#token'])) { // Set an early form error to block certain input processing since that // opens the door for CSRF vulnerabilities. @@ -2571,7 +2572,7 @@ function form_type_select_value($element, $input = FALSE) { * for this element. Return nothing to use the default. */ function form_type_textarea_value($element, $input = FALSE) { - if ($input !== FALSE) { + if ($input !== FALSE && $input !== NULL) { // This should be a string, but allow other scalars since they might be // valid input in programmatic form submissions. return is_scalar($input) ? (string) $input : ''; @@ -3028,7 +3029,7 @@ function form_process_password_confirm($element) { function password_confirm_validate($element, &$element_state) { $pass1 = trim($element['pass1']['#value']); $pass2 = trim($element['pass2']['#value']); - if (!empty($pass1) || !empty($pass2)) { + if (strlen($pass1) > 0 || strlen($pass2) > 0) { if (strcmp($pass1, $pass2)) { form_error($element, t('The specified passwords do not match.')); } @@ -3385,9 +3386,12 @@ function form_process_container($element, &$form_state) { /** * Returns HTML to wrap child elements in a container. * - * Used for grouped form items. Can also be used as a #theme_wrapper for any + * Used for grouped form items. Can also be used as a theme wrapper for any * renderable element, to surround it with a
and add attributes such as - * classes or an HTML id. + * classes or an HTML ID. + * + * See the @link forms_api_reference.html Form API reference @endlink for more + * information on the #theme_wrappers render array property. * * @param $variables * An associative array containing: @@ -3542,6 +3546,7 @@ function form_process_tableselect($element) { '#return_value' => $key, '#default_value' => isset($value[$key]) ? $key : NULL, '#attributes' => $element['#attributes'], + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } else { @@ -3979,7 +3984,12 @@ function form_process_autocomplete($element) { // browser interpreting the path plus search string as an actual file. $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; $GLOBALS['conf']['clean_url'] = 0; - $element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE)); + // Force the script path to 'index.php', in case the server is not + // configured to find it automatically. Normally it is the responsibility + // of the site to do this themselves using hook_url_outbound_alter() (see + // url()) but since this code is forcing non-clean URLs on sites that don't + // normally use them, it is done here instead. + $element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE, 'script' => 'index.php')); $GLOBALS['conf']['clean_url'] = $current_clean_url; } return $element; @@ -4484,7 +4494,7 @@ function element_validate_number($element, &$form_state) { * * Sample callback_batch_finished(): * @code - * function batch_test_finished($success, $results, $operations) { + * function my_finished_callback($success, $results, $operations) { * // The 'success' parameter means no fatal PHP errors were detected. All * // other error management should be handled using 'results'. * if ($success) { diff --git a/includes/install.core.inc b/includes/install.core.inc index e5a65865..b18d23d2 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -809,6 +809,13 @@ function install_system_module(&$install_state) { variable_set('install_profile_modules', array_diff($modules, array('system'))); $install_state['database_tables_exist'] = TRUE; + + // Prevent the hook_requirements() check from telling us to convert the + // database to utf8mb4. + $connection = Database::getConnection(); + if ($connection->utf8mb4IsConfigurable() && $connection->utf8mb4IsActive()) { + variable_set('drupal_all_databases_are_utf8mb4', TRUE); + } } /** @@ -1590,7 +1597,9 @@ function install_finished(&$install_state) { } /** - * Batch callback for batch installation of modules. + * Implements callback_batch_operation(). + * + * Performs batch installation of modules. */ function _install_module_batch($module, $module_name, &$context) { // Install and enable the module right away, so that the module will be @@ -1603,6 +1612,8 @@ function _install_module_batch($module, $module_name, &$context) { } /** + * Implements callback_batch_finished(). + * * 'Finished' callback for module installation batch. */ function _install_profile_modules_finished($success, $results, $operations) { diff --git a/includes/install.inc b/includes/install.inc index bd37d067..5065d35c 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -750,7 +750,7 @@ function drupal_install_system() { /** * Uninstalls a given list of disabled modules. * - * @param array $module_list + * @param string[] $module_list * The modules to uninstall. It is the caller's responsibility to ensure that * all modules in this list have already been disabled before this function * is called. @@ -769,6 +769,7 @@ function drupal_install_system() { * included in $module_list). * * @see module_disable() + * @see module_enable() */ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) { if ($uninstall_dependents) { diff --git a/includes/locale.inc b/includes/locale.inc index c7f95838..11f1413e 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -435,6 +435,13 @@ function locale_language_url_rewrite_url(&$path, &$options) { switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: if ($options['language']->domain) { + // Save the original base URL. If it contains a port, we need to + // retain it below. + if (!empty($options['base_url'])) { + // The colon in the URL scheme messes up the port checking below. + $normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']); + } + // Ask for an absolute URL with our modified base_url. global $is_https; $url_scheme = ($is_https) ? 'https://' : 'http://'; @@ -449,6 +456,19 @@ function locale_language_url_rewrite_url(&$path, &$options) { // Apply the appropriate protocol to the URL. $options['base_url'] = $url_scheme . $host; + + // In case either the original base URL or the HTTP host contains a + // port, retain it. + $http_host = $_SERVER['HTTP_HOST']; + if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) { + list($host, $port) = explode(':', $normalized_base_url); + $options['base_url'] .= ':' . $port; + } + elseif (strpos($http_host, ':') !== FALSE) { + list($host, $port) = explode(':', $http_host); + $options['base_url'] .= ':' . $port; + } + if (isset($options['https']) && variable_get('https', FALSE)) { if ($options['https'] === TRUE) { $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); @@ -523,6 +543,22 @@ function locale_language_url_rewrite_session(&$path, &$options) { * possible attack vector (img). */ function locale_string_is_safe($string) { + // Some strings have tokens in them. For tokens in the first part of href or + // src HTML attributes, filter_xss() removes part of the token, the part + // before the first colon. filter_xss() assumes it could be an attempt to + // inject javascript. When filter_xss() removes part of tokens, it causes the + // string to not be translatable when it should be translatable. See + // LocaleStringIsSafeTest::testLocaleStringIsSafe(). + // + // We can recognize tokens since they are wrapped with brackets and are only + // composed of alphanumeric characters, colon, underscore, and dashes. We can + // be sure these strings are safe to strip out before the string is checked in + // filter_xss() because no dangerous javascript will match that pattern. + // + // @todo Do not strip out the token. Fix filter_xss() to not incorrectly + // alter the string. https://www.drupal.org/node/2372127 + $string = preg_replace('/\[[a-z0-9_-]+(:[a-z0-9_-]+)+\]/i', '', $string); + return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))); } @@ -631,9 +667,6 @@ function locale_add_language($langcode, $name = NULL, $native = NULL, $direction * translations). */ function _locale_import_po($file, $langcode, $mode, $group = NULL) { - // Try to allocate enough time to parse and import the data. - drupal_set_time_limit(240); - // Check if we have the language already in the database. if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) { drupal_set_message(t('The language selected for import is not supported.'), 'error'); @@ -717,6 +750,12 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = $lineno = 0; while (!feof($fd)) { + // Refresh the time limit every 10 parsed rows to ensure there is always + // enough time to import the data for large PO files. + if (!($lineno % 10)) { + drupal_set_time_limit(30); + } + // A line should not be longer than 10 * 1024. $line = fgets($fd, 10 * 1024); @@ -2306,6 +2345,8 @@ function _locale_batch_build($files, $finished = NULL, $components = array()) { } /** + * Implements callback_batch_operation(). + * * Perform interface translation import as a batch step. * * @param $filepath @@ -2324,6 +2365,8 @@ function _locale_batch_import($filepath, &$context) { } /** + * Implements callback_batch_finished(). + * * Finished callback of system page locale import batch. * Inform the user of translation files imported. */ @@ -2334,6 +2377,8 @@ function _locale_batch_system_finished($success, $results) { } /** + * Implements callback_batch_finished(). + * * Finished callback of language addition locale import batch. * Inform the user of translation files imported. */ diff --git a/includes/mail.inc b/includes/mail.inc index 0275922b..0e5c1780 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -566,7 +566,7 @@ function _drupal_wrap_mail_line(&$line, $key, $values) { // Use soft-breaks only for purely quoted or unindented text. $line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n"); // Break really long words at the maximum width allowed. - $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n"); + $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n", TRUE); } /** diff --git a/includes/menu.inc b/includes/menu.inc index 0e9c977c..4664d27e 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -229,12 +229,20 @@ define('MENU_CONTEXT_INLINE', 0x0002); define('MENU_FOUND', 1); /** - * Internal menu status code -- Menu item was not found. + * Menu status code -- Not found. + * + * This can be used as the return value from a page callback, although it is + * preferable to use a load function to accomplish this; see the hook_menu() + * documentation for details. */ define('MENU_NOT_FOUND', 2); /** - * Internal menu status code -- Menu item access is denied. + * Menu status code -- Access denied. + * + * This can be used as the return value from a page callback, although it is + * preferable to use an access callback to accomplish this; see the hook_menu() + * documentation for details. */ define('MENU_ACCESS_DENIED', 3); @@ -431,7 +439,7 @@ function menu_set_item($path, $router_item) { * * @param $path * The path; for example, 'node/5'. The function will find the corresponding - * node/% item and return that. + * node/% item and return that. Defaults to the current path. * @param $router_item * Internal use only. * @@ -1598,6 +1606,7 @@ function _menu_tree_data(&$links, $parents, $depth) { * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { + $variables['#tree'] = $variables['tree']; $variables['tree'] = $variables['tree']['#children']; } @@ -2411,7 +2420,7 @@ function menu_set_active_trail($new_trail = NULL) { // argument placeholders (%). Such links are not contained in regular // menu trees, and have only been loaded for the additional // translation that happens here, so as to be able to display them in - // the breadcumb for the current page. + // the breadcrumb for the current page. // @see _menu_tree_check_access() // @see _menu_link_translate() if (strpos($link['href'], '%') !== FALSE) { @@ -2613,10 +2622,30 @@ function menu_get_active_breadcrumb() { */ function menu_get_active_title() { $active_trail = menu_get_active_trail(); + $local_task_title = NULL; foreach (array_reverse($active_trail) as $item) { - if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) { - return $item['title']; + // Local task titles are displayed as tabs and therefore should not be + // repeated as the page title. However, if the local task appears in a + // top-level menu, it is no longer a "local task" anymore (the front page + // of the site does not have tabs) so it is better to use the local task + // title in that case than to fall back on the front page link in the + // active trail (which is usually "Home" and would not make sense in this + // context). + if ((bool) ($item['type'] & MENU_IS_LOCAL_TASK)) { + // A local task title is being skipped; track it in case it needs to be + // used later. + $local_task_title = $item['title']; + } + else { + // This is not a local task, so use it for the page title (unless the + // conditions described above are met). + if (isset($local_task_title) && isset($item['href']) && $item['href'] == '') { + return $local_task_title; + } + else { + return $item['title']; + } } } } @@ -2654,7 +2683,7 @@ function menu_link_load($mlid) { } /** - * Clears the cached cached data for a single named menu. + * Clears the cached data for a single named menu. */ function menu_cache_clear($menu_name = 'navigation') { $cache_cleared = &drupal_static(__FUNCTION__, array()); diff --git a/includes/module.inc b/includes/module.inc index 494924f5..2e251080 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -227,6 +227,10 @@ function system_list_reset() { drupal_static_reset('list_themes'); cache_clear_all('bootstrap_modules', 'cache_bootstrap'); cache_clear_all('system_list', 'cache_bootstrap'); + + // Clean up the bootstrap file scan cache. + drupal_static_reset('_drupal_file_scan_cache'); + cache_clear_all('_drupal_file_scan_cache', 'cache_bootstrap'); } /** @@ -320,16 +324,27 @@ function module_load_install($module) { * The name of the included file, if successful; FALSE otherwise. */ function module_load_include($type, $module, $name = NULL) { + static $files = array(); + if (!isset($name)) { $name = $module; } + $key = $type . ':' . $module . ':' . $name; + if (isset($files[$key])) { + return $files[$key]; + } + if (function_exists('drupal_get_path')) { $file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type"; if (is_file($file)) { require_once $file; + $files[$key] = $file; return $file; } + else { + $files[$key] = FALSE; + } } return FALSE; } @@ -365,20 +380,22 @@ function module_load_all_includes($type, $name = NULL) { * - Invoke hook_modules_installed(). * - Invoke hook_modules_enabled(). * - * @param $module_list + * @param string[] $module_list * An array of module names. - * @param $enable_dependencies + * @param bool $enable_dependencies * If TRUE, dependencies will automatically be added and enabled in the * correct order. This incurs a significant performance cost, so use FALSE * if you know $module_list is already complete and in the correct order. * - * @return + * @return bool * FALSE if one or more dependencies are missing, TRUE otherwise. * * @see hook_install() * @see hook_enable() * @see hook_modules_installed() * @see hook_modules_enabled() + * @see module_disable() + * @see drupal_uninstall_modules() */ function module_enable($module_list, $enable_dependencies = TRUE) { if ($enable_dependencies) { @@ -505,12 +522,15 @@ function module_enable($module_list, $enable_dependencies = TRUE) { /** * Disables a given set of modules. * - * @param $module_list + * @param string[] $module_list * An array of module names. - * @param $disable_dependents + * @param bool $disable_dependents * If TRUE, dependent modules will automatically be added and disabled in the * correct order. This incurs a significant performance cost, so use FALSE * if you know $module_list is already complete and in the correct order. + * + * @see drupal_uninstall_modules() + * @see module_enable() */ function module_disable($module_list, $disable_dependents = TRUE) { if ($disable_dependents) { @@ -676,12 +696,16 @@ function module_hook($module, $hook) { /** * Determines which modules are implementing a hook. * - * @param $hook + * Lazy-loaded include files specified with "group" via hook_hook_info() or + * hook_module_implements_alter() will be automatically included by this + * function when necessary. + * + * @param string $hook * The name of the hook (e.g. "help" or "menu"). - * @param $sort + * @param bool $sort * By default, modules are ordered by weight and filename, settings this option * to TRUE, module list will be ordered by module name. - * @param $reset + * @param bool $reset * For internal use only: Whether to force the stored list of hook * implementations to be regenerated (such as after enabling a new module, * before processing hook_enable). @@ -696,8 +720,10 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__); + $drupal_static_fast['verified'] = &drupal_static(__FUNCTION__ . ':verified'); } $implementations = &$drupal_static_fast['implementations']; + $verified = &$drupal_static_fast['verified']; // We maintain a persistent cache of hook implementations in addition to the // static cache to avoid looping through every module and every hook on each @@ -711,14 +737,19 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { // per request. if ($reset) { $implementations = array(); + $verified = array(); cache_set('module_implements', array(), 'cache_bootstrap'); drupal_static_reset('module_hook_info'); drupal_static_reset('drupal_alter'); cache_clear_all('hook_info', 'cache_bootstrap'); + cache_clear_all('system_cache_tables', 'cache'); return; } // Fetch implementations from cache. + // This happens on the first call to module_implements(*, *, FALSE) during a + // request, but also when $implementations have been reset, e.g. after + // module_enable(). if (empty($implementations)) { $implementations = cache_get('module_implements', 'cache_bootstrap'); if ($implementations === FALSE) { @@ -727,12 +758,17 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { else { $implementations = $implementations->data; } + // Forget all previously "verified" hooks, in case that $implementations + // were cleared via drupal_static_reset('module_implements') instead of + // module_implements(*, *, TRUE). + $verified = array(); } if (!isset($implementations[$hook])) { // The hook is not cached, so ensure that whether or not it has // implementations, that the cache is updated at the end of the request. $implementations['#write_cache'] = TRUE; + // Discover implementations for this hook. $hook_info = module_hook_info(); $implementations[$hook] = array(); $list = module_list(FALSE, FALSE, $sort); @@ -744,13 +780,31 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; } } - // Allow modules to change the weight of specific implementations but avoid + // Allow modules to change the weight of specific implementations, but avoid // an infinite loop. if ($hook != 'module_implements_alter') { + // Remember the implementations before hook_module_implements_alter(). + $implementations_before = $implementations[$hook]; drupal_alter('module_implements', $implementations[$hook], $hook); + // Verify implementations that were added or modified. + foreach (array_diff_assoc($implementations[$hook], $implementations_before) as $module => $group) { + // If drupal_alter('module_implements') changed or added a $group, the + // respective file needs to be included. + if ($group) { + module_load_include('inc', $module, "$module.$group"); + } + // If a new implementation was added, verify that the function exists. + if (!function_exists($module . '_' . $hook)) { + unset($implementations[$hook][$module]); + } + } } + // Implementations for this hook are now "verified". + $verified[$hook] = TRUE; } - else { + elseif (!isset($verified[$hook])) { + // Implementations for this hook were in the cache, but they are not + // "verified" yet. foreach ($implementations[$hook] as $module => $group) { // If this hook implementation is stored in a lazy-loaded file, so include // that file first. @@ -769,6 +823,7 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { $implementations['#write_cache'] = TRUE; } } + $verified[$hook] = TRUE; } return array_keys($implementations[$hook]); @@ -833,6 +888,11 @@ function module_hook_info() { * @see module_implements() */ function module_implements_write_cache() { + // The list of implementations includes vital modules only before full + // bootstrap, so do not write cache if we are not fully bootstrapped yet. + if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) { + return; + } $implementations = &drupal_static('module_implements'); if (isset($implementations['#write_cache'])) { unset($implementations['#write_cache']); @@ -880,7 +940,9 @@ function module_invoke($module, $hook) { * * @return * An array of return values of the hook implementations. If modules return - * arrays from their implementations, those are merged into one array. + * arrays from their implementations, those are merged into one array + * recursively. Note: integer keys in arrays will be lost, as the merge is + * done using array_merge_recursive(). * * @see drupal_alter() */ diff --git a/includes/path.inc b/includes/path.inc index 2e357111..6bd48d30 100644 --- a/includes/path.inc +++ b/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/includes/registry.inc b/includes/registry.inc index ac5b035d..b035cc37 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -180,7 +180,7 @@ function _registry_parse_files($files) { * (optional) Weight of the module. */ function _registry_parse_file($filename, $contents, $module = '', $weight = 0) { - if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) { + if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface|trait)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) { foreach ($matches[2] as $key => $name) { db_merge('registry') ->key(array( diff --git a/includes/session.inc b/includes/session.inc index 84d1983b..25aa3475 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -163,7 +163,7 @@ function _drupal_session_write($sid, $value) { try { if (!drupal_save_session()) { // We don't have anything to do if we are not allowed to save the session. - return; + return TRUE; } // Check whether $_SESSION has been changed in this request. @@ -425,7 +425,7 @@ function _drupal_session_destroy($sid) { // Nothing to do if we are not allowed to change the session. if (!drupal_save_session()) { - return; + return TRUE; } // Delete session data. @@ -446,6 +446,8 @@ function _drupal_session_destroy($sid) { elseif (variable_get('https', FALSE)) { _drupal_session_delete_cookie('S' . session_name(), TRUE); } + + return TRUE; } /** diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc index 48829388..232ff143 100644 --- a/includes/stream_wrappers.inc +++ b/includes/stream_wrappers.inc @@ -133,7 +133,7 @@ interface DrupalStreamWrapperInterface extends StreamWrapperInterface { * @param $uri * A string containing the URI that should be used for this instance. */ - function setUri($uri); + public function setUri($uri); /** * Returns the stream resource URI. @@ -219,7 +219,6 @@ interface DrupalStreamWrapperInterface extends StreamWrapperInterface { public function dirname($uri = NULL); } - /** * Drupal stream wrapper base class for local files. * @@ -549,6 +548,155 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface return fclose($this->handle); } + /** + * Sets metadata on the stream. + * + * WARNING: Do not call this method directly! It will be called internally by + * PHP itself when one of the following functions is called on a stream URL: + * + * @param string $uri + * A string containing the URI to the file to set metadata on. + * @param int $option + * One of: + * - STREAM_META_TOUCH: The method was called in response to touch(). + * - STREAM_META_OWNER_NAME: The method was called in response to chown() + * with string parameter. + * - STREAM_META_OWNER: The method was called in response to chown(). + * - STREAM_META_GROUP_NAME: The method was called in response to chgrp(). + * - STREAM_META_GROUP: The method was called in response to chgrp(). + * - STREAM_META_ACCESS: The method was called in response to chmod(). + * @param mixed $value + * If option is: + * - STREAM_META_TOUCH: Array consisting of two arguments of the touch() + * function. + * - STREAM_META_OWNER_NAME or STREAM_META_GROUP_NAME: The name of the owner + * user/group as string. + * - STREAM_META_OWNER or STREAM_META_GROUP: The value of the owner + * user/group as integer. + * - STREAM_META_ACCESS: The argument of the chmod() as integer. + * + * @return bool + * Returns TRUE on success or FALSE on failure. If $option is not + * implemented, FALSE should be returned. + * + * @see touch() + * @see chmod() + * @see chown() + * @see chgrp() + * @link http://php.net/manual/streamwrapper.stream-metadata.php + */ + public function stream_metadata($uri, $option, $value) { + $target = $this->getLocalPath($uri); + $return = FALSE; + switch ($option) { + case STREAM_META_TOUCH: + if (!empty($value)) { + $return = touch($target, $value[0], $value[1]); + } + else { + $return = touch($target); + } + break; + + case STREAM_META_OWNER_NAME: + case STREAM_META_OWNER: + $return = chown($target, $value); + break; + + case STREAM_META_GROUP_NAME: + case STREAM_META_GROUP: + $return = chgrp($target, $value); + break; + + case STREAM_META_ACCESS: + $return = chmod($target, $value); + break; + } + if ($return) { + // For convenience clear the file status cache of the underlying file, + // since metadata operations are often followed by file status checks. + clearstatcache(TRUE, $target); + } + return $return; + } + + /** + * Truncate stream. + * + * Will respond to truncation; e.g., through ftruncate(). + * + * @param int $new_size + * The new size. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function stream_truncate($new_size) { + return ftruncate($this->handle, $new_size); + } + + /** + * Retrieve the underlying stream resource. + * + * This method is called in response to stream_select(). + * + * @param int $cast_as + * Can be STREAM_CAST_FOR_SELECT when stream_select() is calling + * stream_cast() or STREAM_CAST_AS_STREAM when stream_cast() is called for + * other uses. + * + * @return resource|false + * The underlying stream resource or FALSE if stream_select() is not + * supported. + * + * @see stream_select() + * @link http://php.net/manual/streamwrapper.stream-cast.php + */ + public function stream_cast($cast_as) { + return $this->handle ? $this->handle : FALSE; + } + + /** + * Change stream options. + * + * This method is called to set options on the stream. + * + * Since Windows systems do not allow it and it is not needed for most use + * cases anyway, this method is not supported on local files and will trigger + * an error and return false. If needed, custom subclasses can provide + * OS-specific implementations for advanced use cases. + * + * @param int $option + * One of: + * - STREAM_OPTION_BLOCKING: The method was called in response to + * stream_set_blocking(). + * - STREAM_OPTION_READ_TIMEOUT: The method was called in response to + * stream_set_timeout(). + * - STREAM_OPTION_WRITE_BUFFER: The method was called in response to + * stream_set_write_buffer(). + * @param int $arg1 + * If option is: + * - STREAM_OPTION_BLOCKING: The requested blocking mode: + * - 1 means blocking. + * - 0 means not blocking. + * - STREAM_OPTION_READ_TIMEOUT: The timeout in seconds. + * - STREAM_OPTION_WRITE_BUFFER: The buffer mode, STREAM_BUFFER_NONE or + * STREAM_BUFFER_FULL. + * @param int $arg2 + * If option is: + * - STREAM_OPTION_BLOCKING: This option is not set. + * - STREAM_OPTION_READ_TIMEOUT: The timeout in microseconds. + * - STREAM_OPTION_WRITE_BUFFER: The requested buffer size. + * + * @return bool + * TRUE on success, FALSE otherwise. If $option is not implemented, FALSE + * should be returned. + */ + public function stream_set_option($option, $arg1, $arg2) { + trigger_error('stream_set_option() not supported for local file based stream wrappers', E_USER_WARNING); + return FALSE; + } + /** * Support for unlink(). * diff --git a/includes/theme.inc b/includes/theme.inc index 8d5348df..9b606e9f 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1248,6 +1248,7 @@ function path_to_theme() { function drupal_find_theme_functions($cache, $prefixes) { $implementations = array(); $functions = get_defined_functions(); + $theme_functions = preg_grep('/^(' . implode(')|(', $prefixes) . ')_/', $functions['user']); foreach ($cache as $hook => $info) { foreach ($prefixes as $prefix) { @@ -1264,7 +1265,7 @@ function drupal_find_theme_functions($cache, $prefixes) { // intermediary suggestion. $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); if (!isset($info['base hook']) && !empty($pattern)) { - $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']); + $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $theme_functions); if ($matches) { foreach ($matches as $match) { $new_hook = substr($match, strlen($prefix) + 1); @@ -1710,11 +1711,29 @@ function theme_status_messages($variables) { * copy if none of the enabled modules or the active theme implement any * preprocess or process functions or override this theme implementation. * - * @param $variables - * An associative array containing the keys 'text', 'path', and 'options'. - * See the l() function for information about these variables. + * @param array $variables + * An associative array containing the keys: + * - text: The text of the link. + * - path: The internal path or external URL being linked to. It is used as + * the $path parameter of the url() function. + * - options: (optional) An array that defaults to empty, but can contain: + * - attributes: Can contain optional attributes: + * - class: must be declared in an array. Example: 'class' => + * array('class_name1','class_name2'). + * - title: must be a string. Example: 'title' => 'Example title' + * - Others are more flexible as long as they work with + * drupal_attributes($variables['options']['attributes]). + * - html: Boolean flag that tells whether text contains HTML or plain + * text. If set to TRUE, the text value will not be sanitized so the + calling function must ensure that it already contains safe HTML. + * The elements $variables['options']['attributes'] and + * $variables['options']['html'] are used in this function similarly to the + * way that $options['attributes'] and $options['html'] are used in l(). + * The link itself is built by the url() function, which takes + * $variables['path'] and $variables['options'] as arguments. * * @see l() + * @see url() */ function theme_link($variables) { return '' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . ''; @@ -1791,7 +1810,8 @@ function theme_links($variables) { foreach ($links as $key => $link) { $class = array($key); - // Add first, last and active classes to the list of links to help out themers. + // Add first, last and active classes to the list of links to help out + // themers. if ($i == 1) { $class[] = 'first'; } @@ -1809,7 +1829,8 @@ function theme_links($variables) { $output .= l($link['title'], $link['href'], $link); } elseif (!empty($link['title'])) { - // Some links are actually not links, but we wrap these in for adding title and class attributes. + // Some links are actually not links, but we wrap these in for + // adding title and class attributes. if (empty($link['html'])) { $link['title'] = check_plain($link['title']); } @@ -2618,7 +2639,7 @@ function template_preprocess_page(&$variables) { // Move some variables to the top level for themer convenience and template cleanliness. $variables['show_messages'] = $variables['page']['#show_messages']; - foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) { + foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region_key) { if (!isset($variables['page'][$region_key])) { $variables['page'][$region_key] = array(); } diff --git a/includes/update.inc b/includes/update.inc index a17161c9..2167db79 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -795,6 +795,14 @@ function update_fix_d7_requirements() { function update_fix_d7_install_profile() { $profile = drupal_get_profile(); + // 'Default' profile has been renamed to 'Standard' in D7. + // We change the profile here to prevent a broken record in the system table. + // See system_update_7049(). + if ($profile == 'default') { + $profile = 'standard'; + variable_set('install_profile', $profile); + } + $results = db_select('system', 's') ->fields('s', array('name', 'schema_version')) ->condition('name', $profile) @@ -908,6 +916,8 @@ function update_get_d6_session_name() { } /** + * Implements callback_batch_operation(). + * * Performs one update and stores the results for display on the results page. * * If an update function completes successfully, it should return a message @@ -1078,6 +1088,8 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $ } /** + * Implements callback_batch_finished(). + * * Finishes the update process and stores the results for eventual display. * * After the updates run, all caches are flushed. The update results are diff --git a/includes/xmlrpcs.inc b/includes/xmlrpcs.inc index 8655c05b..c334de15 100644 --- a/includes/xmlrpcs.inc +++ b/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/misc/ajax.js b/misc/ajax.js index bb4a6e14..c944ebbf 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -476,7 +476,7 @@ Drupal.ajax.prototype.getEffect = function (response) { * Handler for the form redirection error. */ Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { - alert(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); + Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); diff --git a/misc/autocomplete.js b/misc/autocomplete.js index d71441b6..af090713 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -310,7 +310,7 @@ Drupal.ACDB.prototype.search = function (searchString) { } }, error: function (xmlhttp) { - alert(Drupal.ajaxError(xmlhttp, db.uri)); + Drupal.displayAjaxError(Drupal.ajaxError(xmlhttp, db.uri)); } }); }, this.delay); diff --git a/misc/drupal.js b/misc/drupal.js index 427c4a1e..19fbc712 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -27,6 +27,42 @@ $.fn.init = function (selector, context, rootjQuery) { }; $.fn.init.prototype = jquery_init.prototype; +/** + * Pre-filter Ajax requests to guard against XSS attacks. + * + * See https://github.com/jquery/jquery/issues/2432 + */ +if ($.ajaxPrefilter) { + // For newer versions of jQuery, use an Ajax prefilter to prevent + // auto-executing script tags from untrusted domains. This is similar to the + // fix that is built in to jQuery 3.0 and higher. + $.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } + }); +} +else if ($.httpData) { + // For the version of jQuery that ships with Drupal core, override + // jQuery.httpData to prevent auto-detecting "script" data types from + // untrusted domains. + var jquery_httpData = $.httpData; + $.httpData = function (xhr, type, s) { + // @todo Consider backporting code from newer jQuery versions to check for + // a cross-domain request here, rather than using Drupal.urlIsLocal() to + // block scripts from all URLs that are not on the same site. + if (!type && !Drupal.urlIsLocal(s.url)) { + var content_type = xhr.getResponseHeader('content-type') || ''; + if (content_type.indexOf('javascript') >= 0) { + // Default to a safe data type. + type = 'text'; + } + } + return jquery_httpData.call(this, xhr, type, s); + }; + $.httpData.prototype = jquery_httpData.prototype; +} + /** * Attach all registered behaviors to a page element. * @@ -137,7 +173,7 @@ Drupal.detachBehaviors = function (context, settings, trigger) { */ Drupal.checkPlain = function (str) { var character, regex, - replace = { '&': '&', '"': '"', '<': '<', '>': '>' }; + replace = { '&': '&', "'": ''', '"': '"', '<': '<', '>': '>' }; str = String(str); for (character in replace) { if (replace.hasOwnProperty(character)) { @@ -168,23 +204,76 @@ Drupal.checkPlain = function (str) { Drupal.formatString = function(str, args) { // Transform arguments before inserting them. for (var key in args) { - switch (key.charAt(0)) { - // Escaped only. - case '@': - args[key] = Drupal.checkPlain(args[key]); - break; - // Pass-through. - case '!': - break; - // Escaped and placeholder. - case '%': - default: - args[key] = Drupal.theme('placeholder', args[key]); - break; + if (args.hasOwnProperty(key)) { + switch (key.charAt(0)) { + // Escaped only. + case '@': + args[key] = Drupal.checkPlain(args[key]); + break; + // Pass-through. + case '!': + break; + // Escaped and placeholder. + default: + args[key] = Drupal.theme('placeholder', args[key]); + break; + } } - str = str.replace(key, args[key]); } - return str; + + return Drupal.stringReplace(str, args, null); +}; + +/** + * Replace substring. + * + * The longest keys will be tried first. Once a substring has been replaced, + * its new value will not be searched again. + * + * @param {String} str + * A string with placeholders. + * @param {Object} args + * Key-value pairs. + * @param {Array|null} keys + * Array of keys from the "args". Internal use only. + * + * @return {String} + * Returns the replaced string. + */ +Drupal.stringReplace = function (str, args, keys) { + if (str.length === 0) { + return str; + } + + // If the array of keys is not passed then collect the keys from the args. + if (!$.isArray(keys)) { + keys = []; + for (var k in args) { + if (args.hasOwnProperty(k)) { + keys.push(k); + } + } + + // Order the keys by the character length. The shortest one is the first. + keys.sort(function (a, b) { return a.length - b.length; }); + } + + if (keys.length === 0) { + return str; + } + + // Take next longest one from the end. + var key = keys.pop(); + var fragments = str.split(key); + + if (keys.length) { + for (var i = 0; i < fragments.length; i++) { + // Process each fragment with a copy of remaining keys. + fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0)); + } + } + + return fragments.join(args[key]); }; /** @@ -251,7 +340,7 @@ Drupal.t = function (str, args, options) { * A translated string. */ Drupal.formatPlural = function (count, singular, plural, args, options) { - var args = args || {}; + args = args || {}; args['@count'] = count; // Determine the index of the plural form. var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1); @@ -413,6 +502,29 @@ Drupal.getSelection = function (element) { return { 'start': element.selectionStart, 'end': element.selectionEnd }; }; +/** + * Add a global variable which determines if the window is being unloaded. + * + * This is primarily used by Drupal.displayAjaxError(). + */ +Drupal.beforeUnloadCalled = false; +$(window).bind('beforeunload pagehide', function () { + Drupal.beforeUnloadCalled = true; +}); + +/** + * Displays a JavaScript error from an Ajax response when appropriate to do so. + */ +Drupal.displayAjaxError = function (message) { + // Skip displaying the message if the user deliberately aborted (for example, + // by reloading the page or navigating to a different page) while the Ajax + // request was still ongoing. See, for example, the discussion at + // http://stackoverflow.com/questions/699941/handle-ajax-error-when-a-user-clicks-refresh. + if (!Drupal.beforeUnloadCalled) { + alert(message); + } +}; + /** * Build an error message from an Ajax response. */ diff --git a/misc/states.js b/misc/states.js index 6d98da81..5aac65d2 100644 --- a/misc/states.js +++ b/misc/states.js @@ -493,7 +493,11 @@ $(document).bind('state:disabled', function(e) { $(document).bind('state:required', function(e) { if (e.trigger) { if (e.value) { - $(e.target).closest('.form-item, .form-wrapper').find('label').append('*'); + var $label = $(e.target).closest('.form-item, .form-wrapper').find('label'); + // Avoids duplicate required markers on initialization. + if (!$label.find('.form-required').length) { + $label.append('*'); + } } else { $(e.target).closest('.form-item, .form-wrapper').find('label .form-required').remove(); diff --git a/misc/tabledrag.js b/misc/tabledrag.js index 3cc27019..7ea88b60 100644 --- a/misc/tabledrag.js +++ b/misc/tabledrag.js @@ -106,8 +106,10 @@ Drupal.tableDrag = function (table, tableSettings) { // Add mouse bindings to the document. The self variable is passed along // as event handlers do not have direct access to the tableDrag object. - $(document).bind('mousemove', function (event) { return self.dragRow(event, self); }); - $(document).bind('mouseup', function (event) { return self.dropRow(event, self); }); + $(document).bind('mousemove pointermove', function (event) { return self.dragRow(event, self); }); + $(document).bind('mouseup pointerup', function (event) { return self.dropRow(event, self); }); + $(document).bind('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); }); + $(document).bind('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); }); }; /** @@ -274,7 +276,10 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) { }); // Add the mousedown action for the handle. - handle.mousedown(function (event) { + handle.bind('mousedown touchstart pointerdown', function (event) { + if (event.originalEvent.type == "touchstart") { + event = event.originalEvent.touches[0]; + } // Create a new dragObject recording the event information. self.dragObject = {}; self.dragObject.initMouseOffset = self.getMouseOffset(item, event); @@ -575,12 +580,20 @@ Drupal.tableDrag.prototype.dropRow = function (event, self) { * Get the mouse coordinates from the event (allowing for browser differences). */ Drupal.tableDrag.prototype.mouseCoords = function (event) { + // Complete support for pointer events was only introduced to jQuery in + // version 1.11.1; between versions 1.7 and 1.11.0 pointer events have the + // clientX and clientY properties undefined. In those cases, the properties + // must be retrieved from the event.originalEvent object instead. + var clientX = event.clientX || event.originalEvent.clientX; + var clientY = event.clientY || event.originalEvent.clientY; + if (event.pageX || event.pageY) { return { x: event.pageX, y: event.pageY }; } + return { - x: event.clientX + document.body.scrollLeft - document.body.clientLeft, - y: event.clientY + document.body.scrollTop - document.body.clientTop + x: clientX + document.body.scrollLeft - document.body.clientLeft, + y: clientY + document.body.scrollTop - document.body.clientTop }; }; diff --git a/modules/aggregator/aggregator.info b/modules/aggregator/aggregator.info index e7e8fa55..2d84f9d1 100644 --- a/modules/aggregator/aggregator.info +++ b/modules/aggregator/aggregator.info @@ -7,8 +7,7 @@ files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 70f8c5cd..02c9ec46 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -455,6 +455,14 @@ function aggregator_save_category($edit) { db_delete('aggregator_category') ->condition('cid', $edit['cid']) ->execute(); + // Remove category from feeds. + db_delete('aggregator_category_feed') + ->condition('cid', $edit['cid']) + ->execute(); + // Remove category from feed items. + db_delete('aggregator_category_item') + ->condition('cid', $edit['cid']) + ->execute(); // Make sure there is no active block for this category. if (module_exists('block')) { db_delete('block') diff --git a/modules/aggregator/aggregator.processor.inc b/modules/aggregator/aggregator.processor.inc index 44ed5499..534cca57 100644 --- a/modules/aggregator/aggregator.processor.inc +++ b/modules/aggregator/aggregator.processor.inc @@ -72,7 +72,7 @@ function aggregator_aggregator_remove($feed) { */ function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) { if (in_array('aggregator', variable_get('aggregator_processors', array('aggregator')))) { - $info = module_invoke('aggregator', 'aggregator_process', 'info'); + $info = module_invoke('aggregator', 'aggregator_process_info'); $items = drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items'); $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval'); $period[AGGREGATOR_CLEAR_NEVER] = t('Never'); diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test index d84ee785..afa791d6 100644 --- a/modules/aggregator/aggregator.test +++ b/modules/aggregator/aggregator.test @@ -418,7 +418,7 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } /** - * Creates a feed and makes sure you can add more than one category to it. + * Creates a feed and makes sure you can add/delete categories to it. */ function testCategorizeFeed() { @@ -448,7 +448,31 @@ class CategorizeFeedTestCase extends AggregatorTestCase { // Assert the feed has two categories. $this->getFeedCategories($db_feed); $this->assertEqual(count($db_feed->categories), 2, 'Feed has 2 categories'); + + // Use aggregator_save_feed() to delete a category. + $category = reset($categories); + aggregator_save_category(array('cid' => $category->cid)); + + // Assert that category is deleted. + $db_category = db_query("SELECT COUNT(*) FROM {aggregator_category} WHERE cid = :cid", array(':cid' => $category->cid))->fetchField(); + $this->assertFalse($db_category, format_string('The category %title has been deleted.', array('%title' => $category->title))); + + // Assert that category has been removed from feed. + $categorized_feeds = db_query("SELECT COUNT(*) FROM {aggregator_category_feed} WHERE cid = :cid", array(':cid' => $category->cid))->fetchField(); + $this->assertFalse($categorized_feeds, format_string('The category %title has been removed from feed %feed_title.', array('%title' => $category->title, '%feed_title' => $feed['title']))); + + // Assert that no broken links (associated with the deleted category) + // appear on one of the other category pages. + $this->createSampleNodes(); + $this->drupalGet('admin/config/services/aggregator'); + $this->clickLink('update items'); + $categories = $this->getCategories(); + $category = reset($categories); + $this->drupalGet('aggregator/categories/' . $category->cid); + global $base_path; + $this->assertNoRaw(','); } + } /** @@ -685,9 +709,21 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } } + // Delete category from feed items when category is deleted. + $cid = reset($feed->categories); + $categories = $this->getCategories(); + $category_title = $categories[$cid]->title; + + // Delete category. + aggregator_save_category(array('cid' => $cid)); + + // Assert category has been removed from feed items. + $categorized_count = db_query("SELECT COUNT(*) FROM {aggregator_category_item} WHERE cid = :cid", array(':cid' => $cid))->fetchField(); + $this->assertFalse($categorized_count, format_string('The category %title has been removed from feed items.', array('%title' => $category_title))); // Delete feed. $this->deleteFeed($feed); } + } /** diff --git a/modules/aggregator/tests/aggregator_test.info b/modules/aggregator/tests/aggregator_test.info index e862225f..91811fcd 100644 --- a/modules/aggregator/tests/aggregator_test.info +++ b/modules/aggregator/tests/aggregator_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/block/block.api.php b/modules/block/block.api.php index d7453b24..e38f7d6e 100644 --- a/modules/block/block.api.php +++ b/modules/block/block.api.php @@ -363,6 +363,31 @@ function hook_block_list_alter(&$blocks) { } } +/** + * Act on block cache ID (cid) parts before the cid is generated. + * + * This hook allows you to add, remove or modify the custom keys used to + * generate a block cache ID (by default, these keys are set to the block + * module and delta). These keys will be combined with the standard ones + * provided by drupal_render_cid_parts() to generate the final block cache ID. + * + * To change the cache granularity used by drupal_render_cid_parts(), this hook + * cannot be used; instead, set the 'cache' key in the block's definition in + * hook_block_info(). + * + * @params $cid_parts + * An array of elements used to build the cid. + * @param $block + * The block object being acted on. + * + * @see _block_get_cache_id() + */ +function hook_block_cid_parts_alter(&$cid_parts, $block) { + global $user; + // This example shows how to cache a block based on the user's timezone. + $cid_parts[] = $user->timezone; +} + /** * @} End of "addtogroup hooks". */ diff --git a/modules/block/block.info b/modules/block/block.info index e5f66a7d..f3f2c7cf 100644 --- a/modules/block/block.info +++ b/modules/block/block.info @@ -6,8 +6,7 @@ core = 7.x files[] = block.test configure = admin/structure/block -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/block/block.js b/modules/block/block.js index acd3f5ae..721dedf1 100644 --- a/modules/block/block.js +++ b/modules/block/block.js @@ -24,7 +24,7 @@ Drupal.behaviors.blockSettingsSummary = { $('fieldset#edit-node-type', context).drupalSetSummary(function (context) { var vals = []; $('input[type="checkbox"]:checked', context).each(function () { - vals.push($.trim($(this).next('label').text())); + vals.push($.trim($(this).next('label').html())); }); if (!vals.length) { vals.push(Drupal.t('Not restricted')); @@ -35,7 +35,7 @@ Drupal.behaviors.blockSettingsSummary = { $('fieldset#edit-role', context).drupalSetSummary(function (context) { var vals = []; $('input[type="checkbox"]:checked', context).each(function () { - vals.push($.trim($(this).next('label').text())); + vals.push($.trim($(this).next('label').html())); }); if (!vals.length) { vals.push(Drupal.t('Not restricted')); @@ -49,7 +49,7 @@ Drupal.behaviors.blockSettingsSummary = { return Drupal.t('Not customizable'); } else { - return $radio.next('label').text(); + return $radio.next('label').html(); } }); } diff --git a/modules/block/block.module b/modules/block/block.module index 48c80d76..d68ea9e7 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -16,7 +16,7 @@ define('BLOCK_REGION_NONE', -1); define('BLOCK_CUSTOM_FIXED', 0); /** - * Shows this block by default, but lets individual users hide it. + * Shows this block by default, but lets individual users hide it. */ define('BLOCK_CUSTOM_ENABLED', 1); @@ -59,6 +59,7 @@ function block_help($path, $arg) { $output .= '
' . t('Users with the Administer blocks permission can add custom blocks, which are then listed on the Blocks administration page. Once created, custom blocks behave just like default and module-generated blocks.', array('@blocks' => url('admin/structure/block'), '@block-add' => url('admin/structure/block/add'))) . '
'; $output .= ''; return $output; + case 'admin/structure/block/add': return '

' . t('Use this page to create a new custom block.') . '

'; } @@ -189,6 +190,7 @@ function _block_themes_access($theme) { * @param $theme * The theme whose blocks are being configured. If not set, the default theme * is assumed. + * * @return * The theme that should be used for the block configuration page, or NULL * to indicate that the default theme should be used. @@ -283,8 +285,7 @@ function block_page_build(&$page) { // Append region description if we are rendering the regions demo page. $item = menu_get_item(); if ($item['path'] == 'admin/structure/block/demo/' . $theme) { - $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - foreach ($visible_regions as $region) { + foreach (system_region_list($theme, REGIONS_VISIBLE, FALSE) as $region) { $description = '
' . $all_regions[$region] . '
'; $page[$region]['block_description'] = array( '#markup' => $description, @@ -343,14 +344,17 @@ function _block_get_renderable_array($list = array()) { // to perform contextual actions on the help block, and the links needlessly // draw attention on it. if ($key != 'system_main' && $key != 'system_help') { - $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($block->module, $block->delta)); + $build[$key]['#contextual_links']['block'] = array( + 'admin/structure/block/manage', + array($block->module, $block->delta), + ); } $build[$key] += array( '#block' => $block, '#weight' => ++$weight, ); - $build[$key]['#theme_wrappers'][] ='block'; + $build[$key]['#theme_wrappers'][] = 'block'; } $build['#sorted'] = TRUE; return $build; @@ -386,18 +390,20 @@ function _block_rehash($theme = NULL) { // Gather the blocks defined by modules. foreach (module_implements('block_info') as $module) { $module_blocks = module_invoke($module, 'block_info'); + $delta_list = array(); foreach ($module_blocks as $delta => $block) { // Compile a condition to retrieve this block from the database. - $condition = db_and() - ->condition('module', $module) - ->condition('delta', $delta); - $or->condition($condition); // Add identifiers. + $delta_list[] = $delta; $block['module'] = $module; - $block['delta'] = $delta; - $block['theme'] = $theme; + $block['delta'] = $delta; + $block['theme'] = $theme; $current_blocks[$module][$delta] = $block; } + if (!empty($delta_list)) { + $condition = db_and()->condition('module', $module)->condition('delta', $delta_list); + $or->condition($condition); + } } // Save the blocks defined in code for alter context. $code_blocks = $current_blocks; @@ -426,23 +432,20 @@ function _block_rehash($theme = NULL) { drupal_alter('block_info', $current_blocks, $theme, $code_blocks); foreach ($current_blocks as $module => $module_blocks) { foreach ($module_blocks as $delta => $block) { - if (!isset($block['pages'])) { - // {block}.pages is type 'text', so it cannot have a - // default value, and not null, so we need to provide - // value if the module did not. - $block['pages'] = ''; - } - // Make sure weight is set. - if (!isset($block['weight'])) { - $block['weight'] = 0; - } + // Make sure certain attributes are set. + $block += array( + 'pages' => '', + 'weight' => 0, + 'status' => 0, + ); + // Check for active blocks in regions that are not available. if (!empty($block['region']) && $block['region'] != BLOCK_REGION_NONE && !isset($regions[$block['region']]) && $block['status'] == 1) { drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block['info'], '%region' => $block['region'])), 'warning'); // Disabled modules are moved into the BLOCK_REGION_NONE later so no // need to move the block to another region. $block['status'] = 0; } - // Set region to none if not enabled and make sure status is set. + // Set region to none if not enabled. if (empty($block['status'])) { $block['status'] = 0; $block['region'] = BLOCK_REGION_NONE; @@ -644,7 +647,8 @@ function block_theme_initialize($theme) { $regions = system_region_list($theme, REGIONS_VISIBLE); $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $block) { - // If the region isn't supported by the theme, assign the block to the theme's default region. + // If the region isn't supported by the theme, assign the block to the + // theme's default region. if ($block['status'] && !isset($regions[$block['region']])) { $block['region'] = system_default_region($theme); } @@ -812,17 +816,18 @@ function block_block_list_alter(&$blocks) { // with different case. Ex: /Page, /page, /PAGE. $pages = drupal_strtolower($block->pages); if ($block->visibility < BLOCK_VISIBILITY_PHP) { - // Convert the Drupal path to lowercase + // Convert the Drupal path to lowercase. $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); // Compare the lowercase internal and lowercase path alias (if any). $page_match = drupal_match_path($path, $pages); if ($path != $_GET['q']) { $page_match = $page_match || drupal_match_path($_GET['q'], $pages); } - // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED), - // the block is displayed on all pages except those listed in $block->pages. - // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those - // pages listed in $block->pages. + // When $block->visibility has a value of 0 + // (BLOCK_VISIBILITY_NOTLISTED), the block is displayed on all pages + // except those listed in $block->pages. When set to 1 + // (BLOCK_VISIBILITY_LISTED), it is displayed only on those pages + // listed in $block->pages. $page_match = !($block->visibility xor $page_match); } elseif (module_exists('php')) { @@ -845,7 +850,8 @@ function block_block_list_alter(&$blocks) { * Render the content and subject for a set of blocks. * * @param $region_blocks - * An array of block objects such as returned for one region by _block_load_blocks(). + * An array of block objects such as returned for one region by + * _block_load_blocks(). * * @return * An array of visible blocks as expected by drupal_render(). @@ -953,6 +959,8 @@ function _block_render_blocks($region_blocks) { * Theme and language contexts are automatically differentiated. * * @param $block + * The block to get the cache_id from. + * * @return * The string used as cache_id for the block. */ @@ -967,6 +975,7 @@ function _block_get_cache_id($block) { // Start with common sub-patterns: block identification, theme, language. $cid_parts[] = $block->module; $cid_parts[] = $block->delta; + drupal_alter('block_cid_parts', $cid_parts, $block); $cid_parts = array_merge($cid_parts, drupal_render_cid_parts($block->cache)); return implode(':', $cid_parts); diff --git a/modules/block/tests/block_test.info b/modules/block/tests/block_test.info index 59a1c5b8..f62fdd7e 100644 --- a/modules/block/tests/block_test.info +++ b/modules/block/tests/block_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/block/tests/themes/block_test_theme/block_test_theme.info b/modules/block/tests/themes/block_test_theme/block_test_theme.info index 0dc755e5..4ce61da1 100644 --- a/modules/block/tests/themes/block_test_theme/block_test_theme.info +++ b/modules/block/tests/themes/block_test_theme/block_test_theme.info @@ -13,8 +13,7 @@ regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/blog/blog.info b/modules/blog/blog.info index c876ba60..ea026380 100644 --- a/modules/blog/blog.info +++ b/modules/blog/blog.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = blog.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/blog/blog.module b/modules/blog/blog.module index 11e3ab95..d7b882f4 100644 --- a/modules/blog/blog.module +++ b/modules/blog/blog.module @@ -152,7 +152,7 @@ function blog_menu_local_tasks_alter(&$data, $router_item, $root_path) { } } // Provide a helper action link to the author on the 'blog/%' page. - elseif ($root_path == 'blog/%' && $router_item['page_arguments'][0]->uid == $user->uid) { + elseif ($root_path == 'blog/%' && isset($router_item['page_arguments'][0]->uid) && $router_item['page_arguments'][0]->uid == $user->uid) { $data['actions']['output']['blog'] = array( '#theme' => 'menu_local_action', ); diff --git a/modules/book/book.info b/modules/book/book.info index fd33b32b..98aca934 100644 --- a/modules/book/book.info +++ b/modules/book/book.info @@ -7,8 +7,7 @@ files[] = book.test configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/color/color.info b/modules/color/color.info index 994091b3..9ca6bcce 100644 --- a/modules/color/color.info +++ b/modules/color/color.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = color.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/color/color.test b/modules/color/color.test index 09043250..f29c0c26 100644 --- a/modules/color/color.test +++ b/modules/color/color.test @@ -122,7 +122,7 @@ class ColorTestCase extends DrupalWebTestCase { $edit['palette[bg]'] = $color; $this->drupalPost($settings_path, $edit, t('Save configuration')); - if($is_valid) { + if ($is_valid) { $this->assertText('The configuration options have been saved.'); } else { diff --git a/modules/comment/comment.info b/modules/comment/comment.info index 77cf16aa..5ea180dc 100644 --- a/modules/comment/comment.info +++ b/modules/comment/comment.info @@ -9,8 +9,7 @@ files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/comment/comment.test b/modules/comment/comment.test index dc7aad3e..e087a717 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -11,9 +11,15 @@ class CommentHelperCase extends DrupalWebTestCase { protected $node; function setUp() { - parent::setUp('comment', 'search'); + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + $modules[] = 'comment'; + parent::setUp($modules); + // Create users and test node. - $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions')); + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions', 'administer fields')); $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments')); $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); } @@ -1490,7 +1496,7 @@ class CommentNodeAccessTest extends CommentHelperCase { } function setUp() { - DrupalWebTestCase::setUp('comment', 'search', 'node_access_test'); + parent::setUp('search', 'node_access_test'); node_access_rebuild(); // Create users and test node. diff --git a/modules/contact/contact.info b/modules/contact/contact.info index 08e50f99..63cf78c5 100644 --- a/modules/contact/contact.info +++ b/modules/contact/contact.info @@ -6,8 +6,7 @@ core = 7.x files[] = contact.test configure = admin/structure/contact -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/contact/contact.module b/modules/contact/contact.module index 9a48f231..9c6671a9 100644 --- a/modules/contact/contact.module +++ b/modules/contact/contact.module @@ -234,7 +234,14 @@ function contact_form_user_profile_form_alter(&$form, &$form_state) { * Implements hook_user_presave(). */ function contact_user_presave(&$edit, $account, $category) { - $edit['data']['contact'] = isset($edit['contact']) ? $edit['contact'] : variable_get('contact_default_status', 1); + if (isset($edit['contact'])) { + // Set new value. + $edit['data']['contact'] = $edit['contact']; + } + elseif (!isset($account->data['contact'])) { + // Use default if none has been set. + $edit['data']['contact'] = variable_get('contact_default_status', 1); + } } /** diff --git a/modules/contact/contact.test b/modules/contact/contact.test index 6693b574..6a1674a0 100644 --- a/modules/contact/contact.test +++ b/modules/contact/contact.test @@ -346,6 +346,28 @@ class ContactPersonalTestCase extends DrupalWebTestCase { $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(200); + // Test that users can disable their contact form. + $this->drupalLogin($this->contact_user); + $edit = array('contact' => FALSE); + $this->drupalPost('user/' . $this->contact_user->uid . '/edit', $edit, 'Save'); + $this->drupalLogout(); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + + // Test that user's contact status stays disabled when saving. + $contact_user_temp = user_load($this->contact_user->uid, TRUE); + user_save($contact_user_temp); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + + // Test that users can enable their contact form. + $this->drupalLogin($this->contact_user); + $edit = array('contact' => TRUE); + $this->drupalPost('user/' . $this->contact_user->uid . '/edit', $edit, 'Save'); + $this->drupalLogout(); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(200); + // Revoke the personal contact permission for the anonymous user. user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); diff --git a/modules/contextual/contextual.info b/modules/contextual/contextual.info index c7df84a0..c2f68e3b 100644 --- a/modules/contextual/contextual.info +++ b/modules/contextual/contextual.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = contextual.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/dashboard/dashboard.info b/modules/dashboard/dashboard.info index 201d71eb..d4778c67 100644 --- a/modules/dashboard/dashboard.info +++ b/modules/dashboard/dashboard.info @@ -7,8 +7,7 @@ files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc index 7c1c0e20..f8a00c26 100644 --- a/modules/dblog/dblog.admin.inc +++ b/modules/dblog/dblog.admin.inc @@ -294,11 +294,18 @@ function theme_dblog_message($variables) { else { $output = t($event->message, unserialize($event->variables)); } + // If the output is expected to be a link, strip all the tags and + // special characters by using filter_xss() without any allowed tags. + // If not, use filter_xss_admin() to allow some tags. if ($variables['link'] && isset($event->wid)) { - // Truncate message to 56 chars. + // Truncate message to 56 chars after stripping all the tags. $output = truncate_utf8(filter_xss($output, array()), 56, TRUE, TRUE); $output = l($output, 'admin/reports/event/' . $event->wid, array('html' => TRUE)); } + else { + // Prevent XSS in log detail pages. + $output = filter_xss_admin($output); + } } return $output; } @@ -413,6 +420,6 @@ function dblog_clear_log_form($form) { */ function dblog_clear_log_submit() { $_SESSION['dblog_overview_filter'] = array(); - db_delete('watchdog')->execute(); + db_truncate('watchdog')->execute(); drupal_set_message(t('Database log cleared.')); } diff --git a/modules/dblog/dblog.info b/modules/dblog/dblog.info index 9b5f5d74..9c78e1b4 100644 --- a/modules/dblog/dblog.info +++ b/modules/dblog/dblog.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = dblog.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/dblog/dblog.install b/modules/dblog/dblog.install index abfd9a2c..c2e41192 100644 --- a/modules/dblog/dblog.install +++ b/modules/dblog/dblog.install @@ -154,6 +154,15 @@ function dblog_update_7002() { db_add_index('watchdog', 'severity', array('severity')); } +/** + * Account for possible legacy systems where dblog was not installed. + */ +function dblog_update_7003() { + if (!db_table_exists('watchdog')) { + db_create_table('watchdog', drupal_get_schema_unprocessed('dblog', 'watchdog')); + } +} + /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/dblog/dblog.module b/modules/dblog/dblog.module index 9183eed6..df305a2c 100644 --- a/modules/dblog/dblog.module +++ b/modules/dblog/dblog.module @@ -144,20 +144,30 @@ function _dblog_get_message_types() { * Note: Some values may be truncated to meet database column size restrictions. */ function dblog_watchdog(array $log_entry) { - Database::getConnection('default', 'default')->insert('watchdog') - ->fields(array( - 'uid' => $log_entry['uid'], - 'type' => substr($log_entry['type'], 0, 64), - 'message' => $log_entry['message'], - 'variables' => serialize($log_entry['variables']), - 'severity' => $log_entry['severity'], - 'link' => substr($log_entry['link'], 0, 255), - 'location' => $log_entry['request_uri'], - 'referer' => $log_entry['referer'], - 'hostname' => substr($log_entry['ip'], 0, 128), - 'timestamp' => $log_entry['timestamp'], - )) - ->execute(); + if (!function_exists('drupal_substr')) { + require_once DRUPAL_ROOT . '/includes/unicode.inc'; + } + try { + Database::getConnection('default', 'default')->insert('watchdog') + ->fields(array( + 'uid' => $log_entry['uid'], + 'type' => drupal_substr($log_entry['type'], 0, 64), + 'message' => $log_entry['message'], + 'variables' => serialize($log_entry['variables']), + 'severity' => $log_entry['severity'], + 'link' => drupal_substr($log_entry['link'], 0, 255), + 'location' => $log_entry['request_uri'], + 'referer' => $log_entry['referer'], + 'hostname' => drupal_substr($log_entry['ip'], 0, 128), + 'timestamp' => $log_entry['timestamp'], + )) + ->execute(); + } + catch (Exception $e) { + // Exception is ignored so that watchdog does not break pages during the + // installation process or is not able to create the watchdog table during + // installation. + } } /** diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test index bf409c94..b0a58ba4 100644 --- a/modules/dblog/dblog.test +++ b/modules/dblog/dblog.test @@ -119,13 +119,18 @@ class DBLogTestCase extends DrupalWebTestCase { private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) { global $base_root; + // This long URL makes it just a little bit harder to pass the link part of + // the test with a mix of English words and a repeating series of random + // percent-encoded Chinese characters. + $link = urldecode('/content/xo%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A-lake-isabelle'); + // Prepare the fields to be logged $log = array( 'type' => $type, 'message' => 'Log entry added to test the dblog row limit.', 'variables' => array(), 'severity' => $severity, - 'link' => NULL, + 'link' => $link, 'user' => $this->big_user, 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0, 'request_uri' => $base_root . request_uri(), @@ -515,6 +520,33 @@ class DBLogTestCase extends DrupalWebTestCase { $this->assertText(t('Database log cleared.'), 'Confirmation message found'); } + /** + * Verifies that exceptions are caught in dblog_watchdog(). + */ + protected function testDBLogException() { + $log = array( + 'type' => 'custom', + 'message' => 'Log entry added to test watchdog handling of Exceptions.', + 'variables' => array(), + 'severity' => WATCHDOG_NOTICE, + 'link' => NULL, + 'user' => $this->big_user, + 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0, + 'request_uri' => request_uri(), + 'referer' => $_SERVER['HTTP_REFERER'], + 'ip' => ip_address(), + 'timestamp' => REQUEST_TIME, + ); + + // Remove watchdog table temporarily to simulate it missing during + // installation. + db_query("DROP TABLE {watchdog}"); + + // Add a watchdog entry. + // This should not throw an Exception, but fail silently. + dblog_watchdog($log); + } + /** * Gets the database log event information from the browser page. * @@ -633,5 +665,32 @@ class DBLogTestCase extends DrupalWebTestCase { // Document Object Model (DOM). $this->assertLink(html_entity_decode($message_text), 0, $message); } -} + /** + * Make sure HTML tags are filtered out in the log detail page. + */ + public function testLogMessageSanitized() { + $this->drupalLogin($this->big_user); + + // Make sure dangerous HTML tags are filtered out in log detail page. + $log = array( + 'uid' => 0, + 'type' => 'custom', + 'message' => " Lorem ipsum", + 'variables' => NULL, + 'severity' => WATCHDOG_NOTICE, + 'link' => 'foo/bar', + 'request_uri' => 'http://example.com?dblog=1', + 'referer' => 'http://example.org?dblog=2', + 'ip' => '0.0.1.0', + 'timestamp' => REQUEST_TIME, + ); + dblog_watchdog($log); + + $wid = db_query('SELECT MAX(wid) FROM {watchdog}')->fetchField(); + $this->drupalGet('admin/reports/event/' . $wid); + $this->assertResponse(200); + $this->assertNoRaw(""); + $this->assertRaw("alert('foo'); Lorem ipsum"); + } +} diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index ba377083..7c0e3a15 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -189,7 +189,7 @@ function field_create_field($field) { } // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_create_field', $field); @@ -288,7 +288,7 @@ function field_update_field($field) { drupal_write_record('field_config', $field, $primary_key); // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_update_field', $field, $prior_field, $has_data); @@ -430,7 +430,7 @@ function field_delete_field($field_name) { ->execute(); // Clear the cache. - field_cache_clear(TRUE); + field_cache_clear(); module_invoke_all('field_delete_field', $field); } diff --git a/modules/field/field.info b/modules/field/field.info index c154828c..4cfb0b6c 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -11,8 +11,7 @@ dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc index f4f1f630..772cd451 100644 --- a/modules/field/field.info.class.inc +++ b/modules/field/field.info.class.inc @@ -612,10 +612,12 @@ class FieldInfo { // Fill in default values. $display += array( 'label' => 'above', - 'type' => $field_type_info['default_formatter'], 'settings' => array(), 'weight' => 0, ); + if (empty($display['type'])) { + $display['type'] = $field_type_info['default_formatter']; + } if ($display['type'] != 'hidden') { $formatter_type_info = field_info_formatter_types($display['type']); // Fall back to default formatter if formatter type is not available. diff --git a/modules/field/field.install b/modules/field/field.install index f6948e3b..c5dd2dc7 100644 --- a/modules/field/field.install +++ b/modules/field/field.install @@ -467,6 +467,27 @@ function field_update_7003() { // Empty update to force a rebuild of the registry. } +/** + * Grant the new "administer fields" permission to trusted users. + */ +function field_update_7004() { + // Assign the permission to anyone that already has a trusted core permission + // that would have previously let them administer fields on an entity type. + $rids = array(); + $permissions = array( + 'administer site configuration', + 'administer content types', + 'administer users', + ); + foreach ($permissions as $permission) { + $rids = array_merge($rids, array_keys(user_roles(FALSE, $permission))); + } + $rids = array_unique($rids); + foreach ($rids as $rid) { + _update_7000_user_role_grant_permissions($rid, array('administer fields'), 'field'); + } +} + /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/field/field.module b/modules/field/field.module index e4039786..8d66813f 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -316,6 +316,21 @@ function field_help($path, $arg) { } } +/** + * Implements hook_permission(). + */ +function field_permission() { + return array( + 'administer fields' => array( + 'title' => t('Administer fields'), + 'description' => t('Additional permissions are required based on what the fields are attached to (for example, administer content types to manage fields attached to content).', array( + '@url' => '#module-node', + )), + 'restrict access' => TRUE, + ), + ); +} + /** * Implements hook_theme(). */ diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.info b/modules/field/modules/field_sql_storage/field_sql_storage.info index dc1631f9..29c7421a 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,8 +7,7 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index 49e7814f..55c539c0 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -7,8 +7,7 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/modules/list/tests/list.test b/modules/field/modules/list/tests/list.test index 84de7e89..b476b5aa 100644 --- a/modules/field/modules/list/tests/list.test +++ b/modules/field/modules/list/tests/list.test @@ -212,7 +212,7 @@ class ListFieldUITestCase extends FieldTestCase { parent::setUp('field_test', 'field_ui'); // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create content type, with underscores. diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info index 912d7bf6..ca74fe3c 100644 --- a/modules/field/modules/list/tests/list_test.info +++ b/modules/field/modules/list/tests/list_test.info @@ -5,8 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index ce55ee33..c2c57a20 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -6,8 +6,7 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index 60465442..2538bdd1 100644 --- a/modules/field/modules/number/number.module +++ b/modules/field/modules/number/number.module @@ -164,6 +164,15 @@ function number_field_presave($entity_type, $entity, $field, $instance, $langcod } } } + if ($field['type'] == 'number_float') { + // Remove the decimal point from float values with decimal + // point but no decimal numbers. + foreach ($items as $delta => $item) { + if (isset($item['value'])) { + $items[$delta]['value'] = floatval($item['value']); + } + } + } } /** @@ -188,7 +197,7 @@ function number_field_formatter_info() { 'label' => t('Default'), 'field types' => array('number_integer'), 'settings' => array( - 'thousand_separator' => ' ', + 'thousand_separator' => '', // The 'decimal_separator' and 'scale' settings are not configurable // through the UI, and will therefore keep their default values. They // are only present so that the 'number_integer' and 'number_decimal' @@ -202,7 +211,7 @@ function number_field_formatter_info() { 'label' => t('Default'), 'field types' => array('number_decimal', 'number_float'), 'settings' => array( - 'thousand_separator' => ' ', + 'thousand_separator' => '', 'decimal_separator' => '.', 'scale' => 2, 'prefix_suffix' => TRUE, @@ -222,6 +231,8 @@ function number_field_formatter_settings_form($field, $instance, $view_mode, $fo $display = $instance['display'][$view_mode]; $settings = $display['settings']; + $element = array(); + if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { $options = array( '' => t(''), diff --git a/modules/field/modules/number/number.test b/modules/field/modules/number/number.test index 88029cdd..839da36c 100644 --- a/modules/field/modules/number/number.test +++ b/modules/field/modules/number/number.test @@ -23,7 +23,7 @@ class NumberFieldTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('field_test'); - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types', 'administer fields')); $this->drupalLogin($this->web_user); } @@ -152,4 +152,50 @@ class NumberFieldTestCase extends DrupalWebTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); } + + /** + * Test number_float field. + */ + function testNumberFloatField() { + $this->field = array( + 'field_name' => drupal_strtolower($this->randomName()), + 'type' => 'number_float', + 'settings' => array( + 'precision' => 8, 'scale' => 4, 'decimal_separator' => '.', + ) + ); + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'widget' => array( + 'type' => 'number', + ), + 'display' => array( + 'default' => array( + 'type' => 'number_float', + ), + ), + ); + field_create_instance($this->instance); + + $langcode = LANGUAGE_NONE; + $value = array( + '9.' => '9', + '.' => '0', + '123.55' => '123.55', + '.55' => '0.55', + '-0.55' => '-0.55', + ); + foreach($value as $key => $value) { + $edit = array( + "{$this->field['field_name']}[$langcode][0][value]" => $key, + ); + $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save')); + $this->assertNoText("PDOException"); + $this->assertRaw($value, 'Correct value is displayed.'); + } + } + } diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index 1a5ba124..71abf417 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -6,8 +6,7 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module index 3862ba77..041b84a6 100644 --- a/modules/field/modules/options/options.module +++ b/modules/field/modules/options/options.module @@ -185,6 +185,7 @@ function _options_properties($type, $multiple, $required, $has_value) { $base = array( 'filter_xss' => FALSE, 'strip_tags' => FALSE, + 'strip_tags_and_unescape' => FALSE, 'empty_option' => FALSE, 'optgroups' => FALSE, ); @@ -195,7 +196,7 @@ function _options_properties($type, $multiple, $required, $has_value) { case 'select': $properties = array( // Select boxes do not support any HTML tag. - 'strip_tags' => TRUE, + 'strip_tags_and_unescape' => TRUE, 'optgroups' => TRUE, ); if ($multiple) { @@ -271,9 +272,16 @@ function _options_prepare_options(&$options, $properties) { _options_prepare_options($options[$value], $properties); } else { + // The 'strip_tags' option is deprecated. Use 'strip_tags_and_unescape' + // when plain text is required (and where the output will be run through + // check_plain() before being inserted back into HTML) or 'filter_xss' + // when HTML is required. if ($properties['strip_tags']) { $options[$value] = strip_tags($label); } + if ($properties['strip_tags_and_unescape']) { + $options[$value] = decode_entities(strip_tags($label)); + } if ($properties['filter_xss']) { $options[$value] = field_filter_xss($label); } diff --git a/modules/field/modules/options/options.test b/modules/field/modules/options/options.test index 7183311b..321c2a4b 100644 --- a/modules/field/modules/options/options.test +++ b/modules/field/modules/options/options.test @@ -23,8 +23,15 @@ class OptionsWidgetsTestCase extends FieldTestCase { 'type' => 'list_integer', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + // Make sure that HTML entities in option text are not double-encoded. + 3 => 'Some HTML encoded markup with < & >', + ), ), ); $this->card_1 = field_create_field($this->card_1); @@ -35,8 +42,13 @@ class OptionsWidgetsTestCase extends FieldTestCase { 'type' => 'list_integer', 'cardinality' => 2, 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 0 works as an option. + 0 => 'Zero', + 1 => 'One', + // Make sure that option text is properly sanitized. + 2 => 'Some & unescaped markup', + ), ), ); $this->card_2 = field_create_field($this->card_2); @@ -47,14 +59,18 @@ class OptionsWidgetsTestCase extends FieldTestCase { 'type' => 'list_boolean', 'cardinality' => 1, 'settings' => array( - // Make sure that 0 works as a 'on' value'. - 'allowed_values' => array(1 => 'Zero', 0 => 'Some & unescaped markup'), + 'allowed_values' => array( + // Make sure that 1 works as a 'on' value'. + 1 => 'Zero', + // Make sure that option text is properly sanitized. + 0 => 'Some & unescaped markup', + ), ), ); $this->bool = field_create_field($this->bool); // Create a web user. - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer fields')); $this->drupalLogin($this->web_user); } @@ -233,6 +249,7 @@ class OptionsWidgetsTestCase extends FieldTestCase { $this->assertNoOptionSelected("edit-card-1-$langcode", 1); $this->assertNoOptionSelected("edit-card-1-$langcode", 2); $this->assertRaw('Some dangerous & unescaped markup', 'Option text was properly filtered.'); + $this->assertRaw('Some HTML encoded markup with < & >', 'HTML entities in option text were properly handled and not double-encoded'); // Submit form: select invalid 'none' option. $edit = array("card_1[$langcode]" => '_none'); @@ -459,7 +476,7 @@ class OptionsWidgetsTestCase extends FieldTestCase { $this->assertNoFieldChecked("edit-bool-$langcode"); // Create admin user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create a test field instance. diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index e8db6294..1de61433 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -7,8 +7,7 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 68fc3cb4..bf0d29d5 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -223,11 +223,13 @@ function text_field_formatter_settings_form($field, $instance, $view_mode, $form if (strpos($display['type'], '_trimmed') !== FALSE) { $element['trim_length'] = array( - '#title' => t('Trim length'), + '#title' => t('Trimmed limit'), '#type' => 'textfield', + '#field_suffix' => t('characters'), '#size' => 10, '#default_value' => $settings['trim_length'], '#element_validate' => array('element_validate_integer_positive'), + '#description' => t('If the summary is not set, the trimmed %label field will be shorter than this character limit.', array('%label' => $instance['label'])), '#required' => TRUE, ); } @@ -245,7 +247,7 @@ function text_field_formatter_settings_summary($field, $instance, $view_mode) { $summary = ''; if (strpos($display['type'], '_trimmed') !== FALSE) { - $summary = t('Trim length') . ': ' . check_plain($settings['trim_length']); + $summary = t('Trimmed limit: @trim_length characters', array('@trim_length' => $settings['trim_length'])); } return $summary; diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test index 2f147382..ad803cf4 100644 --- a/modules/field/modules/text/text.test +++ b/modules/field/modules/text/text.test @@ -424,6 +424,7 @@ class TextTranslationTestCase extends DrupalWebTestCase { 'administer content types', 'access administration pages', 'bypass node access', + 'administer fields', filter_permission_name($full_html_format), )); $this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content')); diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info index 0e66b280..fb1653fb 100644 --- a/modules/field/tests/field_test.info +++ b/modules/field/tests/field_test.info @@ -6,8 +6,7 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php index f0f9d583..460fd2e2 100644 --- a/modules/field/theme/field.tpl.php +++ b/modules/field/theme/field.tpl.php @@ -4,8 +4,10 @@ * @file field.tpl.php * Default template implementation to display the value of a field. * - * This file is not used and is here as a starting point for customization only. - * @see theme_field() + * This file is not used by Drupal core, which uses theme functions instead for + * performance reasons. The markup is the same, though, so if you want to use + * template files rather than functions to extend field theming, copy this to + * your custom theme. See theme_field() for a discussion of performance. * * Available variables: * - $items: An array of field values. Use render() to output them. @@ -45,7 +47,7 @@ */ ?> ' placeholder. if ($mode) { $content = $match[1]; - $hash = md5($content); + $hash = hash('sha256', $content); $comments[$hash] = $content; return ""; } diff --git a/modules/filter/filter.pages.inc b/modules/filter/filter.pages.inc index e602bcef..0f13da84 100644 --- a/modules/filter/filter.pages.inc +++ b/modules/filter/filter.pages.inc @@ -14,10 +14,9 @@ * @see filter_menu() * @see theme_filter_tips() */ -function filter_tips_long() { - $format_id = arg(2); - if ($format_id) { - $output = theme('filter_tips', array('tips' => _filter_tips($format_id, TRUE), 'long' => TRUE)); +function filter_tips_long($format = NULL) { + if (!empty($format)) { + $output = theme('filter_tips', array('tips' => _filter_tips($format->format, TRUE), 'long' => TRUE)); } else { $output = theme('filter_tips', array('tips' => _filter_tips(-1, TRUE), 'long' => TRUE)); diff --git a/modules/filter/filter.test b/modules/filter/filter.test index ddea6afb..34dcf043 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -555,6 +555,27 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { $this->assertTrue(isset($options[$this->allowed_format->format]), 'The allowed text format appears as an option when adding a new node.'); $this->assertFalse(isset($options[$this->disallowed_format->format]), 'The disallowed text format does not appear as an option when adding a new node.'); $this->assertTrue(isset($options[filter_fallback_format()]), 'The fallback format appears as an option when adding a new node.'); + + // Check regular user access to the filter tips pages. + $this->drupalGet('filter/tips/' . $this->allowed_format->format); + $this->assertResponse(200); + $this->drupalGet('filter/tips/' . $this->disallowed_format->format); + $this->assertResponse(403); + $this->drupalGet('filter/tips/' . filter_fallback_format()); + $this->assertResponse(200); + $this->drupalGet('filter/tips/invalid-format'); + $this->assertResponse(404); + + // Check admin user access to the filter tips pages. + $this->drupalLogin($this->admin_user); + $this->drupalGet('filter/tips/' . $this->allowed_format->format); + $this->assertResponse(200); + $this->drupalGet('filter/tips/' . $this->disallowed_format->format); + $this->assertResponse(200); + $this->drupalGet('filter/tips/' . filter_fallback_format()); + $this->assertResponse(200); + $this->drupalGet('filter/tips/invalid-format'); + $this->assertResponse(404); } /** @@ -1099,8 +1120,12 @@ class FilterUnitTestCase extends DrupalUnitTestCase { $f = filter_xss("", array('img')); $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- embedded nulls.'); - $f = filter_xss('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); + // @todo This dataset currently fails under 5.4 because of + // https://www.drupal.org/node/1210798. Restore after it's fixed. + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + $f = filter_xss('', array('img')); + $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); + } $f = filter_xss('', array('img')); $this->assertNoNormalized($f, 'vbscript', 'HTML scheme clearing evasion -- another scheme.'); @@ -1273,6 +1298,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { // Create a e-mail that is too long. $long_email = str_repeat('a', 254) . '@example.com'; $too_long_email = str_repeat('b', 255) . '@example.com'; + $email_with_plus_sign = 'one+two@example.com'; // Filter selection/pattern matching. @@ -1286,12 +1312,13 @@ http://example.com or www.example.com ), // MAILTO URLs. ' -person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . ' +person@example.com or mailto:person2@example.com or ' . $email_with_plus_sign . ' or ' . $long_email . ' but not ' . $too_long_email . ' ' => array( 'person@example.com' => TRUE, 'mailto:person2@example.com' => TRUE, '' . $long_email . '' => TRUE, '' . $too_long_email . '' => FALSE, + '' . $email_with_plus_sign . '' => TRUE, ), // URI parts and special characters. ' @@ -1983,3 +2010,26 @@ class FilterSettingsTestCase extends DrupalWebTestCase { } } } + +/** + * Tests DOMDocument serialization. + */ +class FilterDOMSerializeTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Serialization', + 'description' => 'Test serialization of DOMDocument objects.', + 'group' => 'Filter', + ); + } + + /** + * Tests empty DOMDocument object. + */ + function testFilterEmptyDOMSerialization() { + $document = new DOMDocument(); + $result = filter_dom_serialize($document); + $this->assertEqual('', $result); + } +} diff --git a/modules/forum/forum.info b/modules/forum/forum.info index 5312ec02..6907509f 100644 --- a/modules/forum/forum.info +++ b/modules/forum/forum.info @@ -9,8 +9,7 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/help/help.info b/modules/help/help.info index f51b9062..33e6c811 100644 --- a/modules/help/help.info +++ b/modules/help/help.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/image/image.info b/modules/image/image.info index 3e9ef6be..d6b6942d 100644 --- a/modules/image/image.info +++ b/modules/image/image.info @@ -7,8 +7,7 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/image/image.module b/modules/image/image.module index fac8de95..dab88361 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -64,7 +64,7 @@ function image_help($path, $arg) { $effect = image_effect_definition_load($arg[7]); return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; case 'admin/config/media/image-styles/edit/%/effects/%': - $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]); + $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[7], $arg[5]); return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; } } @@ -801,6 +801,8 @@ function image_style_options($include_empty = TRUE, $output = CHECK_PLAIN) { * * @param $style * The image style + * @param $scheme + * The file scheme, for example 'public' for public files. */ function image_style_deliver($style, $scheme) { $args = func_get_args(); @@ -833,8 +835,8 @@ function image_style_deliver($style, $scheme) { file_download($scheme, file_uri_target($derivative_uri)); } else { - $headers = module_invoke_all('file_download', $image_uri); - if (in_array(-1, $headers) || empty($headers)) { + $headers = file_download_headers($image_uri); + if (empty($headers)) { return MENU_ACCESS_DENIED; } if (count($headers)) { diff --git a/modules/image/image.test b/modules/image/image.test index 35919794..0c26ffa8 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -32,7 +32,7 @@ class ImageFieldTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('image'); - $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields')); $this->drupalLogin($this->admin_user); } @@ -77,6 +77,24 @@ class ImageFieldTestCase extends DrupalWebTestCase { return field_create_instance($instance); } + /** + * Create a random style. + * + * @return array + * A list containing the details of the generated image style. + */ + function createRandomStyle() { + $style_name = strtolower($this->randomName(10)); + $style_label = $this->randomString(); + image_style_save(array('name' => $style_name, 'label' => $style_label)); + $style_path = 'admin/config/media/image-styles/edit/' . $style_name; + return array( + 'name' => $style_name, + 'label' => $style_label, + 'path' => $style_path, + ); + } + /** * Upload an image to a node. * @@ -183,6 +201,22 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->assertResponse(404, 'Accessing an image style URL with a source image that does not exist provides a 404 error response.'); } + /** + * Test that we do not pass an array to drupal_add_http_header. + */ + function testImageContentTypeHeaders() { + $files = $this->drupalGetTestFiles('image'); + $file = array_shift($files); + // Copy the test file to private folder. + $private_file = file_copy($file, 'private://', FILE_EXISTS_RENAME); + // Tell image_module_test module to return the headers we want to test. + variable_set('image_module_test_invalid_headers', $private_file->uri); + // Invoke image_style_deliver so it will try to set headers. + $generated_url = image_style_url($this->style_name, $private_file->uri); + $this->drupalGet($generated_url); + variable_del('image_module_test_invalid_headers'); + } + /** * Test image_style_url(). */ @@ -251,7 +285,7 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], 'Expected Content-Length was reported.'); if ($scheme == 'private') { $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was set to prevent caching.'); + $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was set to prevent caching.'); $this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', 'Expected custom header has been added.'); // Make sure that a second request to the already existing derivate works @@ -469,6 +503,58 @@ class ImageEffectsUnitTest extends ImageToolkitTestCase { } } +/** + * Tests the administrative user interface. + */ +class ImageAdminUiTestCase extends ImageFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'Administrative user interface', + 'description' => 'Tests the forms used in the administrative user interface.', + 'group' => 'Image', + ); + } + + function setUp() { + parent::setUp(array('image')); + } + + /** + * Test if the help text is available on the add effect form. + */ + function testAddEffectHelpText() { + // Create a random image style. + $style = $this->createRandomStyle(); + + // Open the add effect form and check for the help text. + $this->drupalGet($style['path'] . '/add/image_crop'); + $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the add effect page.'); + } + + /** + * Test if the help text is available on the edit effect form. + */ + function testEditEffectHelpText() { + // Create a random image style. + $random_style = $this->createRandomStyle(); + + // Add the crop effect to the image style. + $edit = array(); + $edit['data[width]'] = 20; + $edit['data[height]'] = 20; + $this->drupalPost($random_style['path'] . '/add/image_crop', $edit, t('Add effect')); + + // Open the edit effect form and check for the help text. + drupal_static_reset('image_styles'); + $style = image_style_load($random_style['name']); + + foreach ($style['effects'] as $ieid => $effect) { + $this->drupalGet($random_style['path'] . '/effects/' . $ieid); + $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the edit effect page.'); + } + } +} + /** * Tests creation, deletion, and editing of image styles and effects. */ diff --git a/modules/image/tests/image_module_test.info b/modules/image/tests/image_module_test.info index 702e8901..588044e8 100644 --- a/modules/image/tests/image_module_test.info +++ b/modules/image/tests/image_module_test.info @@ -6,8 +6,7 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/image/tests/image_module_test.module b/modules/image/tests/image_module_test.module index 8a322fb9..fc66d9b8 100644 --- a/modules/image/tests/image_module_test.module +++ b/modules/image/tests/image_module_test.module @@ -9,6 +9,9 @@ function image_module_test_file_download($uri) { if (variable_get('image_module_test_file_download', FALSE) == $uri) { return array('X-Image-Owned-By' => 'image_module_test'); } + if (variable_get('image_module_test_invalid_headers', FALSE) == $uri) { + return array('Content-Type' => 'image/png'); + } } /** diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index e813962d..acf6eb2e 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -1194,7 +1194,7 @@ function locale_translate_edit_form_submit($form, &$form_state) { $translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $key))->fetchField(); if (!empty($value)) { // Only update or insert if we have a value to use. - if (!empty($translation)) { + if (is_string($translation)) { db_update('locales_target') ->fields(array( 'translation' => $value, diff --git a/modules/locale/locale.info b/modules/locale/locale.info index 61abe8a6..45d1d677 100644 --- a/modules/locale/locale.info +++ b/modules/locale/locale.info @@ -6,8 +6,7 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 90865872..db87e055 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -393,6 +393,16 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { // The indicator should not be here. $this->assertNoRaw($language_indicator, 'String is translated.'); + // Verify that a translation set which has an empty target string can be + // updated without any database error. + db_update('locales_target') + ->fields(array('translation' => '')) + ->condition('language', $langcode, '=') + ->condition('lid', $lid, '=') + ->execute(); + $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations')); + $this->assertText(t('The string has been saved.'), 'The string has been saved.'); + // Try to edit a non-existent string and ensure we're redirected correctly. // Assuming we don't have 999,999 strings already. $random_lid = 999999; @@ -809,7 +819,7 @@ class LocalePluralFormatTest extends DrupalWebTestCase { * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -1103,7 +1113,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase { * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -1330,7 +1340,7 @@ class LocaleExportFunctionalTest extends DrupalWebTestCase { function testExportTranslation() { // First import some known translations. // This will also automatically enable the 'fr' language. - $name = tempnam('temporary://', "po_") . '.po'; + $name = drupal_tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $this->getPoFile()); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', @@ -2237,6 +2247,37 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { $this->drupalLogout(); } + + /** + * Verifies that nodes may be created with different languages. + */ + function testNodeCreationWithLanguage() { + // Create an admin user and log them in. + $perms = array( + // Standard node permissions. + 'create page content', + 'administer content types', + 'administer nodes', + 'bypass node access', + // Locale. + 'administer languages', + ); + $web_user = $this->drupalCreateUser($perms); + $this->drupalLogin($web_user); + + // Create some test nodes using different langcodes. + foreach (array(LANGUAGE_NONE, 'en', 'fr') as $langcode) { + $node_args = array( + 'type' => 'page', + 'promote' => 1, + 'language' => $langcode, + ); + $node = $this->drupalCreateNode($node_args); + $node_reloaded = node_load($node->nid, NULL, TRUE); + $this->assertEqual($node_reloaded->language, $langcode, format_string('The language code of the node was successfully set to @langcode.', array('@langcode' => $langcode))); + } + } + } /** @@ -2629,6 +2670,68 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { $this->drupalGet("$prefix/$path"); $this->assertResponse(404, $message2); } + + /** + * Check URL rewriting when using a domain name and a non-standard port. + */ + function testDomainNameNegotiationPort() { + $language_domain = 'example.fr'; + $edit = array( + 'locale_language_negotiation_url_part' => 1, + ); + $this->drupalPost('admin/config/regional/language/configure/url', $edit, t('Save configuration')); + $edit = array( + 'prefix' => '', + 'domain' => $language_domain + ); + $this->drupalPost('admin/config/regional/language/edit/fr', $edit, t('Save language')); + + // Enable domain configuration. + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN); + + // Reset static caching. + drupal_static_reset('language_list'); + drupal_static_reset('language_url_outbound_alter'); + drupal_static_reset('language_url_rewrite_url'); + + // In case index.php is part of the URLs, we need to adapt the asserted + // URLs as well. + $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE; + + // Remember current HTTP_HOST. + $http_host = $_SERVER['HTTP_HOST']; + + // Fake a different port. + $_SERVER['HTTP_HOST'] .= ':88'; + + // Create an absolute French link. + $languages = language_list(); + $language = $languages['fr']; + $url = url('', array( + 'absolute' => TRUE, + 'language' => $language + )); + + $expected = 'http://example.fr:88/'; + $expected .= $index_php ? 'index.php/' : ''; + + $this->assertEqual($url, $expected, 'The right port is used.'); + + // If we set the port explicitly in url(), it should not be overriden. + $url = url('', array( + 'absolute' => TRUE, + 'language' => $language, + 'base_url' => $GLOBALS['base_url'] . ':90', + )); + + $expected = 'http://example.fr:90/'; + $expected .= $index_php ? 'index.php/' : ''; + + $this->assertEqual($url, $expected, 'A given port is not overriden.'); + + // Restore HTTP_HOST. + $_SERVER['HTTP_HOST'] = $http_host; + } } /** @@ -3141,3 +3244,46 @@ class LocaleCSSAlterTest extends DrupalWebTestCase { $this->assertRaw('@import url("' . $base_url . '/modules/system/system.messages.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.messages-rtl.css' . $query_string . '");' . "\n", 'CSS: system.messages-rtl.css is added directly after system.messages.css.'); } } + +/** + * Tests locale translation safe string handling. + */ +class LocaleStringIsSafeTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Test if a string is safe', + 'description' => 'Tests locale translation safe string handling.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Tests for locale_string_is_safe(). + */ + public function testLocaleStringIsSafe() { + // Check a translatable string without HTML. + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + + // Check a translatable string which includes trustable HTML. + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + + // Check an untranslatable string which includes untrustable HTML (according + // to the locale_string_is_safe() function definition). + $string = 'Hello world!'; + $result = locale_string_is_safe($string); + $this->assertFalse($result); + + // Check a translatable string which includes a token in an href attribute. + $string = 'Hi user'; + $result = locale_string_is_safe($string); + $this->assertTrue($result); + } +} diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info index 5e8d50f7..20ae2b47 100644 --- a/modules/locale/tests/locale_test.info +++ b/modules/locale/tests/locale_test.info @@ -5,8 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc index 66bd6f3b..a24703c9 100644 --- a/modules/menu/menu.admin.inc +++ b/modules/menu/menu.admin.inc @@ -281,6 +281,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) { $form['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), + '#maxlength' => 255, '#default_value' => $item['link_title'], '#description' => t('The text to be used for this link in the menu.'), '#required' => TRUE, @@ -305,7 +306,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) { '#title' => t('Path'), '#maxlength' => 255, '#default_value' => $path, - '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')), + '#description' => t('The path for this menu link. This can be an internal path such as %add-node or an external URL such as %example. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%example' => 'http://example.com')), '#required' => TRUE, ); $form['actions']['delete'] = array( diff --git a/modules/menu/menu.info b/modules/menu/menu.info index 7212d9ab..b44e1301 100644 --- a/modules/menu/menu.info +++ b/modules/menu/menu.info @@ -6,8 +6,7 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/menu/menu.module b/modules/menu/menu.module index dc8f015d..27b1675a 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -674,6 +674,7 @@ function menu_form_node_form_alter(&$form, $form_state) { $form['menu']['link']['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), + '#maxlength' => 255, '#default_value' => $link['link_title'], ); diff --git a/modules/menu/menu.test b/modules/menu/menu.test index a9bdb5f2..bb792ee8 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -72,6 +72,17 @@ class MenuTestCase extends DrupalWebTestCase { $saved_item = menu_link_load($item['mlid']); $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)'); $this->resetMenuLink($item, $old_title); + + // Test that the page title is correct when a local task appears in a + // top-level menu item. See https://www.drupal.org/node/1973262. + $item = $this->addMenuLink(0, 'user/register', 'user-menu'); + $this->drupalGet('user/password'); + $this->assertNoTitle('Home | Drupal'); + $this->drupalLogout(); + $this->drupalGet('user/register'); + $this->assertTitle($item['link_title'] . ' | Drupal'); + $this->drupalGet('user'); + $this->assertNoTitle('Home | Drupal'); } /** @@ -637,7 +648,12 @@ class MenuNodeTestCase extends DrupalWebTestCase { ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - // Create a node. + // Verify that the menu link title on the node add form has the correct + // maxlength. + $this->drupalGet('node/add/page'); + $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.'); + + // Create a node with menu link disabled. $node_title = $this->randomName(); $language = LANGUAGE_NONE; $edit = array( @@ -673,6 +689,10 @@ class MenuNodeTestCase extends DrupalWebTestCase { $this->drupalGet('node/' . $node->nid . '/edit'); $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form'); + // Verify that the menu link title on the node edit form has the correct + // maxlength. + $this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.'); + // Edit the node and remove the menu link. $edit = array( 'menu[enabled]' => FALSE, diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 55af6670..c451dc7e 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -11,7 +11,7 @@ function node_overview_types() { $types = node_type_get_types(); $names = node_type_get_names(); - $field_ui = module_exists('field_ui'); + $field_ui = module_exists('field_ui') && user_access('administer fields'); $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2')); $rows = array(); diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc index 35f4c1d5..eead4ea9 100644 --- a/modules/node/node.admin.inc +++ b/modules/node/node.admin.inc @@ -329,6 +329,8 @@ function _node_mass_update_helper($nid, $updates) { } /** + * Implements callback_batch_operation(). + * * Executes a batch operation for node_mass_update(). * * @param array $nodes @@ -367,7 +369,9 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) { } /** - * Menu callback: Reports the status of batch operation for node_mass_update(). + * Implements callback_batch_finished(). + * + * Reports the status of batch operation for node_mass_update(). * * @param bool $success * A boolean indicating whether the batch mass update operation successfully @@ -504,14 +508,17 @@ function node_admin_nodes() { $options = array(); foreach ($nodes as $node) { $langcode = entity_language('node', $node); - $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array(); + $uri = entity_uri('node', $node); + if ($langcode != LANGUAGE_NONE && isset($languages[$langcode])) { + $uri['options']['language'] = $languages[$langcode]; + } $options[$node->nid] = array( 'title' => array( 'data' => array( '#type' => 'link', '#title' => $node->title, - '#href' => 'node/' . $node->nid, - '#options' => $l_options, + '#href' => $uri['path'], + '#options' => $uri['options'], '#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))), ), ), diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 9a4d0959..c8176a7d 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -950,7 +950,7 @@ function hook_node_info() { * 'recent', or 'comments'. The values should be arrays themselves, with the * following keys available: * - title: (required) The human readable name of the ranking mechanism. - * - join: (optional) The part of a query string to join to any additional + * - join: (optional) An array with information to join any additional * necessary table. This is not necessary if the table required is already * joined to by the base query, such as for the {node} table. Other tables * should use the full table name as an alias to avoid naming collisions. @@ -974,7 +974,12 @@ function hook_ranking() { 'title' => t('Average vote'), // Note that we use i.sid, the search index's search item id, rather than // n.nid. - 'join' => 'LEFT JOIN {vote_node_data} vote_node_data ON vote_node_data.nid = i.sid', + 'join' => array( + 'type' => 'LEFT', + 'table' => 'vote_node_data', + 'alias' => 'vote_node_data', + 'on' => 'vote_node_data.nid = i.sid', + ), // The highest possible score should be 1, and the lowest possible score, // always 0, should be 0. 'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)', @@ -1079,19 +1084,9 @@ function hook_delete($node) { * @ingroup node_api_hooks */ function hook_prepare($node) { - $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE)); - if ($file) { - if (!image_get_info($file->uri)) { - form_set_error($field_name, t('Uploaded file is not a valid image')); - return; - } - } - else { - return; + if (!isset($node->mymodule_value)) { + $node->mymodule_value = 'foo'; } - $node->images['_original'] = $file->uri; - _image_build_derivatives($node, TRUE); - $node->new_file = TRUE; } /** diff --git a/modules/node/node.info b/modules/node/node.info index 758179dc..0094ad6a 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -9,8 +9,7 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/node/node.install b/modules/node/node.install index 0b0a7bd5..3c4e7c2a 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -410,6 +410,7 @@ function node_schema() { 'nid' => array( 'description' => 'The {node}.nid that was read.', 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, ), @@ -943,6 +944,23 @@ function node_update_7015() { ->execute(); } +/** + * Change {history}.nid to an unsigned int in order to match {node}.nid. + */ +function node_update_7016() { + db_drop_primary_key('history'); + db_drop_index('history', 'nid'); + db_change_field('history', 'nid', 'nid', array( + 'description' => 'The {node}.nid that was read.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + )); + db_add_primary_key('history', array('uid', 'nid')); + db_add_index('history', 'nid', array('nid')); +} + /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/node/node.module b/modules/node/node.module index 7a6246d5..1d88834c 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -2953,7 +2953,10 @@ function node_search_validate($form, &$form_state) { * system. When adding a node listing to your module, be sure to use a dynamic * query created by db_select() and add a tag of "node_access". This will allow * modules dealing with node access to ensure only nodes to which the user has - * access are retrieved, through the use of hook_query_TAG_alter(). + * access are retrieved, through the use of hook_query_TAG_alter(). Tagging a + * query with "node_access" does not check the published/unpublished status of + * nodes, so the base query is responsible for ensuring that unpublished nodes + * are not displayed to inappropriate users. * * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access() * will block access to the node. Therefore, implementers should take care to @@ -3669,6 +3672,8 @@ function node_access_rebuild($batch_mode = FALSE) { } /** + * Implements callback_batch_operation(). + * * Performs batch operation for node_access_rebuild(). * * This is a multistep operation: we go through all nodes by packs of 20. The @@ -3683,7 +3688,7 @@ function _node_access_rebuild_batch_operation(&$context) { // Initiate multistep processing. $context['sandbox']['progress'] = 0; $context['sandbox']['current_node'] = 0; - $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); + $context['sandbox']['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField(); } // Process the next 20 nodes. @@ -3707,6 +3712,8 @@ function _node_access_rebuild_batch_operation(&$context) { } /** + * Implements callback_batch_finished(). + * * Performs post-processing for node_access_rebuild(). * * @param bool $success diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index cc3908e3..72b0ea7c 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -396,7 +396,6 @@ function node_preview($node) { $cloned_node->changed = REQUEST_TIME; $nodes = array($cloned_node->nid => $cloned_node); - field_attach_prepare_view('node', $nodes, 'full'); // Display a preview of the node. if (!form_get_errors()) { diff --git a/modules/node/node.test b/modules/node/node.test index 5c9118eb..e8eb459e 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -457,10 +457,70 @@ class PagePreviewTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp(); + parent::setUp(array('taxonomy', 'node')); $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); $this->drupalLogin($web_user); + + // Add a vocabulary so we can test different view modes. + $vocabulary = (object) array( + 'name' => $this->randomName(), + 'description' => $this->randomName(), + 'machine_name' => drupal_strtolower($this->randomName()), + 'help' => '', + 'nodes' => array('page' => 'page'), + ); + taxonomy_vocabulary_save($vocabulary); + + $this->vocabulary = $vocabulary; + + // Add a term to the vocabulary. + $term = (object) array( + 'name' => $this->randomName(), + 'description' => $this->randomName(), + // Use the first available text format. + 'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(), + 'vid' => $this->vocabulary->vid, + 'vocabulary_machine_name' => $vocabulary->machine_name, + ); + taxonomy_term_save($term); + + $this->term = $term; + + // Set up a field and instance. + $this->field_name = drupal_strtolower($this->randomName()); + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'taxonomy_term_reference', + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->machine_name, + 'parent' => '0', + ), + ), + ) + ); + + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'page', + 'widget' => array( + 'type' => 'options_select', + ), + // Hide on full display but render on teaser. + 'display' => array( + 'default' => array( + 'type' => 'hidden', + ), + 'teaser' => array( + 'type' => 'taxonomy_term_reference_link', + ), + ), + ); + field_create_instance($this->instance); } /** @@ -470,21 +530,26 @@ class PagePreviewTestCase extends DrupalWebTestCase { $langcode = LANGUAGE_NONE; $title_key = "title"; $body_key = "body[$langcode][0][value]"; + $term_key = "{$this->field_name}[$langcode]"; // Fill in node creation form and preview node. $edit = array(); $edit[$title_key] = $this->randomName(8); $edit[$body_key] = $this->randomName(16); + $edit[$term_key] = $this->term->tid; $this->drupalPost('node/add/page', $edit, t('Preview')); - // Check that the preview is displaying the title and body. + // Check that the preview is displaying the title, body, and term. $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.'); $this->assertText($edit[$title_key], 'Title displayed.'); $this->assertText($edit[$body_key], 'Body displayed.'); + $this->assertText($this->term->name, 'Term displayed.'); - // Check that the title and body fields are displayed with the correct values. + // Check that the title, body, and term fields are displayed with the + // correct values. $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.'); } /** @@ -494,6 +559,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { $langcode = LANGUAGE_NONE; $title_key = "title"; $body_key = "body[$langcode][0][value]"; + $term_key = "{$this->field_name}[$langcode]"; // Force revision on "Basic page" content. variable_set('node_options_page', array('status', 'revision')); @@ -501,17 +567,21 @@ class PagePreviewTestCase extends DrupalWebTestCase { $edit = array(); $edit[$title_key] = $this->randomName(8); $edit[$body_key] = $this->randomName(16); + $edit[$term_key] = $this->term->tid; $edit['log'] = $this->randomName(32); $this->drupalPost('node/add/page', $edit, t('Preview')); - // Check that the preview is displaying the title and body. + // Check that the preview is displaying the title, body, and term. $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.'); $this->assertText($edit[$title_key], 'Title displayed.'); $this->assertText($edit[$body_key], 'Body displayed.'); + $this->assertText($this->term->name, 'Term displayed.'); - // Check that the title and body fields are displayed with the correct values. + // Check that the title, body, and term fields are displayed with the + // correct values. $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.'); $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.'); + $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.'); // Check that the log field has the correct value. $this->assertFieldByName('log', $edit['log'], 'Log field displayed.'); @@ -1448,7 +1518,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { * Tests editing a node type using the UI. */ function testNodeTypeEditing() { - $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer fields')); $this->drupalLogin($web_user); $instance = field_info_instance('node', 'body', 'page'); @@ -2698,8 +2768,8 @@ class NodeAccessFieldTestCase extends NodeWebTestCase { node_access_rebuild(); // Create some users. - $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access')); - $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access', 'administer fields')); + $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields')); // Add a custom field to the page content type. $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); @@ -2916,3 +2986,36 @@ class NodePageCacheTest extends NodeWebTestCase { $this->assertResponse(404); } } + +/** + * Tests that multi-byte UTF-8 characters are stored and retrieved correctly. + */ +class NodeMultiByteUtf8Test extends NodeWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Multi-byte UTF-8', + 'description' => 'Test that multi-byte UTF-8 characters are stored and retrieved correctly.', + 'group' => 'Node', + ); + } + + /** + * Tests that multi-byte UTF-8 characters are stored and retrieved correctly. + */ + public function testMultiByteUtf8() { + $connection = Database::getConnection(); + // On MySQL, this test will only run if 'charset' is set to 'utf8mb4' in + // settings.php. + if (!($connection->utf8mb4IsSupported() && $connection->utf8mb4IsActive())) { + return; + } + $title = '🐙'; + $this->assertTrue(drupal_strlen($title, 'utf-8') < strlen($title), 'Title has multi-byte characters.'); + $node = $this->drupalCreateNode(array('title' => $title)); + $this->drupalGet('node/' . $node->nid); + $result = $this->xpath('//h1[@id="page-title"]'); + $this->assertEqual(trim((string) $result[0]), $title, 'The passed title was returned.'); + } + +} diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info index 87683bdb..a7aad51e 100644 --- a/modules/node/tests/node_access_test.info +++ b/modules/node/tests/node_access_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info index c91df020..86337664 100644 --- a/modules/node/tests/node_test.info +++ b/modules/node/tests/node_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info index 3a805b39..7c495bfe 100644 --- a/modules/node/tests/node_test_exception.info +++ b/modules/node/tests/node_test_exception.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/openid/openid.info b/modules/openid/openid.info index 62800a65..09a49f79 100644 --- a/modules/openid/openid.info +++ b/modules/openid/openid.info @@ -5,8 +5,7 @@ package = Core core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/openid/openid.test b/modules/openid/openid.test index 5f7493a5..d0708e03 100644 --- a/modules/openid/openid.test +++ b/modules/openid/openid.test @@ -680,11 +680,11 @@ class OpenIDTestCase extends DrupalWebTestCase { * Test _openid_dh_XXX_to_XXX() functions. */ function testConversion() { - $this->assertEqual(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.'); - $this->assertEqual(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '09876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.'); + $this->assertIdentical(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.'); + $this->assertIdentical(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '9876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.'); - $this->assertEqual(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.'); - $this->assertEqual(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '09876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.'); + $this->assertIdentical(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.'); + $this->assertIdentical(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '9876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.'); } /** diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info index 3c66f075..712801ad 100644 --- a/modules/openid/tests/openid_test.info +++ b/modules/openid/tests/openid_test.info @@ -6,8 +6,7 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 7859821b..efb26370 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -350,7 +350,7 @@ Drupal.overlay.setFocusBefore = function ($element, document) { * TRUE if the URL represents an administrative link, FALSE otherwise. */ Drupal.overlay.isAdminLink = function (url) { - if (Drupal.overlay.isExternalLink(url)) { + if (!Drupal.urlIsLocal(url)) { return false; } @@ -378,6 +378,8 @@ Drupal.overlay.isAdminLink = function (url) { /** * Determine whether a link is external to the site. * + * Deprecated. Use Drupal.urlIsLocal() instead. + * * @param url * The URL to be tested. * @@ -385,8 +387,7 @@ Drupal.overlay.isAdminLink = function (url) { * TRUE if the URL is external to the site, FALSE otherwise. */ Drupal.overlay.isExternalLink = function (url) { - var re = RegExp('^((f|ht)tps?:)?//(?!' + window.location.host + ')'); - return re.test(url); + return !Drupal.urlIsLocal(url); }; /** @@ -405,7 +406,7 @@ Drupal.overlay.isExternalLink = function (url) { */ Drupal.overlay.getInternalUrl = function (path) { var url = Drupal.settings.basePath + path; - if (!this.isExternalLink(url)) { + if (Drupal.urlIsLocal(url)) { return url; } }; diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info index ec0cdf4d..656c6577 100644 --- a/modules/overlay/overlay.info +++ b/modules/overlay/overlay.info @@ -4,8 +4,7 @@ package = Core version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index 7b2fc939..7e54734e 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -78,6 +78,20 @@ function overlay_theme() { ); } +/** + * Implements hook_form_alter(). + */ +function overlay_form_alter(&$form, &$form_state) { + // Add a hidden element to prevent dropping out of the overlay when a form is + // submitted inside the overlay using a GET method. + if (isset($form['#method']) && $form['#method'] == 'get' && isset($_REQUEST['render']) && $_REQUEST['render'] == 'overlay' && !isset($form['render'])) { + $form['render'] = array( + '#type' => 'hidden', + '#value' => 'overlay', + ); + } +} + /** * Implements hook_form_FORM_ID_alter(). */ diff --git a/modules/path/path.info b/modules/path/path.info index a70da447..0edafb7d 100644 --- a/modules/path/path.info +++ b/modules/path/path.info @@ -6,8 +6,7 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/path/path.module b/modules/path/path.module index 81c7bb2c..4614b0fa 100644 --- a/modules/path/path.module +++ b/modules/path/path.module @@ -185,7 +185,7 @@ function path_form_element_validate($element, &$form_state, $complete_form) { * Implements hook_node_insert(). */ function path_node_insert($node) { - if (isset($node->path)) { + if (isset($node->path) && isset($node->path['alias'])) { $path = $node->path; $path['alias'] = trim($path['alias']); // Only save a non-empty alias. @@ -205,9 +205,9 @@ function path_node_insert($node) { function path_node_update($node) { if (isset($node->path)) { $path = $node->path; - $path['alias'] = trim($path['alias']); + $path['alias'] = isset($path['alias']) ? trim($path['alias']) : ''; // Delete old alias if user erased it. - if (!empty($path['pid']) && empty($path['alias'])) { + if (!empty($path['pid']) && !$path['alias']) { path_delete($path['pid']); } path_node_insert($node); diff --git a/modules/path/path.test b/modules/path/path.test index edecff5c..f6131ce6 100644 --- a/modules/path/path.test +++ b/modules/path/path.test @@ -21,7 +21,7 @@ class PathTestCase extends DrupalWebTestCase { parent::setUp('path'); // Create test user and login. - $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases')); + $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases', 'access content overview')); $this->drupalLogin($web_user); } @@ -160,6 +160,34 @@ class PathTestCase extends DrupalWebTestCase { $this->drupalGet($edit['path[alias]']); $this->assertNoText($node1->title, 'Alias was successfully deleted.'); $this->assertResponse(404); + + // Create third test node. + $node3 = $this->drupalCreateNode(); + + // Create an invalid alias with a leading slash and verify that the slash + // is removed when the link is generated. This ensures that URL aliases + // cannot be used to inject external URLs. + // @todo The user interface should either display an error message or + // automatically trim these invalid aliases, rather than allowing them to + // be silently created, at which point the functional aspects of this + // test will need to be moved elsewhere and switch to using a + // programmatically-created alias instead. + $alias = $this->randomName(8); + $edit = array('path[alias]' => '/' . $alias); + $this->drupalPost('node/' . $node3->nid . '/edit', $edit, t('Save')); + $this->drupalGet('admin/content'); + // This checks the link href before clicking it, rather than using + // DrupalWebTestCase::assertUrl() after clicking it, because the test + // browser does not always preserve the correct number of slashes in the + // URL when it visits internal links; using DrupalWebTestCase::assertUrl() + // would actually make the test pass unconditionally on the testbot (or + // anywhere else where Drupal is installed in a subdirectory). + $link_xpath = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $node3->title)); + $link_href = (string) $link_xpath[0]['href']; + $link_prefix = base_path() . (variable_get('clean_url', 0) ? '' : '?q='); + $this->assertEqual($link_href, $link_prefix . $alias); + $this->clickLink($node3->title); + $this->assertResponse(404); } /** diff --git a/modules/php/php.info b/modules/php/php.info index 93aed895..9ea7278c 100644 --- a/modules/php/php.info +++ b/modules/php/php.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/poll/poll.info b/modules/poll/poll.info index aa203f9b..3f0d235b 100644 --- a/modules/poll/poll.info +++ b/modules/poll/poll.info @@ -6,8 +6,7 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/poll/poll.module b/modules/poll/poll.module index bfc72bf9..336e4456 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -631,9 +631,6 @@ function poll_delete($node) { * The node object to load. */ function poll_block_latest_poll_view($node) { - global $user; - $output = ''; - // This is necessary for shared objects because PHP doesn't copy objects, but // passes them by reference. So when the objects are cached it can result in // the wrong output being displayed on subsequent calls. The cloning and @@ -674,9 +671,6 @@ function poll_block_latest_poll_view($node) { * Implements hook_view(). */ function poll_view($node, $view_mode) { - global $user; - $output = ''; - if (!empty($node->allowvotes) && empty($node->show_results)) { $node->content['poll_view_voting'] = drupal_get_form('poll_view_voting', $node); } @@ -694,7 +688,7 @@ function poll_view($node, $view_mode) { function poll_teaser($node) { $teaser = NULL; if (is_array($node->choice)) { - foreach ($node->choice as $k => $choice) { + foreach ($node->choice as $choice) { if ($choice['chtext'] != '') { $teaser .= '* ' . check_plain($choice['chtext']) . "\n"; } diff --git a/modules/profile/profile.info b/modules/profile/profile.info index 4aa11a86..ce6d9549 100644 --- a/modules/profile/profile.info +++ b/modules/profile/profile.info @@ -11,8 +11,7 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/profile/profile.test b/modules/profile/profile.test index 6cb07391..18924714 100644 --- a/modules/profile/profile.test +++ b/modules/profile/profile.test @@ -342,7 +342,7 @@ class ProfileTestAutocomplete extends ProfileTestCase { // Autocomplete always uses non-clean URLs. $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; $GLOBALS['conf']['clean_url'] = 0; - $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE)); + $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE, 'script' => 'index.php')); $GLOBALS['conf']['clean_url'] = $current_clean_url; $autocomplete_id = drupal_html_id('edit-' . $field['form_name'] . '-autocomplete'); $autocomplete_html = ''; diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info index af080ce4..ea28b0e3 100644 --- a/modules/rdf/rdf.info +++ b/modules/rdf/rdf.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info index 3b80f351..7e0e2b21 100644 --- a/modules/rdf/tests/rdf_test.info +++ b/modules/rdf/tests/rdf_test.info @@ -4,9 +4,9 @@ package = Testing version = VERSION core = 7.x hidden = TRUE +dependencies[] = blog -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/search/search.admin.inc b/modules/search/search.admin.inc index a609485a..a37d37b9 100644 --- a/modules/search/search.admin.inc +++ b/modules/search/search.admin.inc @@ -125,6 +125,16 @@ function search_admin_settings($form) { '#options' => $module_options, '#description' => t('Choose which search module is the default.') ); + $form['logging'] = array( + '#type' => 'fieldset', + '#title' => t('Logging') + ); + $form['logging']['search_logging'] = array( + '#type' => 'checkbox', + '#title' => t('Log searches'), + '#default_value' => variable_get('search_logging', 1), + '#description' => t('If checked, all searches will be logged. Uncheck to skip logging. Logging may affect performance.'), + ); $form['#validate'][] = 'search_admin_settings_validate'; $form['#submit'][] = 'search_admin_settings_submit'; diff --git a/modules/search/search.api.php b/modules/search/search.api.php index 62d53b85..8c17bb4f 100644 --- a/modules/search/search.api.php +++ b/modules/search/search.api.php @@ -30,8 +30,9 @@ * * @return * Array with optional keys: - * - title: Title for the tab on the search page for this module. Defaults - * to the module name if not given. + * - title: Title for the tab on the search page for this module. Title must + * be untranslated. Outside of this return array, pass the title through the + * t() function to register it as a translatable string. * - path: Path component after 'search/' for searching with this module. * Defaults to the module name if not given. * - conditions_callback: An implementation of callback_search_conditions(). @@ -39,6 +40,9 @@ * @ingroup search */ function hook_search_info() { + // Make the title translatable. + t('Content'); + return array( 'title' => 'Content', 'path' => 'node', diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc index 72cea647..40742569 100644 --- a/modules/search/search.extender.inc +++ b/modules/search/search.extender.inc @@ -409,10 +409,10 @@ class SearchQuery extends SelectQueryExtender { * used. However, if at least one call to addScore() has taken place, the * keyword relevance score is not automatically added. * - * Also note that if you call orderBy() directly on the query, search scores - * will not automatically be used to order search results. Your orderBy() - * expression can reference 'calculated_score', which will be the total - * calculated score value. + * Note that you must use this method to add ordering to your searches, and + * not call orderBy() directly, when using the SearchQuery extender. This is + * because of the two-pass system the SearchQuery class uses to normalize + * scores. * * @param $score * The score expression, which should evaluate to a number between 0 and 1. diff --git a/modules/search/search.info b/modules/search/search.info index 5743fd51..31328786 100644 --- a/modules/search/search.info +++ b/modules/search/search.info @@ -8,8 +8,7 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/search/search.install b/modules/search/search.install index f0113b3f..c91283c4 100644 --- a/modules/search/search.install +++ b/modules/search/search.install @@ -12,6 +12,7 @@ function search_uninstall() { variable_del('minimum_word_size'); variable_del('overlap_cjk'); variable_del('search_cron_limit'); + variable_del('search_logging'); } /** diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc index 9dd00a6d..b24de8ed 100644 --- a/modules/search/search.pages.inc +++ b/modules/search/search.pages.inc @@ -49,7 +49,7 @@ function search_view($module = NULL, $keys = '') { // which will get us back to this page callback. In other words, the search // form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle. - if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') { + if (empty($_POST['form_id']) || ($_POST['form_id'] != 'search_form' && $_POST['form_id'] != 'search_block_form')) { $conditions = NULL; if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) { // Build an optional array of more search conditions. @@ -57,9 +57,10 @@ function search_view($module = NULL, $keys = '') { } // Only search if there are keywords or non-empty conditions. if ($keys || !empty($conditions)) { - // Log the search keys. - watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); - + if (variable_get('search_logging', TRUE)) { + // Log the search keys. + watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); + } // Collect the search results. $results = search_data($keys, $info['module'], $conditions); } diff --git a/modules/search/search.test b/modules/search/search.test index 5f16db3f..d3a60b49 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -666,6 +666,24 @@ class SearchBlockTestCase extends DrupalWebTestCase { url('search/node/', array('absolute' => TRUE)), 'Redirected to correct url.' ); + + // Test that after entering a too-short keyword in the form, you can then + // search again with a longer keyword. First test using the block form. + $terms = array('search_block_form' => 'a'); + $this->drupalPost('node', $terms, t('Search')); + $this->assertText('You must include at least one positive keyword with 3 characters or more'); + $terms = array('search_block_form' => 'foo'); + $this->drupalPost(NULL, $terms, t('Search')); + $this->assertNoText('You must include at least one positive keyword with 3 characters or more'); + $this->assertText('Your search yielded no results'); + + // Same test again, using the search page form for the second search this time. + $terms = array('search_block_form' => 'a'); + $this->drupalPost('node', $terms, t('Search')); + $terms = array('keys' => 'foo'); + $this->drupalPost(NULL, $terms, t('Search')); + $this->assertNoText('You must include at least one positive keyword with 3 characters or more'); + $this->assertText('Your search yielded no results'); } } @@ -1435,7 +1453,7 @@ class SearchConfigSettingsForm extends DrupalWebTestCase { parent::setUp('search', 'search_extra_type'); // Login as a user that can create and search content. - $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks')); + $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks', 'access site reports')); $this->drupalLogin($this->search_user); // Add a single piece of content and index it. @@ -1484,6 +1502,19 @@ class SearchConfigSettingsForm extends DrupalWebTestCase { ); $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); $this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.'); + + // Test logging setting. It should be on by default. + $text = $this->randomName(5); + $this->drupalPost('search/node', array('keys' => $text), t('Search')); + $this->drupalGet('admin/reports/dblog'); + $this->assertLink('Searched Content for ' . $text . '.', 0, 'Search was logged'); + + // Turn off logging. + variable_set('search_logging', FALSE); + $text = $this->randomName(5); + $this->drupalPost('search/node', array('keys' => $text), t('Search')); + $this->drupalGet('admin/reports/dblog'); + $this->assertNoLink('Searched Content for ' . $text . '.', 'Search was not logged'); } /** @@ -2029,10 +2060,11 @@ class SearchNodeAccessTest extends DrupalWebTestCase { } /** - * Tests that search returns results with punctuation in the search phrase. + * Tests that search works with punctuation and HTML entities. */ function testPhraseSearchPunctuation() { $node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => "The bunny's ears were fuzzy."))))); + $node2 = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'Dignissim Aliquam & Quieligo meus natu quae quia te. Damnum© erat— neo pneum. Facilisi feugiat ibidem ratis.'))))); // Update the search index. module_invoke_all('update_index'); @@ -2045,6 +2077,17 @@ class SearchNodeAccessTest extends DrupalWebTestCase { $edit = array('keys' => '"bunny\'s"'); $this->drupalPost('search/node', $edit, t('Search')); $this->assertText($node->title); + + // Search for "&" and verify entities are not broken up in the output. + $edit = array('keys' => '&'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertNoRaw('&amp;'); + $this->assertText('You must include at least one positive keyword'); + + $edit = array('keys' => '&'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertNoRaw('&amp;'); + $this->assertText('You must include at least one positive keyword'); } } diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info index 2e671e39..0874c63a 100644 --- a/modules/search/tests/search_embedded_form.info +++ b/modules/search/tests/search_embedded_form.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/search/tests/search_extra_type.info b/modules/search/tests/search_extra_type.info index e83c32fa..67b8566d 100644 --- a/modules/search/tests/search_extra_type.info +++ b/modules/search/tests/search_extra_type.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/search/tests/search_node_tags.info b/modules/search/tests/search_node_tags.info index 266778d6..f260f0b4 100644 --- a/modules/search/tests/search_node_tags.info +++ b/modules/search/tests/search_node_tags.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info index 60aa6ae2..1c1b2a5b 100644 --- a/modules/shortcut/shortcut.info +++ b/modules/shortcut/shortcut.info @@ -6,8 +6,7 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index b67c478a..3124ffe8 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -39,6 +39,13 @@ abstract class DrupalTestCase { */ protected $originalFileDirectory = NULL; + /** + * URL to the verbose output file directory. + * + * @var string + */ + protected $verboseDirectoryUrl; + /** * Time limit for the test. */ @@ -461,8 +468,11 @@ protected function error($message = '', $group = 'Other', array $caller = NULL) protected function verbose($message) { if ($id = simpletest_verbose($message)) { $class_safe = str_replace('\\', '_', get_class($this)); - $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html'); - $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice'); + $url = $this->verboseDirectoryUrl . '/' . $class_safe . '-' . $id . '.html'; + // Not using l() to avoid invoking the theme system, so that unit tests + // can use verbose() as well. + $link = '' . t('Verbose message') . ''; + $this->error($link, 'User notice'); } } @@ -719,10 +729,17 @@ function __construct($test_id = NULL) { * method. */ protected function setUp() { - global $conf; + global $conf, $language; // Store necessary current values before switching to the test environment. $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose'); + + // Set up English language. + $this->originalLanguage = $language; + $this->originalLanguageDefault = variable_get('language_default'); + unset($conf['language_default']); + $language = language_default(); // Reset all statics so that test is performed with a clean environment. drupal_static_reset(); @@ -764,7 +781,7 @@ protected function setUp() { } protected function tearDown() { - global $conf; + global $conf, $language; // Get back to the original connection. Database::removeConnection('default'); @@ -775,6 +792,12 @@ protected function tearDown() { if (isset($this->originalModuleList)) { module_list(TRUE, FALSE, FALSE, $this->originalModuleList); } + + // Reset language. + $language = $this->originalLanguage; + if ($this->originalLanguageDefault) { + $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; + } } } @@ -853,6 +876,13 @@ class DrupalWebTestCase extends DrupalTestCase { */ protected $cookieFile = NULL; + /** + * The cookies of the page currently loaded in the internal browser. + * + * @var array + */ + protected $cookies = array(); + /** * Additional cURL options. * @@ -942,7 +972,6 @@ function drupalGetNodeByTitle($title, $reset = FALSE) { protected function drupalCreateNode($settings = array()) { // Populate defaults array. $settings += array( - 'body' => array(LANGUAGE_NONE => array(array())), 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, @@ -957,6 +986,12 @@ protected function drupalCreateNode($settings = array()) { 'language' => LANGUAGE_NONE, ); + // Add the body after the language is defined so that it may be set + // properly. + $settings += array( + 'body' => array($settings['language'] => array(array())), + ); + // Use the original node's created time for existing nodes. if (isset($settings['created']) && !isset($settings['date'])) { $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); @@ -1015,9 +1050,7 @@ protected function drupalCreateContentType($settings = array()) { 'description' => '', 'help' => '', 'title_label' => 'Title', - 'body_label' => 'Body', 'has_title' => 1, - 'has_body' => 1, ); // Imposed values for a custom type. $forced = array( @@ -1067,7 +1100,7 @@ protected function drupalGetTestFiles($type, $size = NULL) { $lines = array(16, 256, 1024, 2048, 20480); $count = 0; foreach ($lines as $line) { - simpletest_generate_file('text-' . $count++, 64, $line); + simpletest_generate_file('text-' . $count++, 64, $line, 'text'); } // Copy other test files from simpletest. @@ -1364,12 +1397,14 @@ protected function changeDatabasePrefix() { * @see DrupalWebTestCase::tearDown() */ protected function prepareEnvironment() { - global $user, $language, $conf; + global $user, $language, $language_url, $conf; // Store necessary current values before switching to prefixed database. $this->originalLanguage = $language; + $this->originalLanguageUrl = $language_url; $this->originalLanguageDefault = variable_get('language_default'); $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); + $this->verboseDirectoryUrl = file_create_url($this->originalFileDirectory . '/simpletest/verbose'); $this->originalProfile = drupal_get_profile(); $this->originalCleanUrl = variable_get('clean_url', 0); $this->originalUser = $user; @@ -1377,7 +1412,7 @@ protected function prepareEnvironment() { // Set to English to prevent exceptions from utf8_truncate() from t() // during install if the current language is not 'en'. // The following array/object conversion is copied from language_default(). - $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); + $language_url = $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); // Save and clean the shutdown callbacks array because it is static cached // and will be changed by the test run. Otherwise it will contain callbacks @@ -1435,7 +1470,7 @@ protected function prepareEnvironment() { * @see DrupalWebTestCase::prepareEnvironment() */ protected function setUp() { - global $user, $language, $conf; + global $user, $language, $language_url, $conf; // Create the database prefix for this test. $this->prepareDatabasePrefix(); @@ -1532,7 +1567,7 @@ protected function setUp() { // Set up English language. unset($conf['language_default']); - $language = language_default(); + $language_url = $language = language_default(); // Use the test mail class instead of the default mail handler class. variable_set('mail_system', array('default-system' => 'TestingMailSystem')); @@ -1626,7 +1661,7 @@ protected function refreshVariables() { * and reset the database prefix. */ protected function tearDown() { - global $user, $language; + global $user, $language, $language_url; // In case a fatal error occurred that was not in the test process read the // log to pick up any fatal errors. @@ -1691,12 +1726,15 @@ protected function tearDown() { // Reset language. $language = $this->originalLanguage; + $language_url = $this->originalLanguageUrl; if ($this->originalLanguageDefault) { $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; } - // Close the CURL handler. + // Close the CURL handler and reset the cookies array so test classes + // containing multiple tests are not polluted. $this->curlClose(); + $this->cookies = array(); } /** @@ -2586,6 +2624,11 @@ protected function buildXPathQuery($xpath, array $args = array()) { * * @param $xpath * The xpath string to use in the search. + * @param array $arguments + * An array of arguments with keys in the form ':name' matching the + * placeholders in the query. The values may be either strings or numeric + * values. + * * @return * The return value of the xpath search. For details on the xpath string * format and return values see the SimpleXML documentation, @@ -2757,7 +2800,7 @@ protected function getAbsoluteUrl($path) { $path = substr($path, $length); } // Ensure that we have an absolute path. - if ($path[0] !== '/') { + if (empty($path) || $path[0] !== '/') { $path = '/' . $path; } // Finally, prepend the $base_url. diff --git a/modules/simpletest/files/image-test-no-transparency.gif b/modules/simpletest/files/image-test-no-transparency.gif new file mode 100644 index 00000000..15ae7772 Binary files /dev/null and b/modules/simpletest/files/image-test-no-transparency.gif differ diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index 723736b6..316eb84a 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -11,6 +11,7 @@ configure = admin/config/development/testing/settings files[] = tests/actions.test files[] = tests/ajax.test files[] = tests/batch.test +files[] = tests/boot.test files[] = tests/bootstrap.test files[] = tests/cache.test files[] = tests/common.test @@ -56,8 +57,7 @@ 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 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module index 91f0f906..cf830478 100644 --- a/modules/simpletest/simpletest.module +++ b/modules/simpletest/simpletest.module @@ -154,7 +154,7 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') { } /** - * Batch operation callback. + * Implements callback_batch_operation(). */ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { simpletest_classloader_register(); @@ -205,6 +205,9 @@ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { $context['finished'] = 1 - $size / $max; } +/** + * Implements callback_batch_finished(). + */ function _simpletest_batch_finished($success, $results, $operations, $elapsed) { if ($success) { drupal_set_message(t('The test run finished in @elapsed.', array('@elapsed' => $elapsed))); @@ -371,7 +374,10 @@ function simpletest_test_get_all() { // If this test class requires a non-existing module, skip it. if (!empty($info['dependencies'])) { foreach ($info['dependencies'] as $module) { - if (!drupal_get_filename('module', $module)) { + // Pass FALSE as fourth argument so no error gets created for + // the missing file. + $found_module = drupal_get_filename('module', $module, NULL, FALSE); + if (!$found_module) { continue 2; } } @@ -509,25 +515,25 @@ function simpletest_registry_files_alter(&$files, $modules) { * Generate test file. */ function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') { - $size = $width * $lines - $lines; - - // Generate random text $text = ''; - for ($i = 0; $i < $size; $i++) { - switch ($type) { - case 'text': - $text .= chr(rand(32, 126)); - break; - case 'binary': - $text .= chr(rand(0, 31)); - break; - case 'binary-text': - default: - $text .= rand(0, 1); - break; + for ($i = 0; $i < $lines; $i++) { + // Generate $width - 1 characters to leave space for the "\n" character. + for ($j = 0; $j < $width - 1; $j++) { + switch ($type) { + case 'text': + $text .= chr(rand(32, 126)); + break; + case 'binary': + $text .= chr(rand(0, 31)); + break; + case 'binary-text': + default: + $text .= rand(0, 1); + break; + } } + $text .= "\n"; } - $text = wordwrap($text, $width - 1, "\n", TRUE) . "\n"; // Add \n for symmetrical file. // Create filename. file_put_contents('public://' . $filename . '.txt', $text); diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index f22ef955..5d1c718c 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -322,6 +322,14 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { * Test internal testing framework browser. */ class SimpleTestBrowserTestCase extends DrupalWebTestCase { + + /** + * A flag indicating whether a cookie has been set in a test. + * + * @var bool + */ + protected static $cookieSet = FALSE; + public static function getInfo() { return array( 'name' => 'SimpleTest browser', @@ -380,6 +388,46 @@ EOF; $urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley')); $this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.'); } + + /** + * Tests that cookies set during a request are available for testing. + */ + public function testCookies() { + // Check that the $this->cookies property is populated when a user logs in. + $user = $this->drupalCreateUser(); + $edit = array('name' => $user->name, 'pass' => $user->pass_raw); + $this->drupalPost('', $edit, t('Log in')); + $this->assertEqual(count($this->cookies), 1, 'A cookie is set when the user logs in.'); + + // Check that the name and value of the cookie match the request data. + $cookie_header = $this->drupalGetHeader('set-cookie', TRUE); + + // The name and value are located at the start of the string, separated by + // an equals sign and ending in a semicolon. + preg_match('/^([^=]+)=([^;]+)/', $cookie_header, $matches); + $name = $matches[1]; + $value = $matches[2]; + + $this->assertTrue(array_key_exists($name, $this->cookies), 'The cookie name is correct.'); + $this->assertEqual($value, $this->cookies[$name]['value'], 'The cookie value is correct.'); + + // Set a flag indicating that a cookie has been set in this test. + // @see SimpleTestBrowserTestCase::testCookieDoesNotBleed(). + self::$cookieSet = TRUE; + } + + /** + * Tests that the cookies from a previous test do not bleed into a new test. + * + * @see SimpleTestBrowserTestCase::testCookies(). + */ + public function testCookieDoesNotBleed() { + // In order for this test to be effective it should always run after the + // testCookies() test. + $this->assertTrue(self::$cookieSet, 'Tests have been executed in the expected order.'); + $this->assertEqual(count($this->cookies), 0, 'No cookies are present at the start of a new test.'); + } + } class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info index b880d5d7..dd6a1b20 100644 --- a/modules/simpletest/tests/actions_loop_test.info +++ b/modules/simpletest/tests/actions_loop_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info index f0353069..5b1038d0 100644 --- a/modules/simpletest/tests/ajax_forms_test.info +++ b/modules/simpletest/tests/ajax_forms_test.info @@ -5,8 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info index a09870c8..c85f9aaa 100644 --- a/modules/simpletest/tests/ajax_test.info +++ b/modules/simpletest/tests/ajax_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/batch_test.callbacks.inc b/modules/simpletest/tests/batch_test.callbacks.inc index 75e66553..6564413d 100644 --- a/modules/simpletest/tests/batch_test.callbacks.inc +++ b/modules/simpletest/tests/batch_test.callbacks.inc @@ -7,6 +7,8 @@ */ /** + * Implements callback_batch_operation(). + * * Simple batch operation. */ function _batch_test_callback_1($id, $sleep, &$context) { @@ -20,6 +22,8 @@ function _batch_test_callback_1($id, $sleep, &$context) { } /** + * Implements callback_batch_operation(). + * * Multistep batch operation. */ function _batch_test_callback_2($start, $total, $sleep, &$context) { @@ -53,6 +57,8 @@ function _batch_test_callback_2($start, $total, $sleep, &$context) { } /** + * Implements callback_batch_operation(). + * * Simple batch operation. */ function _batch_test_callback_5($id, $sleep, &$context) { @@ -68,6 +74,8 @@ function _batch_test_callback_5($id, $sleep, &$context) { } /** + * Implements callback_batch_operation(). + * * Batch operation setting up its own batch. */ function _batch_test_nested_batch_callback() { @@ -76,6 +84,8 @@ function _batch_test_nested_batch_callback() { } /** + * Implements callback_batch_finished(). + * * Common 'finished' callbacks for batches 1 to 4. */ function _batch_test_finished_helper($batch_id, $success, $results, $operations) { @@ -99,6 +109,8 @@ function _batch_test_finished_helper($batch_id, $success, $results, $operations) } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 0. */ function _batch_test_finished_0($success, $results, $operations) { @@ -106,6 +118,8 @@ function _batch_test_finished_0($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 1. */ function _batch_test_finished_1($success, $results, $operations) { @@ -113,6 +127,8 @@ function _batch_test_finished_1($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 2. */ function _batch_test_finished_2($success, $results, $operations) { @@ -120,6 +136,8 @@ function _batch_test_finished_2($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 3. */ function _batch_test_finished_3($success, $results, $operations) { @@ -127,6 +145,8 @@ function _batch_test_finished_3($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 4. */ function _batch_test_finished_4($success, $results, $operations) { @@ -134,6 +154,8 @@ function _batch_test_finished_4($success, $results, $operations) { } /** + * Implements callback_batch_finished(). + * * 'finished' callback for batch 5. */ function _batch_test_finished_5($success, $results, $operations) { diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info index be057e33..f04ed7c9 100644 --- a/modules/simpletest/tests/batch_test.info +++ b/modules/simpletest/tests/batch_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/boot.test b/modules/simpletest/tests/boot.test new file mode 100644 index 00000000..562b082e --- /dev/null +++ b/modules/simpletest/tests/boot.test @@ -0,0 +1,38 @@ + 'Early bootstrap test', + 'description' => 'Confirm that calling module_implements() during early bootstrap does not pollute the module_implements() cache.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('boot_test_1', 'boot_test_2'); + } + + /** + * Test hook_boot() on both regular and "early exit" pages. + */ + public function testHookBoot() { + $paths = array('', 'early_exit'); + foreach ($paths as $path) { + // Empty the module_implements() caches. + module_implements(NULL, FALSE, TRUE); + // Do a request to the front page, which will call module_implements() + // during hook_boot(). + $this->drupalGet($path); + // Reset the static cache so we get implementation data from the persistent + // cache. + drupal_static_reset(); + // Make sure we get a full list of all modules implementing hook_help(). + $modules = module_implements('help'); + $this->assertTrue(in_array('boot_test_2', $modules)); + } + } +} diff --git a/modules/simpletest/tests/boot_test_1.info b/modules/simpletest/tests/boot_test_1.info new file mode 100644 index 00000000..405171c8 --- /dev/null +++ b/modules/simpletest/tests/boot_test_1.info @@ -0,0 +1,11 @@ +name = Early bootstrap tests +description = A support module for hook_boot testing. +core = 7.x +package = Testing +version = VERSION +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" +project = "drupal" +datestamp = "1539816636" diff --git a/modules/simpletest/tests/boot_test_1.module b/modules/simpletest/tests/boot_test_1.module new file mode 100644 index 00000000..a452e289 --- /dev/null +++ b/modules/simpletest/tests/boot_test_1.module @@ -0,0 +1,21 @@ +proxy_ip; + $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->proxy_ip; + drupal_static_reset('ip_address'); + $this->assertTrue( + ip_address() == $this->proxy_ip, + 'Visiting from trusted proxy got proxy IP address.' + ); + // Multi-tier architecture with comma separated values in header. $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip)); @@ -152,7 +161,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->drupalLogin($user); $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag)); $this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absense of Page was not cached.'); + $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.'); $this->assertFalse($this->drupalGetHeader('ETag'), 'ETag HTTP headers are not present for logged in users.'); $this->assertFalse($this->drupalGetHeader('Last-Modified'), 'Last-Modified HTTP headers are not present for logged in users.'); } @@ -191,7 +200,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was sent.'); + $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was sent.'); $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); @@ -313,6 +322,10 @@ class BootstrapAutoloadTestCase extends DrupalWebTestCase { $this->assertTrue(drupal_autoload_interface('drupalautoloadtestinterface'), 'drupal_autoload_interface() recognizes DrupalAutoloadTestInterface in lower case.'); // Test class autoloader. $this->assertTrue(drupal_autoload_class('drupalautoloadtestclass'), 'drupal_autoload_class() recognizes DrupalAutoloadTestClass in lower case.'); + // Test trait autoloader. + if (version_compare(PHP_VERSION, '5.4') >= 0) { + $this->assertTrue(drupal_autoload_trait('drupalautoloadtesttrait'), 'drupal_autoload_trait() recognizes DrupalAutoloadTestTrait in lower case.'); + } } } @@ -375,12 +388,19 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { public static function getInfo() { return array( - 'name' => 'Get filename test', - 'description' => 'Test that drupal_get_filename() works correctly when the file is not found in the database.', + 'name' => 'Get filename test (without the system table)', + 'description' => 'Test that drupal_get_filename() works correctly when the database is not available.', 'group' => 'Bootstrap', ); } + /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + /** * Test that drupal_get_filename() works correctly when the file is not found in the database. */ @@ -410,6 +430,203 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { // automatically check there for 'script' files, just as it does for (e.g.) // 'module' files in modules. $this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.')); + + // When searching for a module that does not exist, drupal_get_filename() + // should return NULL and trigger an appropriate error message. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for an item that does not exist triggers the correct error.'); + restore_error_handler(); + + // Check that the result is stored in the file system scan cache. + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + + // Performing the search again in the same request still should not find + // the file, but the error message should not be repeated (therefore we do + // not override the error handler here). + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL during the second search.'); + } + + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } +} + +/** + * Test drupal_get_filename() in the context of a full Drupal installation. + */ +class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Get filename test (full installation)', + 'description' => 'Test that drupal_get_filename() works correctly in the context of a full Drupal installation.', + 'group' => 'Bootstrap', + ); + } + + function setUp() { + parent::setUp('system_test'); + } + + /** + * The last file-related error message triggered by the filename test. + * + * Used by BootstrapGetFilenameWebTestCase::testDrupalGetFilename(). + */ + protected $getFilenameTestTriggeredError; + + /** + * Test that drupal_get_filename() works correctly with a full Drupal site. + */ + function testDrupalGetFilename() { + // Search for a module that exists in the file system and the {system} + // table and make sure that it is found. + $this->assertIdentical(drupal_get_filename('module', 'node'), 'modules/node/node.module', 'Module found at expected location.'); + + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate error is triggered and + // that the module winds up in the static and persistent cache. + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $non_existing_module = $this->randomName(); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that does not exist triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files persistent cache.'); + + // Simulate moving a module to a location that does not match the location + // in the {system} table and perform similar tests as above. + db_update('system') + ->fields(array('filename' => 'modules/simpletest/tests/fake_location/module_test.module')) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'module_test'), 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved finds the module at its new location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'module_test'))) === 0, 'Searching for a module that has moved triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the {system} table but does not exist + // in the file system and perform similar tests as above. + $non_existing_module = $this->randomName(); + db_update('system') + ->fields(array('name' => $non_existing_module)) + ->condition('name', 'module_test') + ->condition('type', 'module') + ->execute(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that exists in the system table but not in the file system returns NULL.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that exists in the system table but not in the file system triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files persistent cache.'); + + // Simulate a module that exists in the file system but not in the {system} + // table and perform similar tests as above. + db_delete('system') + ->condition('name', 'common_test') + ->condition('type', 'module') + ->execute(); + system_list_reset(); + $this->getFilenameTestTriggeredError = NULL; + set_error_handler(array($this, 'fileNotFoundErrorHandler')); + $this->assertIdentical(drupal_get_filename('module', 'common_test'), 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table finds the module at its actual location.'); + $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'common_test'))) === 0, 'Searching for a module that does not exist in the system table triggers the correct error.'); + restore_error_handler(); + $file_scans = _drupal_file_scan_cache(); + $this->assertIdentical($file_scans['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files static variable.'); + drupal_file_scan_write_cache(); + $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap'); + $this->assertIdentical($cache->data['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files persistent cache.'); + } + + /** + * Skips handling of "file not found" errors. + */ + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + // Skip error handling if this is a "file not found" error. + if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { + $this->getFilenameTestTriggeredError = $message; + return; + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } + + /** + * Test that watchdog messages about missing files are correctly recorded. + */ + public function testWatchdog() { + // Search for a module that does not exist in either the file system or the + // {system} table. Make sure that an appropriate warning is recorded in the + // logs. + $non_existing_module = $this->randomName(); + $query_parameters = array( + ':type' => 'php', + ':severity' => WATCHDOG_WARNING, + ); + $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before searching for a module that does not exist.'); + // Trigger the drupal_get_filename() call. This must be done via a request + // to a separate URL since the watchdog() will happen in a shutdown + // function, and so that SimpleTest can be told to ignore (and not fail as + // a result of) the expected PHP warnings generated during this process. + variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module); + $this->drupalGet('system-test/drupal-get-filename'); + $message_variables = db_query('SELECT variables FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchCol(); + $this->assertEqual(count($message_variables), 1, 'A single warning message appears in the logs after searching for a module that does not exist.'); + $variables = reset($message_variables); + $variables = unserialize($variables); + $this->assertTrue(isset($variables['!message']) && strpos($variables['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) !== FALSE, 'The warning message that appears in the logs after searching for a module that does not exist contains the expected text.'); + } + + /** + * Test that drupal_get_filename() does not break recursive rebuilds. + */ + public function testRecursiveRebuilds() { + // Ensure that the drupal_get_filename() call due to a missing module does + // not break the data returned by an attempted recursive rebuild. The code + // path which is tested is as follows: + // - Call drupal_get_schema(). + // - Within a hook_schema() implementation, trigger a drupal_get_filename() + // search for a nonexistent module. + // - In the watchdog() call that results from that, trigger + // drupal_get_schema() again. + // Without some kind of recursion protection, this could cause the second + // drupal_get_schema() call to return incomplete results. This test ensures + // that does not happen. + $non_existing_module = $this->randomName(); + variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module); + $this->drupalGet('system-test/drupal-get-filename-with-schema-rebuild'); + $original_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_original_tables'); + $final_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_final_tables'); + $this->assertTrue(!empty($original_drupal_get_schema_tables)); + $this->assertTrue(!empty($final_drupal_get_schema_tables)); + $this->assertEqual($original_drupal_get_schema_tables, $final_drupal_get_schema_tables); } } diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 88fdd04d..f86be2a7 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -76,7 +76,7 @@ class DrupalAlterTestCase extends DrupalWebTestCase { class CommonURLUnitTest extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => 'URL generation tests', + 'name' => 'URL generation unit tests', 'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.', 'group' => 'System', ); @@ -169,7 +169,7 @@ class CommonURLUnitTest extends DrupalWebTestCase { $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'); $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'); $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'); - $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', 'Nested array was properly encoded.'); + $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'); } /** @@ -372,6 +372,97 @@ class CommonURLUnitTest extends DrupalWebTestCase { } } +/** + * Web tests for URL generation functions. + */ +class CommonURLWebTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'URL generation web tests', + 'description' => 'Confirm that URL-generating functions work correctly on specific site paths.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('common_test'); + } + + /** + * Tests the url() function on internal paths which mimic external URLs. + */ + function testInternalPathMimicsExternal() { + // Ensure that calling url(current_path()) on "/http://example.com" (an + // internal path which mimics an external URL) always links to the internal + // path, not the external URL. This helps protect against external URL link + // injection vulnerabilities. + variable_set('common_test_link_to_current_path', TRUE); + $this->drupalGet('/http://example.com'); + $this->clickLink('link which should point to the current path'); + $this->assertUrl('/http://example.com'); + $this->assertText('link which should point to the current path'); + } +} + +/** + * 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(). */ @@ -888,6 +979,31 @@ class DrupalHTMLIdentifierTestCase extends DrupalUnitTestCase { // Verify that invalid characters (including non-breaking space) are stripped from the identifier. $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.'); + + // Verify that double underscores are replaced in the identifier by default. + $identifier = 'css__identifier__with__double__underscores'; + $expected = 'css--identifier--with--double--underscores'; + $this->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.'); + + // Verify that double underscores are preserved in the identifier if the + // variable allow_css_double_underscores is set to TRUE. + $this->setAllowCSSDoubleUnderscores(TRUE); + $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.'); + + // To avoid affecting other test cases, set the variable + // allow_css_double_underscores to FALSE which is the default value. + $this->setAllowCSSDoubleUnderscores(FALSE); + } + + /** + * Set the variable allow_css_double_underscores and reset the cache. + * + * @param $value bool + * A new value to be set to allow_css_double_underscores. + */ + function setAllowCSSDoubleUnderscores($value) { + $GLOBALS['conf']['allow_css_double_underscores'] = $value; + drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores'); } /** @@ -1195,7 +1311,7 @@ class DrupalSetContentTestCase extends DrupalWebTestCase { function testRegions() { global $theme_key; - $block_regions = array_keys(system_region_list($theme_key)); + $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE); $delimiter = $this->randomName(32); $values = array(); // Set some random content for each region available. @@ -1256,6 +1372,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/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info index 1719db7a..744d932c 100644 --- a/modules/simpletest/tests/common_test.info +++ b/modules/simpletest/tests/common_test.info @@ -7,8 +7,7 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module index 674a4944..d092c924 100644 --- a/modules/simpletest/tests/common_test.module +++ b/modules/simpletest/tests/common_test.module @@ -92,6 +92,18 @@ 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()); + } + if (variable_get('common_test_link_to_current_path', FALSE)) { + drupal_set_message(l('link which should point to the current path', current_path())); + } +} + /** * Print destination query parameter. */ diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info index e00bd241..1f1f8554 100644 --- a/modules/simpletest/tests/common_test_cron_helper.info +++ b/modules/simpletest/tests/common_test_cron_helper.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info index e5022598..381e3d21 100644 --- a/modules/simpletest/tests/database_test.info +++ b/modules/simpletest/tests/database_test.info @@ -5,8 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info index f78833af..82237d84 100644 --- a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info @@ -7,8 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module index 37aa94eb..edd5d77c 100644 --- a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module @@ -4,3 +4,19 @@ * @file * Test module to check code registry. */ + +/** + * Implements hook_registry_files_alter(). + */ +function drupal_autoload_test_registry_files_alter(&$files, $modules) { + foreach ($modules as $module) { + // Add the drupal_autoload_test_trait.sh file to the registry when PHP 5.4+ + // is being used. + if ($module->name == 'drupal_autoload_test' && version_compare(PHP_VERSION, '5.4') >= 0) { + $files["$module->dir/drupal_autoload_test_trait.sh"] = array( + 'module' => $module->name, + 'weight' => $module->weight, + ); + } + } +} diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh new file mode 100644 index 00000000..69ce9ec0 --- /dev/null +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh @@ -0,0 +1,16 @@ +assertText('The form has become outdated. Copy any unsaved work in the form below'); } + /** + * Tests that a form with a disabled CSRF token can be validated. + */ + function testDisabledToken() { + $this->drupalPost('form-test/validate-no-token', array(), 'Save'); + $this->assertText('The form_test_validate_no_token form has been submitted successfully.'); + } + /** * Tests partial form validation through #limit_validation_errors. */ @@ -994,6 +1002,26 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase { $this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for radio buttons.'); } + /** + * Test presence of ajax functionality + */ + function testAjax() { + $rows = array('row1', 'row2', 'row3'); + // Test checkboxes (#multiple == TRUE). + foreach ($rows as $row) { + $element = 'tableselect[' . $row . ']'; + $edit = array($element => TRUE); + $result = $this->drupalPostAJAX('form_test/tableselect/multiple-true', $edit, $element); + $this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', array('@row' => $row))); + } + // Test radios (#multiple == FALSE). + $element = 'tableselect'; + foreach ($rows as $row) { + $edit = array($element => $row); + $result = $this->drupalPostAjax('form_test/tableselect/multiple-false', $edit, $element); + $this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', array('@row' => $row))); + } + } /** * Helper function for the option check test to submit a form while collecting errors. @@ -2099,3 +2127,36 @@ class HTMLIdTestCase extends DrupalWebTestCase { $this->assertNoDuplicateIds('There are no duplicate IDs'); } } + +/** + * Tests for form textarea. + */ +class FormTextareaTestCase extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'Form textarea', + 'description' => 'Tests form textarea related functions.', + 'group' => 'Form API', + ); + } + + /** + * Tests that textarea value is properly set. + */ + public function testValueCallback() { + $element = array(); + $form_state = array(); + $test_cases = array( + array(NULL, FALSE), + array(NULL, NULL), + array('', array('test')), + array('test', 'test'), + array('123', 123), + ); + foreach ($test_cases as $test_case) { + list($expected, $input) = $test_case; + $this->assertIdentical($expected, form_type_textarea_value($element, $input, $form_state)); + } + } +} diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info index 1b327b30..40254ae5 100644 --- a/modules/simpletest/tests/form_test.info +++ b/modules/simpletest/tests/form_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index 602b4090..097e5884 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -37,6 +37,13 @@ function form_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['form-test/validate-no-token'] = array( + 'title' => 'Form validation without a CSRF token', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_validate_no_token'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); $items['form-test/limit-validation-errors'] = array( 'title' => 'Form validation with some error suppression', 'page callback' => 'drupal_get_form', @@ -454,6 +461,27 @@ function form_test_validate_required_form_no_title_submit($form, &$form_state) { drupal_set_message('The form_test_validate_required_form_no_title form was submitted successfully.'); } +/** + * Form builder for testing submission of a form without a CSRF token. + */ +function form_test_validate_no_token($form, &$form_state) { + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Save', + ); + + $form['#token'] = FALSE; + + return $form; +} + +/** + * Form submission handler for form_test_validate_no_token(). + */ +function form_test_validate_no_token_submit($form, &$form_state) { + drupal_set_message('The form_test_validate_no_token form has been submitted successfully.'); +} + /** * Builds a simple form with a button triggering partial validation. */ @@ -589,11 +617,17 @@ function _form_test_tableselect_form_builder($form, $form_state, $element_proper $form['tableselect'] = $element_properties; $form['tableselect'] += array( + '#prefix' => '
', + '#suffix' => '
', '#type' => 'tableselect', '#header' => $header, '#options' => $options, '#multiple' => FALSE, '#empty' => t('Empty text.'), + '#ajax' => array( + 'callback' => '_form_test_tableselect_ajax_callback', + 'wrapper' => 'tableselect-wrapper', + ), ); $form['submit'] = array( @@ -697,6 +731,13 @@ function _form_test_vertical_tabs_form($form, &$form_state) { return $form; } +/** +* Ajax callback that returns the form element. +*/ +function _form_test_tableselect_ajax_callback($form, &$form_state) { + return $form['tableselect']; +} + /** * A multistep form for testing the form storage. * diff --git a/modules/simpletest/tests/image.test b/modules/simpletest/tests/image.test index 84970221..7ca1d3a0 100644 --- a/modules/simpletest/tests/image.test +++ b/modules/simpletest/tests/image.test @@ -207,9 +207,11 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { protected $green = array(0, 255, 0, 0); protected $blue = array(0, 0, 255, 0); protected $yellow = array(255, 255, 0, 0); - protected $fuchsia = array(255, 0, 255, 0); // Used as background colors. - protected $transparent = array(0, 0, 0, 127); protected $white = array(255, 255, 255, 0); + protected $transparent = array(0, 0, 0, 127); + // Used as rotate background colors. + protected $fuchsia = array(255, 0, 255, 0); + protected $rotate_transparent = array(255, 255, 255, 127); protected $width = 40; protected $height = 20; @@ -275,6 +277,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { $files = array( 'image-test.png', 'image-test.gif', + 'image-test-no-transparency.gif', 'image-test.jpg', ); @@ -334,13 +337,6 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { // Systems using non-bundled GD2 don't have imagerotate. Test if available. if (function_exists('imagerotate')) { $operations += array( - 'rotate_5' => array( - 'function' => 'rotate', - 'arguments' => array(5, 0xFF00FF), // Fuchsia background. - 'width' => 42, - 'height' => 24, - 'corners' => array_fill(0, 4, $this->fuchsia), - ), 'rotate_90' => array( 'function' => 'rotate', 'arguments' => array(90, 0xFF00FF), // Fuchsia background. @@ -348,13 +344,6 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { 'height' => 40, 'corners' => array($this->fuchsia, $this->red, $this->green, $this->blue), ), - 'rotate_transparent_5' => array( - 'function' => 'rotate', - 'arguments' => array(5), - 'width' => 42, - 'height' => 24, - 'corners' => array_fill(0, 4, $this->transparent), - ), 'rotate_transparent_90' => array( 'function' => 'rotate', 'arguments' => array(90), @@ -363,6 +352,49 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { 'corners' => array($this->transparent, $this->red, $this->green, $this->blue), ), ); + // As of PHP version 5.5, GD uses a different algorithm to rotate images + // than version 5.4 and below, resulting in different dimensions. + // See https://bugs.php.net/bug.php?id=65148. + // For the 40x20 test images, the dimensions resulting from rotation will + // be 1 pixel smaller in both width and height in PHP 5.5 and above. + // @todo: If and when the PHP bug gets solved, add an upper limit + // version check. + if (version_compare(PHP_VERSION, '5.5', '>=')) { + $operations += array( + 'rotate_5' => array( + 'function' => 'rotate', + 'arguments' => array(5, 0xFF00FF), // Fuchsia background. + 'width' => 41, + 'height' => 23, + 'corners' => array_fill(0, 4, $this->fuchsia), + ), + 'rotate_transparent_5' => array( + 'function' => 'rotate', + 'arguments' => array(5), + 'width' => 41, + 'height' => 23, + 'corners' => array_fill(0, 4, $this->rotate_transparent), + ), + ); + } + else { + $operations += array( + 'rotate_5' => array( + 'function' => 'rotate', + 'arguments' => array(5, 0xFF00FF), // Fuchsia background. + 'width' => 42, + 'height' => 24, + 'corners' => array_fill(0, 4, $this->fuchsia), + ), + 'rotate_transparent_5' => array( + 'function' => 'rotate', + 'arguments' => array(5), + 'width' => 42, + 'height' => 24, + 'corners' => array_fill(0, 4, $this->rotate_transparent), + ), + ); + } } // Systems using non-bundled GD2 don't have imagefilter. Test if available. @@ -430,6 +462,11 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase { } // Now check each of the corners to ensure color correctness. foreach ($values['corners'] as $key => $corner) { + // The test gif that does not have transparency has yellow where the + // others have transparent. + if ($file === 'image-test-no-transparency.gif' && $corner === $this->transparent) { + $corner = $this->yellow; + } // Get the location of the corner. switch ($key) { case 0: diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info index 942597b5..99446c57 100644 --- a/modules/simpletest/tests/image_test.info +++ b/modules/simpletest/tests/image_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test index 70a43cb4..3e40e13a 100644 --- a/modules/simpletest/tests/mail.test +++ b/modules/simpletest/tests/mail.test @@ -441,7 +441,7 @@ class DrupalHtmlToTextTestCase extends DrupalWebTestCase { * is 1000 characters." */ function testVeryLongLineWrap() { - $input = 'Drupal

' . str_repeat('x', 2100) . '
Drupal'; + $input = 'Drupal

' . str_repeat('x', 2100) . '


Drupal'; $output = drupal_html_to_text($input); // This awkward construct comes from includes/mail.inc lines 8-13. $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); @@ -455,7 +455,6 @@ class DrupalHtmlToTextTestCase extends DrupalWebTestCase { $maximum_line_length = max($maximum_line_length, strlen($line . $eol)); } $verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.'; - // @todo This should assert that $maximum_line_length <= 1000. - $this->pass($verbose); + $this->assertTrue($maximum_line_length <= 1000, $verbose); } } diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info index 162e912a..0599f903 100644 --- a/modules/simpletest/tests/menu_test.info +++ b/modules/simpletest/tests/menu_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/module.test b/modules/simpletest/tests/module.test index 371339f3..eea3b51e 100644 --- a/modules/simpletest/tests/module.test +++ b/modules/simpletest/tests/module.test @@ -302,3 +302,45 @@ class ModuleUninstallTestCase extends DrupalWebTestCase { $this->assertEqual(0, $count, 'Permissions were all removed.'); } } + +class ModuleImplementsAlterTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Module implements alter', + 'description' => 'Tests hook_module_implements_alter().', + 'group' => 'Module', + ); + } + + /** + * Tests hook_module_implements_alter() adding an implementation. + */ + function testModuleImplementsAlter() { + module_enable(array('module_test'), FALSE); + $this->assertTrue(module_exists('module_test'), 'Test module is enabled.'); + + // Assert that module_test.module is now included. + $this->assertTrue(function_exists('module_test_permission'), + 'The file module_test.module was successfully included.'); + + $modules = module_implements('permission'); + $this->assertTrue(in_array('module_test', $modules), 'module_test implements hook_permission.'); + + $modules = module_implements('module_implements_alter'); + $this->assertTrue(in_array('module_test', $modules), 'module_test implements hook_module_implements_alter().'); + + // Assert that module_test.implementations.inc is not included yet. + $this->assertFalse(function_exists('module_test_altered_test_hook'), + 'The file module_test.implementations.inc is not included yet.'); + + // Assert that module_test_module_implements_alter(*, 'altered_test_hook') + // has added an implementation + $this->assertTrue(in_array('module_test', module_implements('altered_test_hook')), + 'module_test implements hook_altered_test_hook().'); + + // Assert that module_test.implementations.inc was included as part of the process. + $this->assertTrue(function_exists('module_test_altered_test_hook'), + 'The file module_test.implementations.inc was included.'); + } + +} diff --git a/modules/simpletest/tests/module_test.implementations.inc b/modules/simpletest/tests/module_test.implementations.inc new file mode 100644 index 00000000..63c866ea --- /dev/null +++ b/modules/simpletest/tests/module_test.implementations.inc @@ -0,0 +1,10 @@ +2.0) -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/system_incompatible_module_version_test.info b/modules/simpletest/tests/system_incompatible_module_version_test.info index f469bd7f..837ea2e3 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,8 +5,7 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/system_project_namespace_test.info b/modules/simpletest/tests/system_project_namespace_test.info new file mode 100644 index 00000000..1f30959e --- /dev/null +++ b/modules/simpletest/tests/system_project_namespace_test.info @@ -0,0 +1,12 @@ +name = "System project namespace test" +description = "Support module for testing project namespace dependencies." +package = Testing +version = VERSION +core = 7.x +hidden = TRUE +dependencies[] = drupal:filter + +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" +project = "drupal" +datestamp = "1539816636" diff --git a/modules/simpletest/tests/system_project_namespace_test.module b/modules/simpletest/tests/system_project_namespace_test.module new file mode 100644 index 00000000..b3d9bbc7 --- /dev/null +++ b/modules/simpletest/tests/system_project_namespace_test.module @@ -0,0 +1 @@ + MENU_CALLBACK, ); + $items['system-test/drupal-get-filename'] = array( + 'title' => 'Test drupal_get_filename()', + 'page callback' => 'system_test_drupal_get_filename', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + $items['system-test/drupal-get-filename-with-schema-rebuild'] = array( + 'title' => 'Test drupal_get_filename() with a schema rebuild', + 'page callback' => 'system_test_drupal_get_filename_with_schema_rebuild', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + return $items; } @@ -296,6 +310,9 @@ function system_test_system_info_alter(&$info, $file, $type) { } } + if ($file->name == 'system_project_namespace_test') { + $info['hidden'] = FALSE; + } // Make the system_dependencies_test visible by default. if ($file->name == 'system_dependencies_test') { $info['hidden'] = FALSE; @@ -479,3 +496,76 @@ function system_test_request_destination() { // information. exit; } + +/** + * Page callback to run drupal_get_filename() on a particular module. + */ +function system_test_drupal_get_filename() { + // Prevent SimpleTest from failing as a result of the expected PHP warnings + // this function causes. Any warnings will be recorded in the database logs + // for examination by the tests. + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + drupal_get_filename('module', $module_name); + + return ''; +} + +/** + * Page callback to run drupal_get_filename() and do a schema rebuild. + */ +function system_test_drupal_get_filename_with_schema_rebuild() { + // Prevent SimpleTest from failing as a result of the expected PHP warnings + // this function causes. + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + + // Record the original database tables from drupal_get_schema(). + variable_set('system_test_drupal_get_filename_with_schema_rebuild_original_tables', array_keys(drupal_get_schema(NULL, TRUE))); + + // Trigger system_test_schema() and system_test_watchdog() to perform an + // attempted recursive rebuild when drupal_get_schema() is called. See + // BootstrapGetFilenameWebTestCase::testRecursiveRebuilds(). + variable_set('system_test_drupal_get_filename_attempt_recursive_rebuild', TRUE); + drupal_get_schema(NULL, TRUE); + + return ''; +} + +/** + * Implements hook_watchdog(). + */ +function system_test_watchdog($log_entry) { + // If an attempted recursive schema rebuild has been triggered by + // system_test_drupal_get_filename_with_schema_rebuild(), perform the rebuild + // in response to the missing file message triggered by system_test_schema(). + if (!variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) { + return; + } + if ($log_entry['type'] != 'php' || $log_entry['severity'] != WATCHDOG_WARNING) { + return; + } + $module_name = variable_get('system_test_drupal_get_filename_test_module_name'); + if (!isset($log_entry['variables']['!message']) || strpos($log_entry['variables']['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $module_name))) === FALSE) { + return; + } + variable_set('system_test_drupal_get_filename_with_schema_rebuild_final_tables', array_keys(drupal_get_schema())); +} + +/** + * Implements hook_module_implements_alter(). + */ +function system_test_module_implements_alter(&$implementations, $hook) { + // For BootstrapGetFilenameWebTestCase::testRecursiveRebuilds() to work + // correctly, this module's hook_schema() implementation cannot be either the + // first implementation (since that would trigger a potential recursive + // rebuild before anything is in the drupal_get_schema() cache) or the last + // implementation (since that would trigger a potential recursive rebuild + // after the cache is already complete). So put it somewhere in the middle. + if ($hook == 'schema') { + $group = $implementations['system_test']; + unset($implementations['system_test']); + $count = count($implementations); + $implementations = array_merge(array_slice($implementations, 0, $count / 2, TRUE), array('system_test' => $group), array_slice($implementations, $count / 2, NULL, TRUE)); + } +} diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info index ebb752e3..94aef660 100644 --- a/modules/simpletest/tests/taxonomy_test.info +++ b/modules/simpletest/tests/taxonomy_test.info @@ -6,8 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/taxonomy_test.module b/modules/simpletest/tests/taxonomy_test.module index f82950c3..f4144380 100644 --- a/modules/simpletest/tests/taxonomy_test.module +++ b/modules/simpletest/tests/taxonomy_test.module @@ -109,3 +109,33 @@ function taxonomy_test_get_antonym($tid) { ->execute() ->fetchField(); } + +/** + * Implements hook_query_alter(). + */ +function taxonomy_test_query_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function taxonomy_test_query_term_access_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function taxonomy_test_query_taxonomy_term_access_alter(QueryAlterableInterface $query) { + $value = variable_get(__FUNCTION__); + if (isset($value)) { + variable_set(__FUNCTION__, ++$value); + } +} diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test index f5ddfa9b..5f095bd5 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -646,3 +646,34 @@ class ThemeDebugMarkupTestCase extends DrupalWebTestCase { } } + +/** + * Tests module-provided theme engines. + */ +class ModuleProvidedThemeEngineTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Theme engine test', + 'description' => 'Tests module-provided theme engines.', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test'); + theme_enable(array('test_theme', 'test_theme_nyan_cat')); + } + + /** + * Ensures that the module provided theme engine is found and used by core. + */ + function testEngineIsFoundAndWorking() { + variable_set('theme_default', 'test_theme_nyan_cat'); + variable_set('admin_theme', 'test_theme_nyan_cat'); + + $this->drupalGet('theme-test/engine-info-test'); + $this->assertText('Miaou'); + } + +} diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info index 9df1b617..c3635173 100644 --- a/modules/simpletest/tests/theme_test.info +++ b/modules/simpletest/tests/theme_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/theme_test.module b/modules/simpletest/tests/theme_test.module index 948d8175..1dbc3b95 100644 --- a/modules/simpletest/tests/theme_test.module +++ b/modules/simpletest/tests/theme_test.module @@ -27,9 +27,18 @@ function theme_test_system_theme_info() { $themes['test_theme'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme/test_theme.info'; $themes['test_basetheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_basetheme/test_basetheme.info'; $themes['test_subtheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_subtheme/test_subtheme.info'; + $themes['test_theme_nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme_nyan_cat/test_theme_nyan_cat.info'; return $themes; } +/** + * Implements hook_system_theme_engine_info(). + */ +function theme_test_system_theme_engine_info() { + $theme_engines['nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/engines/nyan_cat/nyan_cat.engine'; + return $theme_engines; +} + /** * Implements hook_menu(). */ @@ -58,6 +67,12 @@ function theme_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['theme-test/engine-info-test'] = array( + 'description' => "Serves a simple page rendered using a Nyan Cat theme engine template.", + 'page callback' => '_theme_test_engine_info_test', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); return $items; } @@ -139,6 +154,15 @@ function _theme_test_drupal_add_region_content() { return 'Hello'; } +/** + * Serves a simple page renderered using a Nyan Cat theme engine template. + */ +function _theme_test_engine_info_test() { + return array( + '#markup' => theme('theme_test_template_test'), + ); +} + /** * Theme function for testing theme('theme_test_foo'). */ diff --git a/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine b/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine new file mode 100644 index 00000000..1b8ffef8 --- /dev/null +++ b/modules/simpletest/tests/themes/engines/nyan_cat/nyan_cat.engine @@ -0,0 +1,53 @@ +filename) . '/template.theme'; + if (file_exists($file)) { + include_once DRUPAL_ROOT . '/' . $file; + } +} + +/** + * Implements hook_theme(). + */ +function nyan_cat_theme($existing, $type, $theme, $path) { + $templates = drupal_find_theme_functions($existing, array($theme)); + $templates += drupal_find_theme_templates($existing, '.nyan-cat.html', $path); + return $templates; +} + +/** + * Implements hook_extension(). + */ +function nyan_cat_extension() { + return '.nyan-cat.html'; +} + +/** + * Implements hook_render_template(). + * + * @param string $template_file + * The filename of the template to render. + * @param mixed[] $variables + * A keyed array of variables that will appear in the output. + * + * @return string + * The output generated by the template. + */ +function nyan_cat_render_template($template_file, $variables) { + $output = str_replace('div', 'nyancat', file_get_contents(DRUPAL_ROOT . '/' . $template_file)); + foreach ($variables as $key => $variable) { + if (strpos($output, '9' . $key) !== FALSE) { + $output = str_replace('9' . $key, $variable, $output); + } + } + return $output; +} diff --git a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index acba3d5f..8788b00b 100644 --- a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,8 +6,7 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index 120fc3a6..04c5975a 100644 --- a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,8 +6,7 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/themes/test_theme/test_theme.info b/modules/simpletest/tests/themes/test_theme/test_theme.info index 4679f32e..e88a344d 100644 --- a/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,8 +17,7 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html b/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html new file mode 100644 index 00000000..7202dd48 --- /dev/null +++ b/modules/simpletest/tests/themes/test_theme_nyan_cat/templates/theme_test_template_test.nyan-cat.html @@ -0,0 +1 @@ +Miaou \ No newline at end of file diff --git a/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info b/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info new file mode 100644 index 00000000..12f46b5c --- /dev/null +++ b/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info @@ -0,0 +1,10 @@ +name = Nyan cat engine based test theme +description = Theme for testing the module-provided theme engines. +core = 7.x +hidden = TRUE +engine = nyan_cat + +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" +project = "drupal" +datestamp = "1539816636" diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info index 5a174fc6..2c7c5125 100644 --- a/modules/simpletest/tests/update_script_test.info +++ b/modules/simpletest/tests/update_script_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/update_script_test.install b/modules/simpletest/tests/update_script_test.install index 6955ef11..4024fb4a 100644 --- a/modules/simpletest/tests/update_script_test.install +++ b/modules/simpletest/tests/update_script_test.install @@ -31,6 +31,19 @@ function update_script_test_requirements($phase) { 'severity' => REQUIREMENT_ERROR, ); break; + case REQUIREMENT_INFO: + $requirements['update_script_test_stop'] = array( + 'title' => 'Update script test stop', + 'value' => 'Error', + 'description' => 'This is a requirements error provided by the update_script_test module to stop the page redirect for the info.', + 'severity' => REQUIREMENT_ERROR, + ); + $requirements['update_script_test'] = array( + 'title' => 'Update script test', + 'description' => 'This is a requirements info provided by the update_script_test module.', + 'severity' => REQUIREMENT_INFO, + ); + break; } } diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info index 4484ad46..36d36422 100644 --- a/modules/simpletest/tests/update_test_1.info +++ b/modules/simpletest/tests/update_test_1.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info index 4484ad46..36d36422 100644 --- a/modules/simpletest/tests/update_test_2.info +++ b/modules/simpletest/tests/update_test_2.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info index 4484ad46..36d36422 100644 --- a/modules/simpletest/tests/update_test_3.info +++ b/modules/simpletest/tests/update_test_3.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/upgrade/drupal-6.filled.database.php b/modules/simpletest/tests/upgrade/drupal-6.filled.database.php index a9162813..10b9040c 100644 --- a/modules/simpletest/tests/upgrade/drupal-6.filled.database.php +++ b/modules/simpletest/tests/upgrade/drupal-6.filled.database.php @@ -19919,7 +19919,7 @@ 'vid' => '1', 'name' => 'vocabulary 1 (i=0)', 'description' => 'description of vocabulary 1 (i=0)', - 'help' => '', + 'help' => 'help for vocabulary 1 (i=0)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -19932,7 +19932,7 @@ 'vid' => '2', 'name' => 'vocabulary 2 (i=1)', 'description' => 'description of vocabulary 2 (i=1)', - 'help' => '', + 'help' => 'help for vocabulary 2 (i=1)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -19945,7 +19945,7 @@ 'vid' => '3', 'name' => 'vocabulary 3 (i=2)', 'description' => 'description of vocabulary 3 (i=2)', - 'help' => '', + 'help' => 'help for vocabulary 3 (i=2)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -19958,7 +19958,7 @@ 'vid' => '4', 'name' => 'vocabulary 4 (i=3)', 'description' => 'description of vocabulary 4 (i=3)', - 'help' => '', + 'help' => 'help for vocabulary 4 (i=3)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -19971,7 +19971,7 @@ 'vid' => '5', 'name' => 'vocabulary 5 (i=4)', 'description' => 'description of vocabulary 5 (i=4)', - 'help' => '', + 'help' => 'help for vocabulary 5 (i=4)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -19984,7 +19984,7 @@ 'vid' => '6', 'name' => 'vocabulary 6 (i=5)', 'description' => 'description of vocabulary 6 (i=5)', - 'help' => '', + 'help' => 'help for vocabulary 6 (i=5)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -19997,7 +19997,7 @@ 'vid' => '7', 'name' => 'vocabulary 7 (i=6)', 'description' => 'description of vocabulary 7 (i=6)', - 'help' => '', + 'help' => 'help for vocabulary 7 (i=6)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20010,7 +20010,7 @@ 'vid' => '8', 'name' => 'vocabulary 8 (i=7)', 'description' => 'description of vocabulary 8 (i=7)', - 'help' => '', + 'help' => 'help for vocabulary 8 (i=7)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20023,7 +20023,7 @@ 'vid' => '9', 'name' => 'vocabulary 9 (i=8)', 'description' => 'description of vocabulary 9 (i=8)', - 'help' => '', + 'help' => 'help for vocabulary 9 (i=8)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20036,7 +20036,7 @@ 'vid' => '10', 'name' => 'vocabulary 10 (i=9)', 'description' => 'description of vocabulary 10 (i=9)', - 'help' => '', + 'help' => 'help for vocabulary 10 (i=9)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20049,7 +20049,7 @@ 'vid' => '11', 'name' => 'vocabulary 11 (i=10)', 'description' => 'description of vocabulary 11 (i=10)', - 'help' => '', + 'help' => 'help for vocabulary 11 (i=10)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20062,7 +20062,7 @@ 'vid' => '12', 'name' => 'vocabulary 12 (i=11)', 'description' => 'description of vocabulary 12 (i=11)', - 'help' => '', + 'help' => 'help for vocabulary 12 (i=11)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -20075,7 +20075,7 @@ 'vid' => '13', 'name' => 'vocabulary 13 (i=12)', 'description' => 'description of vocabulary 13 (i=12)', - 'help' => '', + 'help' => 'help for vocabulary 13 (i=12)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20088,7 +20088,7 @@ 'vid' => '14', 'name' => 'vocabulary 14 (i=13)', 'description' => 'description of vocabulary 14 (i=13)', - 'help' => '', + 'help' => 'help for vocabulary 14 (i=13)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20101,7 +20101,7 @@ 'vid' => '15', 'name' => 'vocabulary 15 (i=14)', 'description' => 'description of vocabulary 15 (i=14)', - 'help' => '', + 'help' => 'help for vocabulary 15 (i=14)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20114,7 +20114,7 @@ 'vid' => '16', 'name' => 'vocabulary 16 (i=15)', 'description' => 'description of vocabulary 16 (i=15)', - 'help' => '', + 'help' => 'help for vocabulary 16 (i=15)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20127,7 +20127,7 @@ 'vid' => '17', 'name' => 'vocabulary 17 (i=16)', 'description' => 'description of vocabulary 17 (i=16)', - 'help' => '', + 'help' => 'help for vocabulary 17 (i=16)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20140,7 +20140,7 @@ 'vid' => '18', 'name' => 'vocabulary 18 (i=17)', 'description' => 'description of vocabulary 18 (i=17)', - 'help' => '', + 'help' => 'help for vocabulary 18 (i=17)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', @@ -20153,7 +20153,7 @@ 'vid' => '19', 'name' => 'vocabulary 19 (i=18)', 'description' => 'description of vocabulary 19 (i=18)', - 'help' => '', + 'help' => 'help for vocabulary 19 (i=18)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '0', @@ -20166,7 +20166,7 @@ 'vid' => '20', 'name' => 'vocabulary 20 (i=19)', 'description' => 'description of vocabulary 20 (i=19)', - 'help' => '', + 'help' => 'help for vocabulary 20 (i=19)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '1', @@ -20179,7 +20179,7 @@ 'vid' => '21', 'name' => 'vocabulary 21 (i=20)', 'description' => 'description of vocabulary 21 (i=20)', - 'help' => '', + 'help' => 'help for vocabulary 21 (i=20)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '0', @@ -20192,7 +20192,7 @@ 'vid' => '22', 'name' => 'vocabulary 22 (i=21)', 'description' => 'description of vocabulary 22 (i=21)', - 'help' => '', + 'help' => 'help for vocabulary 22 (i=21)', 'relations' => '1', 'hierarchy' => '0', 'multiple' => '1', @@ -20205,7 +20205,7 @@ 'vid' => '23', 'name' => 'vocabulary 23 (i=22)', 'description' => 'description of vocabulary 23 (i=22)', - 'help' => '', + 'help' => 'help for vocabulary 23 (i=22)', 'relations' => '1', 'hierarchy' => '1', 'multiple' => '0', @@ -20218,7 +20218,7 @@ 'vid' => '24', 'name' => 'vocabulary 24 (i=23)', 'description' => 'description of vocabulary 24 (i=23)', - 'help' => '', + 'help' => 'help for vocabulary 24 (i=23)', 'relations' => '1', 'hierarchy' => '2', 'multiple' => '1', diff --git a/modules/simpletest/tests/upgrade/drupal-6.upload.database.php b/modules/simpletest/tests/upgrade/drupal-6.upload.database.php index 46ebe2cb..3fd602af 100644 --- a/modules/simpletest/tests/upgrade/drupal-6.upload.database.php +++ b/modules/simpletest/tests/upgrade/drupal-6.upload.database.php @@ -127,6 +127,38 @@ 'status' => '1', 'timestamp' => '1285708958', )) +// On some Drupal 6 sites, more than one file can have the same filepath. See +// https://www.drupal.org/node/1260938. +->values(array( + 'fid' => '12', + 'uid' => '1', + 'filename' => 'duplicate-name.png', + 'filepath' => 'sites/default/files/duplicate-name.png', + 'filemime' => 'image/png', + 'filesize' => '314', + 'status' => '1', + 'timestamp' => '1285708958', +)) +->values(array( + 'fid' => '13', + 'uid' => '1', + 'filename' => 'duplicate-name.png', + 'filepath' => 'sites/default/files/duplicate-name.png', + 'filemime' => 'image/png', + 'filesize' => '315', + 'status' => '1', + 'timestamp' => '1285708958', +)) +->values(array( + 'fid' => '14', + 'uid' => '1', + 'filename' => 'duplicate-name.png', + 'filepath' => 'sites/default/files/duplicate-name.png', + 'filemime' => 'image/png', + 'filesize' => '316', + 'status' => '1', + 'timestamp' => '1285708958', +)) ->execute(); db_insert('node')->fields(array( @@ -196,6 +228,23 @@ 'sticky' => '0', 'tnid' => '0', 'translate' => '0', +)) +->values(array( + 'nid' => '41', + 'vid' => '55', + 'type' => 'page', + 'language' => '', + 'title' => 'node title 41 revision 55', + 'uid' => '1', + 'status' => '1', + 'created' => '1285709012', + 'changed' => '1285709012', + 'comment' => '0', + 'promote' => '0', + 'moderate' => '0', + 'sticky' => '0', + 'tnid' => '0', + 'translate' => '0', )) ->execute(); @@ -253,6 +302,28 @@ 'log' => '', 'timestamp' => '1285709012', 'format' => '1', +)) +->values(array( + 'nid' => '41', + 'vid' => '54', + 'uid' => '1', + 'title' => 'node title 41 revision 54', + 'body' => "Attachments:\r\nduplicate-name.png", + 'teaser' => "Attachments:\r\nduplicate-name.png", + 'log' => '', + 'timestamp' => '1285709012', + 'format' => '1', +)) +->values(array( + 'nid' => '41', + 'vid' => '55', + 'uid' => '1', + 'title' => 'node title 41 revision 55', + 'body' => "Attachments:\r\nduplicate-name.png\r\nduplicate-name.png", + 'teaser' => "Attachments:\r\nduplicate-name.png\r\nduplicate-name.png", + 'log' => '', + 'timestamp' => '1285709012', + 'format' => '1', )) ->execute(); @@ -415,6 +486,30 @@ 'list' => '1', 'weight' => '0', )) +->values(array( + 'fid' => '12', + 'nid' => '41', + 'vid' => '54', + 'description' => 'duplicate-name.png', + 'list' => '1', + 'weight' => '0', +)) +->values(array( + 'fid' => '13', + 'nid' => '41', + 'vid' => '55', + 'description' => 'first description', + 'list' => '0', + 'weight' => '0', +)) +->values(array( + 'fid' => '14', + 'nid' => '41', + 'vid' => '55', + 'description' => 'second description', + 'list' => '1', + 'weight' => '0', +)) ->execute(); // Add series of entries for invalid node vids to the {upload} table. @@ -431,7 +526,7 @@ ->values(array( 'fid' => $i, 'nid' => '40', - 'vid' => 24 + $i, + 'vid' => 26 + $i, 'description' => 'crazy-basename.png', 'list' => '1', 'weight' => '0', @@ -440,7 +535,7 @@ ->values(array( 'fid' => 2, 'nid' => '40', - 'vid' => 24 + $i + 1, + 'vid' => 26 + $i + 1, 'description' => 'crazy-basename.png', 'list' => '1', 'weight' => '0', diff --git a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test index 58a4d5c1..51402ed7 100644 --- a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test +++ b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test @@ -74,9 +74,10 @@ class UpgradePathTaxonomyTestCase extends UpgradePathTestCase { $this->assertEqual($voc_keys, $inst_keys, 'Node type page has instances for every vocabulary.'); // Ensure instance variables are getting through. - foreach ($instances as $instance) { - $this->assertTrue(isset($instance['required']), 'The required setting was preserved during the upgrade path.'); - $this->assertTrue($instance['description'], 'The description was preserved during the upgrade path'); + foreach (array_unique($instances) as $instance) { + $field_instance = field_info_instance('node', $instance, 'page'); + $this->assertTrue(isset($field_instance['required']), 'The required setting was preserved during the upgrade path.'); + $this->assertTrue($field_instance['description'], 'The description was preserved during the upgrade path'); } // Node type 'story' was not explicitly in $vocabulary->nodes but diff --git a/modules/simpletest/tests/upgrade/upgrade.upload.test b/modules/simpletest/tests/upgrade/upgrade.upload.test index be352bd4..dfa94a00 100644 --- a/modules/simpletest/tests/upgrade/upgrade.upload.test +++ b/modules/simpletest/tests/upgrade/upgrade.upload.test @@ -64,12 +64,35 @@ class UploadUpgradePathTestCase extends UpgradePathTestCase { } $this->assertIdentical($filenames, $recorded_filenames, 'The uploaded files are present in the same order after the upgrade.'); } + // Test for the file with repeating basename to only have the streaming // path replaced. $node = node_load(40, 53); $repeated_basename_file = $node->upload[LANGUAGE_NONE][4]; $this->assertEqual($repeated_basename_file['uri'], 'private://drupal-6/file/directory/path/crazy-basename.png', "The file with the repeated basename path only had the stream portion replaced"); + // Ensure that filepaths are deduplicated. + $node0 = node_load(41, 54); + $node1 = node_load(41, 55); + // Ensure that both revisions point to the same file ID. + $items0 = field_get_items('node', $node0, 'upload'); + $this->assertEqual(count($items0), 1); + $items1 = field_get_items('node', $node1, 'upload'); + $this->assertEqual(count($items1), 2); + $this->assertEqual($items0[0]['fid'], $items1[0]['fid']); + $this->assertEqual($items0[0]['fid'], $items1[1]['fid']); + // The revision with more than one reference to the same file should retain + // the original settings for each reference. + $this->assertEqual($items1[0]['description'], 'first description'); + $this->assertEqual($items1[0]['display'], 0); + $this->assertEqual($items1[1]['description'], 'second description'); + $this->assertEqual($items1[1]['display'], 1); + // Ensure that the latest version of the files are used. + $this->assertEqual($items1[0]['filesize'], 316); + $this->assertEqual($items1[1]['filesize'], 316); + // No duplicate files should remain on the Drupal 7 site. + $this->assertEqual(0, db_query("SELECT COUNT(*) FROM {file_managed} GROUP BY uri HAVING COUNT(fid) > 1")->fetchField()); + // Make sure the file settings were properly migrated. $d6_file_directory_temp = '/drupal-6/file/directory/temp'; $d6_file_directory_path = '/drupal-6/file/directory/path'; diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info index 8af9f248..e4d1812f 100644 --- a/modules/simpletest/tests/url_alter_test.info +++ b/modules/simpletest/tests/url_alter_test.info @@ -5,8 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/simpletest/tests/xmlrpc.test b/modules/simpletest/tests/xmlrpc.test index 1a9ef234..bb74f059 100644 --- a/modules/simpletest/tests/xmlrpc.test +++ b/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/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info index 0cf5bbac..70c09ca8 100644 --- a/modules/simpletest/tests/xmlrpc_test.info +++ b/modules/simpletest/tests/xmlrpc_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index 92756356..8d711697 100644 --- a/modules/statistics/statistics.info +++ b/modules/statistics/statistics.info @@ -6,8 +6,7 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module index f665a14f..356a0e25 100644 --- a/modules/statistics/statistics.module +++ b/modules/statistics/statistics.module @@ -118,10 +118,9 @@ function statistics_node_view($node, $view_mode) { // Attach Ajax node count statistics if configured. if (variable_get('statistics_count_content_views', 0) && variable_get('statistics_count_content_views_ajax', 0)) { if (!empty($node->nid) && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) { - $node->content['#attached']['js'] = array( - drupal_get_path('module', 'statistics') . '/statistics.js' => array( - 'scope' => 'footer' - ), + $statistics = drupal_get_path('module', 'statistics') . '/statistics.js'; + $node->content['#attached']['js'][$statistics] = array( + 'scope' => 'footer', ); $settings = array('data' => array('nid' => $node->nid), 'url' => url(drupal_get_path('module', 'statistics') . '/statistics.php')); $node->content['#attached']['js'][] = array( @@ -246,7 +245,7 @@ function statistics_user_delete($account) { * Implements hook_cron(). */ function statistics_cron() { - $statistics_timestamp = variable_get('statistics_day_timestamp', ''); + $statistics_timestamp = variable_get('statistics_day_timestamp', 0); if ((REQUEST_TIME - $statistics_timestamp) >= 86400) { // Reset day counts. diff --git a/modules/statistics/statistics.php b/modules/statistics/statistics.php index f00e0397..48340c89 100644 --- a/modules/statistics/statistics.php +++ b/modules/statistics/statistics.php @@ -15,17 +15,19 @@ include_once DRUPAL_ROOT . '/includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); if (variable_get('statistics_count_content_views', 0) && variable_get('statistics_count_content_views_ajax', 0)) { - $nid = $_POST['nid']; - if (is_numeric($nid)) { - db_merge('node_counter') - ->key(array('nid' => $nid)) - ->fields(array( - 'daycount' => 1, - 'totalcount' => 1, - 'timestamp' => REQUEST_TIME, - )) - ->expression('daycount', 'daycount + 1') - ->expression('totalcount', 'totalcount + 1') - ->execute(); + if (isset($_POST['nid'])) { + $nid = $_POST['nid']; + if (is_numeric($nid)) { + db_merge('node_counter') + ->key(array('nid' => $nid)) + ->fields(array( + 'daycount' => 1, + 'totalcount' => 1, + 'timestamp' => REQUEST_TIME, + )) + ->expression('daycount', 'daycount + 1') + ->expression('totalcount', 'totalcount + 1') + ->execute(); + } } } diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test index 7e038d61..50accd74 100644 --- a/modules/statistics/statistics.test +++ b/modules/statistics/statistics.test @@ -35,7 +35,7 @@ class StatisticsTestCase extends DrupalWebTestCase { 'title' => 'test', 'path' => 'node/1', 'url' => 'http://example.com', - 'hostname' => '192.168.1.1', + 'hostname' => '1.2.3.3', 'uid' => 0, 'sid' => 10, 'timer' => 10, @@ -268,7 +268,7 @@ class StatisticsBlockVisitorsTestCase extends StatisticsTestCase { */ function testIPAddressBlocking() { // IP address for testing. - $test_ip_address = '192.168.1.1'; + $test_ip_address = '1.2.3.3'; // Verify the IP address from accesslog appears on the top visitors page // and that a 'block IP address' link is displayed. diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info index e0b8a44a..5d3304ec 100644 --- a/modules/syslog/syslog.info +++ b/modules/syslog/syslog.info @@ -6,8 +6,7 @@ core = 7.x files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/system/image.gd.inc b/modules/system/image.gd.inc index 913b0de5..3d0797e4 100644 --- a/modules/system/image.gd.inc +++ b/modules/system/image.gd.inc @@ -116,38 +116,62 @@ function image_gd_rotate(stdClass $image, $degrees, $background = NULL) { return FALSE; } - $width = $image->info['width']; - $height = $image->info['height']; + // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy + // behavior on negative multiples of 90 degrees we convert any negative + // angle to a positive one between 0 and 360 degrees. + $degrees -= floor($degrees / 360) * 360; - // Convert the hexadecimal background value to a color index value. + // Convert the hexadecimal background value to a RGBA array. if (isset($background)) { - $rgb = array(); - for ($i = 16; $i >= 0; $i -= 8) { - $rgb[] = (($background >> $i) & 0xFF); - } - $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0); + $background = array( + 'red' => $background >> 16 & 0xFF, + 'green' => $background >> 8 & 0xFF, + 'blue' => $background & 0xFF, + 'alpha' => 0, + ); } - // Set the background color as transparent if $background is NULL. else { - // Get the current transparent color. - $background = imagecolortransparent($image->resource); - - // If no transparent colors, use white. - if ($background == 0) { - $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0); - } + // Background color is not specified: use transparent white as background. + $background = array( + 'red' => 255, + 'green' => 255, + 'blue' => 255, + 'alpha' => 127 + ); } + // Store the color index for the background as that is what GD uses. + $background_idx = imagecolorallocatealpha($image->resource, $background['red'], $background['green'], $background['blue'], $background['alpha']); + // Images are assigned a new color palette when rotating, removing any // transparency flags. For GIF images, keep a record of the transparent color. if ($image->info['extension'] == 'gif') { - $transparent_index = imagecolortransparent($image->resource); - if ($transparent_index != 0) { - $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index); + // GIF does not work with a transparency channel, but can define 1 color + // in its palette to act as transparent. + + // Get the current transparent color, if any. + $gif_transparent_id = imagecolortransparent($image->resource); + if ($gif_transparent_id !== -1) { + // The gif already has a transparent color set: remember it to set it on + // the rotated image as well. + $transparent_gif_color = imagecolorsforindex($image->resource, $gif_transparent_id); + + if ($background['alpha'] >= 127) { + // We want a transparent background: use the color already set to act + // as transparent, as background. + $background_idx = $gif_transparent_id; + } + } + else { + // The gif does not currently have a transparent color set. + if ($background['alpha'] >= 127) { + // But as the background is transparent, it should get one. + $transparent_gif_color = $background; + } } } - $image->resource = imagerotate($image->resource, 360 - $degrees, $background); + $image->resource = imagerotate($image->resource, 360 - $degrees, $background_idx); // GIFs need to reassign the transparent color after performing the rotate. if (isset($transparent_gif_color)) { diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 0f525c6c..cdcc78fb 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1856,7 +1856,7 @@ function system_image_toolkit_settings() { if (count($toolkits_available) == 0) { variable_del('image_toolkit'); $form['image_toolkit_help'] = array( - '#markup' => t("No image toolkits were detected. Drupal includes support for PHP's built-in image processing functions but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))), + '#markup' => t("No image toolkits were detected. Drupal includes support for PHP's built-in image processing functions but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('!gd-link' => url('http://php.net/gd'))), ); return $form; } @@ -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); } @@ -2592,6 +2597,8 @@ function theme_status_report($variables) { if (empty($requirement['#type'])) { $severity = $severities[isset($requirement['severity']) ? (int) $requirement['severity'] : REQUIREMENT_OK]; $severity['icon'] = '
' . $severity['title'] . '
'; + // The requirement's 'value' key is optional, provide a default value. + $requirement['value'] = isset($requirement['value']) ? $requirement['value'] : ''; // Output table row(s) if (!empty($requirement['description'])) { @@ -2875,13 +2882,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/modules/system/system.api.php b/modules/system/system.api.php index d6cbc769..f1855b96 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -113,21 +113,21 @@ function hook_hook_info_alter(&$hooks) { * translation handlers. Array keys are the module names, array values * can be any data structure the module uses to provide field translation. * Any empty value disallows the module to appear as a translation handler. - * - entity keys: An array describing how the Field API can extract the - * information it needs from the objects of the type. Elements: + * - entity keys: (optional) An array describing how the Field API can extract + * the information it needs from the objects of the type. Elements: * - id: The name of the property that contains the primary id of the * entity. Every entity object passed to the Field API must have this * property and its value must be numeric. * - revision: The name of the property that contains the revision id of * the entity. The Field API assumes that all revision ids are unique * across all entities of a type. This entry can be omitted if the - * entities of this type are not versionable. + * entities of this type are not versionable. Defaults to an empty string. * - bundle: The name of the property that contains the bundle name for the * entity. The bundle name defines which set of fields are attached to * the entity (e.g. what nodes call "content type"). This entry can be * omitted if this entity type exposes a single bundle (all entities have * the same collection of fields). The name of this single bundle will be - * the same as the entity type. + * the same as the entity type. Defaults to an empty string. * - label: The name of the property that contains the entity label. For * example, if the entity's label is located in $entity->subject, then * 'subject' should be specified here. If complex logic is required to @@ -1797,6 +1797,8 @@ function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) { * the $form_id input matched your module's format for dynamically-generated * form IDs, and if so, act appropriately. * + * Third, forms defined in classes can be defined this way. + * * @param $form_id * The unique string identifying the desired form. * @param $args @@ -1807,19 +1809,22 @@ function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) { * @return * An associative array whose keys define form_ids and whose values are an * associative array defining the following keys: - * - callback: The name of the form builder function to invoke. This will be - * used for the base form ID, for example, to target a base form using - * hook_form_BASE_FORM_ID_alter(). + * - callback: The callable returning the form array. If it is the name of + * the form builder function then this will be used for the base + * form ID, for example, to target a base form using + * hook_form_BASE_FORM_ID_alter(). Otherwise use the base_form_id key to + * define the base form ID. * - callback arguments: (optional) Additional arguments to pass to the * function defined in 'callback', which are prepended to $args. - * - wrapper_callback: (optional) The name of a form builder function to - * invoke before the form builder defined in 'callback' is invoked. This - * wrapper callback may prepopulate the $form array with form elements, - * which will then be already contained in the $form that is passed on to - * the form builder defined in 'callback'. For example, a wrapper callback - * could setup wizard-alike form buttons that are the same for a variety of - * forms that belong to the wizard, which all share the same wrapper - * callback. + * - base_form_id: The base form ID can be specified explicitly. This is + * required when callback is not the name of a function. + * - wrapper_callback: (optional) Any callable to invoke before the form + * builder defined in 'callback' is invoked. This wrapper callback may + * prepopulate the $form array with form elements, which will then be + * already contained in the $form that is passed on to the form builder + * defined in 'callback'. For example, a wrapper callback could setup + * wizard-like form buttons that are the same for a variety of forms that + * belong to the wizard, which all share the same wrapper callback. */ function hook_forms($form_id, $args) { // Simply reroute the (non-existing) $form_id 'mymodule_first_form' to @@ -1843,6 +1848,15 @@ function hook_forms($form_id, $args) { 'wrapper_callback' => 'mymodule_main_form_wrapper', ); + // Build a form with a static class callback. + $forms['mymodule_class_generated_form'] = array( + // This will call: MyClass::generateMainForm(). + 'callback' => array('MyClass', 'generateMainForm'), + // The base_form_id is required when the callback is a static function in + // a class. This can also be used to keep newer code backwards compatible. + 'base_form_id' => 'mymodule_main_form', + ); + return $forms; } @@ -2036,6 +2050,22 @@ function hook_system_theme_info() { return $themes; } +/** + * Return additional theme engines provided by modules. + * + * This hook is invoked from _system_rebuild_theme_data() and allows modules to + * register additional theme engines outside of the regular 'themes/engines' + * directories of a Drupal installation. + * + * @return + * An associative array. Each key is the system name of a theme engine and + * each value is the corresponding path to the theme engine's .engine file. + */ +function hook_system_theme_engine_info() { + $theme_engines['izumi'] = drupal_get_path('module', 'mymodule') . '/izumi/izumi.engine'; + return $theme_engines; +} + /** * Alter the information parsed from module and theme .info files * @@ -2632,6 +2662,8 @@ function hook_flush_caches() { * module_enable() for a detailed description of the order in which install and * enable hooks are invoked. * + * This hook should be implemented in a .module file, not in an .install file. + * * @param $modules * An array of the modules that were installed. * @@ -3173,7 +3205,9 @@ function hook_requirements($phase) { * creation and alteration of the supported database engines. * * See the Schema API Handbook at http://drupal.org/node/146843 for details on - * schema definition structures. + * schema definition structures. Note that foreign key definitions are for + * documentation purposes only; foreign keys are not created in the database, + * nor are they enforced by Drupal. * * @return array * A schema definition structure array. For each element of the @@ -3225,6 +3259,8 @@ function hook_schema() { 'nid_vid' => array('nid', 'vid'), 'vid' => array('vid'), ), + // For documentation purposes only; foreign keys are not created in the + // database. 'foreign keys' => array( 'node_revision' => array( 'table' => 'node_revision', diff --git a/modules/system/system.info b/modules/system/system.info index 97f3bdf3..730dff6f 100644 --- a/modules/system/system.info +++ b/modules/system/system.info @@ -12,8 +12,7 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/system/system.install b/modules/system/system.install index 89e7d5b8..2aab53e7 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -170,7 +170,7 @@ function system_requirements($phase) { if (empty($drivers)) { $database_ok = FALSE; $pdo_message = $t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that Drupal supports.', array( - '@drupal-databases' => 'http://drupal.org/node/270#database', + '@drupal-databases' => 'https://www.drupal.org/requirements/database', )); } // Make sure the native PDO extension is available, not the older PEAR @@ -206,6 +206,12 @@ function system_requirements($phase) { ); } + // Test database-specific multi-byte UTF-8 related requirements. + $charset_requirements = _system_check_db_utf8mb4_requirements($phase); + if (!empty($charset_requirements)) { + $requirements['database_charset'] = $charset_requirements; + } + // Test PHP memory_limit $memory_limit = ini_get('memory_limit'); $requirements['php_memory_limit'] = array( @@ -527,6 +533,75 @@ function system_requirements($phase) { return $requirements; } +/** + * Checks whether the requirements for multi-byte UTF-8 support are met. + * + * @param string $phase + * The hook_requirements() stage. + * + * @return array + * A requirements array with the result of the charset check. + */ +function _system_check_db_utf8mb4_requirements($phase) { + global $install_state; + // In the requirements check of the installer, skip the utf8mb4 check unless + // the database connection info has been preconfigured by hand with valid + // information before running the installer, as otherwise we cannot get a + // valid database connection object. + if (isset($install_state['settings_verified']) && !$install_state['settings_verified']) { + return array(); + } + + $connection = Database::getConnection(); + $t = get_t(); + $requirements['title'] = $t('Database 4 byte UTF-8 support'); + + $utf8mb4_configurable = $connection->utf8mb4IsConfigurable(); + $utf8mb4_active = $connection->utf8mb4IsActive(); + $utf8mb4_supported = $connection->utf8mb4IsSupported(); + $driver = $connection->driver(); + $documentation_url = 'https://www.drupal.org/node/2754539'; + + if ($utf8mb4_active) { + if ($utf8mb4_supported) { + if ($phase != 'install' && $utf8mb4_configurable && !variable_get('drupal_all_databases_are_utf8mb4', FALSE)) { + // Supported, active, and configurable, but not all database tables + // have been converted yet. + $requirements['value'] = $t('Enabled, but database tables need conversion'); + $requirements['description'] = $t('Please convert all database tables to utf8mb4 prior to enabling it in settings.php. See the documentation on adding 4 byte UTF-8 support for more information.', array('@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_ERROR; + } + else { + // Supported, active. + $requirements['value'] = $t('Enabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is enabled.', array('@driver' => $driver)); + $requirements['severity'] = REQUIREMENT_OK; + } + } + else { + // Not supported, active. + $requirements['value'] = $t('Not supported'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is activated, but not supported on your system. Please turn this off in settings.php, or ensure that all database-related requirements are met. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_ERROR; + } + } + else { + if ($utf8mb4_supported) { + // Supported, not active. + $requirements['value'] = $t('Not enabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is not activated, but it is supported on your system. It is recommended that you enable this to allow 4-byte UTF-8 input such as emojis, Asian symbols and mathematical symbols to be stored correctly. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_INFO; + } + else { + // Not supported, not active. + $requirements['value'] = $t('Disabled'); + $requirements['description'] = $t('4 byte UTF-8 for @driver is disabled. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url)); + $requirements['severity'] = REQUIREMENT_INFO; + } + } + return $requirements; +} + /** * Implements hook_install(). */ @@ -542,6 +617,9 @@ function system_install() { module_list(TRUE); module_implements('', FALSE, TRUE); + // Ensure the schema versions are not based on a previous module list. + drupal_static_reset('drupal_get_schema_versions'); + // Load system theme data appropriately. system_rebuild_theme_data(); @@ -810,6 +888,7 @@ function system_schema() { 'type' => 'varchar', 'length' => 100, 'not null' => TRUE, + 'binary' => TRUE, ), 'type' => array( 'description' => 'The date format type, e.g. medium.', @@ -2813,6 +2892,16 @@ function system_update_7061(&$sandbox) { ->from($query) ->execute(); + // Retrieve a list of duplicate files with the same filepath. Only the + // most-recently uploaded of these will be moved to the new {file_managed} + // table (and all references will be updated to point to it), since + // duplicate file URIs are not allowed in Drupal 7. + // Since the Drupal 6 to 7 upgrade path leaves the {files} table behind + // after it's done, custom or contributed modules which need to migrate + // file references of their own can use a similar query to determine the + // file IDs that duplicate filepaths were mapped to. + $sandbox['duplicate_filepath_fids_to_use'] = db_query("SELECT filepath, MAX(fid) FROM {files} GROUP BY filepath HAVING COUNT(*) > 1")->fetchAllKeyed(); + // Initialize batch update information. $sandbox['progress'] = 0; $sandbox['last_vid_processed'] = -1; @@ -2842,6 +2931,16 @@ function system_update_7061(&$sandbox) { continue; } + // If this file has a duplicate filepath, replace it with the + // most-recently uploaded file that has the same filepath. + if (isset($sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) && $record->fid != $sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) { + $file = db_select('files', 'f') + ->fields('f', array('fid', 'uid', 'filename', 'filepath', 'filemime', 'filesize', 'status', 'timestamp')) + ->condition('f.fid', $sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) + ->execute() + ->fetchAssoc(); + } + // Add in the file information from the upload table. $file['description'] = $record->description; $file['display'] = $record->list; @@ -3167,6 +3266,35 @@ function system_update_7079() { db_change_field('file_managed', 'filesize', 'filesize', $spec); } +/** + * Convert the 'format' column in {date_format_locale} to case sensitive varchar. + */ +function system_update_7080() { + $spec = array( + 'description' => 'The date format string.', + 'type' => 'varchar', + 'length' => 100, + 'not null' => TRUE, + 'binary' => TRUE, + ); + db_change_field('date_format_locale', 'format', 'format', $spec); +} + +/** + * Remove the Drupal 6 default install profile if it is still in the database. + */ +function system_update_7081() { + // Sites which used the default install profile in Drupal 6 and then updated + // to Drupal 7.44 or earlier will still have a record of this install profile + // in the database that needs to be deleted. + db_delete('system') + ->condition('filename', 'profiles/default/default.profile') + ->condition('type', 'module') + ->condition('status', 0) + ->condition('schema_version', 0) + ->execute(); +} + /** * @} End of "defgroup updates-7.x-extra". * The next series of updates should start at 8000. diff --git a/modules/system/system.js b/modules/system/system.js index 910fb5d3..c0e76d38 100644 --- a/modules/system/system.js +++ b/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/modules/system/system.mail.inc b/modules/system/system.mail.inc index 443e5740..9a17f55f 100644 --- a/modules/system/system.mail.inc +++ b/modules/system/system.mail.inc @@ -70,7 +70,9 @@ class DefaultMailSystem implements MailSystemInterface { // hosts. The return value of this method will still indicate whether mail // was sent successfully. if (!isset($_SERVER['WINDIR']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') === FALSE) { - if (isset($message['Return-Path']) && !ini_get('safe_mode')) { + // We validate the return path, unless it is equal to the site mail, which + // we assume to be safe. + if (isset($message['Return-Path']) && !ini_get('safe_mode') && (variable_get('site_mail', ini_get('sendmail_from')) === $message['Return-Path'] || self::_isShellSafe($message['Return-Path']))) { // On most non-Windows systems, the "-f" option to the sendmail command // is used to set the Return-Path. There is no space between -f and // the value of the return path. @@ -109,6 +111,36 @@ class DefaultMailSystem implements MailSystemInterface { } return $mail_result; } + + /** + * Disallows potentially unsafe shell characters. + * + * Functionally similar to PHPMailer::isShellSafe() which resulted from + * CVE-2016-10045. Note that escapeshellarg and escapeshellcmd are inadequate + * for this purpose. + * + * @param string $string + * The string to be validated. + * + * @return bool + * True if the string is shell-safe. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 + * @see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.phpmailer.php#L1430 + * + * @todo Rename to ::isShellSafe() and/or discuss whether this is the correct + * location for this helper. + */ + protected static function _isShellSafe($string) { + if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))) { + return FALSE; + } + if (preg_match('/[^a-zA-Z0-9@_\-.]/', $string) !== 0) { + return FALSE; + } + return TRUE; + } + } /** diff --git a/modules/system/system.module b/modules/system/system.module index 284597f8..f891998e 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -2417,6 +2417,10 @@ function _system_rebuild_module_data() { // Merge in defaults and save. $modules[$key]->info = $module->info + $defaults; + // The "name" key is required, but to avoid a fatal error in the menu system + // we set a reasonable default if it is not provided. + $modules[$key]->info += array('name' => $key); + // Prefix stylesheets and scripts with module path. $path = dirname($module->uri); if (isset($module->info['stylesheets'])) { @@ -2523,6 +2527,16 @@ function _system_rebuild_theme_data() { // Find theme engines $engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines'); + // Allow modules to add further theme engines. + if ($module_engines = module_invoke_all('system_theme_engine_info')) { + foreach ($module_engines as $name => $theme_engine_path) { + $engines[$name] = (object) array( + 'uri' => $theme_engine_path, + 'filename' => basename($theme_engine_path), + 'name' => $name, + ); + } + } // Set defaults for theme info. $defaults = array( @@ -2552,6 +2566,10 @@ function _system_rebuild_theme_data() { $themes[$key]->filename = $theme->uri; $themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults; + // The "name" key is required, but to avoid a fatal error in the menu system + // we set a reasonable default if it is not provided. + $themes[$key]->info += array('name' => $key); + // Add the info file modification time, so it becomes available for // contributed modules to use for ordering theme lists. $themes[$key]->info['mtime'] = filemtime($theme->uri); @@ -2703,10 +2721,17 @@ function system_find_base_themes($themes, $key, $used_keys = array()) { * @param $show * Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden * regions. - * @return - * An array of regions in the form $region['name'] = 'description'. + * @param bool $labels + * (optional) Boolean to specify whether the human readable machine names + * should be returned or not. Defaults to TRUE, but calling code can set + * this to FALSE for better performance, if it only needs machine names. + * + * @return array + * An associative array of regions in the form $region['name'] = 'description' + * if $labels is set to TRUE, or $region['name'] = 'name', if $labels is set + * to FALSE. */ -function system_region_list($theme_key, $show = REGIONS_ALL) { +function system_region_list($theme_key, $show = REGIONS_ALL, $labels = TRUE) { $themes = list_themes(); if (!isset($themes[$theme_key])) { return array(); @@ -2717,10 +2742,14 @@ function system_region_list($theme_key, $show = REGIONS_ALL) { // If requested, suppress hidden regions. See block_admin_display_form(). foreach ($info['regions'] as $name => $label) { if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) { - $list[$name] = t($label); + if ($labels) { + $list[$name] = t($label); + } + else { + $list[$name] = $name; + } } } - return $list; } @@ -2741,12 +2770,13 @@ function system_system_info_alter(&$info, $file, $type) { * * @param $theme * The name of a theme. + * * @return * A string that is the region name. */ function system_default_region($theme) { - $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - return isset($regions[0]) ? $regions[0] : ''; + $regions = system_region_list($theme, REGIONS_VISIBLE, FALSE); + return $regions ? reset($regions) : ''; } /** @@ -2813,7 +2843,7 @@ function system_settings_form_submit($form, &$form_state) { function _system_sort_requirements($a, $b) { if (!isset($a['weight'])) { if (!isset($b['weight'])) { - return strcmp($a['title'], $b['title']); + return strcasecmp($a['title'], $b['title']); } return -$b['weight']; } @@ -2869,7 +2899,7 @@ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, // Prepare cancel link. if (isset($_GET['destination'])) { - $options = drupal_parse_url(urldecode($_GET['destination'])); + $options = drupal_parse_url($_GET['destination']); } elseif (is_array($path)) { $options = $path; @@ -3054,8 +3084,20 @@ function system_cron() { } } - $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); - $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + // Delete expired cache entries. + // Avoid invoking hook_flush_cashes() on every cron run because some modules + // use this hook to perform expensive rebuilding operations (which are only + // designed to happen on full cache clears), rather than just returning a + // list of cache tables to be cleared. + $cache_object = cache_get('system_cache_tables'); + if (empty($cache_object)) { + $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); + $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + cache_set('system_cache_tables', $cache_tables); + } + else { + $cache_tables = $cache_object->data; + } foreach ($cache_tables as $table) { cache_clear_all(NULL, $table); } @@ -3303,7 +3345,7 @@ function system_goto_action_form($context) { $form['url'] = array( '#type' => 'textfield', '#title' => t('URL'), - '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'), + '#description' => t('The URL to which the user should be redirected. This can be an internal path like node/1234 or an external URL like http://example.com.'), '#default_value' => isset($context['url']) ? $context['url'] : '', '#required' => TRUE, ); @@ -3340,7 +3382,8 @@ function system_goto_action($entity, $context) { */ function system_block_ip_action() { $ip = ip_address(); - db_insert('blocked_ips') + db_merge('blocked_ips') + ->key(array('ip' => $ip)) ->fields(array('ip' => $ip)) ->execute(); watchdog('action', 'Banned IP address %ip', array('%ip' => $ip)); @@ -3502,8 +3545,7 @@ function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $repl function system_page_alter(&$page) { // Find all non-empty page regions, and add a theme wrapper function that // allows them to be consistently themed. - $regions = system_region_list($GLOBALS['theme']); - foreach (array_keys($regions) as $region) { + foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region) { if (!empty($page[$region])) { $page[$region]['#theme_wrappers'][] = 'region'; $page[$region]['#region'] = $region; diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc index 6eeaae19..c17084de 100644 --- a/modules/system/system.queue.inc +++ b/modules/system/system.queue.inc @@ -326,6 +326,7 @@ class MemoryQueue implements DrupalQueueInterface { $item->created = time(); $item->expire = 0; $this->queue[$item->item_id] = $item; + return TRUE; } public function numberOfItems() { diff --git a/modules/system/system.tar.inc b/modules/system/system.tar.inc index 32bf7f06..86e4e3de 100644 --- a/modules/system/system.tar.inc +++ b/modules/system/system.tar.inc @@ -30,81 +30,148 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * - * @category File_Formats - * @package Archive_Tar - * @author Vincent Blavet - * @copyright 1997-2008 The Authors - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp - * @link http://pear.php.net/package/Archive_Tar + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2010 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Archive_Tar + */ + + /** + * Note on Drupal 8 porting. + * This file origin is Tar.php, release 1.4.0 (stable) with some code + * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net. + * To simplify future porting from pear of this file, you should not + * do cosmetic or other non significant changes to this file. + * The following changes have been done: + * Added namespace Drupal\Core\Archiver. + * Removed require_once 'PEAR.php'. + * Added defintion of OS_WINDOWS taken from PEAR.php. + * Renamed class to ArchiveTar. + * Removed extends PEAR from class. + * Removed call parent:: __construct(). + * Changed PEAR::loadExtension($extname) to this->loadExtension($extname). + * Added function loadExtension() taken from PEAR.php. + * Changed all calls of unlink() to drupal_unlink(). + * Changed $this->error_object = &$this->raiseError($p_message) + * to throw new \Exception($p_message). + */ + + /** + * Note on Drupal 7 backporting from Drupal 8. + * File origin is core/lib/Drupal/Core/Archiver/ArchiveTar.php from Drupal 8. + * The following changes have been done: + * Removed namespace Drupal\Core\Archiver. + * Renamed class to Archive_Tar. + * Changed \Exception to Exception. */ -//require_once 'PEAR.php'; -// -// -define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001); -define ('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +// Drupal removal require_once 'PEAR.php'. + +// Drupal addition OS_WINDOWS as defined in PEAR.php. +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); +} else { + define('OS_WINDOWS', false); +} + +define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +if (!function_exists('gzopen') && function_exists('gzopen64')) { + function gzopen($filename, $mode, $use_include_path = 0) + { + return gzopen64($filename, $mode, $use_include_path); + } +} + +if (!function_exists('gztell') && function_exists('gztell64')) { + function gztell($zp) + { + return gztell64($zp); + } +} + +if (!function_exists('gzseek') && function_exists('gzseek64')) { + function gzseek($zp, $offset, $whence = SEEK_SET) + { + return gzseek64($zp, $offset, $whence); + } +} /** -* Creates a (compressed) Tar archive -* -* @author Vincent Blavet -* @version Revision: 1.43 -* @license http://www.opensource.org/licenses/bsd-license.php New BSD License -* @package Archive_Tar -*/ -class Archive_Tar // extends PEAR + * Creates a (compressed) Tar archive + * + * @package Archive_Tar + * @author Vincent Blavet + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version $Revision$ + */ +// Drupal change class Archive_Tar extends PEAR. +class Archive_Tar { /** - * @var string Name of the Tar - */ - var $_tarname=''; + * @var string Name of the Tar + */ + public $_tarname = ''; /** - * @var boolean if true, the Tar file will be gzipped - */ - var $_compress=false; + * @var boolean if true, the Tar file will be gzipped + */ + public $_compress = false; /** - * @var string Type of compression : 'none', 'gz' or 'bz2' - */ - var $_compress_type='none'; + * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2' + */ + public $_compress_type = 'none'; /** - * @var string Explode separator - */ - var $_separator=' '; + * @var string Explode separator + */ + public $_separator = ' '; /** - * @var file descriptor - */ - var $_file=0; + * @var file descriptor + */ + public $_file = 0; /** - * @var string Local Tar name of a remote Tar (http:// or ftp://) - */ - var $_temp_tarname=''; + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + public $_temp_tarname = ''; - // {{{ constructor /** - * Archive_Tar Class constructor. This flavour of the constructor only - * declare a new Archive_Tar object, identifying it by the name of the - * tar file. - * If the compress argument is set the tar will be read or created as a - * gzip or bz2 compressed TAR file. - * - * @param string $p_tarname The name of the tar archive to create - * @param string $p_compress can be null, 'gz' or 'bz2'. This - * parameter indicates if gzip or bz2 compression - * is required. For compatibility reason the - * boolean value 'true' means 'gz'. - * @access public - */ -// function Archive_Tar($p_tarname, $p_compress = null) - function __construct($p_tarname, $p_compress = null) + * @var string regular expression for ignoring files or directories + */ + public $_ignore_regexp = ''; + + /** + * @var object PEAR_Error object + */ + public $error_object = null; + + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This + * parameter indicates if gzip, bz2 or lzma2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * + * @return bool + */ + public function __construct($p_tarname, $p_compress = null) { -// $this->PEAR(); + // Drupal removal parent::__construct(). + $this->_compress = false; $this->_compress_type = 'none'; if (($p_compress === null) || ($p_compress == '')) { @@ -116,10 +183,13 @@ class Archive_Tar // extends PEAR if ($data == "\37\213") { $this->_compress = true; $this->_compress_type = 'gz'; - // No sure it's enought for a magic code .... + // No sure it's enought for a magic code .... } elseif ($data == "BZ") { $this->_compress = true; $this->_compress_type = 'bz2'; + } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; } } } else { @@ -129,151 +199,177 @@ class Archive_Tar // extends PEAR $this->_compress = true; $this->_compress_type = 'gz'; } elseif ((substr($p_tarname, -3) == 'bz2') || - (substr($p_tarname, -2) == 'bz')) { + (substr($p_tarname, -2) == 'bz') + ) { $this->_compress = true; $this->_compress_type = 'bz2'; + } else { + if (substr($p_tarname, -2) == 'xz') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; + } } } } else { if (($p_compress === true) || ($p_compress == 'gz')) { $this->_compress = true; $this->_compress_type = 'gz'; - } else if ($p_compress == 'bz2') { - $this->_compress = true; - $this->_compress_type = 'bz2'; } else { - die("Unsupported compression type '$p_compress'\n". - "Supported types are 'gz' and 'bz2'.\n"); - return false; + if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + if ($p_compress == 'lzma2') { + $this->_compress = true; + $this->_compress_type = 'lzma2'; + } else { + $this->_error( + "Unsupported compression type '$p_compress'\n" . + "Supported types are 'gz', 'bz2' and 'lzma2'.\n" + ); + return false; + } + } } } $this->_tarname = $p_tarname; - if ($this->_compress) { // assert zlib or bz2 extension support - if ($this->_compress_type == 'gz') + if ($this->_compress) { // assert zlib or bz2 or xz extension support + if ($this->_compress_type == 'gz') { $extname = 'zlib'; - else if ($this->_compress_type == 'bz2') - $extname = 'bz2'; + } else { + if ($this->_compress_type == 'bz2') { + $extname = 'bz2'; + } else { + if ($this->_compress_type == 'lzma2') { + $extname = 'xz'; + } + } + } if (!extension_loaded($extname)) { -// PEAR::loadExtension($extname); + // Drupal change PEAR::loadExtension($extname). $this->loadExtension($extname); } if (!extension_loaded($extname)) { - die("The extension '$extname' couldn't be found.\n". - "Please make sure your version of PHP was built ". - "with '$extname' support.\n"); + $this->_error( + "The extension '$extname' couldn't be found.\n" . + "Please make sure your version of PHP was built " . + "with '$extname' support.\n" + ); return false; } } } - // }}} + public function __destruct() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') { + @drupal_unlink($this->_temp_tarname); + } + } + + // Drupal addition from PEAR.php. /** * OS independent PHP extension load. Remember to take care * on the correct extension name for case sensitive OSes. - * The function is the copy of PEAR::loadExtension(). * * @param string $ext The extension name * @return bool Success or not on the dl() call */ function loadExtension($ext) { - if (!extension_loaded($ext)) { - // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { - return false; - } + if (extension_loaded($ext)) { + return true; + } - if (OS_WINDOWS) { - $suffix = '.dll'; - } elseif (PHP_OS == 'HP-UX') { - $suffix = '.sl'; - } elseif (PHP_OS == 'AIX') { - $suffix = '.a'; - } elseif (PHP_OS == 'OSX') { - $suffix = '.bundle'; - } else { - $suffix = '.so'; - } + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 || + ini_get('safe_mode') == 1 + ) { + return false; + } - return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; } - return true; + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); } - // {{{ destructor -// function _Archive_Tar() - function __destruct() - { - $this->_close(); - // ----- Look for a local copy to delete - if ($this->_temp_tarname != '') - @drupal_unlink($this->_temp_tarname); -// $this->_PEAR(); - } - // }}} - - // {{{ create() /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If a file with the same name exist and is writable, it is replaced - * by the new tar. - * The method return false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * For each directory added in the archive, the files and - * sub-directories are also added. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function create($p_filelist) + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + */ + public function create($p_filelist) { return $this->createModify($p_filelist, '', ''); } - // }}} - // {{{ add() /** - * This method add the files / directories that are listed in $p_filelist in - * the archive. If the archive does not exist it is created. - * The method return false and a PEAR error text. - * The files and directories listed are only added at the end of the archive, - * even if a file with the same name is already archived. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function add($p_filelist) + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + public function add($p_filelist) { return $this->addModify($p_filelist, '', ''); } - // }}} - // {{{ extract() - function extract($p_path='') + /** + * @param string $p_path + * @param bool $p_preserve + * @return bool + */ + public function extract($p_path = '', $p_preserve = false) { - return $this->extractModify($p_path, ''); + return $this->extractModify($p_path, '', $p_preserve); } - // }}} - // {{{ listContent() - function listContent() + /** + * @return array|int + */ + public function listContent() { $v_list_detail = array(); @@ -287,57 +383,56 @@ class Archive_Tar // extends PEAR return $v_list_detail; } - // }}} - // {{{ createModify() /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If the file already exists and is writable, it is replaced by the - * new tar. It is a create and not an add. If the file exists and is - * read-only or is a directory it is not replaced. The method return - * false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * See also addModify() method for file adding properties. - * - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated by - * a single blank space. - * @param string $p_add_dir A string which contains a path to be added - * to the memorized path of each element in - * the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of each - * element in the list, when relevant. - * @return boolean true on success, false on error. - * @access public - * @see addModify() - */ - function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * + * @return boolean true on success, false on error. + * @see addModify() + */ + public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '') { $v_result = true; - if (!$this->_openWrite()) + if (!$this->_openWrite()) { return false; + } if ($p_filelist != '') { - if (is_array($p_filelist)) + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_cleanFile(); $this->_error('Invalid file list'); return false; @@ -349,67 +444,69 @@ class Archive_Tar // extends PEAR if ($v_result) { $this->_writeFooter(); $this->_close(); - } else + } else { $this->_cleanFile(); + } return $v_result; } - // }}} - // {{{ addModify() /** - * This method add the files / directories listed in $p_filelist at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * If a file/dir is already in the archive it will only be added at the - * end of the archive. There is no update of the existing archived - * file/dir. However while extracting the archive, the last file will - * replace the first one. This results in a none optimization of the - * archive size. - * If a file/dir does not exist the file/dir is ignored. However an - * error text is send to PEAR error. - * If a file/dir is not readable the file/dir is ignored. However an - * error text is send to PEAR error. - * - * @param array $p_filelist An array of filenames and directory - * names, or a single string with names - * separated by a single blank space. - * @param string $p_add_dir A string which contains a path to be - * added to the memorized path of each - * element in the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of - * each element in the list, when - * relevant. - * @return true on success, false on error. - * @access public - */ - function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * + * @return true on success, false on error. + */ + public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '') { $v_result = true; - if (!$this->_isArchive()) - $v_result = $this->createModify($p_filelist, $p_add_dir, - $p_remove_dir); - else { - if (is_array($p_filelist)) + if (!$this->_isArchive()) { + $v_result = $this->createModify( + $p_filelist, + $p_add_dir, + $p_remove_dir + ); + } else { + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_error('Invalid file list'); return false; } @@ -419,24 +516,41 @@ class Archive_Tar // extends PEAR return $v_result; } - // }}} - // {{{ addString() /** - * This method add a single string as a file at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * - * @param string $p_filename A string which contains the full - * filename path that will be associated - * with the string. - * @param string $p_string The content of the file added in - * the archive. - * @return true on success, false on error. - * @access public - */ - function addString($p_filename, $p_string) + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * @param bool|int $p_datetime A custom date/time (unix timestamp) + * for the file (optional). + * @param array $p_params An array of optional params: + * stamp => the datetime (replaces + * datetime above if it exists) + * mode => the permissions on the + * file (600 by default) + * type => is this a link? See the + * tar specification for details. + * (default = regular file) + * uid => the user ID of the file + * (default = 0 = root) + * gid => the group ID of the file + * (default = 0 = root) + * + * @return true on success, false on error. + */ + public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) { + $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); + $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; + $p_type = @$p_params["type"] ? $p_params["type"] : ""; + $p_uid = @$p_params["uid"] ? $p_params["uid"] : ""; + $p_gid = @$p_params["gid"] ? $p_params["gid"] : ""; $v_result = true; if (!$this->_isArchive()) { @@ -446,11 +560,12 @@ class Archive_Tar // extends PEAR $this->_close(); } - if (!$this->_openAppend()) + if (!$this->_openAppend()) { return false; + } // Need to check the get back to the temporary file ? .... - $v_result = $this->_addString($p_filename, $p_string); + $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params); $this->_writeFooter(); @@ -458,131 +573,138 @@ class Archive_Tar // extends PEAR return $v_result; } - // }}} - // {{{ extractModify() /** - * This method extract all the content of the archive in the directory - * indicated by $p_path. When relevant the memorized path of the - * files/dir can be modified by removing the $p_remove_path path at the - * beginning of the file/dir path. - * While extracting a file, if the directory path does not exists it is - * created. - * While extracting a file, if the file already exists it is replaced - * without looking for last modification date. - * While extracting a file, if the file already exists and is write - * protected, the extraction is aborted. - * While extracting a file, if a directory with the same name already - * exists, the extraction is aborted. - * While extracting a directory, if a file with the same name already - * exists, the extraction is aborted. - * While extracting a file/directory if the destination directory exist - * and is write protected, or does not exist but can not be created, - * the extraction is aborted. - * If after extraction an extracted file does not show the correct - * stored file size, the extraction is aborted. - * When the extraction is aborted, a PEAR error text is set and false - * is returned. However the result can be a partial extraction that may - * need to be manually cleaned. - * - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @return boolean true on success, false on error. - * @access public - * @see extractList() - */ - function extractModify($p_path, $p_remove_path) + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return boolean true on success, false on error. + * @see extractList() + */ + public function extractModify($p_path, $p_remove_path, $p_preserve = false) { $v_result = true; $v_list_detail = array(); if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, - "complete", 0, $p_remove_path); + $v_result = $this->_extractList( + $p_path, + $v_list_detail, + "complete", + 0, + $p_remove_path, + $p_preserve + ); $this->_close(); } return $v_result; } - // }}} - // {{{ extractInString() /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * @param string $p_filename The path of the file to extract in a string. - * @return a string with the file content or NULL. - * @access public - */ - function extractInString($p_filename) + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or NULL. + */ + public function extractInString($p_filename) { if ($this->_openRead()) { $v_result = $this->_extractInString($p_filename); $this->_close(); } else { - $v_result = NULL; + $v_result = null; } return $v_result; } - // }}} - // {{{ extractList() /** - * This method extract from the archive only the files indicated in the - * $p_filelist. These files are extracted in the current directory or - * in the directory indicated by the optional $p_path parameter. - * If indicated the $p_remove_path can be used in the same way as it is - * used in extractModify() method. - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated - * by a single blank space. - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @return true on success, false on error. - * @access public - * @see extractModify() - */ - function extractList($p_filelist, $p_path='', $p_remove_path='') + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return true on success, false on error. + * @see extractModify() + */ + public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false) { $v_result = true; $v_list_detail = array(); - if (is_array($p_filelist)) + if (is_array($p_filelist)) { $v_list = $p_filelist; - elseif (is_string($p_filelist)) + } elseif (is_string($p_filelist)) { $v_list = explode($this->_separator, $p_filelist); - else { + } else { $this->_error('Invalid string list'); return false; } if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, "partial", - $v_list, $p_remove_path); + $v_result = $this->_extractList( + $p_path, + $v_list_detail, + "partial", + $v_list, + $p_remove_path, + $p_preserve + ); $this->_close(); } return $v_result; } - // }}} - // {{{ setAttribute() /** - * This method set specific attributes of the archive. It uses a variable - * list of parameters, in the format attribute code + attribute values : - * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); - * @param mixed $argv variable list of attributes and values - * @return true on success, false on error. - * @access public - */ - function setAttribute() + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * + * @return true on success, false on error. + */ + public function setAttribute() { $v_result = true; @@ -592,30 +714,32 @@ class Archive_Tar // extends PEAR } // ----- Get the arguments - $v_att_list = &func_get_args(); + $v_att_list = & func_get_args(); // ----- Read the attributes - $i=0; - while ($i<$v_size) { + $i = 0; + while ($i < $v_size) { // ----- Look for next option switch ($v_att_list[$i]) { // ----- Look for options that request a string value case ARCHIVE_TAR_ATT_SEPARATOR : // ----- Check the number of parameters - if (($i+1) >= $v_size) { - $this->_error('Invalid number of parameters for ' - .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + if (($i + 1) >= $v_size) { + $this->_error( + 'Invalid number of parameters for ' + . 'attribute ARCHIVE_TAR_ATT_SEPARATOR' + ); return false; } // ----- Get the value - $this->_separator = $v_att_list[$i+1]; + $this->_separator = $v_att_list[$i + 1]; $i++; - break; + break; default : - $this->_error('Unknow attribute code '.$v_att_list[$i].''); + $this->_error('Unknown attribute code ' . $v_att_list[$i] . ''); return false; } @@ -625,151 +749,248 @@ class Archive_Tar // extends PEAR return $v_result; } - // }}} - // {{{ _error() - function _error($p_message) + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * + * @param string $regexp regular expression defining which files or directories to ignore + */ + public function setIgnoreRegexp($regexp) + { + $this->_ignore_regexp = $regexp; + } + + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * + * @param array $list a list of file or directory names to ignore + * + * @access public + */ + public function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/' . join('$|/', $list) . '#'; + $this->setIgnoreRegexp($regexp); + } + + /** + * @param string $p_message + */ + public function _error($p_message) { - // ----- To be completed -// $this->raiseError($p_message); + // Drupal change $this->error_object = $this->raiseError($p_message). throw new Exception($p_message); } - // }}} - // {{{ _warning() - function _warning($p_message) + /** + * @param string $p_message + */ + public function _warning($p_message) { - // ----- To be completed -// $this->raiseError($p_message); + // Drupal change $this->error_object = $this->raiseError($p_message). throw new Exception($p_message); } - // }}} - // {{{ _isArchive() - function _isArchive($p_filename=NULL) + /** + * @param string $p_filename + * @return bool + */ + public function _isArchive($p_filename = null) { - if ($p_filename == NULL) { + if ($p_filename == null) { $p_filename = $this->_tarname; } clearstatcache(); return @is_file($p_filename) && !@is_link($p_filename); } - // }}} - // {{{ _openWrite() - function _openWrite() + /** + * @return bool + */ + public function _openWrite() { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz' && function_exists('gzopen')) { $this->_file = @gzopen($this->_tarname, "wb9"); - else if ($this->_compress_type == 'bz2') - $this->_file = @bzopen($this->_tarname, "w"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "wb"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { + $this->_file = @bzopen($this->_tarname, "w"); + } else { + if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { + $this->_file = @xzopen($this->_tarname, 'w'); + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($this->_tarname, "wb"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in write mode \'' - .$this->_tarname.'\''); + $this->_error( + 'Unable to open in write mode \'' + . $this->_tarname . '\'' + ); return false; } return true; } - // }}} - // {{{ _openRead() - function _openRead() + /** + * @return bool + */ + public function _openRead() { if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { - // ----- Look if a local copy need to be done - if ($this->_temp_tarname == '') { - $this->_temp_tarname = uniqid('tar').'.tmp'; - if (!$v_file_from = @fopen($this->_tarname, 'rb')) { - $this->_error('Unable to open in read mode \'' - .$this->_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { - $this->_error('Unable to open in write mode \'' - .$this->_temp_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - while ($v_data = @fread($v_file_from, 1024)) - @fwrite($v_file_to, $v_data); - @fclose($v_file_from); - @fclose($v_file_to); - } + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar') . '.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error( + 'Unable to open in read mode \'' + . $this->_tarname . '\'' + ); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error( + 'Unable to open in write mode \'' + . $this->_temp_tarname . '\'' + ); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) { + @fwrite($v_file_to, $v_data); + } + @fclose($v_file_from); + @fclose($v_file_to); + } - // ----- File to open if the local copy - $v_filename = $this->_temp_tarname; + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + } else { + // ----- File to open if the normal Tar file - } else - // ----- File to open if the normal Tar file - $v_filename = $this->_tarname; + $v_filename = $this->_tarname; + } - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz' && function_exists('gzopen')) { $this->_file = @gzopen($v_filename, "rb"); - else if ($this->_compress_type == 'bz2') - $this->_file = @bzopen($v_filename, "r"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($v_filename, "rb"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { + $this->_file = @bzopen($v_filename, "r"); + } else { + if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { + $this->_file = @xzopen($v_filename, "r"); + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($v_filename, "rb"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in read mode \''.$v_filename.'\''); + $this->_error('Unable to open in read mode \'' . $v_filename . '\''); return false; } return true; } - // }}} - // {{{ _openReadWrite() - function _openReadWrite() + /** + * @return bool + */ + public function _openReadWrite() { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz') { $this->_file = @gzopen($this->_tarname, "r+b"); - else if ($this->_compress_type == 'bz2') { - $this->_error('Unable to open bz2 in read/write mode \'' - .$this->_tarname.'\' (limitation of bz2 extension)'); - return false; - } else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "r+b"); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2') { + $this->_error( + 'Unable to open bz2 in read/write mode \'' + . $this->_tarname . '\' (limitation of bz2 extension)' + ); + return false; + } else { + if ($this->_compress_type == 'lzma2') { + $this->_error( + 'Unable to open lzma2 in read/write mode \'' + . $this->_tarname . '\' (limitation of lzma2 extension)' + ); + return false; + } else { + if ($this->_compress_type == 'none') { + $this->_file = @fopen($this->_tarname, "r+b"); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + return false; + } + } + } + } if ($this->_file == 0) { - $this->_error('Unable to open in read/write mode \'' - .$this->_tarname.'\''); + $this->_error( + 'Unable to open in read/write mode \'' + . $this->_tarname . '\'' + ); return false; } return true; } - // }}} - // {{{ _close() - function _close() + /** + * @return bool + */ + public function _close() { //if (isset($this->_file)) { if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') + if ($this->_compress_type == 'gz') { @gzclose($this->_file); - else if ($this->_compress_type == 'bz2') - @bzclose($this->_file); - else if ($this->_compress_type == 'none') - @fclose($this->_file); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'bz2') { + @bzclose($this->_file); + } else { + if ($this->_compress_type == 'lzma2') { + @xzclose($this->_file); + } else { + if ($this->_compress_type == 'none') { + @fclose($this->_file); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } $this->_file = 0; } @@ -783,10 +1004,11 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _cleanFile() - function _cleanFile() + /** + * @return bool + */ + public function _cleanFile() { $this->_close(); @@ -803,296 +1025,419 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _writeBlock() - function _writeBlock($p_binary_data, $p_len=null) + /** + * @param mixed $p_binary_data + * @param integer $p_len + * @return bool + */ + public function _writeBlock($p_binary_data, $p_len = null) { - if (is_resource($this->_file)) { - if ($p_len === null) { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } else { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data, $p_len); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - } - return true; + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') { + @gzputs($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'bz2') { + @bzwrite($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'lzma2') { + @xzwrite($this->_file, $p_binary_data); + } else { + if ($this->_compress_type == 'none') { + @fputs($this->_file, $p_binary_data); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } else { + if ($this->_compress_type == 'gz') { + @gzputs($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'bz2') { + @bzwrite($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'lzma2') { + @xzwrite($this->_file, $p_binary_data, $p_len); + } else { + if ($this->_compress_type == 'none') { + @fputs($this->_file, $p_binary_data, $p_len); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + } + return true; } - // }}} - // {{{ _readBlock() - function _readBlock() + /** + * @return null|string + */ + public function _readBlock() { - $v_block = null; - if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') - $v_block = @gzread($this->_file, 512); - else if ($this->_compress_type == 'bz2') - $v_block = @bzread($this->_file, 512); - else if ($this->_compress_type == 'none') - $v_block = @fread($this->_file, 512); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } - return $v_block; + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') { + $v_block = @gzread($this->_file, 512); + } else { + if ($this->_compress_type == 'bz2') { + $v_block = @bzread($this->_file, 512); + } else { + if ($this->_compress_type == 'lzma2') { + $v_block = @xzread($this->_file, 512); + } else { + if ($this->_compress_type == 'none') { + $v_block = @fread($this->_file, 512); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + return $v_block; } - // }}} - // {{{ _jumpBlock() - function _jumpBlock($p_len=null) - { - if (is_resource($this->_file)) { - if ($p_len === null) - $p_len = 1; - - if ($this->_compress_type == 'gz') { - @gzseek($this->_file, gztell($this->_file)+($p_len*512)); - } - else if ($this->_compress_type == 'bz2') { - // ----- Replace missing bztell() and bzseek() - for ($i=0; $i<$p_len; $i++) - $this->_readBlock(); - } else if ($this->_compress_type == 'none') - @fseek($this->_file, ftell($this->_file)+($p_len*512)); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - return true; - } - // }}} + /** + * @param null $p_len + * @return bool + */ + public function _jumpBlock($p_len = null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + $p_len = 1; + } + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file) + ($p_len * 512)); + } else { + if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i = 0; $i < $p_len; $i++) { + $this->_readBlock(); + } + } else { + if ($this->_compress_type == 'lzma2') { + // ----- Replace missing xztell() and xzseek() + for ($i = 0; $i < $p_len; $i++) { + $this->_readBlock(); + } + } else { + if ($this->_compress_type == 'none') { + @fseek($this->_file, $p_len * 512, SEEK_CUR); + } else { + $this->_error( + 'Unknown or missing compression type (' + . $this->_compress_type . ')' + ); + } + } + } + } + } + return true; + } + + /** + * @return bool + */ + public function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + + /** + * @param array $p_list + * @param string $p_add_dir + * @param string $p_remove_dir + * @return bool + */ + public function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result = true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) { + return true; + } + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) { + continue; + } + + if ($v_filename == '') { + continue; + } + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename) && !is_link($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) { + return false; + } + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") { + $p_temp_list[0] = $v_filename . '/' . $p_hitem; + } else { + $p_temp_list[0] = $p_hitem; + } + + $v_result = $this->_addList( + $p_temp_list, + $p_add_dir, + $p_remove_dir + ); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } - // {{{ _writeFooter() - function _writeFooter() - { - if (is_resource($this->_file)) { - // ----- Write the last 0 filled block for end of archive - $v_binary_data = pack('a1024', ''); - $this->_writeBlock($v_binary_data); - } - return true; + return $v_result; } - // }}} - // {{{ _addList() - function _addList($p_list, $p_add_dir, $p_remove_dir) + /** + * @param string $p_filename + * @param mixed $p_header + * @param string $p_add_dir + * @param string $p_remove_dir + * @param null $v_stored_filename + * @return bool + */ + public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null) { - $v_result=true; - $v_header = array(); + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } - // ----- Remove potential windows directory separator - $p_add_dir = $this->_translateWinPath($p_add_dir); - $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } + if (is_null($v_stored_filename)) { + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + $v_stored_filename = $p_filename; - if (sizeof($p_list) == 0) - return true; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } - foreach ($p_list as $v_filename) { - if (!$v_result) { - break; - } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') { + $p_remove_dir .= '/'; + } - // ----- Skip the current tar name - if ($v_filename == $this->_tarname) - continue; + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) { + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + } - if ($v_filename == '') - continue; + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') { + $v_stored_filename = $p_add_dir . $v_stored_filename; + } else { + $v_stored_filename = $p_add_dir . '/' . $v_stored_filename; + } + } - if (!file_exists($v_filename)) { - $this->_warning("File '$v_filename' does not exist"); - continue; + $v_stored_filename = $this->_pathReduction($v_stored_filename); } - // ----- Add the file or directory header - if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) - return false; + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning( + "Unable to open file '" . $p_filename + . "' in binary read mode" + ); + return true; + } - if (@is_dir($v_filename) && !@is_link($v_filename)) { - if (!($p_hdir = opendir($v_filename))) { - $this->_warning("Directory '$v_filename' can not be read"); - continue; + if (!$this->_writeHeader($p_filename, $v_stored_filename)) { + return false; } - while (false !== ($p_hitem = readdir($p_hdir))) { - if (($p_hitem != '.') && ($p_hitem != '..')) { - if ($v_filename != ".") - $p_temp_list[0] = $v_filename.'/'.$p_hitem; - else - $p_temp_list[0] = $p_hitem; - - $v_result = $this->_addList($p_temp_list, - $p_add_dir, - $p_remove_dir); - } + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); } - unset($p_temp_list); - unset($p_hdir); - unset($p_hitem); + fclose($v_file); + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) { + return false; + } } - } - return $v_result; + return true; } - // }}} - // {{{ _addFile() - function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) + /** + * @param string $p_filename + * @param string $p_string + * @param bool $p_datetime + * @param array $p_params + * @return bool + */ + public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - $v_stored_filename = $p_filename; - if (strcmp($p_filename, $p_remove_dir) == 0) { - return true; - } - if ($p_remove_dir != '') { - if (substr($p_remove_dir, -1) != '/') - $p_remove_dir .= '/'; - - if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) - $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); - } - $v_stored_filename = $this->_translateWinPath($v_stored_filename); - if ($p_add_dir != '') { - if (substr($p_add_dir, -1) == '/') - $v_stored_filename = $p_add_dir.$v_stored_filename; - else - $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; - } - - $v_stored_filename = $this->_pathReduction($v_stored_filename); - - if ($this->_isArchive($p_filename)) { - if (($v_file = @fopen($p_filename, "rb")) == 0) { - $this->_warning("Unable to open file '".$p_filename - ."' in binary read mode"); - return true; - } - - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - - while (($v_buffer = fread($v_file, 512)) != '') { - $v_binary_data = pack("a512", "$v_buffer"); - $this->_writeBlock($v_binary_data); - } - - fclose($v_file); - - } else { - // ----- Only header for dir - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - } - - return true; - } - // }}} + $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); + $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; + $p_type = @$p_params["type"] ? $p_params["type"] : ""; + $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0; + $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0; + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } - // {{{ _addString() - function _addString($p_filename, $p_string) - { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - - if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), - time(), 384, "", 0, 0)) - return false; - - $i=0; - while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { - $v_binary_data = pack("a512", $v_buffer); - $this->_writeBlock($v_binary_data); - } - - return true; + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + + // ----- If datetime is not specified, set current time + if ($p_datetime === false) { + $p_datetime = time(); + } + + if (!$this->_writeHeaderBlock( + $p_filename, + strlen($p_string), + $p_stamp, + $p_mode, + $p_type, + $p_uid, + $p_gid + ) + ) { + return false; + } + + $i = 0; + while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; } - // }}} - // {{{ _writeHeader() - function _writeHeader($p_filename, $p_stored_filename) + /** + * @param string $p_filename + * @param string $p_stored_filename + * @return bool + */ + public function _writeHeader($p_filename, $p_stored_filename) { - if ($p_stored_filename == '') + if ($p_stored_filename == '') { $p_stored_filename = $p_filename; + } $v_reduce_filename = $this->_pathReduction($p_stored_filename); if (strlen($v_reduce_filename) > 99) { - if (!$this->_writeLongHeader($v_reduce_filename)) - return false; + if (!$this->_writeLongHeader($v_reduce_filename)) { + return false; + } } $v_info = lstat($p_filename); - $v_uid = sprintf("%6s ", DecOct($v_info[4])); - $v_gid = sprintf("%6s ", DecOct($v_info[5])); - $v_perms = sprintf("%6s ", DecOct($v_info['mode'])); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); - $v_mtime = sprintf("%11s", DecOct($v_info['mode'])); + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); $v_linkname = ''; if (@is_link($p_filename)) { - $v_typeflag = '2'; - $v_linkname = readlink($p_filename); - $v_size = sprintf("%11s ", DecOct(0)); + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); } elseif (@is_dir($p_filename)) { - $v_typeflag = "5"; - $v_size = sprintf("%11s ", DecOct(0)); + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); } else { - $v_typeflag = ''; - clearstatcache(); - $v_size = sprintf("%11s ", DecOct($v_info['size'])); + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); } - $v_magic = ''; + $v_magic = 'ustar '; - $v_version = ''; + $v_version = ' '; - $v_uname = ''; + if (function_exists('posix_getpwuid')) { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); - $v_gname = ''; + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } else { + $v_uname = ''; + $v_gname = ''; + } $v_devmajor = ''; @@ -1100,31 +1445,49 @@ class Archive_Tar // extends PEAR $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - $v_reduce_filename, $v_perms, $v_uid, - $v_gid, $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12a12", + $v_reduce_filename, + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1133,40 +1496,62 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _writeHeaderBlock() - function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, - $p_type='', $p_uid=0, $p_gid=0) - { + /** + * @param string $p_filename + * @param int $p_size + * @param int $p_mtime + * @param int $p_perms + * @param string $p_type + * @param int $p_uid + * @param int $p_gid + * @return bool + */ + public function _writeHeaderBlock( + $p_filename, + $p_size, + $p_mtime = 0, + $p_perms = 0, + $p_type = '', + $p_uid = 0, + $p_gid = 0 + ) { $p_filename = $this->_pathReduction($p_filename); if (strlen($p_filename) > 99) { - if (!$this->_writeLongHeader($p_filename)) - return false; + if (!$this->_writeLongHeader($p_filename)) { + return false; + } } if ($p_type == "5") { - $v_size = sprintf("%11s ", DecOct(0)); + $v_size = sprintf("%011s", DecOct(0)); } else { - $v_size = sprintf("%11s ", DecOct($p_size)); + $v_size = sprintf("%011s", DecOct($p_size)); } - $v_uid = sprintf("%6s ", DecOct($p_uid)); - $v_gid = sprintf("%6s ", DecOct($p_gid)); - $v_perms = sprintf("%6s ", DecOct($p_perms)); + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); $v_mtime = sprintf("%11s", DecOct($p_mtime)); $v_linkname = ''; - $v_magic = ''; + $v_magic = 'ustar '; - $v_version = ''; + $v_version = ' '; - $v_uname = ''; + if (function_exists('posix_getpwuid')) { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); - $v_gname = ''; + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } else { + $v_uname = ''; + $v_gname = ''; + } $v_devmajor = ''; @@ -1174,31 +1559,49 @@ class Archive_Tar // extends PEAR $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - $p_filename, $v_perms, $v_uid, $v_gid, - $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $p_type, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12A12", + $p_filename, + $v_perms, + $v_uid, + $v_gid, + $v_size, + $v_mtime + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $p_type, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1207,10 +1610,12 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _writeLongHeader() - function _writeLongHeader($p_filename) + /** + * @param string $p_filename + * @return bool + */ + public function _writeLongHeader($p_filename) { $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); @@ -1232,30 +1637,49 @@ class Archive_Tar // extends PEAR $v_prefix = ''; - $v_binary_data_first = pack("a100a8a8a8a12A12", - '././@LongLink', 0, 0, 0, $v_size, 0); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); + $v_binary_data_first = pack( + "a100a8a8a8a12a12", + '././@LongLink', + 0, + 0, + 0, + $v_size, + 0 + ); + $v_binary_data_last = pack( + "a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, + $v_linkname, + $v_magic, + $v_version, + $v_uname, + $v_gname, + $v_devmajor, + $v_devminor, + $v_prefix, + '' + ); // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data_first, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $v_checksum += ord(substr($v_binary_data_last, $j, 1)); + } // ----- Write the first 148 bytes of the header in the archive $this->_writeBlock($v_binary_data_first, 148); // ----- Write the calculated checksum - $v_checksum = sprintf("%6s ", DecOct($v_checksum)); + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); $v_binary_data = pack("a8", $v_checksum); $this->_writeBlock($v_binary_data, 8); @@ -1263,27 +1687,30 @@ class Archive_Tar // extends PEAR $this->_writeBlock($v_binary_data_last, 356); // ----- Write the filename as content of the block - $i=0; - while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $i = 0; + while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') { $v_binary_data = pack("a512", "$v_buffer"); $this->_writeBlock($v_binary_data); } return true; } - // }}} - // {{{ _readHeader() - function _readHeader($v_binary_data, &$v_header) + /** + * @param mixed $v_binary_data + * @param mixed $v_header + * @return bool + */ + public function _readHeader($v_binary_data, &$v_header) { - if (strlen($v_binary_data)==0) { + if (strlen($v_binary_data) == 0) { $v_header['filename'] = ''; return true; } if (strlen($v_binary_data) != 512) { $v_header['filename'] = ''; - $this->_error('Invalid block size : '.strlen($v_binary_data)); + $this->_error('Invalid block size : ' . strlen($v_binary_data)); return false; } @@ -1293,19 +1720,32 @@ class Archive_Tar // extends PEAR // ----- Calculate the checksum $v_checksum = 0; // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); + for ($i = 0; $i < 148; $i++) { + $v_checksum += ord(substr($v_binary_data, $i, 1)); + } // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) + for ($i = 148; $i < 156; $i++) { $v_checksum += ord(' '); + } // ..... Last part of the header - for ($i=156; $i<512; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); + for ($i = 156; $i < 512; $i++) { + $v_checksum += ord(substr($v_binary_data, $i, 1)); + } - $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" - ."a8checksum/a1typeflag/a100link/a6magic/a2version/" - ."a32uname/a32gname/a8devmajor/a8devminor", - $v_binary_data); + if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) { + $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . + "a8checksum/a1typeflag/a100link/a6magic/a2version/" . + "a32uname/a32gname/a8devmajor/a8devminor/a131prefix"; + } else { + $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" . + "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" . + "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix"; + } + $v_data = unpack($fmt, $v_binary_data); + + if (strlen($v_data["prefix"]) > 0) { + $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; + } // ----- Extract the checksum $v_header['checksum'] = OctDec(trim($v_data['checksum'])); @@ -1313,20 +1753,25 @@ class Archive_Tar // extends PEAR $v_header['filename'] = ''; // ----- Look for last block (empty block) - if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) { return true; + } - $this->_error('Invalid checksum for file "'.$v_data['filename'] - .'" : '.$v_checksum.' calculated, ' - .$v_header['checksum'].' expected'); + $this->_error( + 'Invalid checksum for file "' . $v_data['filename'] + . '" : ' . $v_checksum . ' calculated, ' + . $v_header['checksum'] . ' expected' + ); return false; } // ----- Extract the properties - $v_header['filename'] = trim($v_data['filename']); + $v_header['filename'] = rtrim($v_data['filename'], "\0"); if ($this->_maliciousFilename($v_header['filename'])) { - $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . - '" will not install in desired directory tree'); + $this->_error( + 'Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree' + ); return false; } $v_header['mode'] = OctDec(trim($v_data['mode'])); @@ -1335,11 +1780,11 @@ class Archive_Tar // extends PEAR $v_header['size'] = OctDec(trim($v_data['size'])); $v_header['mtime'] = OctDec(trim($v_data['mtime'])); if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { - $v_header['size'] = 0; + $v_header['size'] = 0; } $v_header['link'] = trim($v_data['link']); /* ----- All these fields are removed form the header because - they do not carry interesting info + they do not carry interesting info $v_header[magic] = trim($v_data[magic]); $v_header[version] = trim($v_data[version]); $v_header[uname] = trim($v_data[uname]); @@ -1350,17 +1795,15 @@ class Archive_Tar // extends PEAR return true; } - // }}} - // {{{ _maliciousFilename() /** * Detect and report a malicious file name * * @param string $file + * * @return bool - * @access private */ - function _maliciousFilename($file) + private function _maliciousFilename($file) { if (strpos($file, '/../') !== false) { return true; @@ -1370,386 +1813,507 @@ class Archive_Tar // extends PEAR } return false; } - // }}} - // {{{ _readLongHeader() - function _readLongHeader(&$v_header) + /** + * @param $v_header + * @return bool + */ + public function _readLongHeader(&$v_header) { - $v_filename = ''; - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } - - // ----- Read the next header - $v_binary_data = $this->_readBlock(); - - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; + $v_filename = ''; + $v_filesize = $v_header['size']; + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } - $v_filename = trim($v_filename); - $v_header['filename'] = $v_filename; + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) { + return false; + } + + $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0"); + $v_header['filename'] = $v_filename; if ($this->_maliciousFilename($v_filename)) { - $this->_error('Malicious .tar detected, file "' . $v_filename . - '" will not install in desired directory tree'); + $this->_error( + 'Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree' + ); return false; - } + } - return true; + return true; } - // }}} - // {{{ _extractInString() /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * @param string $p_filename The path of the file to extract in a string. - * @return a string with the file content or NULL. - * @access private - */ - function _extractInString($p_filename) + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or null on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or null. + */ + private function _extractInString($p_filename) { $v_result_str = ""; - While (strlen($v_binary_data = $this->_readBlock()) != 0) - { - if (!$this->_readHeader($v_binary_data, $v_header)) - return NULL; - - if ($v_header['filename'] == '') - continue; - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return NULL; - } - - if ($v_header['filename'] == $p_filename) { - if ($v_header['typeflag'] == "5") { - $this->_error('Unable to extract in string a directory ' - .'entry {'.$v_header['filename'].'}'); - return NULL; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_result_str .= $this->_readBlock(); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_result_str .= substr($v_content, 0, - ($v_header['size'] % 512)); - } - return $v_result_str; - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - } - - return NULL; - } - // }}} + while (strlen($v_binary_data = $this->_readBlock()) != 0) { + if (!$this->_readHeader($v_binary_data, $v_header)) { + return null; + } - // {{{ _extractList() - function _extractList($p_path, &$p_list_detail, $p_mode, - $p_file_list, $p_remove_path) - { - $v_result=true; - $v_nb = 0; - $v_extract_all = true; - $v_listing = false; - - $p_path = $this->_translateWinPath($p_path, false); - if ($p_path == '' || (substr($p_path, 0, 1) != '/' - && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { - $p_path = "./".$p_path; - } - $p_remove_path = $this->_translateWinPath($p_remove_path); - - // ----- Look for path to remove format (should end by /) - if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) - $p_remove_path .= '/'; - $p_remove_path_size = strlen($p_remove_path); - - switch ($p_mode) { - case "complete" : - $v_extract_all = TRUE; - $v_listing = FALSE; - break; - case "partial" : - $v_extract_all = FALSE; - $v_listing = FALSE; - break; - case "list" : - $v_extract_all = FALSE; - $v_listing = TRUE; - break; - default : - $this->_error('Invalid extract mode ('.$p_mode.')'); - return false; + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) { + return null; + } + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error( + 'Unable to extract in string a directory ' + . 'entry {' . $v_header['filename'] . '}' + ); + return null; + } else { + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr( + $v_content, + 0, + ($v_header['size'] % 512) + ); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + } + + return null; } - clearstatcache(); + /** + * @param string $p_path + * @param string $p_list_detail + * @param string $p_mode + * @param string $p_file_list + * @param string $p_remove_path + * @param bool $p_preserve + * @return bool + */ + public function _extractList( + $p_path, + &$p_list_detail, + $p_mode, + $p_file_list, + $p_remove_path, + $p_preserve = false + ) { + $v_result = true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':')) + ) { + $p_path = "./" . $p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) { + $p_remove_path .= '/'; + } + $p_remove_path_size = strlen($p_remove_path); - while (strlen($v_binary_data = $this->_readBlock()) != 0) - { - $v_extract_file = FALSE; - $v_extraction_stopped = 0; + switch ($p_mode) { + case "complete" : + $v_extract_all = true; + $v_listing = false; + break; + case "partial" : + $v_extract_all = false; + $v_listing = false; + break; + case "list" : + $v_extract_all = false; + $v_listing = true; + break; + default : + $this->_error('Invalid extract mode (' . $p_mode . ')'); + return false; + } - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; + clearstatcache(); - if ($v_header['filename'] == '') { - continue; - } - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return false; - } - - if ((!$v_extract_all) && (is_array($p_file_list))) { - // ----- By default no unzip if the file is not found - $v_extract_file = false; - - for ($i=0; $i strlen($p_file_list[$i])) - && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) - == $p_file_list[$i])) { - $v_extract_file = TRUE; - break; + while (strlen($v_binary_data = $this->_readBlock()) != 0) { + $v_extract_file = false; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) { + return false; + } + + if ($v_header['filename'] == '') { + continue; } - } - - // ----- It is a file, so compare the file names - elseif ($p_file_list[$i] == $v_header['filename']) { - $v_extract_file = TRUE; - break; - } - } - } else { - $v_extract_file = TRUE; - } - - // ----- Look if this file need to be extracted - if (($v_extract_file) && (!$v_listing)) - { - if (($p_remove_path != '') - && (substr($v_header['filename'], 0, $p_remove_path_size) - == $p_remove_path)) - $v_header['filename'] = substr($v_header['filename'], - $p_remove_path_size); - if (($p_path != './') && ($p_path != '/')) { - while (substr($p_path, -1) == '/') - $p_path = substr($p_path, 0, strlen($p_path)-1); - - if (substr($v_header['filename'], 0, 1) == '/') - $v_header['filename'] = $p_path.$v_header['filename']; - else - $v_header['filename'] = $p_path.'/'.$v_header['filename']; - } - if (file_exists($v_header['filename'])) { - if ( (@is_dir($v_header['filename'])) - && ($v_header['typeflag'] == '')) { - $this->_error('File '.$v_header['filename'] - .' already exists as a directory'); - return false; - } - if ( ($this->_isArchive($v_header['filename'])) - && ($v_header['typeflag'] == "5")) { - $this->_error('Directory '.$v_header['filename'] - .' already exists as a file'); - return false; - } - if (!is_writeable($v_header['filename'])) { - $this->_error('File '.$v_header['filename'] - .' already exists and is write protected'); - return false; - } - if (filemtime($v_header['filename']) > $v_header['mtime']) { - // To be completed : An error or silent no replace ? - } - } - - // ----- Check the directory availability and create it if necessary - elseif (($v_result - = $this->_dirCheck(($v_header['typeflag'] == "5" - ?$v_header['filename'] - :dirname($v_header['filename'])))) != 1) { - $this->_error('Unable to create path for '.$v_header['filename']); - return false; - } - if ($v_extract_file) { - if ($v_header['typeflag'] == "5") { - if (!@file_exists($v_header['filename'])) { - // Drupal integration. - // Changed the code to use drupal_mkdir() instead of mkdir(). - if (!@drupal_mkdir($v_header['filename'], 0777)) { - $this->_error('Unable to create directory {' - .$v_header['filename'].'}'); + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) { return false; } } - } elseif ($v_header['typeflag'] == "2") { - if (@file_exists($v_header['filename'])) { - @drupal_unlink($v_header['filename']); - } - if (!@symlink($v_header['link'], $v_header['filename'])) { - $this->_error('Unable to extract symbolic link {' - .$v_header['filename'].'}'); - return false; - } - } else { - if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { - $this->_error('Error while opening {'.$v_header['filename'] - .'} in write binary mode'); - return false; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, 512); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); - } - @fclose($v_dest_file); + // ignore extended / pax headers + if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + continue; + } - // ----- Change the file mode, mtime - @touch($v_header['filename'], $v_header['mtime']); - if ($v_header['mode'] & 0111) { - // make file executable, obey umask - $mode = fileperms($v_header['filename']) | (~umask() & 0111); - @chmod($v_header['filename'], $mode); + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i = 0; $i < sizeof($p_file_list); $i++) { + // ----- Look if it is a directory + if (substr($p_file_list[$i], -1) == '/') { + // ----- Look if the directory is in the filename path + if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i]) + ) { + $v_extract_file = true; + break; + } + } // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = true; + break; + } + } + } else { + $v_extract_file = true; } - } - - // ----- Check the file size - clearstatcache(); - if (filesize($v_header['filename']) != $v_header['size']) { - $this->_error('Extracted file '.$v_header['filename'] - .' does not have the correct file size \'' - .filesize($v_header['filename']) - .'\' ('.$v_header['size'] - .' expected). Archive may be corrupted.'); - return false; - } - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - /* TBC : Seems to be unused ... - if ($this->_compress) - $v_end_of_file = @gzeof($this->_file); - else - $v_end_of_file = @feof($this->_file); - */ + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) { + if (($p_remove_path != '') + && (substr($v_header['filename'] . '/', 0, $p_remove_path_size) + == $p_remove_path) + ) { + $v_header['filename'] = substr( + $v_header['filename'], + $p_remove_path_size + ); + if ($v_header['filename'] == '') { + continue; + } + } + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') { + $p_path = substr($p_path, 0, strlen($p_path) - 1); + } - if ($v_listing || $v_extract_file || $v_extraction_stopped) { - // ----- Log extracted files - if (($v_file_dir = dirname($v_header['filename'])) - == $v_header['filename']) - $v_file_dir = ''; - if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) - $v_file_dir = '/'; + if (substr($v_header['filename'], 0, 1) == '/') { + $v_header['filename'] = $p_path . $v_header['filename']; + } else { + $v_header['filename'] = $p_path . '/' . $v_header['filename']; + } + } + if (file_exists($v_header['filename'])) { + if ((@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '') + ) { + $this->_error( + 'File ' . $v_header['filename'] + . ' already exists as a directory' + ); + return false; + } + if (($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5") + ) { + $this->_error( + 'Directory ' . $v_header['filename'] + . ' already exists as a file' + ); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error( + 'File ' . $v_header['filename'] + . ' already exists and is write protected' + ); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck( + ($v_header['typeflag'] == "5" + ? $v_header['filename'] + : dirname($v_header['filename'])) + )) != 1 + ) { + $this->_error('Unable to create path for ' . $v_header['filename']); + return false; + } - $p_list_detail[$v_nb++] = $v_header; - if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { - return true; + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error( + 'Unable to create directory {' + . $v_header['filename'] . '}' + ); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @drupal_unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error( + 'Unable to extract symbolic link {' + . $v_header['filename'] . '}' + ); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error( + 'Error while opening {' . $v_header['filename'] + . '} in write binary mode' + ); + return false; + } else { + $n = floor($v_header['size'] / 512); + for ($i = 0; $i < $n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + if ($p_preserve) { + @chown($v_header['filename'], $v_header['uid']); + @chgrp($v_header['filename'], $v_header['gid']); + } + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (!is_file($v_header['filename'])) { + $this->_error( + 'Extracted file ' . $v_header['filename'] + . 'does not exist. Archive may be corrupted.' + ); + return false; + } + + $filesize = filesize($v_header['filename']); + if ($filesize != $v_header['size']) { + $this->_error( + 'Extracted file ' . $v_header['filename'] + . ' does not have the correct file size \'' + . $filesize + . '\' (' . $v_header['size'] + . ' expected). Archive may be corrupted.' + ); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size'] / 512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename'] + ) { + $v_file_dir = ''; + } + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) { + $v_file_dir = '/'; + } + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } } - } - } return true; } - // }}} - // {{{ _openAppend() - function _openAppend() + /** + * @return bool + */ + public function _openAppend() { - if (filesize($this->_tarname) == 0) - return $this->_openWrite(); + if (filesize($this->_tarname) == 0) { + return $this->_openWrite(); + } if ($this->_compress) { $this->_close(); - if (!@rename($this->_tarname, $this->_tarname.".tmp")) { - $this->_error('Error while renaming \''.$this->_tarname - .'\' to temporary file \''.$this->_tarname - .'.tmp\''); + if (!@rename($this->_tarname, $this->_tarname . ".tmp")) { + $this->_error( + 'Error while renaming \'' . $this->_tarname + . '\' to temporary file \'' . $this->_tarname + . '.tmp\'' + ); return false; } - if ($this->_compress_type == 'gz') - $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); - elseif ($this->_compress_type == 'bz2') - $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + if ($this->_compress_type == 'gz') { + $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb"); + } elseif ($this->_compress_type == 'bz2') { + $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r"); + } elseif ($this->_compress_type == 'lzma2') { + $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r"); + } + if ($v_temp_tar == 0) { - $this->_error('Unable to open file \''.$this->_tarname - .'.tmp\' in binary read mode'); - @rename($this->_tarname.".tmp", $this->_tarname); + $this->_error( + 'Unable to open file \'' . $this->_tarname + . '.tmp\' in binary read mode' + ); + @rename($this->_tarname . ".tmp", $this->_tarname); return false; } if (!$this->_openWrite()) { - @rename($this->_tarname.".tmp", $this->_tarname); + @rename($this->_tarname . ".tmp", $this->_tarname); return false; } if ($this->_compress_type == 'gz') { + $end_blocks = 0; + while (!@gzeof($v_temp_tar)) { $v_buffer = @gzread($v_temp_tar, 512); - if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; // do not copy end blocks, we will re-make them // after appending continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; } $v_binary_data = pack("a512", $v_buffer); $this->_writeBlock($v_binary_data); } @gzclose($v_temp_tar); - } - elseif ($this->_compress_type == 'bz2') { + } elseif ($this->_compress_type == 'bz2') { + $end_blocks = 0; + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { - if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; + // do not copy end blocks, we will re-make them + // after appending continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; } $v_binary_data = pack("a512", $v_buffer); $this->_writeBlock($v_binary_data); } @bzclose($v_temp_tar); - } + } elseif ($this->_compress_type == 'lzma2') { + $end_blocks = 0; + + while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + $end_blocks++; + // do not copy end blocks, we will re-make them + // after appending + continue; + } elseif ($end_blocks > 0) { + for ($i = 0; $i < $end_blocks; $i++) { + $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); + } + $end_blocks = 0; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } - if (!@drupal_unlink($this->_tarname.".tmp")) { - $this->_error('Error while deleting temporary file \'' - .$this->_tarname.'.tmp\''); + @xzclose($v_temp_tar); } + if (!@drupal_unlink($this->_tarname . ".tmp")) { + $this->_error( + 'Error while deleting temporary file \'' + . $this->_tarname . '.tmp\'' + ); + } } else { // ----- For not compressed tar, just add files before the last - // one or two 512 bytes block - if (!$this->_openReadWrite()) - return false; + // one or two 512 bytes block + if (!$this->_openReadWrite()) { + return false; + } clearstatcache(); $v_size = filesize($this->_tarname); @@ -1760,32 +2324,34 @@ class Archive_Tar // extends PEAR fseek($this->_file, $v_size - 1024); if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { fseek($this->_file, $v_size - 1024); - } - elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { fseek($this->_file, $v_size - 512); } } return true; } - // }}} - // {{{ _append() - function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + /** + * @param $p_filelist + * @param string $p_add_dir + * @param string $p_remove_dir + * @return bool + */ + public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '') { - if (!$this->_openAppend()) + if (!$this->_openAppend()) { return false; + } - if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) - $this->_writeFooter(); + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) { + $this->_writeFooter(); + } $this->_close(); return true; } - // }}} - - // {{{ _dirCheck() /** * Check if a directory exists and create it (including parent @@ -1793,24 +2359,25 @@ class Archive_Tar // extends PEAR * * @param string $p_dir directory to check * - * @return bool TRUE if the directory exists or was created + * @return bool true if the directory exists or was created */ - function _dirCheck($p_dir) + public function _dirCheck($p_dir) { clearstatcache(); - if ((@is_dir($p_dir)) || ($p_dir == '')) + if ((@is_dir($p_dir)) || ($p_dir == '')) { return true; + } $p_parent_dir = dirname($p_dir); if (($p_parent_dir != $p_dir) && ($p_parent_dir != '') && - (!$this->_dirCheck($p_parent_dir))) - return false; + (!$this->_dirCheck($p_parent_dir)) + ) { + return false; + } - // Drupal integration. - // Changed the code to use drupal_mkdir() instead of mkdir(). - if (!@drupal_mkdir($p_dir, 0777)) { + if (!@mkdir($p_dir, 0777)) { $this->_error("Unable to create directory '$p_dir'"); return false; } @@ -1818,10 +2385,6 @@ class Archive_Tar // extends PEAR return true; } - // }}} - - // {{{ _pathReduction() - /** * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", * rand emove double slashes. @@ -1829,11 +2392,8 @@ class Archive_Tar // extends PEAR * @param string $p_dir path to reduce * * @return string reduced path - * - * @access private - * */ - function _pathReduction($p_dir) + private function _pathReduction($p_dir) { $v_result = ''; @@ -1843,50 +2403,57 @@ class Archive_Tar // extends PEAR $v_list = explode('/', $p_dir); // ----- Study directories from last to first - for ($i=sizeof($v_list)-1; $i>=0; $i--) { + for ($i = sizeof($v_list) - 1; $i >= 0; $i--) { // ----- Look for current path if ($v_list[$i] == ".") { // ----- Ignore this directory // Should be the first $i=0, but no check is done - } - else if ($v_list[$i] == "..") { - // ----- Ignore it and ignore the $i-1 - $i--; - } - else if ( ($v_list[$i] == '') - && ($i!=(sizeof($v_list)-1)) - && ($i!=0)) { - // ----- Ignore only the double '//' in path, - // but not the first and last / } else { - $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' - .$v_result:''); + if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } else { + if (($v_list[$i] == '') + && ($i != (sizeof($v_list) - 1)) + && ($i != 0) + ) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/' + . $v_result : ''); + } + } } } } - $v_result = strtr($v_result, '\\', '/'); + + if (defined('OS_WINDOWS') && OS_WINDOWS) { + $v_result = strtr($v_result, '\\', '/'); + } + return $v_result; } - // }}} - - // {{{ _translateWinPath() - function _translateWinPath($p_path, $p_remove_disk_letter=true) + /** + * @param $p_path + * @param bool $p_remove_disk_letter + * @return string + */ + public function _translateWinPath($p_path, $p_remove_disk_letter = true) { - if (defined('OS_WINDOWS') && OS_WINDOWS) { - // ----- Look for potential disk letter - if ( ($p_remove_disk_letter) - && (($v_position = strpos($p_path, ':')) != false)) { - $p_path = substr($p_path, $v_position+1); - } - // ----- Change potential windows directory separator - if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { - $p_path = strtr($p_path, '\\', '/'); - } - } - return $p_path; + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if (($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false) + ) { + $p_path = substr($p_path, $v_position + 1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; } - // }}} - } -?> diff --git a/modules/system/system.test b/modules/system/system.test index d4c98f0a..9eaf562b 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -389,6 +389,18 @@ class ModuleDependencyTestCase extends ModuleTestCase { ); } + /** + * Checks functionality of project namespaces for dependencies. + */ + function testProjectNamespaceForDependencies() { + // Enable module with project namespace to ensure nothing breaks. + $edit = array( + 'modules[Testing][system_project_namespace_test][enable]' => TRUE, + ); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('system_project_namespace_test'), TRUE); + } + /** * Attempt to enable translation module without locale enabled. */ @@ -714,7 +726,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // Block a valid IP address. $edit = array(); - $edit['ip'] = '192.168.1.1'; + $edit['ip'] = '1.2.3.3'; $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); $this->assertTrue($ip, t('IP address found in database.')); @@ -722,7 +734,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // Try to block an IP address that's already blocked. $edit = array(); - $edit['ip'] = '192.168.1.1'; + $edit['ip'] = '1.2.3.3'; $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('This IP address is already blocked.')); @@ -758,6 +770,25 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); // $this->assertText(t('You may not block your own IP address.')); } + + /** + * Test duplicate IP addresses are not present in the 'blocked_ips' table. + */ + function testDuplicateIpAddress() { + drupal_static_reset('ip_address'); + $submit_ip = $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; + system_block_ip_action(); + system_block_ip_action(); + $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $this->assertEqual('1', $ip_count); + drupal_static_reset('ip_address'); + $submit_ip = $_SERVER['REMOTE_ADDR'] = ' '; + system_block_ip_action(); + system_block_ip_action(); + system_block_ip_action(); + $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $this->assertEqual('1', $ip_count); + } } class CronRunTestCase extends DrupalWebTestCase { @@ -893,6 +924,29 @@ class CronRunTestCase extends DrupalWebTestCase { $result = variable_get('common_test_cron'); $this->assertEqual($result, 'success', 'Cron correctly handles exceptions thrown during hook_cron() invocations.'); } + + /** + * Tests that hook_flush_caches() is not invoked on every single cron run. + * + * @see system_cron() + */ + public function testCronCacheExpiration() { + module_enable(array('system_cron_test')); + variable_del('system_cron_test_flush_caches'); + + // Invoke cron the first time: hook_flush_caches() should be called and then + // get cached. + drupal_cron_run(); + $this->assertEqual(variable_get('system_cron_test_flush_caches'), 1, 'hook_flush_caches() was invoked the first time.'); + $cache = cache_get('system_cache_tables'); + $this->assertEqual(empty($cache), FALSE, 'Cache is filled with cache table data.'); + + // Run cron again and ensure that hook_flush_caches() is not called. + variable_del('system_cron_test_flush_caches'); + drupal_cron_run(); + $this->assertNull(variable_get('system_cron_test_flush_caches'), 'hook_flush_caches() was not invoked the second time.'); + } + } /** @@ -911,7 +965,7 @@ class CronQueueTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp(array('common_test', 'common_test_cron_helper')); + parent::setUp(array('common_test', 'common_test_cron_helper', 'cron_queue_test')); } /** @@ -931,6 +985,23 @@ class CronQueueTestCase extends DrupalWebTestCase { $this->assertEqual($queue->numberOfItems(), 1, 'Failing item still in the queue after throwing an exception.'); } + /** + * Tests worker defined as a class method callable. + */ + function testCallable() { + $queue = DrupalQueue::get('cron_queue_test_callback'); + + // Enqueue an item for processing. + $queue->createItem(array($this->randomName() => $this->randomName())); + + // Run cron; the worker should perform the task and delete the item from the + // queue. + $this->cronRun(); + + // The queue should be empty. + $this->assertEqual($queue->numberOfItems(), 0); + } + } class AdminMetaTagTestCase extends DrupalWebTestCase { @@ -1298,7 +1369,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.'); @@ -1374,6 +1461,60 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { } } +/** + * Tests date format configuration. + */ +class DateFormatTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Date format', + 'description' => 'Test date format configuration and defaults.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(); + + // Create admin user and log in admin user. + $this->admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test the default date type formats are consistent. + */ + function testDefaultDateFormats() { + // These are the default format values from format_date(). + $default_formats = array( + 'short' => 'm/d/Y - H:i', + 'medium' => 'D, m/d/Y - H:i', + 'long' => 'l, F j, Y - H:i', + ); + + // Clear the date format variables. + variable_del('date_format_short'); + variable_del('date_format_medium'); + variable_del('date_format_long'); + + $this->drupalGet('admin/config/regional/date-time'); + + foreach ($default_formats as $format_name => $format_value) { + $id = 'edit-date-format-' . $format_name; + // Check that the configuration fields match the default format. + $this->assertOptionSelected( + $id, + $format_value, + format_string('The @type format type matches the expected format @format.', + array( + '@type' => $format_name, + '@format' => $format_value, + ) + )); + } + } +} + class PageTitleFiltering extends DrupalWebTestCase { protected $content_user; protected $saved_title; @@ -2286,6 +2427,20 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase { $this->update_user = $this->drupalCreateUser(array('administer software updates')); } + /** + * Tests that there are no pending updates for the first test method. + */ + function testNoPendingUpdates() { + // Ensure that for the first test method in a class, there are no pending + // updates. This tests a drupal_get_schema_versions() bug that previously + // led to the wrong schema version being recorded for the initial install + // of a child site during automated testing. + $this->drupalLogin($this->update_user); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->drupalPost(NULL, array(), t('Continue')); + $this->assertText(t('No pending updates.'), 'End of update process was reached.'); + } + /** * Tests access to the update script. */ @@ -2367,6 +2522,12 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase { $this->assertText('This is a requirements error provided by the update_script_test module.'); $this->clickLink('try again'); $this->assertText('This is a requirements error provided by the update_script_test module.'); + + // Check if the optional 'value' key displays without a notice. + variable_set('update_script_test_requirement_type', REQUIREMENT_INFO); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->assertText('This is a requirements info provided by the update_script_test module.'); + $this->assertNoText('Notice: Undefined index: value in theme_status_report()'); } /** diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc index a14d788b..2a32c4b5 100644 --- a/modules/system/system.updater.inc +++ b/modules/system/system.updater.inc @@ -24,7 +24,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { * found on your system, and if there was a copy in sites/all, we'd see it. */ public function getInstallDirectory() { - if ($relative_path = drupal_get_path('module', $this->name)) { + if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) { $relative_path = dirname($relative_path); } else { @@ -34,7 +34,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { } public function isInstalled() { - return (bool) drupal_get_path('module', $this->name); + return (bool) drupal_get_filename('module', $this->name, NULL, FALSE); } public static function canUpdateDirectory($directory) { @@ -109,7 +109,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface { * found on your system, and if there was a copy in sites/all, we'd see it. */ public function getInstallDirectory() { - if ($relative_path = drupal_get_path('theme', $this->name)) { + if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) { $relative_path = dirname($relative_path); } else { @@ -119,7 +119,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface { } public function isInstalled() { - return (bool) drupal_get_path('theme', $this->name); + return (bool) drupal_get_filename('theme', $this->name, NULL, FALSE); } static function canUpdateDirectory($directory) { diff --git a/modules/system/tests/cron_queue_test.info b/modules/system/tests/cron_queue_test.info index 09f8a323..ef94eeda 100644 --- a/modules/system/tests/cron_queue_test.info +++ b/modules/system/tests/cron_queue_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/system/tests/cron_queue_test.module b/modules/system/tests/cron_queue_test.module index e95c6b6a..0df6396a 100644 --- a/modules/system/tests/cron_queue_test.module +++ b/modules/system/tests/cron_queue_test.module @@ -7,9 +7,21 @@ function cron_queue_test_cron_queue_info() { $queues['cron_queue_test_exception'] = array( 'worker callback' => 'cron_queue_test_exception', ); + $queues['cron_queue_test_callback'] = array( + 'worker callback' => array('CronQueueTestCallbackClass', 'foo'), + ); + return $queues; } function cron_queue_test_exception($item) { throw new Exception('That is not supposed to happen.'); } + +class CronQueueTestCallbackClass { + + static public function foo() { + // Do nothing. + } + +} diff --git a/modules/system/tests/system_cron_test.info b/modules/system/tests/system_cron_test.info new file mode 100644 index 00000000..d9d4edd3 --- /dev/null +++ b/modules/system/tests/system_cron_test.info @@ -0,0 +1,11 @@ +name = System Cron Test +description = 'Support module for testing the system_cron().' +package = Testing +version = VERSION +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" +project = "drupal" +datestamp = "1539816636" diff --git a/modules/system/tests/system_cron_test.module b/modules/system/tests/system_cron_test.module new file mode 100644 index 00000000..9ef80e23 --- /dev/null +++ b/modules/system/tests/system_cron_test.module @@ -0,0 +1,15 @@ + $bundle->type, 'settings' => array(), 'description' => 'Debris left over after upgrade from Drupal 6', + 'required' => FALSE, 'widget' => array( 'type' => 'taxonomy_autocomplete', 'module' => 'taxonomy', @@ -557,7 +558,7 @@ function taxonomy_update_7005(&$sandbox) { // of term references stored so far for the current revision, which // provides the delta value for each term reference data insert. The // deltas are reset for each new revision. - + $conditions = array( 'type' => 'taxonomy_term_reference', 'deleted' => 0, diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module index e147c1ca..981649d2 100644 --- a/modules/taxonomy/taxonomy.module +++ b/modules/taxonomy/taxonomy.module @@ -25,7 +25,7 @@ function taxonomy_help($path, $arg) { $output .= '

' . t('Uses') . '

'; $output .= '
'; $output .= '
' . t('Creating vocabularies') . '
'; - $output .= '
' . t('Users with sufficient permissions can create vocabularies and terms through the Taxonomy page. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A controlled vocabulary classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment'=>'module-taxonomy')))); + $output .= '
' . t('Users with sufficient permissions can create vocabularies and terms through the Taxonomy page. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A controlled vocabulary classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment' => 'module-taxonomy')))); $output .= '
  • ' . t('vocabulary: Music') . '
  • '; $output .= '
    • ' . t('term: Jazz') . '
    • '; $output .= '
      • ' . t('sub-term: Swing') . '
      • '; @@ -1023,7 +1023,7 @@ function taxonomy_get_parents($tid) { $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid'); $query->addField('t', 'tid'); $query->condition('h.tid', $tid); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); @@ -1081,7 +1081,7 @@ function taxonomy_get_children($tid, $vid = 0) { if ($vid) { $query->condition('t.vid', $vid); } - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); @@ -1129,7 +1129,7 @@ function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); $result = $query ->addTag('translatable') - ->addTag('term_access') + ->addTag('taxonomy_term_access') ->fields('t') ->fields('h', array('parent')) ->condition('t.vid', $vid) @@ -1249,7 +1249,7 @@ class TaxonomyTermController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // When name is passed as a condition use LIKE. if (isset($conditions['name'])) { $query_conditions = &$query->conditions(); diff --git a/modules/taxonomy/taxonomy.pages.inc b/modules/taxonomy/taxonomy.pages.inc index 975ff120..38b24b3b 100644 --- a/modules/taxonomy/taxonomy.pages.inc +++ b/modules/taxonomy/taxonomy.pages.inc @@ -150,7 +150,7 @@ function taxonomy_autocomplete($field_name = '', $tags_typed = '') { $query = db_select('taxonomy_term_data', 't'); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // Do not select already entered terms. if (!empty($tags_typed)) { diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index fdf354b7..a4b7ee83 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -1025,7 +1025,7 @@ class TaxonomyRSSTestCase extends TaxonomyWebTestCase { function setUp() { parent::setUp('taxonomy'); - $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types', 'administer fields')); $this->drupalLogin($this->admin_user); $this->vocabulary = $this->createVocabulary(); @@ -1983,3 +1983,113 @@ class TaxonomyEFQTestCase extends TaxonomyWebTestCase { } } + +/** + * Tests that appropriate query tags are added. + */ +class TaxonomyQueryAlterTestCase extends TaxonomyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Taxonomy query tags', + 'description' => 'Verifies that taxonomy_term_access tags are added to queries.', + 'group' => 'Taxonomy', + ); + } + + public function setUp() { + parent::setUp('taxonomy_test'); + } + + /** + * Tests that appropriate tags are added when querying the database. + */ + public function testTaxonomyQueryAlter() { + // Create a new vocabulary and add a few terms to it. + $vocabulary = $this->createVocabulary(); + $terms = array(); + for ($i = 0; $i < 5; $i++) { + $terms[$i] = $this->createTerm($vocabulary); + } + + // Set up hierarchy. Term 2 is a child of 1. + $terms[2]->parent = array($terms[1]->tid); + taxonomy_term_save($terms[2]); + + $this->setupQueryTagTestHooks(); + $loaded_term = taxonomy_term_load($terms[0]->tid); + $this->assertEqual($loaded_term->tid, $terms[0]->tid, 'First term was loaded'); + $this->assertQueryTagTestResult(1, 'taxonomy_term_load()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_tree($vocabulary->vid); + $this->assertEqual(count($loaded_terms), count($terms), 'All terms were loaded'); + $this->assertQueryTagTestResult(1, 'taxonomy_get_tree()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_parents($terms[2]->tid); + $this->assertEqual(count($loaded_terms), 1, 'All parent terms were loaded'); + $this->assertQueryTagTestResult(2, 'taxonomy_get_parents()'); + + $this->setupQueryTagTestHooks(); + $loaded_terms = taxonomy_get_children($terms[1]->tid); + $this->assertEqual(count($loaded_terms), 1, 'All child terms were loaded'); + $this->assertQueryTagTestResult(2, 'taxonomy_get_children()'); + + $this->setupQueryTagTestHooks(); + $query = db_select('taxonomy_term_data', 't'); + $query->addField('t', 'tid'); + $query->addTag('taxonomy_term_access'); + $tids = $query->execute()->fetchCol(); + $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom db_select() with taxonomy_term_access tag (preferred)'); + + $this->setupQueryTagTestHooks(); + $query = db_select('taxonomy_term_data', 't'); + $query->addField('t', 'tid'); + $query->addTag('term_access'); + $tids = $query->execute()->fetchCol(); + $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom db_select() with term_access tag (deprecated)'); + + $this->setupQueryTagTestHooks(); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'taxonomy_term'); + $query->addTag('taxonomy_term_access'); + $result = $query->execute(); + $this->assertEqual(count($result['taxonomy_term']), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom EntityFieldQuery with taxonomy_term_access tag (preferred)'); + + $this->setupQueryTagTestHooks(); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'taxonomy_term'); + $query->addTag('term_access'); + $result = $query->execute(); + $this->assertEqual(count($result['taxonomy_term']), count($terms), 'All term IDs were retrieved'); + $this->assertQueryTagTestResult(1, 'custom EntityFieldQuery with term_access tag (deprecated)'); + } + + /** + * Sets up the hooks in the test module. + */ + protected function setupQueryTagTestHooks() { + taxonomy_terms_static_reset(); + variable_set('taxonomy_test_query_alter', 0); + variable_set('taxonomy_test_query_term_access_alter', 0); + variable_set('taxonomy_test_query_taxonomy_term_access_alter', 0); + } + + /** + * Verifies invocation of the hooks in the test module. + * + * @param int $expected_invocations + * The number of times the hooks are expected to have been invoked. + * @param string $method + * A string describing the invoked function which generated the query. + */ + protected function assertQueryTagTestResult($expected_invocations, $method) { + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_alter'), 'hook_query_alter() invoked when executing ' . $method); + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_term_access_alter'), 'Deprecated hook_query_term_access_alter() invoked when executing ' . $method); + $this->assertIdentical($expected_invocations, variable_get('taxonomy_test_query_taxonomy_term_access_alter'), 'Preferred hook_query_taxonomy_term_access_alter() invoked when executing ' . $method); + } + +} diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info index 75a670ea..ffa40ee7 100644 --- a/modules/toolbar/toolbar.info +++ b/modules/toolbar/toolbar.info @@ -4,8 +4,7 @@ core = 7.x package = Core version = VERSION -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info index 43ddc511..dbd7384c 100644 --- a/modules/tracker/tracker.info +++ b/modules/tracker/tracker.info @@ -6,8 +6,7 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/tracker/tracker.test b/modules/tracker/tracker.test index 8a48ea81..e4729788 100644 --- a/modules/tracker/tracker.test +++ b/modules/tracker/tracker.test @@ -151,7 +151,6 @@ class TrackerTest extends DrupalWebTestCase { $node = $this->drupalCreateNode(array( 'comment' => 2, - 'title' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(8)))), )); // Add a comment to the page. diff --git a/modules/translation/tests/translation_test.info b/modules/translation/tests/translation_test.info index 14da1a48..9d5b4a5b 100644 --- a/modules/translation/tests/translation_test.info +++ b/modules/translation/tests/translation_test.info @@ -5,8 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/translation/translation.info b/modules/translation/translation.info index 533412ce..6ed39ca6 100644 --- a/modules/translation/translation.info +++ b/modules/translation/translation.info @@ -6,8 +6,7 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info index b4f052fc..196cfcf1 100644 --- a/modules/trigger/tests/trigger_test.info +++ b/modules/trigger/tests/trigger_test.info @@ -4,8 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info index 2b088436..b78c34d7 100644 --- a/modules/trigger/trigger.info +++ b/modules/trigger/trigger.info @@ -6,8 +6,7 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test index 9e5f1142..09169b72 100644 --- a/modules/trigger/trigger.test +++ b/modules/trigger/trigger.test @@ -85,7 +85,7 @@ class TriggerContentTestCase extends TriggerWebTestCase { $this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), 'Make sure the Basic page has actually been created'); // Action should have been fired. $loaded_node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertTrue($loaded_node->$info['property'] == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name']))); + $this->assertTrue($loaded_node->{$info['property']} == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name']))); // Leave action assigned for next test // There should be an error when the action is assigned to the trigger diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info index e0bbebd5..620e82ee 100644 --- a/modules/update/tests/aaa_update_test.info +++ b/modules/update/tests/aaa_update_test.info @@ -4,8 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info index 94c8adea..b9953c16 100644 --- a/modules/update/tests/bbb_update_test.info +++ b/modules/update/tests/bbb_update_test.info @@ -4,8 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info index 09598697..e855562a 100644 --- a/modules/update/tests/ccc_update_test.info +++ b/modules/update/tests/ccc_update_test.info @@ -4,8 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info new file mode 100644 index 00000000..cf1d3c36 --- /dev/null +++ b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info @@ -0,0 +1,9 @@ +name = Update test admin theme +description = Test theme which is used as admin theme. +core = 7.x +hidden = TRUE + +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" +project = "drupal" +datestamp = "1539816636" diff --git a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 6954b3d4..11c81321 100644 --- a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,8 +3,7 @@ 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 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index 81c222ef..aa5bc12c 100644 --- a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,8 +4,7 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info index f899512e..6b4802f6 100644 --- a/modules/update/tests/update_test.info +++ b/modules/update/tests/update_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/tests/update_test.module b/modules/update/tests/update_test.module index 6fe4bdde..594f80f0 100644 --- a/modules/update/tests/update_test.module +++ b/modules/update/tests/update_test.module @@ -11,6 +11,7 @@ function update_test_system_theme_info() { $themes['update_test_basetheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_basetheme/update_test_basetheme.info'; $themes['update_test_subtheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_subtheme/update_test_subtheme.info'; + $themes['update_test_admintheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_admintheme/update_test_admintheme.info'; return $themes; } diff --git a/modules/update/update.authorize.inc b/modules/update/update.authorize.inc index 6ddd2c53..03d37049 100644 --- a/modules/update/update.authorize.inc +++ b/modules/update/update.authorize.inc @@ -97,7 +97,9 @@ function update_authorize_run_install($filetransfer, $project, $updater_name, $l } /** - * Batch callback: Copies project to its proper place when authorized to do so. + * Implements callback_batch_operation(). + * + * Copies project to its proper place when authorized to do so. * * @param string $project * The canonical short name of the project being installed. @@ -168,7 +170,9 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url } /** - * Batch callback: Performs actions when the authorized update batch is done. + * Implements callback_batch_finished(). + * + * Performs actions when the authorized update batch is done. * * This processes the results and stashes them into SESSION such that * authorize.php will render a report. Also responsible for putting the site @@ -235,7 +239,9 @@ function update_authorize_update_batch_finished($success, $results) { } /** - * Batch callback: Performs actions when the authorized install batch is done. + * Implements callback_batch_finished(). + * + * Performs actions when the authorized install batch is done. * * This processes the results and stashes them into SESSION such that * authorize.php will render a report. Also responsible for putting the site diff --git a/modules/update/update.compare.inc b/modules/update/update.compare.inc index 072a0daa..e3e0de3b 100644 --- a/modules/update/update.compare.inc +++ b/modules/update/update.compare.inc @@ -104,7 +104,13 @@ function update_get_projects() { * @see update_get_projects() */ function _update_process_info_list(&$projects, $list, $project_type, $status) { + $admin_theme = variable_get('admin_theme', 'seven'); foreach ($list as $file) { + // The admin theme is a special case. It should always be considered enabled + // for the purposes of update checking. + if ($file->name === $admin_theme) { + $file->status = TRUE; + } // A disabled base theme of an enabled sub-theme still has all of its code // run by the sub-theme, so we include it in our "enabled" projects list. if ($status && !$file->status && !empty($file->sub_themes)) { diff --git a/modules/update/update.fetch.inc b/modules/update/update.fetch.inc index 9dd2f0ba..428cace6 100644 --- a/modules/update/update.fetch.inc +++ b/modules/update/update.fetch.inc @@ -29,7 +29,9 @@ function update_manual_status() { } /** - * Batch callback: Processes a step in batch for fetching available update data. + * Implements callback_batch_operation(). + * + * Processes a step in batch for fetching available update data. * * @param $context * Reference to an array used for Batch API storage. @@ -77,7 +79,9 @@ function update_fetch_data_batch(&$context) { } /** - * Batch callback: Performs actions when all fetch tasks have been completed. + * Implements callback_batch_finished(). + * + * Performs actions when all fetch tasks have been completed. * * @param $success * TRUE if the batch operation was successful; FALSE if there were errors. diff --git a/modules/update/update.info b/modules/update/update.info index ddd1bde6..4b4dde91 100644 --- a/modules/update/update.info +++ b/modules/update/update.info @@ -6,8 +6,7 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc index 85b587de..c7c4e4a6 100644 --- a/modules/update/update.manager.inc +++ b/modules/update/update.manager.inc @@ -59,7 +59,7 @@ * @see update_menu() * @ingroup forms */ -function update_manager_update_form($form, $form_state = array(), $context) { +function update_manager_update_form($form, $form_state, $context) { if (!_update_manager_check_backends($form, 'update')) { return $form; } @@ -335,6 +335,8 @@ function update_manager_update_form_submit($form, &$form_state) { } /** + * Implements callback_batch_finished(). + * * Batch callback: Performs actions when the download batch is completed. * * @param $success @@ -847,7 +849,9 @@ function update_manager_file_get($url) { } /** - * Batch callback: Downloads, unpacks, and verifies a project. + * Implements callback_batch_operation(). + * + * Downloads, unpacks, and verifies a project. * * This function assumes that the provided URL points to a file archive of some * sort. The URL can have any scheme that we have a file stream wrapper to diff --git a/modules/update/update.settings.inc b/modules/update/update.settings.inc index 5cd24149..75de6cdd 100644 --- a/modules/update/update.settings.inc +++ b/modules/update/update.settings.inc @@ -26,7 +26,7 @@ function update_settings($form) { $form['update_check_disabled'] = array( '#type' => 'checkbox', - '#title' => t('Check for updates of disabled modules and themes'), + '#title' => t('Check for updates of disabled and uninstalled modules and themes'), '#default_value' => variable_get('update_check_disabled', FALSE), ); @@ -98,10 +98,11 @@ function update_settings_validate($form, &$form_state) { * Form submission handler for update_settings(). * * Also invalidates the cache of available updates if the "Check for updates of - * disabled modules and themes" setting is being changed. The available updates - * report needs to refetch available update data after this setting changes or - * it would show misleading things (e.g., listing the disabled projects on the - * site with the "No available releases found" warning). + * disabled and uninstalled modules and themes" setting is being changed. The + * available updates report needs to refetch available update data after this + * setting changes or it would show misleading things (e.g., listing the + * disabled projects on the site with the "No available releases found" + * warning). * * @see update_settings_validate() */ diff --git a/modules/update/update.test b/modules/update/update.test index 9e04cdae..5ce5bb88 100644 --- a/modules/update/update.test +++ b/modules/update/update.test @@ -462,6 +462,55 @@ class UpdateTestContribCase extends UpdateTestHelper { $this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), 'Link to the Update test base theme project appears.'); } + /** + * Tests that the admin theme is always notified about security updates. + */ + function testUpdateAdminThemeSecurityUpdate() { + // Disable the admin theme. + db_update('system') + ->fields(array('status' => 0)) + ->condition('type', 'theme') + ->condition('name', 'update_test_%', 'LIKE') + ->execute(); + + variable_set('admin_theme', 'update_test_admintheme'); + + // Define the initial state for core and the themes. + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'update_test_admintheme' => array( + 'project' => 'update_test_admintheme', + 'version' => '7.x-1.0', + 'hidden' => FALSE, + ), + 'update_test_basetheme' => array( + 'project' => 'update_test_basetheme', + 'version' => '7.x-1.1', + 'hidden' => FALSE, + ), + 'update_test_subtheme' => array( + 'project' => 'update_test_subtheme', + 'version' => '7.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + variable_set('update_check_disabled', FALSE); + $xml_mapping = array( + // This is enough because we don't check the update status of the admin + // theme. We want to check that the admin theme is included in the list. + 'drupal' => '0', + ); + $this->refreshUpdateStatus($xml_mapping); + // The admin theme is displayed even if it's disabled. + $this->assertText('update_test_admintheme', "The admin theme is checked for update even if it's disabled"); + // The other disabled themes are not displayed. + $this->assertNoText('update_test_basetheme', 'Disabled theme is not checked for update in the list.'); + $this->assertNoText('update_test_subtheme', 'Disabled theme is not checked for update in the list.'); + } + /** * Tests that disabled themes are only shown when desired. */ @@ -800,4 +849,4 @@ class UpdateCoreUnitTestCase extends DrupalUnitTestCase { $this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'."); } -} \ No newline at end of file +} diff --git a/modules/user/tests/user_form_test.info b/modules/user/tests/user_form_test.info index c8eeeabe..d5a554be 100644 --- a/modules/user/tests/user_form_test.info +++ b/modules/user/tests/user_form_test.info @@ -5,8 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/user/tests/user_form_test.module b/modules/user/tests/user_form_test.module index 4e907f36..382bc57b 100644 --- a/modules/user/tests/user_form_test.module +++ b/modules/user/tests/user_form_test.module @@ -62,3 +62,21 @@ function user_form_test_current_password($form, &$form_state, $account) { function user_form_test_current_password_submit($form, &$form_state) { drupal_set_message(t('The password has been validated and the form submitted successfully.')); } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function user_form_test_form_user_profile_form_alter(&$form, &$form_state) { + if (variable_get('user_form_test_user_profile_form_rebuild', FALSE)) { + $form['#submit'][] = 'user_form_test_user_account_submit'; + } +} + +/** + * Submit function for user_profile_form(). + */ +function user_form_test_user_account_submit($form, &$form_state) { + // Rebuild the form instead of letting the process end. This allows us to + // test for bugs that can be triggered in contributed modules. + $form_state['rebuild'] = TRUE; +} diff --git a/modules/user/user-picture.tpl.php b/modules/user/user-picture.tpl.php index ee821878..11d92cc5 100644 --- a/modules/user/user-picture.tpl.php +++ b/modules/user/user-picture.tpl.php @@ -17,7 +17,7 @@ */ ?> -
        +
        diff --git a/modules/user/user.api.php b/modules/user/user.api.php index edc61bd3..f205a85b 100644 --- a/modules/user/user.api.php +++ b/modules/user/user.api.php @@ -123,8 +123,8 @@ function hook_user_cancel($edit, $account, $method) { * description is NOT used for the radio button, but instead should provide * additional explanation to the user seeking to cancel their account. * - access: (optional) A boolean value indicating whether the user can access - * a method. If #access is defined, the method cannot be configured as default - * method. + * a method. If access is defined, the method cannot be configured as the + * default method. * * @param $methods * An array containing user account cancellation methods, keyed by method id. @@ -183,7 +183,23 @@ function hook_user_operations() { } /** - * Retrieve a list of user setting or profile information categories. + * Define a list of user settings or profile information categories. + * + * There are two steps to using hook_user_categories(): + * - Create the category with hook_user_categories(). + * - Display that category on the form ID of "user_profile_form" with + * hook_form_FORM_ID_alter(). + * + * Step one builds out the category but it won't be visible on your form until + * you explicitly tell it to do so. + * + * The function in step two should contain the following code in order to + * display your new category: + * @code + * if ($form['#user_category'] == 'mycategory') { + * // Return your form here. + * } + * @endcode * * @return * An array of associative arrays. Each inner array has elements: diff --git a/modules/user/user.info b/modules/user/user.info index aaebd74b..3e10c7ee 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -9,8 +9,7 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.39" +; Information added by Drupal.org packaging script on 2018-10-17 +version = "7.60" project = "drupal" -datestamp = "1440020197" - +datestamp = "1539816636" diff --git a/modules/user/user.install b/modules/user/user.install index b573e72d..7a74766a 100644 --- a/modules/user/user.install +++ b/modules/user/user.install @@ -49,6 +49,9 @@ function user_schema() { 'columns' => array('uid' => 'uid'), ), ), + 'indexes' => array( + 'uid_module' => array('uid', 'module'), + ), ); $schema['role_permission'] = array( @@ -910,6 +913,15 @@ function user_update_7018() { } } +/** + * Ensure there is a combined index on {authmap}.uid and {authmap}.module. + */ +function user_update_7019() { + // Check first in case it was already added manually. + if (!db_index_exists('authmap', 'uid_module')) { + db_add_index('authmap', 'uid_module', array('uid', 'module')); + } +} /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/user/user.js b/modules/user/user.js index d182066a..4cf98161 100644 --- a/modules/user/user.js +++ b/modules/user/user.js @@ -93,6 +93,8 @@ Drupal.behaviors.password = { * Returns the estimated strength and the relevant output message. */ Drupal.evaluatePasswordStrength = function (password, translate) { + password = $.trim(password); + var weaknesses = 0, strength = 100, msg = []; var hasLowercase = /[a-z]+/.test(password); diff --git a/modules/user/user.module b/modules/user/user.module index 9637a716..12ca2800 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -418,13 +418,11 @@ function user_load_by_name($name) { * * @return * A fully-loaded $user object upon successful save or FALSE if the save failed. - * - * @todo D8: Drop $edit and fix user_save() to be consistent with others. */ function user_save($account, $edit = array(), $category = 'account') { $transaction = db_transaction(); try { - if (!empty($edit['pass'])) { + if (isset($edit['pass']) && strlen(trim($edit['pass'])) > 0) { // Allow alternate password hashing schemes. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); $edit['pass'] = user_hash_password(trim($edit['pass'])); @@ -791,7 +789,7 @@ function user_role_permissions($roles = array()) { * (optional) The account to check, if not given use currently logged in user. * * @return - * Boolean TRUE if the current user has the requested permission. + * Boolean TRUE if the user has the requested permission. * * All permission checks in Drupal should go through this function. This * way, we guarantee consistent behavior, and ensure that the superuser @@ -958,6 +956,8 @@ function user_search_access() { */ function user_search_execute($keys = NULL, $conditions = NULL) { $find = array(); + // Escape for LIKE matching. + $keys = db_like($keys); // Replace wildcards with MySQL/PostgreSQL wildcards. $keys = preg_replace('!\*+!', '%', $keys); $query = db_select('users')->extend('PagerDefault'); @@ -967,13 +967,13 @@ function user_search_execute($keys = NULL, $conditions = NULL) { // and they don't need to be restricted to only active users. $query->fields('users', array('mail')); $query->condition(db_or()-> - condition('name', '%' . db_like($keys) . '%', 'LIKE')-> - condition('mail', '%' . db_like($keys) . '%', 'LIKE')); + condition('name', '%' . $keys . '%', 'LIKE')-> + condition('mail', '%' . $keys . '%', 'LIKE')); } else { // Regular users can only search via usernames, and we do not show them // blocked accounts. - $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') + $query->condition('name', '%' . $keys . '%', 'LIKE') ->condition('status', 1); } $uids = $query @@ -1088,13 +1088,16 @@ function user_account_form(&$form, &$form_state) { '#description' => t('To change the current user password, enter the new password in both fields.'), ); // To skip the current password field, the user must have logged in via a - // one-time link and have the token in the URL. - $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + // one-time link and have the token in the URL. Store this in $form_state + // so it persists even on subsequent Ajax requests. + if (!isset($form_state['user_pass_reset'])) { + $form_state['user_pass_reset'] = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + } $protected_values = array(); $current_pass_description = ''; // The user may only change their own password without their current // password if they logged in via a one-time login link. - if (!$pass_reset) { + if (!$form_state['user_pass_reset']) { $protected_values['mail'] = $form['account']['mail']['#title']; $protected_values['pass'] = t('Password'); $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.')))); @@ -1160,7 +1163,7 @@ function user_account_form(&$form, &$form_state) { $form['account']['roles'] = array( '#type' => 'checkboxes', '#title' => t('Roles'), - '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()), + '#default_value' => (!$register && !empty($account->roles) ? array_keys(array_filter($account->roles)) : array()), '#options' => $roles, '#access' => $roles && user_access('administer permissions'), DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated, @@ -1230,7 +1233,7 @@ function user_validate_current_pass(&$form, &$form_state) { // that prevent them from being empty if they are changed. if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) { require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); - $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account); + $current_pass_failed = strlen(trim($form_state['values']['current_pass'])) == 0 || !user_check_password($form_state['values']['current_pass'], $account); if ($current_pass_failed) { form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name))); form_set_error($key); @@ -1306,10 +1309,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. @@ -1751,9 +1756,11 @@ function user_menu() { $items['admin/people/create'] = array( 'title' => 'Add user', + 'page callback' => 'user_admin', 'page arguments' => array('create'), 'access arguments' => array('administer users'), 'type' => MENU_LOCAL_ACTION, + 'file' => 'user.admin.inc', ); // Administration pages. @@ -1911,13 +1918,13 @@ function user_menu_link_alter(&$link) { // for authenticated users. Authenticated users should see "My account", but // anonymous users should not see it at all. Therefore, invoke // user_translated_menu_link_alter() to conditionally hide the link. - if ($link['link_path'] == 'user' && $link['module'] == 'system') { + if ($link['link_path'] == 'user' && isset($link['module']) && $link['module'] == 'system') { $link['options']['alter'] = TRUE; } // Force the Logout link to appear on the top-level of 'user-menu' menu by // default (i.e., unless it has been customized). - if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) { + if ($link['link_path'] == 'user/logout' && isset($link['module']) && $link['module'] == 'system' && empty($link['customized'])) { $link['plid'] = 0; } } @@ -2161,7 +2168,7 @@ function user_login_name_validate($form, &$form_state) { */ function user_login_authenticate_validate($form, &$form_state) { $password = trim($form_state['values']['pass']); - if (!empty($form_state['values']['name']) && !empty($password)) { + if (!empty($form_state['values']['name']) && strlen(trim($password)) > 0) { // Do not allow any login from the current user's IP if the limit has been // reached. Default is 50 failed attempts allowed in one hour. This is // independent of the per-user limit to catch attempts from one IP to log @@ -2225,7 +2232,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'])); } } @@ -2248,7 +2259,7 @@ function user_login_final_validate($form, &$form_state) { */ function user_authenticate($name, $password) { $uid = FALSE; - if (!empty($name) && !empty($password)) { + if (!empty($name) && strlen(trim($password)) > 0) { $account = user_load_by_name($name); if ($account) { // Allow alternate password hashing schemes. @@ -2488,7 +2499,9 @@ function user_cancel($edit, $uid, $method) { } /** - * Last batch processing step for cancelling a user account. + * Implements callback_batch_operation(). + * + * Last step for cancelling a user account. * * Since batch and session API require a valid user account, the actual * cancellation of a user account needs to happen last. @@ -2536,6 +2549,8 @@ function _user_cancel($edit, $account, $method) { } /** + * Implements callback_batch_finished(). + * * Finished batch processing callback for cancelling a user account. * * @see user_cancel() @@ -3039,6 +3054,11 @@ function user_role_delete($role) { $role = user_role_load_by_name($role); } + // If this is the administrator role, delete the user_admin_role variable. + if ($role->rid == variable_get('user_admin_role')) { + variable_del('user_admin_role'); + } + db_delete('role') ->condition('rid', $role->rid) ->execute(); @@ -3654,12 +3674,7 @@ function user_form_process_password_confirm($element) { ); $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js'; - // Ensure settings are only added once per page. - static $already_added = FALSE; - if (!$already_added) { - $already_added = TRUE; - $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); - } + $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); return $element; } diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index f21bd134..2a1b291b 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -44,6 +44,12 @@ function user_pass() { $form['name']['#value'] = $user->mail; $form['mail'] = array( '#prefix' => '

        ', + // As of https://www.drupal.org/node/889772 the user no longer must log + // out (if they are still logged in when using the password reset link, + // they will be logged out automatically then), but this text is kept as + // is to avoid breaking translations as well as to encourage the user to + // log out manually at a time of their own choosing (when it will not + // interrupt anything else they may have been in the middle of doing). '#markup' => t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the e-mail.', array('%email' => $user->mail)), '#suffix' => '

        ', ); @@ -54,6 +60,11 @@ function user_pass() { return $form; } +/** + * Form validation handler for user_pass(). + * + * @see user_pass_submit() + */ function user_pass_validate($form, &$form_state) { $name = trim($form_state['values']['name']); // Try to load by email. @@ -72,6 +83,11 @@ function user_pass_validate($form, &$form_state) { } } +/** + * Form submission handler for user_pass(). + * + * @see user_pass_validate() + */ function user_pass_submit($form, &$form_state) { global $language; @@ -96,22 +112,33 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a // When processing the one-time login link, we have to make sure that a user // isn't already logged in. if ($user->uid) { - // The existing user is already logged in. + // The existing user is already logged in. Log them out and reload the + // current page so the password reset process can continue. if ($user->uid == $uid) { - drupal_set_message(t('You are logged in as %user. Change your password.', array('%user' => $user->name, '!user_edit' => url("user/$user->uid/edit")))); + // Preserve the current destination (if any) and ensure the redirect goes + // back to the current page; any custom destination set in + // hook_user_logout() and intended for regular logouts would not be + // appropriate here. + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + } + user_logout_current_user(); + unset($_GET['destination']); + drupal_goto(current_path(), array('query' => drupal_get_query_parameters() + $destination)); } // A different user is already logged in on the computer. else { $reset_link_account = user_load($uid); if (!empty($reset_link_account)) { drupal_set_message(t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please logout and try using the link again.', - array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout')))); + array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout'))), 'warning'); } else { // Invalid one-time link specifies an unknown user. - drupal_set_message(t('The one-time login link you clicked is invalid.')); + drupal_set_message(t('The one-time login link you clicked is invalid.'), 'error'); } + drupal_goto(); } - drupal_goto(); } else { // Time out, in seconds, until login URL expires. Defaults to 24 hours = @@ -123,7 +150,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a if ($timestamp <= $current && $account = reset($users)) { // No time out for first time login. if ($account->login && $current - $timestamp > $timeout) { - drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); drupal_goto('user/password'); } elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { @@ -151,7 +178,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a } } else { - drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'error'); drupal_goto('user/password'); } } @@ -168,6 +195,14 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a * Menu callback; logs the current user out, and redirects to the home page. */ function user_logout() { + user_logout_current_user(); + drupal_goto(); +} + +/** + * Logs the current user out. + */ +function user_logout_current_user() { global $user; watchdog('user', 'Session closed for %name.', array('%name' => $user->name)); @@ -176,8 +211,6 @@ function user_logout() { // Destroy the current session, and reset $user to the anonymous user. session_destroy(); - - drupal_goto(); } /** @@ -294,14 +327,18 @@ function user_profile_form($form, &$form_state, $account, $category = 'account') } /** - * Validation function for the user account and profile editing form. + * Form validation handler for user_profile_form(). + * + * @see user_profile_form_submit() */ function user_profile_form_validate($form, &$form_state) { entity_form_field_validate('user', $form, $form_state); } /** - * Submit function for the user account and profile editing form. + * Form submission handler for user_profile_form(). + * + * @see user_profile_form_validate() */ function user_profile_form_submit($form, &$form_state) { $account = $form_state['user']; @@ -533,7 +570,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') { batch_process(''); } else { - drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error'); drupal_goto("user/$account->uid/cancel"); } } diff --git a/modules/user/user.test b/modules/user/user.test index 07be4c2c..0875e0ac 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -465,6 +465,19 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { ); } + /** + * Retrieves password reset email and extracts the login link. + */ + public function getResetURL() { + // Assume the most recent email. + $_emails = $this->drupalGetMails(); + $email = end($_emails); + $urls = array(); + preg_match('#.+user/reset/.+#', $email['body'], $urls); + + return $urls[0]; + } + /** * Tests password reset functionality. */ @@ -478,6 +491,77 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $this->drupalPost('user/password', $edit, t('E-mail new password')); // Confirm the password reset. $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + + // Create an image field to enable an Ajax request on the user profile page. + $field = array( + 'field_name' => 'field_avatar', + 'type' => 'image', + 'settings' => array(), + 'cardinality' => 1, + ); + field_create_field($field); + + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'user', + 'label' => 'Avatar', + 'bundle' => 'user', + 'required' => FALSE, + 'settings' => array(), + 'widget' => array( + 'type' => 'image_image', + 'settings' => array(), + ), + ); + field_create_instance($instance); + + $resetURL = $this->getResetURL(); + $this->drupalGet($resetURL); + + // Check successful login. + $this->drupalPost(NULL, NULL, t('Log in')); + + // Make sure the Ajax request from uploading a file does not invalidate the + // reset token. + $image = current($this->drupalGetTestFiles('image')); + $edit = array( + 'files[field_avatar_und_0]' => drupal_realpath($image->uri), + ); + $this->drupalPostAJAX(NULL, $edit, 'field_avatar_und_0_upload_button'); + + // Change the forgotten password. + $password = user_password(); + $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); + } + + /** + * Test user password reset while logged in. + */ + function testUserPasswordResetLoggedIn() { + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + // Make sure the test account has a valid password. + user_save($account, array('pass' => user_password())); + + // Generate one time login link. + $reset_url = user_pass_reset_url($account); + $this->drupalGet($reset_url); + + $this->assertText('Reset password'); + $this->drupalPost(NULL, NULL, t('Log in')); + + $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'); + + $pass = user_password(); + $edit = array( + 'pass[pass1]' => $pass, + 'pass[pass2]' => $pass, + ); + $this->drupalPost(NULL, $edit, t('Save')); + + $this->assertText('The changes have been saved.'); } /** @@ -1501,7 +1585,13 @@ class UserTimeZoneFunctionalTest extends DrupalWebTestCase { // Setup date/time settings for Los Angeles time. variable_set('date_default_timezone', 'America/Los_Angeles'); variable_set('configurable_timezones', 1); - variable_set('date_format_medium', 'Y-m-d H:i T'); + + // Override the 'medium' date format, which is the default for node + // creation time. Since we are testing time zones with Daylight Saving + // Time, and need to future proof against changes to the zoneinfo database, + // we choose the 'I' format placeholder instead of a human-readable zone + // name. With 'I', a 1 means the date is in DST, and 0 if not. + variable_set('date_format_medium', 'Y-m-d H:i I'); // Create a user account and login. $web_user = $this->drupalCreateUser(); @@ -1519,11 +1609,11 @@ class UserTimeZoneFunctionalTest extends DrupalWebTestCase { // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-09 21:00 PST', 'Date should be PST.'); + $this->assertText('2007-03-09 21:00 0', 'Date should be PST.'); $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 01:00 PST', 'Date should be PST.'); + $this->assertText('2007-03-11 01:00 0', 'Date should be PST.'); $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-20 21:00 PDT', 'Date should be PDT.'); + $this->assertText('2007-03-20 21:00 1', 'Date should be PDT.'); // Change user time zone to Santiago time. $edit = array(); @@ -1534,11 +1624,11 @@ class UserTimeZoneFunctionalTest extends DrupalWebTestCase { // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-10 02:00 CLST', 'Date should be Chile summer time; five hours ahead of PST.'); + $this->assertText('2007-03-10 02:00 1', 'Date should be Chile summer time; five hours ahead of PST.'); $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 05:00 CLT', 'Date should be Chile time; four hours ahead of PST'); + $this->assertText('2007-03-11 05:00 0', 'Date should be Chile time; four hours ahead of PST'); $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-21 00:00 CLT', 'Date should be Chile time; three hours ahead of PDT.'); + $this->assertText('2007-03-21 00:00 0', 'Date should be Chile time; three hours ahead of PDT.'); } } @@ -1849,6 +1939,19 @@ class UserCreateTestCase extends DrupalWebTestCase { $this->drupalGet('admin/people'); $this->assertText($edit['name'], 'User found in list of users'); } + + // Test that the password '0' is considered a password. + $name = $this->randomName(); + $edit = array( + 'name' => $name, + 'mail' => $name . '@example.com', + 'pass[pass1]' => 0, + 'pass[pass2]' => 0, + 'notify' => FALSE, + ); + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created with password 0'); + $this->assertNoText('Password field is required'); } } @@ -1926,6 +2029,74 @@ class UserEditTestCase extends DrupalWebTestCase { $this->drupalLogin($user1); $this->drupalLogout(); } + + /** + * Tests setting the password to "0". + */ + public function testUserWith0Password() { + $admin = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin); + // Create a regular user. + $user1 = $this->drupalCreateUser(array()); + + $edit = array('pass[pass1]' => '0', 'pass[pass2]' => '0'); + $this->drupalPost("user/" . $user1->uid . "/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + $this->drupalLogout(); + $user1->pass_raw = '0'; + $this->drupalLogin($user1); + $this->drupalLogout(); + } +} + +/** + * Tests editing a user account with and without a form rebuild. + */ +class UserEditRebuildTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'User edit with form rebuild', + 'description' => 'Test user edit page when a form rebuild is triggered.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp('user_form_test'); + } + + /** + * Test user edit page when the form is set to rebuild. + */ + function testUserEditFormRebuild() { + $user1 = $this->drupalCreateUser(array('change own username')); + $this->drupalLogin($user1); + + $roles = array_keys($user1->roles); + // Save the user form twice. + $edit = array(); + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $saved_user1 = entity_load_unchanged('user', $user1->uid); + $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.'); + $diff = array_diff(array_keys($saved_user1->roles), $roles); + $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles)))); + // Set variable that causes the form to be rebuilt in user_form_test.module. + variable_set('user_form_test_user_profile_form_rebuild', TRUE); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + $saved_user1 = entity_load_unchanged('user', $user1->uid); + $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.'); + $diff = array_diff(array_keys($saved_user1->roles), $roles); + $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles)))); + } } /** @@ -2095,12 +2266,16 @@ class UserRoleAdminTestCase extends DrupalWebTestCase { $this->assertFalse(user_role_load_by_name($old_name), 'The role can no longer be retrieved from the database using its old name.'); $this->assertTrue(is_object(user_role_load_by_name($role_name)), 'The role can be retrieved from the database using its new name.'); - // Test deleting a role. + // Test deleting the default administrator role. + $role_name = 'administrator'; + $role = user_role_load_by_name($role_name); $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", NULL, t('Delete role')); $this->drupalPost(NULL, NULL, t('Delete')); $this->assertText(t('The role has been deleted.'), 'The role has been deleted'); $this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", 'Role edit link removed.'); $this->assertFalse(user_role_load_by_name($role_name), 'A deleted role can no longer be loaded.'); + // Make sure this role is no longer configured as the administrator role. + $this->assertNull(variable_get('user_admin_role'), 'The administrator role is no longer configured as the administrator role.'); // Make sure that the system-defined roles cannot be edited via the user // interface. @@ -2226,6 +2401,20 @@ class UserUserSearchTestCase extends DrupalWebTestCase { $this->drupalPost('search/user/', $edit, t('Search')); $this->assertText($keys); + // Verify that wildcard search works. + $keys = $user1->name; + $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2); + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($user1->name, 'Search for username wildcard resulted in user name on page for administrative user.'); + + // Verify that wildcard search works for email. + $keys = $user1->mail; + $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2); + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($user1->name, 'Search for email wildcard resulted in user name on page for administrative user.'); + // Create a blocked user. $blocked_user = $this->drupalCreateUser(); $edit = array('status' => 0); diff --git a/profiles/README.txt b/profiles/README.txt new file mode 100644 index 00000000..91d012ba --- /dev/null +++ b/profiles/README.txt @@ -0,0 +1,28 @@ +Installation profiles define additional steps that run after the base +installation provided by Drupal core when Drupal is first installed. + +WHAT TO PLACE IN THIS DIRECTORY? +-------------------------------- + +Place downloaded and custom installation profiles in this directory. +Installation profiles are generally provided as part of a Drupal distribution. +They only impact the installation of your site. They do not have any effect on +an already running site. + +DOWNLOAD ADDITIONAL DISTRIBUTIONS +--------------------------------- + +Contributed distributions from the Drupal community may be downloaded at +https://www.drupal.org/project/project_distribution. + +MULTISITE CONFIGURATION +----------------------- + +In multisite configurations, installation profiles found in this directory are +available to all sites during their initial site installation. + +MORE INFORMATION +---------------- + +Refer to the "Installation profiles" section of the README.txt in the Drupal +root directory for further information on extending Drupal with custom profiles. diff --git a/profiles/commerce_kickstart/.gitignore b/profiles/commerce_kickstart/.gitignore index 3c445852..d2332d28 100644 --- a/profiles/commerce_kickstart/.gitignore +++ b/profiles/commerce_kickstart/.gitignore @@ -5,3 +5,4 @@ !.htaccess nbproject/ *.kpf +tests/behat/failures diff --git a/profiles/commerce_kickstart/.travis.yml b/profiles/commerce_kickstart/.travis.yml index 761490b3..0628a290 100644 --- a/profiles/commerce_kickstart/.travis.yml +++ b/profiles/commerce_kickstart/.travis.yml @@ -1,8 +1,10 @@ language: php sudo: false - +dist: trusty +addons: + chrome: stable php: - - 5.4 + - 5.6 branches: except: @@ -15,39 +17,45 @@ mysql: env: - UPGRADE=none - - UPGRADE=7.x-2.26 - - UPGRADE=7.x-2.25 - - UPGRADE=7.x-2.24 - - UPGRADE=7.x-2.23 - - UPGRADE=7.x-2.22 - - UPGRADE=7.x-2.21 - - UPGRADE=7.x-2.20 - - UPGRADE=7.x-2.19 - - UPGRADE=7.x-2.18 - - UPGRADE=7.x-2.17 - - UPGRADE=7.x-2.16 - - UPGRADE=7.x-2.15 - - UPGRADE=7.x-2.14 - - UPGRADE=7.x-2.13 - - UPGRADE=7.x-2.12 + - UPGRADE=7.x-2.49 + - UPGRADE=7.x-2.48 + - UPGRADE=7.x-2.47 + - UPGRADE=7.x-2.46 + - UPGRADE=7.x-2.45 + - UPGRADE=7.x-2.44 + - UPGRADE=7.x-2.43 + - UPGRADE=7.x-2.42 + - UPGRADE=7.x-2.41 + - UPGRADE=7.x-2.40 matrix: fast_finish: true include: - - php: 5.5 - env: UPGRADE=none - - php: 5.5 - env: UPGRADE=7.x-2.23 TEST_FEATURES_OVERRIDES=1 - php: 5.6 + env: UPGRADE=7.x-2.23 TEST_FEATURES_OVERRIDES=1 + - php: 7.0 + env: UPGRADE=none + - php: 7.1 env: UPGRADE=none + allow_failures: + - php: 7.0 + - php: 7.1 # Cache Composer & Drush directories. cache: directories: - $HOME/.composer/cache - $HOME/.drush/cache + - tests/behat/vendor +before_install: + - # start your web application and listen on `localhost` + - google-chrome-stable --headless --disable-gpu --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 & install: + # Remove xdebug for the moment. We aren't generating code coverage, and it slows us down. + - rm /home/travis/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini + + # Create database. - "mysql -e 'create database drupal;'" # Install latest Drush 6. @@ -95,16 +103,6 @@ install: # Setup files - chmod -R 777 drupal/sites/all - # Setup display for Selenium - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - - sleep 5 - - # Get Selenium - - wget http://selenium-release.storage.googleapis.com/2.42/selenium-server-standalone-2.42.1.jar - - java -jar selenium-server-standalone-2.42.1.jar > /dev/null 2>&1 & - - nc -zvv localhost 4444; out=$?; while [[ $out -ne 0 ]]; do echo "Retry hit port 4444..."; nc -zvv localhost 4444; out=$?; sleep 5; done - # Disable sendmail - echo sendmail_path=`which true` >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini @@ -112,6 +110,10 @@ install: - echo "mysql.connect_timeout=3000" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - echo "default_socket_timeout=3000" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + # Multibyte string input conversion in PHP is active and must be disabled for Drupal on PHP 5.6. + - echo "mbstring.http_input = pass" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo "mbstring.http_output = pass" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + # Increase the MySQL server timetout and packet size. - mysql -e "SET GLOBAL wait_timeout = 36000;" - mysql -e "SET GLOBAL max_allowed_packet = 33554432;" @@ -136,7 +138,9 @@ before_script: - drush pm-disable dblog --yes # If testing the override, enable it and revert feature it's overriding (because we didn't on the same install.) - - if [[ "$TEST_FEATURES_OVERRIDES" == 1 ]]; then drush en -y commerce_kickstart_overrides_test && drush fr -y commerce_kickstart_blog; fi + # We need to download rc2, because rc3 requires a function in Features 2.7, + # breaking test. + - if [[ "$TEST_FEATURES_OVERRIDES" == 1 ]]; then drush dl features_override-7.x-2.0-rc2 && drush en -y commerce_kickstart_overrides_test && drush fr -y commerce_kickstart_blog; fi - cd ../drupal @@ -153,6 +157,9 @@ script: # If this isn't an upgrade, we test if any features are overridden. - if [[ "$UPGRADE" == none ]]; then ../../scripts/check-overridden.sh; fi + # Fix bad Commerce Migrate release for testing the demo. + - if [[ "$UPGRADE" == "7.x-2.34" || "$UPGRADE" == "7.x-2.33" || "$UPGRADE" == "7.x-2.32" || "$UPGRADE" == "7.x-2.31" || "$UPGRADE" == "7.x-2.30" || "$UPGRADE" == "7.x-2.29" || "$UPGRADE" == "7.x-2.28" || "$UPGRADE" == "7.x-2.27" ]]; then drush mi --all --update; fi + # Run Behat tests. - if [[ "$TEST_FEATURES_OVERRIDES" != 1 ]]; then ./bin/behat --config behat.travis.yml --tags ~@overrides; fi - if [[ "$TEST_FEATURES_OVERRIDES" == 1 ]]; then ./bin/behat --config behat.travis.yml --tags @overrides; fi diff --git a/profiles/commerce_kickstart/commerce_kickstart.info b/profiles/commerce_kickstart/commerce_kickstart.info index 3044e717..dfb1b9fc 100644 --- a/profiles/commerce_kickstart/commerce_kickstart.info +++ b/profiles/commerce_kickstart/commerce_kickstart.info @@ -78,7 +78,6 @@ dependencies[] = entityreference dependencies[] = views_megarow dependencies[] = commerce_addressbook dependencies[] = commerce_discount -dependencies[] = commerce_discount_date dependencies[] = commerce_add_to_cart_confirmation dependencies[] = commerce_message dependencies[] = commerce_backoffice @@ -118,9 +117,8 @@ dependencies[] = commerce_kickstart_migrate ; System Requirements. php_memory_limit = 128M -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/commerce_kickstart.install b/profiles/commerce_kickstart/commerce_kickstart.install index 00b34f3e..89b539a3 100644 --- a/profiles/commerce_kickstart/commerce_kickstart.install +++ b/profiles/commerce_kickstart/commerce_kickstart.install @@ -58,16 +58,18 @@ function commerce_kickstart_install_tasks_alter(&$tasks, $install_state) { $tasks['install_select_locale']['run'] = INSTALL_TASK_SKIP; $tasks['install_profile_modules']['display_name'] = st('Install Commerce Kickstart 2'); - // The "Welcome" screen needs to come after the first two steps - // (profile and language selection), despite the fact that they are disabled. - $new_task['install_welcome'] = array( - 'display' => TRUE, - 'display_name' => st('Welcome'), - 'type' => 'form', - 'run' => isset($install_state['parameters']['welcome']) ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_REACHED, - ); - $old_tasks = $tasks; - $tasks = array_slice($old_tasks, 0, 2) + $new_task + array_slice($old_tasks, 2); + if (PHP_SAPI !== 'cli') { + // The "Welcome" screen needs to come after the first two steps + // (profile and language selection), despite the fact that they are disabled. + $new_task['install_welcome'] = array( + 'display' => TRUE, + 'display_name' => st('Welcome'), + 'type' => 'form', + 'run' => isset($install_state['parameters']['welcome']) ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_REACHED, + ); + $old_tasks = $tasks; + $tasks = array_slice($old_tasks, 0, 2) + $new_task + array_slice($old_tasks, 2); + } // Set the installation theme. _commerce_kickstart_set_theme('commerce_kickstart_admin'); @@ -1274,3 +1276,18 @@ function commerce_kickstart_update_7224() { function commerce_kickstart_update_7225() { module_enable(array('distro_update')); } + +/** + * Disable the Distribution Update Status Manager module. + */ +function commerce_kickstart_update_7226() { + // Causing too much grief with security warnings about modules. + module_disable(array('distro_update')); +} + +/** + * Re-enable the Distribution Update Status Manager module. + */ +function commerce_kickstart_update_7227() { + module_enable(array('distro_update')); +} diff --git a/profiles/commerce_kickstart/drupal-org-core.make b/profiles/commerce_kickstart/drupal-org-core.make index 9bf25e49..db954bc0 100644 --- a/profiles/commerce_kickstart/drupal-org-core.make +++ b/profiles/commerce_kickstart/drupal-org-core.make @@ -1,6 +1,6 @@ api = 2 core = 7.x -projects[drupal][version] = 7.39 +projects[drupal][version] = 7.60 ; Patches for Core projects[drupal][patch][] = "http://drupal.org/files/issues/install-redirect-on-empty-database-728702-36.patch" diff --git a/profiles/commerce_kickstart/drupal-org.make b/profiles/commerce_kickstart/drupal-org.make index 10a683c0..d144a226 100644 --- a/profiles/commerce_kickstart/drupal-org.make +++ b/profiles/commerce_kickstart/drupal-org.make @@ -5,91 +5,75 @@ api = 2 defaults[projects][subdir] = contrib ; Basic contributed modules. -projects[ctools][version] = 1.9 -projects[entity][version] = 1.6 -projects[entityreference][version] = 1.1 -projects[entityreference][patch][] = "http://drupal.org/files/1580348-universal-formatters-17.patch" -projects[rules][version] = 2.9 -projects[views][version] = 3.11 -projects[views][patch][] = "http://drupal.org/files/2059555-reduce-formatter-form-state.patch" -projects[views_bulk_operations][version] = 3.3 -projects[addressfield][version] = 1.1 -projects[features][version] = "2.6" +projects[ctools][version] = 1.13 +projects[entity][version] = 1.9 +projects[entityreference][version] = 1.5 +projects[rules][version] = 2.10 +projects[views][version] = 3.18 +projects[views_bulk_operations][version] = 3.4 +projects[addressfield][version] = 1.2 +projects[features][version] = 2.10 projects[features][patch][2143765] = "http://drupal.org/files/issues/features-fix-modules-enabled-2143765-1.patch" projects[features][patch][2479803] = "https://www.drupal.org/files/issues/ignore_hidden_modules-2479803-1.patch" -projects[features][patch][2534138] = "https://www.drupal.org/files/issues/2534138-field-base-exception-catch-1.patch" -projects[features_override][version] = 2.0-rc2 +projects[features_override][version] = 2.0-rc3 projects[strongarm][version] = 2.0 projects[taxonomy_menu][version] = 1.5 -projects[libraries][version] = 2.2 -projects[views_megarow][version] = 1.4 +projects[libraries][version] = 2.3 +projects[views_megarow][version] = 1.7 ; Drupal Commerce and Commerce contribs. -projects[commerce][version] = 1.11 -projects[commerce_features][version] = 1.1 -projects[commerce_addressbook][version] = 2.0-rc8 -projects[commerce_shipping][version] = 2.2 +projects[commerce][version] = 1.14 +projects[commerce_features][version] = 1.3 +projects[commerce_addressbook][version] = 2.0-rc9 +projects[commerce_shipping][version] = 2.3 projects[commerce_flat_rate][version] = 1.0-beta2 projects[commerce_fancy_attributes][version] = 1.0 -projects[commerce_autosku][version] = 1.x-dev -projects[commerce_autosku][download][type] = git -projects[commerce_autosku][download][revision] = 32e86f4 -projects[commerce_autosku][download][branch] = 7.x-1.x -projects[commerce_migrate][version] = 1.1 -projects[commerce_migrate][patch][1931302] = https://www.drupal.org/files/commerce_products_source_migration-1931302-2.patch -projects[commerce_discount][version] = 1.x-dev -projects[commerce_discount][download][type] = git -projects[commerce_discount][download][revision] = 7a78225 -projects[commerce_discount][download][branch] = 7.x-1.x -projects[commerce_checkout_progress][version] = 1.3 +projects[commerce_autosku][version] = 1.2 +projects[commerce_migrate][version] = 1.2 +projects[commerce_migrate][patch][2701333] = https://www.drupal.org/files/issues/reference_fields_should-2701333-3.patch +projects[commerce_discount][version] = 1.0-beta5 +projects[commerce_checkout_progress][version] = 1.5 projects[commerce_extra_price_formatters][version] = 1.x-dev projects[commerce_extra_price_formatters][download][type] = git projects[commerce_extra_price_formatters][download][revision] = 1371336 projects[commerce_extra_price_formatters][download][branch] = 7.x-1.x -projects[commerce_checkout_redirect][version] = 2.0-rc1 +projects[commerce_checkout_redirect][version] = 2.0 projects[commerce_hosted_pci][version] = 1.0-rc2 projects[commerce_payleap][version] = 1.1 projects[commerce_moneybookers][version] = 1.2 projects[commerce_moneybookers][patch][] = "http://drupal.org/files/commerce_moneybookers-disable_payment_method_by_default-1962226-3.patch" -projects[commerce_paypal][version] = 2.3 -projects[commerce_backoffice][version] = 1.4 -projects[commerce_message][version] = 1.0-rc3 -projects[commerce_search_api][version] = 1.3 -projects[commerce_add_to_cart_confirmation][version] = 1.0-rc2 +projects[commerce_paypal][version] = 2.4 +projects[commerce_backoffice][version] = 1.5 +projects[commerce_message][version] = 1.0 +projects[commerce_search_api][version] = 1.6 +projects[commerce_add_to_cart_confirmation][version] = 1.0-rc3 projects[commerce_kiala][version] = 1.0-rc1 projects[commerce_physical][version] = 1.x-dev projects[commerce_physical][download][type] = git -projects[commerce_physical][download][revision] = e2a8866 +projects[commerce_physical][download][revision] = 477aaee projects[commerce_physical][download][branch] = 7.x-1.x projects[commerce_amex][version] = 1.1 projects[commerce_cba][version] = 1.0-beta1 -projects[commerce_authnet][version] = 1.1 +projects[commerce_authnet][version] = 1.4 projects[commerce_exactor][version] = 1.3 projects[commerce_paymill][version] = 2.4 -projects[commerce_nosto_tagging][version] = 1.0 -projects[commerce_nosto_tagging][patch][] = https://drupal.org/files/issues/issue-2225883.patch -projects[commerce_yotpo][download][type] = "git" -projects[commerce_yotpo][download][revision] = "ecc41f9" -projects[commerce_yotpo][download][branch] = 7.x-1.x -projects[commerce_firstdata_gge4][version] = 1.0 +projects[commerce_nosto_tagging][version] = 1.1 +projects[commerce_yotpo][version] = 1.2 +projects[commerce_firstdata_gge4][version] = 1.1 +projects[commerce_amazon_lpa][version] = 1.3 ; Other contribs. projects[countries][version] = 2.3 projects[remote_stream_wrapper][version] = 1.0-rc1 -projects[colorbox][version] = 2.7 -projects[colorbox][patch][] = https://www.drupal.org/files/issues/plugin_version_detection-2360375-9.patch -projects[physical][version] = 1.x-dev -projects[physical][download][type] = git -projects[physical][download][revision] = 32e1a38 -projects[physical][download][branch] = 7.x-1.x +projects[colorbox][version] = 2.13 +projects[physical][version] = 1.0 projects[crumbs][version] = 1.10 projects[http_client][version] = 2.4 -projects[oauth][version] = 3.2 -projects[oauth][patch][] = "http://drupal.org/files/980340-d7.patch" +projects[oauth][version] = 3.4 projects[connector][version] = 1.0-beta2 projects[oauthconnector][version] = 1.0-beta2 -projects[inline_entity_form][version] = 1.6 -projects[inline_conditions][version] = 1.0-alpha5 +projects[inline_entity_form][version] = 1.8 +projects[inline_conditions][version] = 1.0-rc1 projects[field_extractor][version] = 1.3 projects[service_links][version] = 2.x-dev projects[service_links][download][type] = "git" @@ -97,64 +81,63 @@ projects[service_links][download][revision] = "6f63b84" projects[service_links]download][branch] = 7.x-2.x projects[advanced_help][version] = 1.3 projects[mailsystem][version] = 2.34 -projects[mimemail][version] = 1.0-beta3 -projects[token][version] = 1.6 +projects[mailsystem][patch][1534706] = "https://www.drupal.org/files/mailsystem.1534706.6.patch" +projects[mimemail][version] = 1.1 +projects[token][version] = 1.7 projects[token][patch][] = "http://drupal.org/files/token-token_asort_tokens-1712336_0.patch" -projects[eva][version] = 1.2 -projects[message][version] = 1.10 +projects[eva][version] = 1.3 +projects[message][version] = 1.12 projects[message_notify][version] = 2.5 projects[migrate][version] = 2.8 projects[migrate_extras][version] = 2.5 projects[migrate_extras][patch][] = "http://drupal.org/files/migrate_extras-fix-destid2-array-1951904-4.patch" -projects[date][version] = 2.8 +projects[date][version] = 2.10 projects[yottaa][version] = 1.2 -projects[menu_attributes][version] = 1.0-rc3 -projects[fences][version] = "1.0" -projects[fences][patch][] = "http://drupal.org/files/undefined-index-1561244-7.patch" -projects[fences][patch][] = "http://drupal.org/files/fences-default_markup_option-1857230-2.patch" -projects[title][version] = "1.0-alpha7" -projects[title][patch][] = "http://drupal.org/files/title-translation_overwrite-1269076-35.patch" -projects[kameleoon][version] = "1.1" -projects[mailup][version] = "1.1" -projects[mailjet][version] = "2.3" +projects[menu_attributes][version] = 1.0 +projects[fences][version] = 1.2 +projects[title][version] = 1.0-alpha9 +projects[title][patch][] = "https://www.drupal.org/files/issues/title-fix_description_empty_on_submit-2075041-7.patch" +projects[kameleoon][version] = 1.1 +projects[mailup][version] = 1.4 +projects[mailjet][version] = 2.14 ; Search related modules. -projects[search_api][version] = 1.15 -projects[search_api_db][version] = 1.4 +projects[search_api][version] = 1.22 +projects[search_api_db][version] = 1.6 projects[search_api_ranges][version] = 1.5 projects[search_api_ranges][patch][] = "https://drupal.org/files/issues/search_api_ranges-rewrite-data-alteration-callback-2001846-4.patch" projects[facetapi][version] = 1.5 projects[facetapi][patch][] = "https://drupal.org/files/1616518-term_remove_link-24.patch" projects[facetapi][patch][2378693] = "https://www.drupal.org/files/issues/notice_undefined-2378693-3.patch" -projects[search_api_sorts][version] = 1.5 +projects[search_api_sorts][version] = 1.7 ; UI improvement modules. -projects[module_filter][version] = 2.0 +projects[module_filter][version] = 2.1 projects[image_delta_formatter][version] = 1.0-rc1 -projects[link][version] = 1.3 -projects[pathauto][version] = 1.2 -projects[cloud_zoom][version] = 1.x-dev +projects[link][version] = 1.4 +projects[pathauto][version] = 1.3 +; projects[cloud_zoom][version] = 1.x-dev projects[cloud_zoom][download][type] = git projects[cloud_zoom][download][revision] = 3cff30f projects[cloud_zoom][download][branch] = 7.x-1.x projects[special_menu_items][version] = 2.0 -projects[chosen][version] = 2.x-dev -projects[chosen][download][type] = git -projects[chosen][download][revision] = e7a0d22 -projects[chosen][download][branch] = 7.x-2.x -projects[admin_views][version] = 1.5 +projects[chosen][version] = 2.1 +projects[admin_views][version] = 1.6 projects[distro_update][version] = 1.0-beta4 ; Internationalization projects[variable][version] = 2.5 -projects[i18n][version] = "1.12" -projects[lingotek][version] = 6.02 +projects[i18n][version] = 1.17 +projects[lingotek][version] = 7.21 ; Base theme. projects[omega][version] = 3.1 projects[omega][patch][] = "http://drupal.org/files/relative-src-15.patch" +projects[omega][type] = theme projects[omega_kickstart][version] = 3.4 -projects[shiny][version] = 1.6 +projects[omega_kickstart][type] = theme +projects[shiny][version] = 1.7 +projects[shiny][type] = theme ; Libraries. libraries[colorbox][type] = "libraries" @@ -185,7 +168,7 @@ libraries[selectnav.js][download][revision] = 538237c7c5e95736fc376f4efc3e40f5b9 libraries[selectnav.js][download][branch] = master libraries[ie7-js][type] = "libraries" libraries[ie7-js][download][type] = "file" -libraries[ie7-js][download][url] = "https://ie7-js.googlecode.com/files/ie7-2.1%28beta4%29.zip" +libraries[ie7-js][download][url] = "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/ie7-js/ie7-2.1(beta4).zip" libraries[chosen][type] = "libraries" libraries[chosen][download][type] = "get" libraries[chosen][download][url] = "https://github.com/harvesthq/chosen/releases/download/v1.1.0/chosen_v1.1.0.zip" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_custom.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_custom.inc index dcaaf085..ade56d03 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_custom.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_custom.inc @@ -34,6 +34,5 @@ function commerce_kickstart_block_menu_default_menu_custom() { t('Payment methods'); t('Secondary navigation'); - return $menus; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_links.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_links.inc index 55c765e0..34c94e47 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_links.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.features.menu_links.inc @@ -10,7 +10,7 @@ function commerce_kickstart_block_menu_default_menu_links() { $menu_links = array(); - // Exported menu link: menu-footer-navigation_about-us:node/2 + // Exported menu link: menu-footer-navigation_about-us:node/2. $menu_links['menu-footer-navigation_about-us:node/2'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => 'node/2', @@ -29,7 +29,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'customized' => 1, 'parent_identifier' => 'menu-footer-navigation_company-info:', ); - // Exported menu link: menu-footer-navigation_company-info: + // Exported menu link: menu-footer-navigation_company-info:. $menu_links['menu-footer-navigation_company-info:'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => '', @@ -49,7 +49,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -50, 'customized' => 1, ); - // Exported menu link: menu-footer-navigation_our-security-policy:node/8 + // Exported menu link: menu-footer-navigation_our-security-policy:node/8. $menu_links['menu-footer-navigation_our-security-policy:node/8'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => 'node/8', @@ -70,7 +70,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'customized' => 1, 'parent_identifier' => 'menu-footer-navigation_security--privacy:', ); - // Exported menu link: menu-footer-navigation_press-links:node/6 + // Exported menu link: menu-footer-navigation_press-links:node/6. $menu_links['menu-footer-navigation_press-links:node/6'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => 'node/6', @@ -89,7 +89,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'customized' => 1, 'parent_identifier' => 'menu-footer-navigation_company-info:', ); - // Exported menu link: menu-footer-navigation_security--privacy: + // Exported menu link: menu-footer-navigation_security--privacy:. $menu_links['menu-footer-navigation_security--privacy:'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => '', @@ -109,7 +109,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -48, 'customized' => 1, ); - // Exported menu link: menu-footer-navigation_service--support: + // Exported menu link: menu-footer-navigation_service--support:. $menu_links['menu-footer-navigation_service--support:'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => '', @@ -129,7 +129,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -49, 'customized' => 1, ); - // Exported menu link: menu-footer-navigation_service-agreements:node/7 + // Exported menu link: menu-footer-navigation_service-agreements:node/7. $menu_links['menu-footer-navigation_service-agreements:node/7'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => 'node/7', @@ -150,7 +150,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'customized' => 1, 'parent_identifier' => 'menu-footer-navigation_service--support:', ); - // Exported menu link: menu-footer-navigation_shipping--returns: + // Exported menu link: menu-footer-navigation_shipping--returns:. $menu_links['menu-footer-navigation_shipping--returns:'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => '', @@ -170,7 +170,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -47, 'customized' => 1, ); - // Exported menu link: menu-footer-navigation_shipping-fees:node/5 + // Exported menu link: menu-footer-navigation_shipping-fees:node/5. $menu_links['menu-footer-navigation_shipping-fees:node/5'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => 'node/5', @@ -191,7 +191,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'customized' => 1, 'parent_identifier' => 'menu-footer-navigation_shipping--returns:', ); - // Exported menu link: menu-footer-navigation_terms-of-use:node/3 + // Exported menu link: menu-footer-navigation_terms-of-use:node/3. $menu_links['menu-footer-navigation_terms-of-use:node/3'] = array( 'menu_name' => 'menu-footer-navigation', 'link_path' => 'node/3', @@ -210,7 +210,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'customized' => 1, 'parent_identifier' => 'menu-footer-navigation_security--privacy:', ); - // Exported menu link: menu-payment-methods_american-express: + // Exported menu link: menu-payment-methods_american-express:. $menu_links['menu-payment-methods_american-express:'] = array( 'menu_name' => 'menu-payment-methods', 'link_path' => '', @@ -234,7 +234,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -47, 'customized' => 1, ); - // Exported menu link: menu-payment-methods_mastercard: + // Exported menu link: menu-payment-methods_mastercard:. $menu_links['menu-payment-methods_mastercard:'] = array( 'menu_name' => 'menu-payment-methods', 'link_path' => '', @@ -258,7 +258,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -50, 'customized' => 1, ); - // Exported menu link: menu-payment-methods_paypal: + // Exported menu link: menu-payment-methods_paypal:. $menu_links['menu-payment-methods_paypal:'] = array( 'menu_name' => 'menu-payment-methods', 'link_path' => '', @@ -282,7 +282,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -49, 'customized' => 1, ); - // Exported menu link: menu-payment-methods_visa: + // Exported menu link: menu-payment-methods_visa:. $menu_links['menu-payment-methods_visa:'] = array( 'menu_name' => 'menu-payment-methods', 'link_path' => '', @@ -306,7 +306,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => -48, 'customized' => 1, ); - // Exported menu link: secondary-navigation_about:node/2 + // Exported menu link: secondary-navigation_about:node/2. $menu_links['secondary-navigation_about:node/2'] = array( 'menu_name' => 'secondary-navigation', 'link_path' => 'node/2', @@ -323,7 +323,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => 2, 'customized' => 1, ); - // Exported menu link: secondary-navigation_contact:node/1 + // Exported menu link: secondary-navigation_contact:node/1. $menu_links['secondary-navigation_contact:node/1'] = array( 'menu_name' => 'secondary-navigation', 'link_path' => 'node/1', @@ -340,6 +340,7 @@ function commerce_kickstart_block_menu_default_menu_links() { 'weight' => 1, 'customized' => 1, ); + // Translatables // Included for use with string extractors like potx. t('About'); @@ -359,6 +360,5 @@ function commerce_kickstart_block_menu_default_menu_links() { t('Terms of use'); t('Visa'); - return $menu_links; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info index 4549bef7..91071f64 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info @@ -32,9 +32,8 @@ features[menu_links][] = secondary-navigation_contact:node/1 features[variable][] = menu_secondary_links_source files[] = commerce_kickstart_block.module -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_base.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_base.inc index 9cd1743b..a299c4f3 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_base.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_base.inc @@ -10,7 +10,7 @@ function commerce_kickstart_blog_field_default_field_bases() { $field_bases = array(); - // Exported field_base: 'field_blog_category' + // Exported field_base: 'field_blog_category'. $field_bases['field_blog_category'] = array( 'active' => 1, 'cardinality' => -1, @@ -31,12 +31,13 @@ function commerce_kickstart_blog_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_tags' + // Exported field_base: 'field_tags'. $field_bases['field_tags'] = array( 'active' => 1, 'cardinality' => -1, @@ -57,6 +58,7 @@ function commerce_kickstart_blog_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_instance.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_instance.inc index 653ec831..6c0f47df 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_instance.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.features.field_instance.inc @@ -10,7 +10,7 @@ function commerce_kickstart_blog_field_default_field_instances() { $field_instances = array(); - // Exported field_instance: 'node-blog_post-body' + // Exported field_instance: 'node-blog_post-body'. $field_instances['node-blog_post-body'] = array( 'bundle' => 'blog_post', 'default_value' => NULL, @@ -68,7 +68,7 @@ function commerce_kickstart_blog_field_default_field_instances() { ), ); - // Exported field_instance: 'node-blog_post-field_blog_category' + // Exported field_instance: 'node-blog_post-field_blog_category'. $field_instances['node-blog_post-field_blog_category'] = array( 'bundle' => 'blog_post', 'default_value' => NULL, @@ -115,7 +115,7 @@ function commerce_kickstart_blog_field_default_field_instances() { ), ); - // Exported field_instance: 'node-blog_post-field_image' + // Exported field_instance: 'node-blog_post-field_image'. $field_instances['node-blog_post-field_image'] = array( 'bundle' => 'blog_post', 'deleted' => 0, @@ -181,7 +181,7 @@ function commerce_kickstart_blog_field_default_field_instances() { ), ); - // Exported field_instance: 'node-blog_post-field_tags' + // Exported field_instance: 'node-blog_post-field_tags'. $field_instances['node-blog_post-field_tags'] = array( 'bundle' => 'blog_post', 'default_value' => NULL, @@ -233,7 +233,7 @@ function commerce_kickstart_blog_field_default_field_instances() { ), ); - // Exported field_instance: 'node-blog_post-title_field' + // Exported field_instance: 'node-blog_post-title_field'. $field_instances['node-blog_post-title_field'] = array( 'bundle' => 'blog_post', 'default_value' => NULL, diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info index e53d56f3..e22c8d45 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info @@ -51,9 +51,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_blog.migrate.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.module b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.module index 209301c8..4ef76046 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.module +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.module @@ -19,6 +19,10 @@ function commerce_kickstart_blog_migrate_api() { ), ); } + + // Return a near empty array to avoid API warning. + // @link https://www.drupal.org/node/2544320 + return array('api' => 2); } /** diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info index cecd3267..e168e687 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info @@ -4,9 +4,8 @@ package = Commerce Kickstart core = 7.x dependencies[] = comment -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_dfp/commerce_kickstart_dfp.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_dfp/commerce_kickstart_dfp.info index 7467af23..1d1d77ca 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_dfp/commerce_kickstart_dfp.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_dfp/commerce_kickstart_dfp.info @@ -3,9 +3,8 @@ description = Provides in-distribution content for Commerce Kickstart. core = 7.x package = Commerce Kickstart -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info index 1cf54841..17f322d9 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info @@ -4,9 +4,8 @@ core = 7.x package = Commerce Kickstart dependencies[] = advanced_help -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info index a2e35703..a505006f 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info @@ -5,9 +5,8 @@ package = Commerce Kickstart dependencies[] = advanced_help dependencies[] = commerce_kickstart_help -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info index 8b7e9316..141dea60 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info @@ -55,9 +55,8 @@ features[variable][] = pathauto_taxonomy_term_pattern features_exclude[taxonomy][product_category] = product_category files[] = commerce_kickstart_lite_product.migrate.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info index 588dfa9e..96f70b5d 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info @@ -6,9 +6,8 @@ dependencies[] = toolbar_megamenu stylesheets[all][] = commerce_kickstart_menus.css -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.module b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.module index db9eab75..6b92cae7 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.module +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.module @@ -370,8 +370,8 @@ function commerce_kickstart_menus_menu_alter(&$items) { } // Show the Discounts link under "Store settings". - if (module_exists('commerce_discount') && isset($items['admin/commerce/store/discounts'])) { - $items['admin/commerce/store/discounts']['parent'] = 'admin/commerce/config/promotions'; + if (module_exists('commerce_discount') && isset($items['admin/commerce/discounts'])) { + $items['admin/commerce/discounts']['parent'] = 'admin/commerce/config/promotions'; } $advanced_store_items = array( 'admin/commerce/customer-profiles', @@ -492,26 +492,28 @@ function commerce_kickstart_menus_views_api() { * Implements hook_menu_breadcrumb_alter(). */ function commerce_kickstart_menus_menu_breadcrumb_alter(&$active_trail, $item) { - // Home > Administration > Content > Settings > Content types - if (drupal_match_path($item['path'], "admin/structure/types") || drupal_match_path($item['path'], "admin/structure/types/*")) { - if (drupal_match_path($item['path'], "admin/structure/types/add")) { - return; + if (isset($item['path'])) { + // Home > Administration > Content > Settings > Content types + if (drupal_match_path($item['path'], "admin/structure/types") || drupal_match_path($item['path'], "admin/structure/types/*")) { + if (drupal_match_path($item['path'], "admin/structure/types/add")) { + return; + } + $active_trail_end = array_slice($active_trail, 6); + $active_trail = array_slice($active_trail, 0, 1); + $active_trail[] = _commerce_kickstart_menus_get_item('admin'); + $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content'); + $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content/settings'); + $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content/settings/content-types'); + $active_trail = array_merge($active_trail, $active_trail_end); + } + // Set the breadcrumb for the "Add content" tab of node/add. + else if (drupal_match_path($item['path'], 'node/add')) { + $active_trail = array_slice($active_trail, 0, 1); + $active_trail[] = _commerce_kickstart_menus_get_item('admin'); + $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content'); + $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content/actions'); + $active_trail[] = _commerce_kickstart_menus_get_item('node/add/add-content'); } - $active_trail_end = array_slice($active_trail, 6); - $active_trail = array_slice($active_trail, 0, 1); - $active_trail[] = _commerce_kickstart_menus_get_item('admin'); - $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content'); - $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content/settings'); - $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content/settings/content-types'); - $active_trail = array_merge($active_trail, $active_trail_end); - } - // Set the breadcrumb for the "Add content" tab of node/add. - else if (drupal_match_path($item['path'], 'node/add')) { - $active_trail = array_slice($active_trail, 0, 1); - $active_trail[] = _commerce_kickstart_menus_get_item('admin'); - $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content'); - $active_trail[] = _commerce_kickstart_menus_get_item('admin/commerce/manage-content/actions'); - $active_trail[] = _commerce_kickstart_menus_get_item('node/add/add-content'); } } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_base.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_base.inc index 81359e38..16c1b297 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_base.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_base.inc @@ -10,7 +10,7 @@ function commerce_kickstart_merchandising_field_default_field_bases() { $field_bases = array(); - // Exported field_base: 'field_link' + // Exported field_base: 'field_link'. $field_bases['field_link'] = array( 'active' => 1, 'cardinality' => 1, @@ -37,7 +37,7 @@ function commerce_kickstart_merchandising_field_default_field_bases() { 'type' => 'link_field', ); - // Exported field_base: 'field_tagline' + // Exported field_base: 'field_tagline'. $field_bases['field_tagline'] = array( 'active' => 1, 'cardinality' => 1, diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_instance.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_instance.inc index 7900c2be..737413be 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_instance.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.features.field_instance.inc @@ -10,7 +10,7 @@ function commerce_kickstart_merchandising_field_default_field_instances() { $field_instances = array(); - // Exported field_instance: 'comment-comment_node_ad_push-comment_body' + // Exported field_instance: 'comment-comment_node_ad_push-comment_body'. $field_instances['comment-comment_node_ad_push-comment_body'] = array( 'bundle' => 'comment_node_ad_push', 'default_value' => NULL, @@ -43,7 +43,7 @@ function commerce_kickstart_merchandising_field_default_field_instances() { ), ); - // Exported field_instance: 'node-ad_push-field_image' + // Exported field_instance: 'node-ad_push-field_image'. $field_instances['node-ad_push-field_image'] = array( 'bundle' => 'ad_push', 'deleted' => 0, @@ -109,7 +109,7 @@ function commerce_kickstart_merchandising_field_default_field_instances() { ), ); - // Exported field_instance: 'node-ad_push-field_link' + // Exported field_instance: 'node-ad_push-field_link'. $field_instances['node-ad_push-field_link'] = array( 'bundle' => 'ad_push', 'default_value' => NULL, @@ -176,7 +176,7 @@ function commerce_kickstart_merchandising_field_default_field_instances() { ), ); - // Exported field_instance: 'node-ad_push-field_tagline' + // Exported field_instance: 'node-ad_push-field_tagline'. $field_instances['node-ad_push-field_tagline'] = array( 'bundle' => 'ad_push', 'default_value' => NULL, @@ -228,7 +228,7 @@ function commerce_kickstart_merchandising_field_default_field_instances() { ), ); - // Exported field_instance: 'node-ad_push-title_field' + // Exported field_instance: 'node-ad_push-title_field'. $field_instances['node-ad_push-title_field'] = array( 'bundle' => 'ad_push', 'default_value' => NULL, diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info index b684195d..ae8c337a 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info @@ -41,9 +41,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_merchandising.migrate.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.module b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.module index 6f884ef6..f5b507cf 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.module +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.module @@ -19,6 +19,10 @@ function commerce_kickstart_merchandising_migrate_api() { ), ); } + + // Return a near empty array to avoid API warning. + // @link https://www.drupal.org/node/2544320 + return array('api' => 2); } /** diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info index 22e67f1d..e60fe365 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info @@ -8,9 +8,8 @@ dependencies[] = commerce_migrate files[] = commerce_kickstart_migrate.migrate.inc files[] = plugins/destinations/fields.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info index 2ec1fab1..2a72cd96 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info @@ -8,9 +8,8 @@ dependencies[] = commerce_message dependencies[] = commerce_cart dependencies[] = ctools -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_base.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_base.inc index 29953c75..735b5e02 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_base.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_base.inc @@ -10,7 +10,7 @@ function commerce_kickstart_product_field_default_field_bases() { $field_bases = array(); - // Exported field_base: 'commerce_price' + // Exported field_base: 'commerce_price'. $field_bases['commerce_price'] = array( 'active' => 1, 'cardinality' => 1, @@ -32,7 +32,7 @@ function commerce_kickstart_product_field_default_field_bases() { 'type' => 'commerce_price', ); - // Exported field_base: 'field_bag_size' + // Exported field_base: 'field_bag_size'. $field_bases['field_bag_size'] = array( 'active' => 1, 'cardinality' => 1, @@ -53,12 +53,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_brand' + // Exported field_base: 'field_brand'. $field_bases['field_brand'] = array( 'active' => 1, 'cardinality' => 1, @@ -79,12 +80,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_category' + // Exported field_base: 'field_category'. $field_bases['field_category'] = array( 'active' => 1, 'cardinality' => 1, @@ -105,12 +107,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_category_color' + // Exported field_base: 'field_category_color'. $field_bases['field_category_color'] = array( 'active' => 1, 'cardinality' => 1, @@ -131,7 +134,7 @@ function commerce_kickstart_product_field_default_field_bases() { 'type' => 'text', ); - // Exported field_base: 'field_collection' + // Exported field_base: 'field_collection'. $field_bases['field_collection'] = array( 'active' => 1, 'cardinality' => 1, @@ -152,12 +155,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_color' + // Exported field_base: 'field_color'. $field_bases['field_color'] = array( 'active' => 1, 'cardinality' => 1, @@ -178,12 +182,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_gender' + // Exported field_base: 'field_gender'. $field_bases['field_gender'] = array( 'active' => 1, 'cardinality' => 1, @@ -204,12 +209,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_hat_size' + // Exported field_base: 'field_hat_size'. $field_bases['field_hat_size'] = array( 'active' => 1, 'cardinality' => 1, @@ -230,12 +236,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_images' + // Exported field_base: 'field_images'. $field_bases['field_images'] = array( 'active' => 1, 'cardinality' => -1, @@ -257,7 +264,7 @@ function commerce_kickstart_product_field_default_field_bases() { 'type' => 'image', ); - // Exported field_base: 'field_product' + // Exported field_base: 'field_product'. $field_bases['field_product'] = array( 'active' => 1, 'cardinality' => -1, @@ -278,7 +285,7 @@ function commerce_kickstart_product_field_default_field_bases() { 'type' => 'commerce_product_reference', ); - // Exported field_base: 'field_shoe_size' + // Exported field_base: 'field_shoe_size'. $field_bases['field_shoe_size'] = array( 'active' => 1, 'cardinality' => 1, @@ -299,12 +306,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_storage_capacity' + // Exported field_base: 'field_storage_capacity'. $field_bases['field_storage_capacity'] = array( 'active' => 1, 'cardinality' => 1, @@ -325,12 +333,13 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', ); - // Exported field_base: 'field_top_size' + // Exported field_base: 'field_top_size'. $field_bases['field_top_size'] = array( 'active' => 1, 'cardinality' => 1, @@ -351,6 +360,7 @@ function commerce_kickstart_product_field_default_field_bases() { 'parent' => 0, ), ), + 'options_list_callback' => 'title_taxonomy_allowed_values', ), 'translatable' => 0, 'type' => 'taxonomy_term_reference', diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_instance.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_instance.inc index a21391d8..7532f383 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_instance.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.features.field_instance.inc @@ -10,7 +10,7 @@ function commerce_kickstart_product_field_default_field_instances() { $field_instances = array(); - // Exported field_instance: 'commerce_product-bags_cases-commerce_price' + // Exported field_instance: 'commerce_product-bags_cases-commerce_price'. $field_instances['commerce_product-bags_cases-commerce_price'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -142,7 +142,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-bags_cases-field_bag_size' + // Exported field_instance: 'commerce_product-bags_cases-field_bag_size'. $field_instances['commerce_product-bags_cases-field_bag_size'] = array( 'bundle' => 'bags_cases', 'commerce_cart_settings' => array( @@ -218,7 +218,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-bags_cases-field_color' + // Exported field_instance: 'commerce_product-bags_cases-field_color'. $field_instances['commerce_product-bags_cases-field_color'] = array( 'bundle' => 'bags_cases', 'commerce_cart_settings' => array( @@ -294,7 +294,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-bags_cases-field_images' + // Exported field_instance: 'commerce_product-bags_cases-field_images'. $field_instances['commerce_product-bags_cases-field_images'] = array( 'bundle' => 'bags_cases', 'deleted' => 0, @@ -417,7 +417,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-bags_cases-title_field' + // Exported field_instance: 'commerce_product-bags_cases-title_field'. $field_instances['commerce_product-bags_cases-title_field'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -486,7 +486,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-drinks-commerce_price' + // Exported field_instance: 'commerce_product-drinks-commerce_price'. $field_instances['commerce_product-drinks-commerce_price'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -618,7 +618,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-drinks-field_color' + // Exported field_instance: 'commerce_product-drinks-field_color'. $field_instances['commerce_product-drinks-field_color'] = array( 'bundle' => 'drinks', 'commerce_cart_settings' => array( @@ -695,7 +695,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-drinks-field_images' + // Exported field_instance: 'commerce_product-drinks-field_images'. $field_instances['commerce_product-drinks-field_images'] = array( 'bundle' => 'drinks', 'deleted' => 0, @@ -818,7 +818,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-drinks-title_field' + // Exported field_instance: 'commerce_product-drinks-title_field'. $field_instances['commerce_product-drinks-title_field'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -887,7 +887,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-hats-commerce_price' + // Exported field_instance: 'commerce_product-hats-commerce_price'. $field_instances['commerce_product-hats-commerce_price'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -1019,7 +1019,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-hats-field_color' + // Exported field_instance: 'commerce_product-hats-field_color'. $field_instances['commerce_product-hats-field_color'] = array( 'bundle' => 'hats', 'commerce_cart_settings' => array( @@ -1100,7 +1100,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-hats-field_hat_size' + // Exported field_instance: 'commerce_product-hats-field_hat_size'. $field_instances['commerce_product-hats-field_hat_size'] = array( 'bundle' => 'hats', 'commerce_cart_settings' => array( @@ -1180,7 +1180,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-hats-field_images' + // Exported field_instance: 'commerce_product-hats-field_images'. $field_instances['commerce_product-hats-field_images'] = array( 'bundle' => 'hats', 'deleted' => 0, @@ -1303,7 +1303,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-hats-title_field' + // Exported field_instance: 'commerce_product-hats-title_field'. $field_instances['commerce_product-hats-title_field'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -1372,7 +1372,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-shoes-commerce_price' + // Exported field_instance: 'commerce_product-shoes-commerce_price'. $field_instances['commerce_product-shoes-commerce_price'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -1504,7 +1504,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-shoes-field_color' + // Exported field_instance: 'commerce_product-shoes-field_color'. $field_instances['commerce_product-shoes-field_color'] = array( 'bundle' => 'shoes', 'commerce_cart_settings' => array( @@ -1581,7 +1581,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-shoes-field_images' + // Exported field_instance: 'commerce_product-shoes-field_images'. $field_instances['commerce_product-shoes-field_images'] = array( 'bundle' => 'shoes', 'deleted' => 0, @@ -1704,7 +1704,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-shoes-field_shoe_size' + // Exported field_instance: 'commerce_product-shoes-field_shoe_size'. $field_instances['commerce_product-shoes-field_shoe_size'] = array( 'bundle' => 'shoes', 'commerce_cart_settings' => array( @@ -1780,7 +1780,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-shoes-title_field' + // Exported field_instance: 'commerce_product-shoes-title_field'. $field_instances['commerce_product-shoes-title_field'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -1849,7 +1849,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-storage_devices-commerce_price' + // Exported field_instance: 'commerce_product-storage_devices-commerce_price'. $field_instances['commerce_product-storage_devices-commerce_price'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -1981,7 +1981,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-storage_devices-field_images' + // Exported field_instance: 'commerce_product-storage_devices-field_images'. $field_instances['commerce_product-storage_devices-field_images'] = array( 'bundle' => 'storage_devices', 'deleted' => 0, @@ -2105,7 +2105,7 @@ function commerce_kickstart_product_field_default_field_instances() { ); // Exported field_instance: - // 'commerce_product-storage_devices-field_storage_capacity' + // 'commerce_product-storage_devices-field_storage_capacity'. $field_instances['commerce_product-storage_devices-field_storage_capacity'] = array( 'bundle' => 'storage_devices', 'commerce_cart_settings' => array( @@ -2180,7 +2180,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-storage_devices-title_field' + // Exported field_instance: 'commerce_product-storage_devices-title_field'. $field_instances['commerce_product-storage_devices-title_field'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -2249,7 +2249,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-tops-commerce_price' + // Exported field_instance: 'commerce_product-tops-commerce_price'. $field_instances['commerce_product-tops-commerce_price'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -2381,7 +2381,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-tops-field_color' + // Exported field_instance: 'commerce_product-tops-field_color'. $field_instances['commerce_product-tops-field_color'] = array( 'bundle' => 'tops', 'commerce_cart_settings' => array( @@ -2457,7 +2457,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-tops-field_images' + // Exported field_instance: 'commerce_product-tops-field_images'. $field_instances['commerce_product-tops-field_images'] = array( 'bundle' => 'tops', 'deleted' => 0, @@ -2580,7 +2580,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-tops-field_top_size' + // Exported field_instance: 'commerce_product-tops-field_top_size'. $field_instances['commerce_product-tops-field_top_size'] = array( 'bundle' => 'tops', 'commerce_cart_settings' => array( @@ -2656,7 +2656,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'commerce_product-tops-title_field' + // Exported field_instance: 'commerce_product-tops-title_field'. $field_instances['commerce_product-tops-title_field'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -2725,7 +2725,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-body' + // Exported field_instance: 'node-bags_cases-body'. $field_instances['node-bags_cases-body'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -2790,7 +2790,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-field_brand' + // Exported field_instance: 'node-bags_cases-field_brand'. $field_instances['node-bags_cases-field_brand'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -2848,7 +2848,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-field_category' + // Exported field_instance: 'node-bags_cases-field_category'. $field_instances['node-bags_cases-field_category'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -2905,7 +2905,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-field_collection' + // Exported field_instance: 'node-bags_cases-field_collection'. $field_instances['node-bags_cases-field_collection'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -2962,7 +2962,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-field_gender' + // Exported field_instance: 'node-bags_cases-field_gender'. $field_instances['node-bags_cases-field_gender'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -3019,7 +3019,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-field_product' + // Exported field_instance: 'node-bags_cases-field_product'. $field_instances['node-bags_cases-field_product'] = array( 'bundle' => 'bags_cases', 'deleted' => 0, @@ -3114,7 +3114,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-bags_cases-title_field' + // Exported field_instance: 'node-bags_cases-title_field'. $field_instances['node-bags_cases-title_field'] = array( 'bundle' => 'bags_cases', 'default_value' => NULL, @@ -3176,7 +3176,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-body' + // Exported field_instance: 'node-drinks-body'. $field_instances['node-drinks-body'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -3241,7 +3241,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-field_brand' + // Exported field_instance: 'node-drinks-field_brand'. $field_instances['node-drinks-field_brand'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -3299,7 +3299,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-field_category' + // Exported field_instance: 'node-drinks-field_category'. $field_instances['node-drinks-field_category'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -3356,7 +3356,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-field_collection' + // Exported field_instance: 'node-drinks-field_collection'. $field_instances['node-drinks-field_collection'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -3413,7 +3413,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-field_gender' + // Exported field_instance: 'node-drinks-field_gender'. $field_instances['node-drinks-field_gender'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -3469,7 +3469,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-field_product' + // Exported field_instance: 'node-drinks-field_product'. $field_instances['node-drinks-field_product'] = array( 'bundle' => 'drinks', 'deleted' => 0, @@ -3563,7 +3563,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-drinks-title_field' + // Exported field_instance: 'node-drinks-title_field'. $field_instances['node-drinks-title_field'] = array( 'bundle' => 'drinks', 'default_value' => NULL, @@ -3626,7 +3626,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-body' + // Exported field_instance: 'node-hats-body'. $field_instances['node-hats-body'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -3691,7 +3691,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-field_brand' + // Exported field_instance: 'node-hats-field_brand'. $field_instances['node-hats-field_brand'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -3749,7 +3749,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-field_category' + // Exported field_instance: 'node-hats-field_category'. $field_instances['node-hats-field_category'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -3806,7 +3806,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-field_collection' + // Exported field_instance: 'node-hats-field_collection'. $field_instances['node-hats-field_collection'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -3863,7 +3863,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-field_gender' + // Exported field_instance: 'node-hats-field_gender'. $field_instances['node-hats-field_gender'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -3920,7 +3920,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-field_product' + // Exported field_instance: 'node-hats-field_product'. $field_instances['node-hats-field_product'] = array( 'bundle' => 'hats', 'deleted' => 0, @@ -4015,7 +4015,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-hats-title_field' + // Exported field_instance: 'node-hats-title_field'. $field_instances['node-hats-title_field'] = array( 'bundle' => 'hats', 'default_value' => NULL, @@ -4077,7 +4077,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-body' + // Exported field_instance: 'node-shoes-body'. $field_instances['node-shoes-body'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -4142,7 +4142,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-field_brand' + // Exported field_instance: 'node-shoes-field_brand'. $field_instances['node-shoes-field_brand'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -4200,7 +4200,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-field_category' + // Exported field_instance: 'node-shoes-field_category'. $field_instances['node-shoes-field_category'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -4257,7 +4257,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-field_collection' + // Exported field_instance: 'node-shoes-field_collection'. $field_instances['node-shoes-field_collection'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -4314,7 +4314,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-field_gender' + // Exported field_instance: 'node-shoes-field_gender'. $field_instances['node-shoes-field_gender'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -4371,7 +4371,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-field_product' + // Exported field_instance: 'node-shoes-field_product'. $field_instances['node-shoes-field_product'] = array( 'bundle' => 'shoes', 'deleted' => 0, @@ -4466,7 +4466,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-shoes-title_field' + // Exported field_instance: 'node-shoes-title_field'. $field_instances['node-shoes-title_field'] = array( 'bundle' => 'shoes', 'default_value' => NULL, @@ -4528,7 +4528,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-body' + // Exported field_instance: 'node-storage_devices-body'. $field_instances['node-storage_devices-body'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -4593,7 +4593,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-field_brand' + // Exported field_instance: 'node-storage_devices-field_brand'. $field_instances['node-storage_devices-field_brand'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -4651,7 +4651,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-field_category' + // Exported field_instance: 'node-storage_devices-field_category'. $field_instances['node-storage_devices-field_category'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -4708,7 +4708,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-field_collection' + // Exported field_instance: 'node-storage_devices-field_collection'. $field_instances['node-storage_devices-field_collection'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -4765,7 +4765,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-field_gender' + // Exported field_instance: 'node-storage_devices-field_gender'. $field_instances['node-storage_devices-field_gender'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -4822,7 +4822,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-field_product' + // Exported field_instance: 'node-storage_devices-field_product'. $field_instances['node-storage_devices-field_product'] = array( 'bundle' => 'storage_devices', 'deleted' => 0, @@ -4909,7 +4909,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-storage_devices-title_field' + // Exported field_instance: 'node-storage_devices-title_field'. $field_instances['node-storage_devices-title_field'] = array( 'bundle' => 'storage_devices', 'default_value' => NULL, @@ -4971,7 +4971,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-body' + // Exported field_instance: 'node-tops-body'. $field_instances['node-tops-body'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -5034,7 +5034,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-field_brand' + // Exported field_instance: 'node-tops-field_brand'. $field_instances['node-tops-field_brand'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -5092,7 +5092,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-field_category' + // Exported field_instance: 'node-tops-field_category'. $field_instances['node-tops-field_category'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -5149,7 +5149,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-field_collection' + // Exported field_instance: 'node-tops-field_collection'. $field_instances['node-tops-field_collection'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -5206,7 +5206,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-field_gender' + // Exported field_instance: 'node-tops-field_gender'. $field_instances['node-tops-field_gender'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -5263,7 +5263,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-field_product' + // Exported field_instance: 'node-tops-field_product'. $field_instances['node-tops-field_product'] = array( 'bundle' => 'tops', 'deleted' => 0, @@ -5358,7 +5358,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'node-tops-title_field' + // Exported field_instance: 'node-tops-title_field'. $field_instances['node-tops-title_field'] = array( 'bundle' => 'tops', 'default_value' => NULL, @@ -5420,7 +5420,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'taxonomy_term-category-field_category_color' + // Exported field_instance: 'taxonomy_term-category-field_category_color'. $field_instances['taxonomy_term-category-field_category_color'] = array( 'bundle' => 'category', 'default_value' => array( @@ -5464,7 +5464,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'taxonomy_term-collection-field_image' + // Exported field_instance: 'taxonomy_term-collection-field_image'. $field_instances['taxonomy_term-collection-field_image'] = array( 'bundle' => 'collection', 'deleted' => 0, @@ -5523,7 +5523,7 @@ function commerce_kickstart_product_field_default_field_instances() { ), ); - // Exported field_instance: 'taxonomy_term-color-field_category_color' + // Exported field_instance: 'taxonomy_term-color-field_category_color'. $field_instances['taxonomy_term-color-field_category_color'] = array( 'bundle' => 'color', 'default_value' => NULL, diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info index f7f2b7a8..659f5215 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info @@ -230,9 +230,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_product.migrate.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info index 023d2226..b6a2625b 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info @@ -7,9 +7,8 @@ dependencies[] = commerce_product dependencies[] = views dependencies[] = libraries -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info index 92673c72..f3ad9bd1 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info @@ -2,9 +2,8 @@ name = Commerce Kickstart Reset core = 7.x package = Commerce Kickstart -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info index 5ca57242..21f04581 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info @@ -15,9 +15,8 @@ dependencies[] = search_api_views dependencies[] = views scripts[] = commerce_kickstart_search.js -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_base.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_base.inc index 07be029c..427e7fdb 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_base.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_base.inc @@ -10,7 +10,7 @@ function commerce_kickstart_slideshow_field_default_field_bases() { $field_bases = array(); - // Exported field_base: 'comment_body' + // Exported field_base: 'comment_body'. $field_bases['comment_body'] = array( 'active' => 1, 'cardinality' => 1, @@ -31,7 +31,7 @@ function commerce_kickstart_slideshow_field_default_field_bases() { 'type' => 'text_long', ); - // Exported field_base: 'field_headline' + // Exported field_base: 'field_headline'. $field_bases['field_headline'] = array( 'active' => 1, 'cardinality' => 1, @@ -52,7 +52,7 @@ function commerce_kickstart_slideshow_field_default_field_bases() { 'type' => 'text', ); - // Exported field_base: 'field_link' + // Exported field_base: 'field_link'. $field_bases['field_link'] = array( 'active' => 1, 'cardinality' => 1, @@ -79,7 +79,7 @@ function commerce_kickstart_slideshow_field_default_field_bases() { 'type' => 'link_field', ); - // Exported field_base: 'field_tagline' + // Exported field_base: 'field_tagline'. $field_bases['field_tagline'] = array( 'active' => 1, 'cardinality' => 1, diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_instance.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_instance.inc index 0263556c..19ebe96c 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_instance.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.features.field_instance.inc @@ -10,7 +10,7 @@ function commerce_kickstart_slideshow_field_default_field_instances() { $field_instances = array(); - // Exported field_instance: 'comment-comment_node_slideshow-comment_body' + // Exported field_instance: 'comment-comment_node_slideshow-comment_body'. $field_instances['comment-comment_node_slideshow-comment_body'] = array( 'bundle' => 'comment_node_slideshow', 'default_value' => NULL, @@ -43,7 +43,7 @@ function commerce_kickstart_slideshow_field_default_field_instances() { ), ); - // Exported field_instance: 'node-slideshow-field_headline' + // Exported field_instance: 'node-slideshow-field_headline'. $field_instances['node-slideshow-field_headline'] = array( 'bundle' => 'slideshow', 'default_value' => NULL, @@ -95,7 +95,7 @@ function commerce_kickstart_slideshow_field_default_field_instances() { ), ); - // Exported field_instance: 'node-slideshow-field_image' + // Exported field_instance: 'node-slideshow-field_image'. $field_instances['node-slideshow-field_image'] = array( 'bundle' => 'slideshow', 'deleted' => 0, @@ -161,7 +161,7 @@ function commerce_kickstart_slideshow_field_default_field_instances() { ), ); - // Exported field_instance: 'node-slideshow-field_link' + // Exported field_instance: 'node-slideshow-field_link'. $field_instances['node-slideshow-field_link'] = array( 'bundle' => 'slideshow', 'default_value' => NULL, @@ -228,7 +228,7 @@ function commerce_kickstart_slideshow_field_default_field_instances() { ), ); - // Exported field_instance: 'node-slideshow-field_tagline' + // Exported field_instance: 'node-slideshow-field_tagline'. $field_instances['node-slideshow-field_tagline'] = array( 'bundle' => 'slideshow', 'default_value' => NULL, @@ -280,7 +280,7 @@ function commerce_kickstart_slideshow_field_default_field_instances() { ), ); - // Exported field_instance: 'node-slideshow-title_field' + // Exported field_instance: 'node-slideshow-title_field'. $field_instances['node-slideshow-title_field'] = array( 'bundle' => 'slideshow', 'default_value' => NULL, diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info index 4914ac21..9ae7ab7f 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info @@ -50,9 +50,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_slideshow.migrate.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_custom.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_custom.inc index 1e02ae6c..dc233c5f 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_custom.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_custom.inc @@ -21,6 +21,5 @@ function commerce_kickstart_social_menu_default_menu_custom() { t('Connect with us'); t('The "Connect with us" menu.'); - return $menus; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_links.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_links.inc index 873af166..9f101930 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_links.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.features.menu_links.inc @@ -10,7 +10,7 @@ function commerce_kickstart_social_menu_default_menu_links() { $menu_links = array(); - // Exported menu link: menu-social-connection_follow-us-on-twitter:https://twitter.com/commerceguys + // Exported menu link: menu-social-connection_follow-us-on-twitter:https://twitter.com/commerceguys. $menu_links['menu-social-connection_follow-us-on-twitter:https://twitter.com/commerceguys'] = array( 'menu_name' => 'menu-social-connection', 'link_path' => 'https://twitter.com/commerceguys', @@ -34,7 +34,7 @@ function commerce_kickstart_social_menu_default_menu_links() { 'weight' => -49, 'customized' => 1, ); - // Exported menu link: menu-social-connection_like-us-on-facebook:https://www.facebook.com/commerceguys + // Exported menu link: menu-social-connection_like-us-on-facebook:https://www.facebook.com/commerceguys. $menu_links['menu-social-connection_like-us-on-facebook:https://www.facebook.com/commerceguys'] = array( 'menu_name' => 'menu-social-connection', 'link_path' => 'https://www.facebook.com/commerceguys', @@ -58,7 +58,7 @@ function commerce_kickstart_social_menu_default_menu_links() { 'weight' => -50, 'customized' => 1, ); - // Exported menu link: menu-social-connection_what-we-like-on-pinterest:http://pinterest.com/ + // Exported menu link: menu-social-connection_what-we-like-on-pinterest:http://pinterest.com/. $menu_links['menu-social-connection_what-we-like-on-pinterest:http://pinterest.com/'] = array( 'menu_name' => 'menu-social-connection', 'link_path' => 'http://pinterest.com/', @@ -82,12 +82,12 @@ function commerce_kickstart_social_menu_default_menu_links() { 'weight' => -48, 'customized' => 1, ); + // Translatables // Included for use with string extractors like potx. t('Follow Us on Twitter'); t('Like us on Facebook'); t('What We Like on Pinterest'); - return $menu_links; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info index c41f6d69..513a0d0d 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info @@ -2,7 +2,6 @@ name = Commerce Kickstart Social description = Social features for Commerce kickstart. core = 7.x package = Commerce Kickstart -php = 5.2.4 dependencies[] = block dependencies[] = connector_action_default_register_form dependencies[] = ctools @@ -33,9 +32,8 @@ features[variable][] = service_links_show features[variable][] = service_links_style features[variable][] = service_links_visibility_for_node -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.strongarm.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.strongarm.inc index 6cdaa0fa..356f7424 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.strongarm.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.strongarm.inc @@ -31,6 +31,33 @@ function commerce_kickstart_social_strongarm() { $strongarm->value = ''; $export['service_links_label_in_node'] = $strongarm; + $strongarm = new stdClass(); + $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'service_links_node_types'; + $strongarm->value = array( + 0 => 'bags_cases', + 1 => 'drinks', + 2 => 'hats', + 3 => 'shoes', + 4 => 'storage_devices', + 5 => 'tops', + ); + $export['service_links_node_types'] = $strongarm; + + $strongarm = new stdClass(); + $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'service_links_node_view_modes'; + $strongarm->value = array( + 'full' => 'full', + 'teaser' => 0, + 'rss' => 0, + 'token' => 0, + 'product_list' => 0, + ); + $export['service_links_node_view_modes'] = $strongarm; + $strongarm = new stdClass(); $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ $strongarm->api_version = 1; @@ -56,25 +83,5 @@ function commerce_kickstart_social_strongarm() { $strongarm->value = '0'; $export['service_links_visibility_for_node'] = $strongarm; - $strongarm = new stdClass(); - $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ - $strongarm->api_version = 1; - $strongarm->name = 'service_links_node_types'; - $strongarm->value = array_keys(commerce_product_reference_node_types()); - $export['service_links_node_types'] = $strongarm; - - $strongarm = new stdClass(); - $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ - $strongarm->api_version = 1; - $strongarm->name = 'service_links_node_view_modes'; - $strongarm->value = array( - 'full' => 'full', - 'teaser' => 0, - 'rss' => 0, - 'token' => 0, - 'product_list' => 0, - ); - $export['service_links_node_view_modes'] = $strongarm; - return $export; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info index e3255ccb..aeb79ee9 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info @@ -9,9 +9,8 @@ dependencies[] = views ; Views files files[] = includes/views/handlers/commerce_kickstart_taxonomy_handler_field_text.inc -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.module b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.module index 5dfd0bb7..e04982af 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.module +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.module @@ -98,7 +98,7 @@ function commerce_kickstart_taxonomy_term_path($vid, $tid) { } else { $path = $vocabulary->machine_name . '/' . $tid; - if (variable_get(_taxonomy_menu_build_variable('display_decendants', $vid), FALSE)) { + if (variable_get(_taxonomy_menu_build_variable('display_descendants', $vid), FALSE)) { // Use 'all' at the end of the path if (variable_get(_taxonomy_menu_build_variable('end_all', $vid), FALSE)) { $path .= '/all'; diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/includes/views/commerce_kickstart_taxonomy.views_default.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/includes/views/commerce_kickstart_taxonomy.views_default.inc index d6fde2f6..67cdea45 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/includes/views/commerce_kickstart_taxonomy.views_default.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/includes/views/commerce_kickstart_taxonomy.views_default.inc @@ -199,6 +199,10 @@ function commerce_kickstart_taxonomy_views_default_views() { * Add specific overrides for the no demo store. */ function commerce_kickstart_taxonomy_views_default_views_alter(&$views) { + if (!isset($views['collection_products'])) { + return; + } + $install_demo_store = variable_get('commerce_kickstart_demo_store', FALSE); if (empty($install_demo_store)) { $view = &$views['collection_products']; diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_custom.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_custom.inc index ad83b5b6..91cb5a00 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_custom.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_custom.inc @@ -20,6 +20,5 @@ function commerce_kickstart_user_menu_default_menu_custom() { // Included for use with string extractors like potx. t('Kickstart User menu'); - return $menus; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_links.inc b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_links.inc index 8ab65d91..709e757b 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_links.inc +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.features.menu_links.inc @@ -10,7 +10,7 @@ function commerce_kickstart_user_menu_default_menu_links() { $menu_links = array(); - // Exported menu link: menu-user-menu_create-account:user/register + // Exported menu link: menu-user-menu_create-account:user/register. $menu_links['menu-user-menu_create-account:user/register'] = array( 'menu_name' => 'menu-user-menu', 'link_path' => 'user/register', @@ -28,7 +28,7 @@ function commerce_kickstart_user_menu_default_menu_links() { 'weight' => 2, 'customized' => 1, ); - // Exported menu link: menu-user-menu_log-in:user/login + // Exported menu link: menu-user-menu_log-in:user/login. $menu_links['menu-user-menu_log-in:user/login'] = array( 'menu_name' => 'menu-user-menu', 'link_path' => 'user/login', @@ -46,7 +46,7 @@ function commerce_kickstart_user_menu_default_menu_links() { 'weight' => 1, 'customized' => 1, ); - // Exported menu link: menu-user-menu_log-out:user/logout + // Exported menu link: menu-user-menu_log-out:user/logout. $menu_links['menu-user-menu_log-out:user/logout'] = array( 'menu_name' => 'menu-user-menu', 'link_path' => 'user/logout', @@ -64,7 +64,7 @@ function commerce_kickstart_user_menu_default_menu_links() { 'weight' => 4, 'customized' => 1, ); - // Exported menu link: menu-user-menu_my-account:user + // Exported menu link: menu-user-menu_my-account:user. $menu_links['menu-user-menu_my-account:user'] = array( 'menu_name' => 'menu-user-menu', 'link_path' => 'user', @@ -83,6 +83,7 @@ function commerce_kickstart_user_menu_default_menu_links() { 'weight' => 3, 'customized' => 1, ); + // Translatables // Included for use with string extractors like potx. t('Create account'); @@ -90,6 +91,5 @@ function commerce_kickstart_user_menu_default_menu_links() { t('Log out'); t('My account'); - return $menu_links; } diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info index 09446aed..b5de2b22 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info @@ -39,9 +39,8 @@ features[variable][] = user_email_verification features[variable][] = user_pictures features[variable][] = user_register -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info b/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info index 45ce985b..186aa679 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info @@ -3,9 +3,8 @@ description = "Extension of Toolbar to support dropdown megamenus." core = 7.x dependencies[] = toolbar -; Information added by Drupal.org packaging script on 2015-08-20 -version = "7.x-2.28" +; Information added by Drupal.org packaging script on 2018-10-18 +version = "7.x-2.55" core = "7.x" project = "commerce_kickstart" -datestamp = "1440110572" - +datestamp = "1539873597" diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addresses.txt b/profiles/commerce_kickstart/modules/contrib/addressfield/addresses.txt index e64b8202..1f11bd89 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addresses.txt +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addresses.txt @@ -1,298 +1,298 @@ -AT NULL Feldkirch NULL 6800 Pater Grimm Weg 20 -AU NULL Melbourne NULL -AU NULL Sydney NULL -AU 04 NULL NORMANBY NULL 4059 30 Normanby Terrace -BD NULL Dhaka NULL 1205 23, Subal Das Road, Chowdhury Bazar, Lalbagh -BD NULL Dhaka NULL 1207 R-1,H-19,Kallaynpur,Mirpur,Dhaka -BD NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar -BD NULL Dhaka NULL 1209 House# 66B, Flat# B2 Zigatola -BD NULL Dhaka NULL 1219 390 West Rampura Dhaka -BD NULL Dhaka NULL 1230 Uttara -BD 81 NULL Dhaka NULL 1000 Institute of Water and Flood Management -BD 81 NULL Dhaka NULL 1203 84/a maniknagar -BD 81 NULL Dhaka NULL 1205 Dhaka Bangladesh -BD 81 NULL Dhaka NULL 1207 BetterStories Limited 17 West Panthopath -BD 81 NULL Dhaka NULL 1216 Mirpur, Dhaka -BD 81 NULL Dhaka NULL 1230 830, Prembagan, Dhakshin Khan -BD 82 NULL khulna NULL 9203 -BD NULL NULL Dhaka NULL 1000 Institute of Water and Flood Management -BD NULL NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar -BE NULL Brussels NULL -BE NULL Watermael-Boitsfort NULL 1170 Avenue des Staphylins -BH NULL Manama NULL 00973 Manama Bahrain Manama Bahrain -BR NULL Porto Alegre NULL -BR NULL Recife NULL -BR RJ NULL Rio de Janeiro NULL -BW NULL Francistown NULL NULL -BW NULL NULL Francistown NULL NULL -CA NULL Montreal NULL -CA NULL Toronto NULL -CA BC NULL Vancouver NULL -CA ON NULL Kitchener NULL -CA ON NULL wterloo NULL n2l3g1 200 University Avenue West -CH NULL Geneva NULL 1202 15, chemin Louis-Dunant -CH 25 NULL Zurich NULL 8098 UBS Optimus Foundation Augustinerhof 1 -DE NULL Berlin NULL -DE 05 NULL Frankfurt am Main NULL 60386 Johanna-Tesch-Platz 7 -DK NULL Aarhus NULL -ES NULL Bilbao NULL -ET 44 NULL ADDIS ABABA NULL 11945 ADDIS ABABA,P.O.BOX 11945 -FI NULL Espoo NULL 02130 Mahlarinne 3B -FI NULL Helsinki NULL 00580 Hermannin rantatie 2 A Hermannin rantatie 2 A -FI NULL Tampere NULL 33101 Tampere Univerity of Technology -FI 13 NULL Espoo NULL 02150 Aalto Venture Garage Betonimiehenkuja 3 -GB NULL Exeter NULL -GB NULL London NULL -GB NULL London NULL N4 2DP 2 Myddleton Ave -GB NULL London NULL N7 0AH 104 St George’s Avenue -GB NULL London NULL SE16 3UL 25 Blue Anchor Lane -GB NULL London NULL SW18 5SP Flat 1 150 Merton road -GB NULL London NULL W1T 4BQ 13 Fitzroy Street -GB NULL Oxford NULL -GB NULL Southampton NULL -GB C3 NULL NULL cb244qg 32 market street swavesey -GB E7 NULL London NULL SE3 7TP -GB F3 NULL Wood Green NULL N22 5RU 6 Cedar House -GB H1 NULL London NULL SE11 5JD 47-49 Durham Street -GB H6 NULL London NULL SE8 4DD 8 Harton St Deptford -GB K2 NULL Oxford NULL OX2 6QY 3 The Villas, Rutherway -GH 05 NULL NSAWAM NULL NULL P.O.BOX 455 -GH NULL NULL Accra NULL NULL -ID NULL Bandung NULL 40134 Jalan Sadang Hegar 1 No. 12 RT04 RW13 Sadang Serang -ID NULL Bekasi NULL 17411 Jl.Binadharma 1 No.62. Jatiwaringin -ID NULL Jakarta NULL -ID NULL Jakarta NULL 12440 Jl. H. Niin 7 Lebak Bulus, Cilandak -ID NULL Jakarta NULL 13330 Otista -ID NULL Jakarta selatan NULL 12000 jl. rawa jati timur 6 no. 10 -ID NULL Jakarta Timur NULL Jl.Mulia No.15B Kel.Bidara Cina, Kec.Jatinegara, Jakarta Timur -ID NULL Pematang Siantar NULL 51511 Jl. Durian I 30 -ID 04 NULL Bogor NULL 16165 -ID 04 NULL jakarta NULL otista -ID 04 NULL Jakarta NULL 12520 Jl. Pertanian Raya III No.42 Jakarta Selatan Pasar Minggu -ID 04 NULL Jakarta NULL 13330 Jakarta -ID 04 NULL Jakarta NULL 13330 Jl Sensus IIC Bidaracina Jaktim -ID 04 NULL Jakarta NULL 13330 Jl. Bonasut 2 no.22 -ID 04 NULL Jakarta NULL 13330 Otista 64c -ID 04 NULL jakarta NULL 13330 Otista jaktim -ID 04 NULL Jakarta Timur NULL 13330 Kebon Sayur I no. 1 RT 10/15 -ID 04 NULL Jakarta Timur NULL 13460 Jl. Pondok Kopi Blok G4/5 RT. 005/08 Jakarta Timur -ID 04 NULL Jakarta Timur NULL 13810 Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur -ID 07 NULL Brebes NULL 54321 Jl Kersem Blok D14 Perum Taman Indo Kaligangsa Wetan Brebes -ID 07 NULL Semarang NULL 50143 Puspowarno Tengah 2/2 -ID 08 NULL Lumajang NULL 67373 Desa Tumpeng Kecamatan Candipuro Lumajang -ID 30 NULL Bandung NULL 55241 Jl Pelesiran No 55A/56 -ID 30 NULL Bekasi NULL 17510 bekasi West Java Indonesia -ID 30 NULL Depok NULL 16245 Jalan juragan sinda 2 no 10 -ID 30 NULL Depok NULL 16424 Jalan Margonda RayaJalan Kober Gang Mawar -ID 30 NULL Depok NULL 16424 Jl. Haji Yahya Nuih no.24, Pondok Cina -ID 30 NULL Depok NULL 16425 Kukusan Kelurahan -ID 30 NULL Depok NULL 16518 Jl. Salak No.1/C.88 Durenseribu Bojongsari -ID 30 NULL Depok NULL 16952 Jl. Merak No.34 -36 Rt.004/014 Jl. Merak No. 34 -36 Rt. 004/014 -ID 36 NULL biak numfor NULL 98111 jl. s. mamberamo no 6782 biak numfor -IL NULL Tel Aviv NULL -IN NULL Bangalore NULL -IN NULL India NULL -IN NULL new delhi NULL 110003 55 lodi estate -IN 07 NULL NEW DELHI NULL 110018 15/11 A 1ST FLOOR TILAK NAGAR -IN 07 NULL New Delhu NULL 110075 B 54 Hilansh Apartments Plot No 1, Sector 10, Dwarka -IN 10 NULL Gurgaon NULL D- 201 Ivy Apartments Sushant Lok 1 Gurgaon Haryana -IN 13 NULL Trivandrum NULL 695010 TC 9/1615, SRMH Road, Sasthamangalam, Trivandrum -IN 16 NULL Mumbai NULL 400020 Bharat Mahal, Flat#55 Marine Drive -IN 16 NULL Mumbai NULL 400028 303,Shree Parvati Co-Op Housing Society, D.L.Vaidya Road,Dadar -IN 16 NULL Pune NULL -IN 16 NULL Pune NULL Infosys Campus Hinjewadi Phase 2 -IN 16 NULL Pune NULL 400705 #22 Iris Garden Gokhale Road -IN 16 NULL PUNE NULL 411043 -IN 16 NULL Pune NULL 411051 -IN 16 NULL Pune NULL 411057 Infosys Ltd. Rajiv gandhi infostech park Hinjewadi phase 2 -IN 16 NULL Pune NULL 412108 Pune Maharatshtra -IN 16 NULL Pune NULL 433011 502 utkarsh vihar golande state pune -IN 19 NULL Bangalore NULL 560080 Indian Institute for Human Settlements IIHS Bangalore City Campus, Sadashivanagar, -IN 19 NULL Bangalore NULL 560100 electronic city -IN 19 NULL Bhalki NULL 585411 bhalki,bidar ,karnataka karnataka -IN 24 NULL Jaipur NULL 302011 Institute of Health Management Research 1, Prabhu Dayal Marg -IR 26 NULL Tehran NULL 1118844454 Baharestan sq. mostafa khomeini str., javahery Ave., no. 11, -IT NULL Trento NULL -JM 08 NULL Kingston NULL Kgn 7 MOna Campus UWI -KE NULL Nairobi NULL -KE 05 NULL Nairobi NULL 30300 212,kapsabet -KH NULL NULL Phnom Penh NULL -LR NULL Monrovia NULL 00000 -NG 11 NULL Abuja NULL 930001 17 Bechar street Wuse zone 2 -PE 15 NULL Lima NULL 18 Lima Lima -PE 15 NULL Lima NULL Lima 18 123 Miraflores -PE NULL NULL Lima NULL 03 Calle Granada 104 -PE NULL NULL Lima NULL 18 Lima Lima -PH NULL Manila NULL Globe Telepark 111 Valero Street -PH NULL Quezon Coty NULL 1109 86 Harvard Street, Cubao, Quezon City, Philippines 84 Harvard Street, Cubao, Quezon City,hilippines -PH 20 NULL Silang NULL 4118 370 Bayungan Kaong Silang Cavite -PH 57 NULL Kidapawan NULL 9400 Kidapawan City Kidapawan City -PH 66 NULL zamboanga NULL 7000 29-tripplet rd san jose 29-tripplet rd san jose -PH D9 NULL Pasig City NULL World Bank Office Manila, 20/F Taipan Place F. Ortigas Jr. Road, Ortigas Center -PK NULL Lahore NULL 54000 17-R Model Town Lahore -PK NULL Lahore NULL 54000 53- chamber lane road Lahore -PK NULL Lahore NULL 54000 85 E block Model Town -PK NULL Lahore NULL 54000 House no 227, street no 5, Imamia Colony Shahadra Lahore -PK NULL LAHORE NULL 54000 room no.6 khalid bim waleed hall, near New Anarkali, LAHORE room no.6 khalid bim waleed hall, near New Anarkali, LAHORE -PK NULL Lahore NULL pk097 LUMS, Lahore, -PK NULL Sheikhupura NULL 03935 D.H.Q.Hospital Sheikhupura House number 08. Room no 109 Khalid bin waleed haal, punjab University lahore old campus. -PK 02 NULL Quetta NULL 87000 Postal Address 87000, Kuchlak, Quetta, Balochistan. H#24 Peer Abul Khair road Quetta, Balochistan. -PK 02 NULL Quetta NULL 87300 block no-1 falt no. 7 New Crime Branch Abbas Ali Road Cantt -PK 02 NULL Quetta NULL 87300 Flat no. 3 Shafeen Centre Jinnah Town ,Near I.T university , Quetta -PK 02 NULL Quetta NULL 87300 H-no. C-220 Zarghoonabad Phase-2 , Nawa Killi ,Quetta -PK 04 NULL burewala NULL 60101 Fatima Fayyaz Hazrat Sakina hall girls hostel number 9 Punjab university Lahore Pakistan Sardar Wajid Azim Azeem abad Burewala dist Vehari Pakistan -PK 04 NULL Faisalabad NULL 38000 P 101/1, Green Town, Millat Road, Faisalabad -PK 04 NULL Islamabad NULL 44000 P.O Tarlai kalan chappar Islamabad -PK 04 NULL lahore NULL -PK 04 NULL Lahore NULL 54000 -PK 04 NULL lahore NULL 54000 Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. -PK 04 NULL Lahore NULL 54000 1149-1-D2 Green Town Lahore -PK 04 NULL Lahore NULL 54000 124, street# 2, karim block Allama Iqbal Town lahore. 124, street# 2, karim block Allama Iqbal Town lahore. -PK 04 NULL Lahore NULL 54000 150 A Qila Lachman Singh Ravi Road lahore -PK 04 NULL Lahore NULL 54000 166/1L DHA Lahore -PK 04 NULL Lahore NULL 54000 172 A2 Township Lahore -PK 04 NULL Lahore NULL 54000 183,S/Block, Model Town, Lhr -PK 04 NULL lahore NULL 54000 19- A block ,Eden Lane Villas Raiwind Road ,Lahore -PK 04 NULL lahore NULL 54000 3-c kaliyar road opposite kids lyceum, rustam park near mor samnabad -PK 04 NULL Lahore NULL 54000 31 Saeed Block, Canal Bank Scheme -PK 04 NULL Lahore NULL 54000 31c DHA Lahore -PK 04 NULL Lahore NULL 54000 387 E1 wapda town, Lahore -PK 04 NULL Lahore NULL 54000 45-D dha eme sector multan road,lahore -PK 04 NULL Lahore NULL 54000 5 Zafar Ali Road -PK 04 NULL Lahore NULL 54000 54-R PGECHS -PK 04 NULL lahore NULL 54000 566 E-1 johar town lahore 566 E-1 johar town lahore -PK 04 NULL Lahore NULL 54000 82/1 Z Block, Phase 3 DHA -PK 04 NULL Lahore NULL 54000 A-1 VRI Zarrar shaheed road lahore cantt A-1 VRI Zarrar shaheed road lahore cantt -PK 04 NULL lahore NULL 54000 e5/39D street 6 zaman colony cavalry ground ext -PK 04 NULL Lahore NULL 54000 Ho # 61, Block G3, Johar Town Lahore -PK 04 NULL LAhore NULL 54000 House #19-A street #5 Usman nagr Ghaziabad Lahore -PK 04 NULL lahore NULL 54000 House no 692 street no 67 sadar bazar -PK 04 NULL Lahore NULL 54000 Khosa Law Chamber 1 Turner Road -PK 04 NULL Lahore NULL 54000 Lahore,Pakistan Lahore,Pakistan -PK 04 NULL Lahore NULL 54000 room no 69, khalid bin waleed hall, anarkali -PK 04 NULL Lahore NULL 54000 Suite # 8, Al-Hafeez Suites, Gulberg II -PK 04 NULL Lahore NULL 54085 199 Shadman 2 -PK 04 NULL Lahore NULL 54300 Mughalpura Lahore Pakistan -PK 04 NULL Lahore NULL 54660 SD 69 falcon complex gulberg III lahore -PK 04 NULL lahore NULL 54800 764-G4 johar town ,lahore -PK 04 NULL Rawalpindi NULL 44000 House 522, F-Block Sattellite Town, Rawalpindi -PK 04 NULL Rawalpindi NULL 46000 1950/c, Indusroad 2, Tariqabad, Rawalpindi Cantt -PK 04 NULL Rawalpindi NULL 46000 House 54-E Lane 9 Sector 4, AECHS Chaklala Rawalpindi -PK 04 NULL Rawalpindi NULL 46000 House B-1343, Sattellite town Rawalpindi -PK 04 NULL Rawalpindi NULL 46000 House CB-299F, Street 1, Lane 4 Peshawar Road Rawalpindi -PK 04 NULL Rawalpindi NULL 46300 House No 1518 Umer Block phase 8 BehriaTown -PK 04 NULL sialkot NULL 51310 The National Model School, Ismaiealabad, Pacca Garah Sialkot -PK 08 NULL Islamabad NULL CIomsats Institute of Information Technology Islamabad -PK 08 NULL Islamabad NULL 38700 COMSATS tarlai boys hostel Islamabad. COMSATS tarlai boys hostel Islamabad (Room 30) -PK 08 NULL Islamabad NULL 44000 -PK 08 NULL Islamabad NULL 44000 House # 256, Street # 9, Shahzad Town, Islamabad. -PK 08 NULL Islamabad NULL 44000 Islamabad , Comsats University Islamabd ,Pakistan -PK 08 NULL Islamabad NULL 44000 World Bank Building Sector G 5 -PK 08 NULL lahore NULL 54000 3c zafar ali road gulburg 5 3c zafar ali road gulburg 5 -PK 08 NULL lahore NULL 54000 49-a bilal park, chaburgy 49-a bilal park, chaburgy -PK NULL NULL Lahore NULL 54000 -PK NULL NULL Lahore NULL 54000 85 E block Model Town -SN 01 NULL NULL ouakam cité comico en face 217 -SN 01 NULL Dakar NULL -SN 01 NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11 -SN 01 NULL Dakar NULL liberte 6/ dakar -SN 01 NULL Dakar NULL ngor -SN 01 NULL Dakar NULL 4027 ZAC Mbao Cité Fadia -SN NULL NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11 -TZ NULL Dar es Salaam NULL NULL -TZ NULL Dar es salaam NULL NULL 76021 Dar es salaam 1507 Morogoro -TZ NULL Dar es salaam NULL NULL dar es salaam nassoro.ahmedy@yahoo.com -TZ NULL DAR ES SALAAM NULL NULL dar es salaam UDSM -TZ NULL Dar es salaam NULL NULL NA -TZ NULL DAR ES SALAAM NULL NULL P O BOX 23409 -TZ NULL dar es salaam NULL NULL p. o. box 104994 -TZ NULL Dar es Salaam NULL NULL P.o. BOX 71415 Dar es Salaam -TZ NULL Dar es Salaam NULL NULL P.O.BOx 66675 DSM -TZ NULL Dar es salaam NULL NULL Tz Tz -TZ NULL dsm NULL NULL -TZ 02 NULL Bagamoyo NULL NULL PO.Box 393 -TZ 02 NULL Dar es salaam NULL NULL 22548 -TZ 03 NULL Dar-es-salaam NULL NULL Dodoma Municipal Kimara, Dar-es-salaa, -TZ 23 NULL Dar es Salaam NULL NULL -TZ 23 NULL dar es salaam NULL NULL 35074 -TZ 23 NULL dar es salaam NULL NULL 67389 -TZ 23 NULL Dar es Salaam NULL NULL COSTECH, Dar es Salaam, Tanzania -TZ 23 NULL Dar es salaam NULL NULL na -TZ 23 NULL dar es salaam NULL NULL p o box 60164 -TZ 23 NULL dar es salaam NULL NULL P. O. Box 77588 -TZ 23 NULL dar es salaam NULL NULL P.O BOX 78144 -TZ 23 NULL Dar es Salaam NULL NULL P.O.BOX 78373 -TZ 23 NULL Dar es salaam NULL NULL UDSM Dar es Salaam -TZ 23 NULL Dar es salaam NULL NULL udsm udsm -TZ 23 NULL Temeke NULL NULL P.O. Box 50127 -TZ NULL NULL Dar es Salaam NULL NULL -TZ NULL NULL Dar es Salaam NULL NULL Kigoma -TZ NULL NULL Dar es Salaam NULL NULL Mwanza -UG NULL Kampala NULL NULL -UG NULL Kampala NULL NULL Kampala Uganda East Africa -US NULL London NULL SE1 8RT Capital Tower 91 Waterloo Road -US CA NULL Los Angeles NULL -US CA NULL Pleasanton NULL 94588 3412 Pickens Lane -US CA NULL Sacramento NULL -US CA NULL San Francisco NULL -US CA NULL seattle NULL 98113 1234 1st st -US CO NULL Denver NULL 80235 6666 West Quincy Ave -US CT NULL Greenwich NULL 06830 140 Milbank -US CT NULL Hartford NULL 06106 Center for Urban and Global Studies at Trinity College, 70 Vernon Street -US DC NULL Washington NULL -US DC NULL Washington NULL 20007 World Bank Headquarters 1818 H Street NW -US DC NULL Washington NULL 20010 -US DC NULL Washington NULL 20036 -US DC NULL Washington NULL 20405 1889 F St NW -US DC NULL Washington NULL 20433 -US DC NULL Washington NULL 20433 1818 H Street NW -US DC NULL Washington NULL 20433 1818H St -US DC NULL Washington NULL 20433 1818 H Street NW -US DC NULL Washington DC NULL 20005 1424, K Street, NW Suite 600 -US DC NULL Washington DC NULL 20010 1818 H Street, NW -US DC NULL Washington, DC NULL 20003 1818 H Street NW -US DE NULL Virgin Islands|Charlotte Amalie,Cruz Bay,Christiansted NULL Morocco|Tafraout,Rabat,Tangier,Tetouan,Casablanca,Marrakesh,Fez,Oujda,Meknes,Agadir United Arab Emirates|Garhoud,Dubai,Bur Dubai,Ras al Khaymah,Abu Dhabi,Ajman,Al Fujayrah,Sharjah -US FL NULL Falmouth NULL Falmouth Falmouth -US FL NULL Lilongwe NULL Lilongwe Lilongwe -US GA NULL Atlanta NULL -US GU NULL Herndon NULL 15642 Ht USA -US GU NULL Miami NULL Miami Miami -US MD NULL Gaithersburg NULL 20877 554 N Frederick Avenue Suite 216 -US MD NULL Potomac NULL 20854 14 Sandalfoot Court -US MD NULL Silver Spring NULL 20901 9202 Whitney St. -US MI NULL Traverse City NULL 49685 PO Box 792 -US ND NULL Pirassununga NULL Pirassununga Pirassununga -US NJ NULL Princeton NULL -US NY NULL Brooklyn NULL 11206-1980 25 Montrose Ave. Apt 304 -US NY NULL Brooklyn NULL 11225 975 washington ave 2d -US NY NULL Brooklyn NULL 11217 150 4TH AVE APT 9E -US NY NULL New York NULL -US NY NULL New York NULL 10013 148 Lafayette St. PH -US NY NULL New York NULL 10017 UNICEF 3 UN Plaza -US NY NULL New York NULL 10019 25 Columbus Circle Suite 52E -US NY NULL New York NULL 10024 65 West 85th Street 3A -US NY NULL New York NULL 10027 606 W. 116th Street #22 -US NY NULL New York NULL 10037 -US NY NULL Rochester NULL -US NY NULL Scarsdale NULL 10583-1423 54 Walworth Avenue -US OR NULL Portland NULL -US PA NULL Philadelphia NULL -US PA NULL Philadelphia NULL -US PR NULL Colonel Hill NULL Colonel Hill Colonel Hill -US SD NULL Banjul NULL Banjul Banjul -US SD NULL London NULL London London -US TX NULL Aledo NULL 76008 1588 Hunterglenn Dr -US TX NULL Keller NULL 76248 810 Placid View Ct. -US WA NULL Seattle NULL -ZA NULL Cape Town NULL -ZA NULL Cape Town NULL 7945 Alexander Road Muizenberg -ZA NULL Pretoria NULL -ZA 11 NULL Cape Town NULL -ZA 11 NULL Cape Town NULL 7435 PostNet Suite #57, Private Bag X18 Milnerton -ZA 11 NULL Cape town NULL 7508 24 Solyet Court, Lansdowne Road Claremont -ZA 11 NULL Cape Town NULL 7701 -ZA 11 NULL Cape Town NULL 7785 10 Nyamakazi Road Luzuko Park Phillipi East -ZA 11 NULL Cape Town NULL 7915 66 Albert Rd -ZA 11 NULL Cape Town NULL 8001 210 Long Street -ZM NULL Lusaka NULL -ZM 09 NULL LUSAKA NULL 10101 P.O. BOX FW 174 +AT NULL Feldkirch NULL 6800 Pater Grimm Weg 20 NULL +AU NULL Melbourne NULL NULL +AU NULL Sydney NULL NULL +AU 4 NULL NORMANBY NULL 4059 30 Normanby Terrace NULL +BD NULL Dhaka NULL 1205 23, Subal Das Road, Chowdhury Bazar, Lalbagh NULL +BD NULL Dhaka NULL 1207 R-1,H-19,Kallaynpur,Mirpur,Dhaka NULL +BD NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar NULL +BD NULL Dhaka NULL 1209 House# 66B, Flat# B2 Zigatola NULL +BD NULL Dhaka NULL 1219 390 West Rampura Dhaka NULL +BD NULL Dhaka NULL 1230 Uttara NULL +BD 81 NULL Dhaka NULL 1000 Institute of Water and Flood Management NULL +BD 81 NULL Dhaka NULL 1203 84/a maniknagar NULL +BD 81 NULL Dhaka NULL 1205 Dhaka Bangladesh NULL +BD 81 NULL Dhaka NULL 1207 BetterStories Limited 17 West Panthopath NULL +BD 81 NULL Dhaka NULL 1216 Mirpur, Dhaka NULL +BD 81 NULL Dhaka NULL 1230 830, Prembagan, Dhakshin Khan NULL +BD 82 NULL khulna NULL 9203 NULL +BD NULL NULL Dhaka NULL 1000 Institute of Water and Flood Management NULL +BD NULL NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar NULL +BE NULL Brussels NULL NULL +BE NULL Watermael-Boitsfort NULL 1170 Avenue des Staphylins NULL +BH NULL Manama NULL 973 Manama Bahrain Manama Bahrain NULL +BR NULL Porto Alegre NULL NULL +BR NULL Recife NULL NULL +BR RJ NULL Rio de Janeiro NULL NULL +BW NULL Francistown NULL NULL NULL +BW NULL NULL Francistown NULL NULL NULL +CA NULL Montreal NULL NULL +CA NULL Toronto NULL NULL +CA BC NULL Vancouver NULL NULL +CA ON NULL Kitchener NULL NULL +CA ON NULL wterloo NULL n2l3g1 200 University Avenue West NULL +CH NULL Geneva NULL 1202 15, chemin Louis-Dunant NULL +CH 25 NULL Zurich NULL 8098 UBS Optimus Foundation Augustinerhof 1 NULL +DE NULL Berlin NULL NULL +DE 5 NULL Frankfurt am Main NULL 60386 Johanna-Tesch-Platz 7 NULL +DK NULL Aarhus NULL NULL +ES NULL Bilbao NULL NULL +ET 44 NULL ADDIS ABABA NULL 11945 ADDIS ABABA,P.O.BOX 11945 NULL +FI NULL Espoo NULL 2130 Mahlarinne 3B NULL +FI NULL Helsinki NULL 580 Hermannin rantatie 2 A Hermannin rantatie 2 A NULL +FI NULL Tampere NULL 33101 Tampere University of Technology NULL +FI 13 NULL Espoo NULL 2150 Aalto Venture Garage Betonimiehenkuja 3 NULL +GB NULL Exeter NULL NULL +GB NULL London NULL NULL +GB NULL London NULL N4 2DP 2 Myddleton Ave NULL +GB NULL London NULL N7 0AH 104 St George’s Avenue NULL +GB NULL London NULL SE16 3UL 25 Blue Anchor Lane NULL +GB NULL London NULL SW18 5SP Flat 1 150 Merton road NULL +GB NULL London NULL W1T 4BQ 13 Fitzroy Street NULL +GB NULL Oxford NULL NULL +GB NULL Southampton NULL NULL +GB C3 NULL NULL cb244qg 32 market street swavesey NULL +GB E7 NULL London NULL SE3 7TP NULL +GB F3 NULL Wood Green NULL N22 5RU 6 Cedar House NULL +GB H1 NULL London NULL SE11 5JD 47-49 Durham Street NULL +GB H6 NULL London NULL SE8 4DD 8 Harton St Deptford NULL +GB K2 NULL Oxford NULL OX2 6QY 3 The Villas, Rutherway NULL +GH 5 NULL NSAWAM NULL NULL P.O.BOX 455 NULL +GH NULL NULL Accra NULL NULL NULL +ID NULL Bandung NULL 40134 Jalan Sadang Hegar 1 No. 12 RT04 RW13 Sadang Serang NULL +ID NULL Bekasi NULL 17411 Jl.Binadharma 1 No.62. Jatiwaringin NULL +ID NULL Jakarta NULL NULL +ID NULL Jakarta NULL 12440 Jl. H. Niin 7 Lebak Bulus, Cilandak NULL +ID NULL Jakarta NULL 13330 Otista NULL +ID NULL Jakarta selatan NULL 12000 jl. rawa jati timur 6 no. 10 NULL +ID NULL Jakarta Timur NULL Jl.Mulia No.15B Kel.Bidara Cina, Kec.Jatinegara, Jakarta Timur NULL +ID NULL Pematang Siantar NULL 51511 Jl. Durian I 30 NULL +ID 4 NULL Bogor NULL 16165 NULL +ID 4 NULL jakarta NULL otista NULL +ID 4 NULL Jakarta NULL 12520 Jl. Pertanian Raya III No.42 Jakarta Selatan Pasar Minggu NULL +ID 4 NULL Jakarta NULL 13330 Jakarta NULL +ID 4 NULL Jakarta NULL 13330 Jl Sensus IIC Bidaracina Jaktim NULL +ID 4 NULL Jakarta NULL 13330 Jl. Bonasut 2 no.22 NULL +ID 4 NULL Jakarta NULL 13330 Otista 64c NULL +ID 4 NULL jakarta NULL 13330 Otista jaktim NULL +ID 4 NULL Jakarta Timur NULL 13330 Kebon Sayur I no. 1 RT 10/15 NULL +ID 4 NULL Jakarta Timur NULL 13460 Jl. Pondok Kopi Blok G4/5 RT. 005/08 Jakarta Timur NULL +ID 4 NULL Jakarta Timur NULL 13810 Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur NULL +ID 7 NULL Brebes NULL 54321 Jl Kersem Blok D14 Perum Taman Indo Kaligangsa Wetan Brebes NULL +ID 7 NULL Semarang NULL 50143 Puspowarno Tengah 2/2 NULL +ID 8 NULL Lumajang NULL 67373 Desa Tumpeng Kecamatan Candipuro Lumajang NULL +ID 30 NULL Bandung NULL 55241 Jl Pelesiran No 55A/56 NULL +ID 30 NULL Bekasi NULL 17510 bekasi West Java Indonesia NULL +ID 30 NULL Depok NULL 16245 Jalan juragan sinda 2 no 10 NULL +ID 30 NULL Depok NULL 16424 Jalan Margonda RayaJalan Kober Gang Mawar NULL +ID 30 NULL Depok NULL 16424 Jl. Haji Yahya Nuih no.24, Pondok Cina NULL +ID 30 NULL Depok NULL 16425 Kukusan Kelurahan NULL +ID 30 NULL Depok NULL 16518 Jl. Salak No.1/C.88 Durenseribu Bojongsari NULL +ID 30 NULL Depok NULL 16952 Jl. Merak No.34 -36 Rt.004/014 Jl. Merak No. 34 -36 Rt. 004/014 NULL +ID 36 NULL biak numfor NULL 98111 jl. s. mamberamo no 6782 biak numfor NULL +IL NULL Tel Aviv NULL NULL +IN NULL Bangalore NULL NULL +IN NULL India NULL NULL +IN NULL new delhi NULL 110003 55 lodi estate NULL +IN 7 NULL NEW DELHI NULL 110018 15/11 A 1ST FLOOR TILAK NAGAR NULL +IN 7 NULL New Delhu NULL 110075 B 54 Hilansh Apartments Plot No 1, Sector 10, Dwarka NULL +IN 10 NULL Gurgaon NULL D- 201 Ivy Apartments Sushant Lok 1 Gurgaon Haryana NULL +IN 13 NULL Trivandrum NULL 695010 TC 9/1615, SRMH Road, Sasthamangalam, Trivandrum NULL +IN 16 NULL Mumbai NULL 400020 Bharat Mahal, Flat#55 Marine Drive NULL +IN 16 NULL Mumbai NULL 400028 303,Shree Parvati Co-Op Housing Society, D.L.Vaidya Road,Dadar NULL +IN 16 NULL Pune NULL NULL +IN 16 NULL Pune NULL Infosys Campus Hinjewadi Phase 2 NULL +IN 16 NULL Pune NULL 400705 #22 Iris Garden Gokhale Road NULL +IN 16 NULL PUNE NULL 411043 NULL +IN 16 NULL Pune NULL 411051 NULL +IN 16 NULL Pune NULL 411057 Infosys Ltd. Rajiv gandhi infostech park Hinjewadi phase 2 NULL +IN 16 NULL Pune NULL 412108 Pune Maharatshtra NULL +IN 16 NULL Pune NULL 433011 502 utkarsh vihar golande state pune NULL +IN 19 NULL Bangalore NULL 560080 Indian Institute for Human Settlements IIHS Bangalore City Campus, Sadashivanagar, NULL +IN 19 NULL Bangalore NULL 560100 electronic city NULL +IN 19 NULL Bhalki NULL 585411 bhalki,bidar ,karnataka karnataka NULL +IN 24 NULL Jaipur NULL 302011 Institute of Health Management Research 1, Prabhu Dayal Marg NULL +IR 26 NULL Tehran NULL 1118844454 Baharestan sq. mostafa khomeini str., javahery Ave., no. 11, NULL +IT NULL Trento NULL NULL +JM 8 NULL Kingston NULL Kgn 7 MOna Campus UWI NULL +KE NULL Nairobi NULL NULL +KE 5 NULL Nairobi NULL 30300 212,kapsabet NULL +KH NULL NULL Phnom Penh NULL NULL +LR NULL Monrovia NULL 0 NULL +NG 11 NULL Abuja NULL 930001 17 Bechar street Wuse zone 2 NULL +PE 15 NULL Lima NULL 18 Lima Lima NULL +PE 15 NULL Lima NULL Lima 18 123 Miraflores NULL +PE NULL NULL Lima NULL 3 Calle Granada 104 NULL +PE NULL NULL Lima NULL 18 Lima Lima NULL +PH NULL Manila NULL Globe Telepark 111 Valero Street NULL +PH NULL Quezon Coty NULL 1109 86 Harvard Street, Cubao, Quezon City, Philippines 84 Harvard Street, Cubao, Quezon City,hilippines NULL +PH 20 NULL Silang NULL 4118 370 Bayungan Kaong Silang Cavite NULL +PH 57 NULL Kidapawan NULL 9400 Kidapawan City Kidapawan City NULL +PH 66 NULL zamboanga NULL 7000 29-tripplet rd san jose 29-tripplet rd san jose NULL +PH D9 NULL Pasig City NULL World Bank Office Manila, 20/F Taipan Place F. Ortigas Jr. Road, Ortigas Center NULL +PK NULL Lahore NULL 54000 17-R Model Town Lahore NULL +PK NULL Lahore NULL 54000 53- chamber lane road Lahore NULL +PK NULL Lahore NULL 54000 85 E block Model Town NULL +PK NULL Lahore NULL 54000 House no 227, street no 5, Imamia Colony Shahadra Lahore NULL +PK NULL LAHORE NULL 54000 room no.6 khalid bim waleed hall, near New Anarkali, LAHORE room no.6 khalid bim waleed hall, near New Anarkali, LAHORE NULL +PK NULL Lahore NULL pk097 LUMS, Lahore, NULL +PK NULL Sheikhupura NULL 3935 D.H.Q.Hospital Sheikhupura House number 08. Room no 109 Khalid bin waleed haal, punjab University lahore old campus. NULL +PK 2 NULL Quetta NULL 87000 Postal Address 87000, Kuchlak, Quetta, Balochistan. H#24 Peer Abul Khair road Quetta, Balochistan. NULL +PK 2 NULL Quetta NULL 87300 block no-1 falt no. 7 New Crime Branch Abbas Ali Road Cantt NULL +PK 2 NULL Quetta NULL 87300 Flat no. 3 Shafeen Centre Jinnah Town ,Near I.T university , Quetta NULL +PK 2 NULL Quetta NULL 87300 H-no. C-220 Zarghoonabad Phase-2 , Nawa Killi ,Quetta NULL +PK 4 NULL burewala NULL 60101 Fatima Fayyaz Hazrat Sakina hall girls hostel number 9 Punjab university Lahore Pakistan Sardar Wajid Azim Azeem abad Burewala dist Vehari Pakistan NULL +PK 4 NULL Faisalabad NULL 38000 P 101/1, Green Town, Millat Road, Faisalabad NULL +PK 4 NULL Islamabad NULL 44000 P.O Tarlai kalan chappar Islamabad NULL +PK 4 NULL lahore NULL NULL +PK 4 NULL Lahore NULL 54000 NULL +PK 4 NULL lahore NULL 54000 Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. NULL +PK 4 NULL Lahore NULL 54000 1149-1-D2 Green Town Lahore NULL +PK 4 NULL Lahore NULL 54000 124, street# 2, karim block Allama Iqbal Town lahore. 124, street# 2, karim block Allama Iqbal Town lahore. NULL +PK 4 NULL Lahore NULL 54000 150 A Qila Lachman Singh Ravi Road lahore NULL +PK 4 NULL Lahore NULL 54000 166/1L DHA Lahore NULL +PK 4 NULL Lahore NULL 54000 172 A2 Township Lahore NULL +PK 4 NULL Lahore NULL 54000 183,S/Block, Model Town, Lhr NULL +PK 4 NULL lahore NULL 54000 19- A block ,Eden Lane Villas Raiwind Road ,Lahore NULL +PK 4 NULL lahore NULL 54000 3-c kaliyar road opposite kids lyceum, rustam park near mor samnabad NULL +PK 4 NULL Lahore NULL 54000 31 Saeed Block, Canal Bank Scheme NULL +PK 4 NULL Lahore NULL 54000 31c DHA Lahore NULL +PK 4 NULL Lahore NULL 54000 387 E1 wapda town, Lahore NULL +PK 4 NULL Lahore NULL 54000 45-D dha eme sector multan road,lahore NULL +PK 4 NULL Lahore NULL 54000 5 Zafar Ali Road NULL +PK 4 NULL Lahore NULL 54000 54-R PGECHS NULL +PK 4 NULL lahore NULL 54000 566 E-1 johar town lahore 566 E-1 johar town lahore NULL +PK 4 NULL Lahore NULL 54000 82/1 Z Block, Phase 3 DHA NULL +PK 4 NULL Lahore NULL 54000 A-1 VRI Zarrar shaheed road lahore cantt A-1 VRI Zarrar shaheed road lahore cantt NULL +PK 4 NULL lahore NULL 54000 e5/39D street 6 zaman colony cavalry ground ext NULL +PK 4 NULL Lahore NULL 54000 Ho # 61, Block G3, Johar Town Lahore NULL +PK 4 NULL LAhore NULL 54000 House #19-A street #5 Usman nagr Ghaziabad Lahore NULL +PK 4 NULL lahore NULL 54000 House no 692 street no 67 sadar bazar NULL +PK 4 NULL Lahore NULL 54000 Khosa Law Chamber 1 Turner Road NULL +PK 4 NULL Lahore NULL 54000 Lahore,Pakistan Lahore,Pakistan NULL +PK 4 NULL Lahore NULL 54000 room no 69, khalid bin waleed hall, anarkali NULL +PK 4 NULL Lahore NULL 54000 Suite # 8, Al-Hafeez Suites, Gulberg II NULL +PK 4 NULL Lahore NULL 54085 199 Shadman 2 NULL +PK 4 NULL Lahore NULL 54300 Mughalpura Lahore Pakistan NULL +PK 4 NULL Lahore NULL 54660 SD 69 falcon complex gulberg III lahore NULL +PK 4 NULL lahore NULL 54800 764-G4 johar town ,lahore NULL +PK 4 NULL Rawalpindi NULL 44000 House 522, F-Block Sattellite Town, Rawalpindi NULL +PK 4 NULL Rawalpindi NULL 46000 1950/c, Indusroad 2, Tariqabad, Rawalpindi Cantt NULL +PK 4 NULL Rawalpindi NULL 46000 House 54-E Lane 9 Sector 4, AECHS Chaklala Rawalpindi NULL +PK 4 NULL Rawalpindi NULL 46000 House B-1343, Sattellite town Rawalpindi NULL +PK 4 NULL Rawalpindi NULL 46000 House CB-299F, Street 1, Lane 4 Peshawar Road Rawalpindi NULL +PK 4 NULL Rawalpindi NULL 46300 House No 1518 Umer Block phase 8 BehriaTown NULL +PK 4 NULL sialkot NULL 51310 The National Model School, Ismaiealabad, Pacca Garah Sialkot NULL +PK 8 NULL Islamabad NULL CIomsats Institute of Information Technology Islamabad NULL +PK 8 NULL Islamabad NULL 38700 COMSATS tarlai boys hostel Islamabad. COMSATS tarlai boys hostel Islamabad (Room 30) NULL +PK 8 NULL Islamabad NULL 44000 NULL +PK 8 NULL Islamabad NULL 44000 House # 256, Street # 9, Shahzad Town, Islamabad. NULL +PK 8 NULL Islamabad NULL 44000 Islamabad , Comsats University Islamabd ,Pakistan NULL +PK 8 NULL Islamabad NULL 44000 World Bank Building Sector G 5 NULL +PK 8 NULL lahore NULL 54000 3c zafar ali road gulburg 5 3c zafar ali road gulburg 5 NULL +PK 8 NULL lahore NULL 54000 49-a bilal park, chaburgy 49-a bilal park, chaburgy NULL +PK NULL NULL Lahore NULL 54000 NULL +PK NULL NULL Lahore NULL 54000 85 E block Model Town NULL +SN 1 NULL NULL ouakam cité comico en face 217 NULL +SN 1 NULL Dakar NULL NULL +SN 1 NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11 NULL +SN 1 NULL Dakar NULL liberte 6/ dakar NULL +SN 1 NULL Dakar NULL ngor NULL +SN 1 NULL Dakar NULL 4027 ZAC Mbao Cité Fadia NULL +SN NULL NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11 NULL +TZ NULL Dar es Salaam NULL NULL NULL +TZ NULL Dar es salaam NULL NULL 76021 Dar es salaam 1507 Morogoro NULL +TZ NULL Dar es salaam NULL NULL dar es salaam nassoro.ahmedy@yahoo.com NULL +TZ NULL DAR ES SALAAM NULL NULL dar es salaam UDSM NULL +TZ NULL Dar es salaam NULL NULL NA NULL +TZ NULL DAR ES SALAAM NULL NULL P O BOX 23409 NULL +TZ NULL dar es salaam NULL NULL p. o. box 104994 NULL +TZ NULL Dar es Salaam NULL NULL P.o. BOX 71415 Dar es Salaam NULL +TZ NULL Dar es Salaam NULL NULL P.O.BOx 66675 DSM NULL +TZ NULL Dar es salaam NULL NULL Tz Tz NULL +TZ NULL dsm NULL NULL NULL +TZ 2 NULL Bagamoyo NULL NULL PO.Box 393 NULL +TZ 2 NULL Dar es salaam NULL NULL 22548 NULL +TZ 3 NULL Dar-es-salaam NULL NULL Dodoma Municipal Kimara, Dar-es-salaa, NULL +TZ 23 NULL Dar es Salaam NULL NULL NULL +TZ 23 NULL dar es salaam NULL NULL 35074 NULL +TZ 23 NULL dar es salaam NULL NULL 67389 NULL +TZ 23 NULL Dar es Salaam NULL NULL COSTECH, Dar es Salaam, Tanzania NULL +TZ 23 NULL Dar es salaam NULL NULL na NULL +TZ 23 NULL dar es salaam NULL NULL p o box 60164 NULL +TZ 23 NULL dar es salaam NULL NULL P. O. Box 77588 NULL +TZ 23 NULL dar es salaam NULL NULL P.O BOX 78144 NULL +TZ 23 NULL Dar es Salaam NULL NULL P.O.BOX 78373 NULL +TZ 23 NULL Dar es salaam NULL NULL UDSM Dar es Salaam NULL +TZ 23 NULL Dar es salaam NULL NULL udsm udsm NULL +TZ 23 NULL Temeke NULL NULL P.O. Box 50127 NULL +TZ NULL NULL Dar es Salaam NULL NULL NULL +TZ NULL NULL Dar es Salaam NULL NULL Kigoma NULL +TZ NULL NULL Dar es Salaam NULL NULL Mwanza NULL +UG NULL Kampala NULL NULL NULL +UG NULL Kampala NULL NULL Kampala Uganda East Africa NULL +US NULL London NULL SE1 8RT Capital Tower 91 Waterloo Road NULL +US CA NULL Los Angeles NULL NULL +US CA NULL Pleasanton NULL 94588 3412 Pickens Lane NULL +US CA NULL Sacramento NULL NULL +US CA NULL San Francisco NULL NULL +US CA NULL seattle NULL 98113 1234 1st st NULL +US CO NULL Denver NULL 80235 6666 West Quincy Ave NULL +US CT NULL Greenwich NULL 6830 140 Milbank NULL +US CT NULL Hartford NULL 6106 Center for Urban and Global Studies at Trinity College, 70 Vernon Street NULL +US DC NULL Washington NULL NULL +US DC NULL Washington NULL 20007 World Bank Headquarters 1818 H Street NW NULL +US DC NULL Washington NULL 20010 NULL +US DC NULL Washington NULL 20036 NULL +US DC NULL Washington NULL 20405 1889 F St NW NULL +US DC NULL Washington NULL 20433 NULL +US DC NULL Washington NULL 20433 1818 H Street NW NULL +US DC NULL Washington NULL 20433 1818H St NULL +US DC NULL Washington NULL 20433 1818 H Street NW NULL +US DC NULL Washington DC NULL 20005 1424, K Street, NW Suite 600 NULL +US DC NULL Washington DC NULL 20010 1818 H Street, NW NULL +US DC NULL Washington, DC NULL 20003 1818 H Street NW NULL +US DE NULL Virgin Islands|Charlotte Amalie,Cruz Bay,Christiansted NULL Morocco|Tafraout,Rabat,Tangier,Tetouan,Casablanca,Marrakesh,Fez,Oujda,Meknes,Agadir United Arab Emirates|Garhoud,Dubai,Bur Dubai,Ras al Khaymah,Abu Dhabi,Ajman,Al Fujayrah,Sharjah NULL +US FL NULL Falmouth NULL Falmouth Falmouth NULL +US FL NULL Lilongwe NULL Lilongwe Lilongwe NULL +US GA NULL Atlanta NULL NULL +US GU NULL Herndon NULL 15642 Ht USA NULL +US GU NULL Miami NULL Miami Miami NULL +US MD NULL Gaithersburg NULL 20877 554 N Frederick Avenue Suite 216 NULL +US MD NULL Potomac NULL 20854 14 Sandalfoot Court NULL +US MD NULL Silver Spring NULL 20901 9202 Whitney St. NULL +US MI NULL Traverse City NULL 49685 PO Box 792 NULL +US ND NULL Pirassununga NULL Pirassununga Pirassununga NULL +US NJ NULL Princeton NULL NULL +US NY NULL Brooklyn NULL 11206-1980 25 Montrose Ave. Apt 304 NULL +US NY NULL Brooklyn NULL 11225 975 washington ave 2d NULL +US NY NULL Brooklyn NULL 11217 150 4TH AVE APT 9E NULL +US NY NULL New York NULL NULL +US NY NULL New York NULL 10013 148 Lafayette St. PH NULL +US NY NULL New York NULL 10017 UNICEF 3 UN Plaza NULL +US NY NULL New York NULL 10019 25 Columbus Circle Suite 52E NULL +US NY NULL New York NULL 10024 65 West 85th Street 3A NULL +US NY NULL New York NULL 10027 606 W. 116th Street #22 NULL +US NY NULL New York NULL 10037 NULL +US NY NULL Rochester NULL NULL +US NY NULL Scarsdale NULL 10583-1423 54 Walworth Avenue NULL +US OR NULL Portland NULL NULL +US PA NULL Philadelphia NULL NULL +US PA NULL Philadelphia NULL NULL +US PR NULL Colonel Hill NULL Colonel Hill Colonel Hill NULL +US SD NULL Banjul NULL Banjul Banjul NULL +US SD NULL London NULL London London NULL +US TX NULL Aledo NULL 76008 1588 Hunterglenn Dr NULL +US TX NULL Keller NULL 76248 810 Placid View Ct. NULL +US WA NULL Seattle NULL NULL +ZA NULL Cape Town NULL NULL +ZA NULL Cape Town NULL 7945 Alexander Road Muizenberg NULL +ZA NULL Pretoria NULL NULL +ZA 11 NULL Cape Town NULL NULL +ZA 11 NULL Cape Town NULL 7435 PostNet Suite #57, Private Bag X18 Milnerton NULL +ZA 11 NULL Cape town NULL 7508 24 Solyet Court, Lansdowne Road Claremont NULL +ZA 11 NULL Cape Town NULL 7701 NULL +ZA 11 NULL Cape Town NULL 7785 10 Nyamakazi Road Luzuko Park Phillipi East NULL +ZA 11 NULL Cape Town NULL 7915 66 Albert Rd NULL +ZA 11 NULL Cape Town NULL 8001 210 Long Street NULL +ZM NULL Lusaka NULL NULL +ZM 9 NULL LUSAKA NULL 10101 P.O. BOX FW 174 NULL diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.address_formats.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.address_formats.inc index 793e2f1e..19e7958b 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.address_formats.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.address_formats.inc @@ -41,11 +41,11 @@ function addressfield_get_address_format($country_code) { // postal code in 'used_fields'. $countries_with_optional_postal_code = array( 'AC', 'AD', 'AL', 'AZ', 'BA', 'BB', 'BD', 'BG', 'BH', 'BM', 'BN', 'BT', - 'CR', 'CY', 'CZ', 'DO', 'DZ', 'EC', 'EH', 'ET', 'FO', 'GE', 'GN', 'GT', + 'CR', 'CY', 'DO', 'DZ', 'EC', 'EH', 'ET', 'FO', 'GE', 'GN', 'GT', 'GW', 'HR', 'HT', 'IL', 'IS', 'JO', 'KE', 'KG', 'KH', 'KW', 'LA', 'LA', 'LB', 'LK', 'LR', 'LS', 'MA', 'MC', 'MD', 'ME', 'MG', 'MK', 'MM', 'MT', 'MU', 'MV', 'NE', 'NP', 'OM', 'PK', 'PY', 'RO', 'RS', 'SA', 'SI', - 'SK', 'SN', 'SZ', 'TA', 'TJ', 'TM', 'TN', 'VA', 'VC', 'VG', 'XK', 'ZM', + 'SN', 'SZ', 'TA', 'TJ', 'TM', 'TN', 'VA', 'VC', 'VG', 'XK', 'ZM', ); foreach ($countries_with_optional_postal_code as $code) { $address_formats[$code] = array( @@ -56,9 +56,9 @@ function addressfield_get_address_format($country_code) { // These formats differ from the default only by the presence of the // postal code in 'used_fields' and 'required_fields'. $countries_with_required_postal_code = array( - 'AT', 'AX', 'BE', 'BL', 'CH', 'DE', 'DK', 'FI', 'FK', 'FR', 'GF', 'GG', + 'AT', 'AX', 'BE', 'BL', 'CH', 'CZ', 'DE', 'DK', 'FI', 'FK', 'FR', 'GF', 'GG', 'GL', 'GP', 'GR', 'GS', 'HU', 'IM', 'IO', 'JE', 'LI', 'LU', 'MF', 'MQ', 'NC', - 'NL', 'NO', 'PL', 'PM', 'PN', 'PT', 'RE', 'SE', 'SH', 'SJ', 'TC', 'WF', + 'NL', 'NO', 'PL', 'PM', 'PN', 'PT', 'RE', 'SE', 'SH', 'SJ', 'SK', 'TC', 'WF', 'YT', ); foreach ($countries_with_required_postal_code as $code) { @@ -114,7 +114,7 @@ function addressfield_get_address_format($country_code) { 'used_fields' => array('locality', 'administrative_area', 'postal_code'), ); $address_formats['CL'] = array( - 'used_fields' => array('locality', 'administrative_area', 'postal_code'), + 'used_fields' => array('dependent_locality', 'locality', 'administrative_area', 'postal_code'), 'administrative_area_label' => t('State', array(), array('context' => 'Territory of a country')), 'render_administrative_area_value' => TRUE, ); @@ -124,7 +124,7 @@ function addressfield_get_address_format($country_code) { 'dependent_locality_label' => t('District'), ); $address_formats['CO'] = array( - 'used_fields' => array('locality', 'administrative_area'), + 'used_fields' => array('locality', 'administrative_area', 'postal_code'), 'administrative_area_label' => t('Department', array(), array('context' => 'Territory of a country')), ); $address_formats['CV'] = array( @@ -235,6 +235,7 @@ function addressfield_get_address_format($country_code) { 'used_fields' => array('dependent_locality', 'locality', 'administrative_area', 'postal_code'), 'required_fields' => array('locality', 'administrative_area', 'postal_code'), 'dependent_locality_label' => t('District'), + 'render_administrative_area_value' => TRUE, ); $address_formats['KY'] = array( 'used_fields' => array('administrative_area', 'postal_code'), diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.administrative_areas.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.administrative_areas.inc index 88343de9..aa6ebad6 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.administrative_areas.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.administrative_areas.inc @@ -11,6 +11,29 @@ * NULL if not found. */ function addressfield_get_administrative_areas($country_code) { + // Maintain a static cache to avoid passing the administrative areas through + // t() more than once per request. + $administrative_areas = &drupal_static(__FUNCTION__, array()); + if (empty($administrative_areas)) { + // Get the default administrative areas. + $administrative_areas = _addressfield_get_administrative_areas_defaults(); + + // Allow other modules to alter the administrative areas. + drupal_alter('addressfield_administrative_areas', $administrative_areas); + } + + return isset($administrative_areas[$country_code]) ? $administrative_areas[$country_code] : NULL; +} + +/** + * Provides the default administrative areas. + */ +function _addressfield_get_administrative_areas_defaults() { + // To avoid needless pollution of the strings list we only pass to t() + // those administrative areas that are in English (or a latin transcription), + // and belong to a country that either has multiple official languages (CA) + // or uses a non-latin script (AE, CN, JP, KR, UA, RU, etc). + // No translation is expected in other cases. $administrative_areas = array(); $administrative_areas['AE'] = array( 'AZ' => t('Abu Dhabi'), @@ -22,69 +45,69 @@ function addressfield_get_administrative_areas($country_code) { 'AJ' => t('Ajmān'), ); $administrative_areas['AR'] = array( - 'B' => t('Buenos Aires'), - 'K' => t('Catamarca'), - 'H' => t('Chaco'), - 'U' => t('Chubut'), - 'C' => t('Ciudad de Buenos Aires'), - 'X' => t('Córdoba'), - 'W' => t('Corrientes'), - 'E' => t('Entre Ríos'), - 'P' => t('Formosa'), - 'Y' => t('Jujuy'), - 'L' => t('La Pampa'), - 'F' => t('La Rioja'), - 'M' => t('Mendoza'), - 'N' => t('Misiones'), - 'Q' => t('Neuquén'), - 'R' => t('Río Negro'), - 'A' => t('Salta'), - 'J' => t('San Juan'), - 'D' => t('San Luis'), - 'Z' => t('Santa Cruz'), - 'S' => t('Santa Fe'), - 'G' => t('Santiago del Estero'), - 'V' => t('Tierra del Fuego'), - 'T' => t('Tucumán'), + 'B' => 'Buenos Aires', + 'K' => 'Catamarca', + 'H' => 'Chaco', + 'U' => 'Chubut', + 'C' => 'Ciudad de Buenos Aires', + 'X' => 'Córdoba', + 'W' => 'Corrientes', + 'E' => 'Entre Ríos', + 'P' => 'Formosa', + 'Y' => 'Jujuy', + 'L' => 'La Pampa', + 'F' => 'La Rioja', + 'M' => 'Mendoza', + 'N' => 'Misiones', + 'Q' => 'Neuquén', + 'R' => 'Río Negro', + 'A' => 'Salta', + 'J' => 'San Juan', + 'D' => 'San Luis', + 'Z' => 'Santa Cruz', + 'S' => 'Santa Fe', + 'G' => 'Santiago del Estero', + 'V' => 'Tierra del Fuego', + 'T' => 'Tucumán', ); $administrative_areas['AU'] = array( - 'ACT' => t('Australian Capital Territory'), - 'NSW' => t('New South Wales'), - 'NT' => t('Northern Territory'), - 'QLD' => t('Queensland'), - 'SA' => t('South Australia'), - 'TAS' => t('Tasmania'), - 'VIC' => t('Victoria'), - 'WA' => t('Western Australia'), + 'ACT' => 'Australian Capital Territory', + 'NSW' => 'New South Wales', + 'NT' => 'Northern Territory', + 'QLD' => 'Queensland', + 'SA' => 'South Australia', + 'TAS' => 'Tasmania', + 'VIC' => 'Victoria', + 'WA' => 'Western Australia', ); $administrative_areas['BR'] = array( - 'AC' => t('Acre'), - 'AL' => t('Alagoas'), - 'AM' => t('Amazonas'), - 'AP' => t('Amapá'), - 'BA' => t('Bahia'), - 'CE' => t('Ceará'), - 'DF' => t('Distrito Federal'), - 'ES' => t('Espírito Santo'), - 'GO' => t('Goiás'), - 'MA' => t('Maranhão'), - 'MG' => t('Minas Gerais'), - 'MS' => t('Mato Grosso do Sul'), - 'MT' => t('Mato Grosso'), - 'PA' => t('Pará'), - 'PB' => t('Paraíba'), - 'PE' => t('Pernambuco'), - 'PI' => t('Piauí'), - 'PR' => t('Paraná'), - 'RJ' => t('Rio de Janeiro'), - 'RN' => t('Rio Grande do Norte'), - 'RO' => t('Rondônia'), - 'RR' => t('Roraima'), - 'RS' => t('Rio Grande do Sul'), - 'SC' => t('Santa Catarina'), - 'SE' => t('Sergipe'), - 'SP' => t('São Paulo'), - 'TO' => t('Tocantins'), + 'AC' => 'Acre', + 'AL' => 'Alagoas', + 'AM' => 'Amazonas', + 'AP' => 'Amapá', + 'BA' => 'Bahia', + 'CE' => 'Ceará', + 'DF' => 'Distrito Federal', + 'ES' => 'Espírito Santo', + 'GO' => 'Goiás', + 'MA' => 'Maranhão', + 'MG' => 'Minas Gerais', + 'MS' => 'Mato Grosso do Sul', + 'MT' => 'Mato Grosso', + 'PA' => 'Pará', + 'PB' => 'Paraíba', + 'PE' => 'Pernambuco', + 'PI' => 'Piauí', + 'PR' => 'Paraná', + 'RJ' => 'Rio de Janeiro', + 'RN' => 'Rio Grande do Norte', + 'RO' => 'Rondônia', + 'RR' => 'Roraima', + 'RS' => 'Rio Grande do Sul', + 'SC' => 'Santa Catarina', + 'SE' => 'Sergipe', + 'SP' => 'São Paulo', + 'TO' => 'Tocantins', ); $administrative_areas['CA'] = array( 'AB' => t('Alberta'), @@ -102,21 +125,21 @@ function addressfield_get_administrative_areas($country_code) { 'YT' => t('Yukon Territory'), ); $administrative_areas['CL'] = array( - 'AI' => t('Aysén del General Carlos Ibáñez del Campo'), - 'AN' => t('Antofagasta'), - 'AR' => t('Araucanía'), - 'AP' => t('Arica y Parinacota'), - 'AT' => t('Atacama'), - 'BI' => t('Biobío'), - 'CO' => t('Coquimbo'), - 'LI' => t('Libertador General Bernardo O\'Higgins'), - 'LL' => t('Los Lagos'), - 'LR' => t('Los Ríos'), - 'MA' => t('Magallanes y de la Antártica Chilena'), - 'ML' => t('Maule'), - 'RM' => t('Metropolitana de Santiago'), - 'TA' => t('Tarapacá'), - 'VS' => t('Valparaíso'), + 'AI' => 'Aysén del General Carlos Ibáñez del Campo', + 'AN' => 'Antofagasta', + 'AR' => 'Araucanía', + 'AP' => 'Arica y Parinacota', + 'AT' => 'Atacama', + 'BI' => 'Biobío', + 'CO' => 'Coquimbo', + 'LI' => 'Libertador General Bernardo O\'Higgins', + 'LL' => 'Los Lagos', + 'LR' => 'Los Ríos', + 'MA' => 'Magallanes y de la Antártica Chilena', + 'ML' => 'Maule', + 'RM' => 'Metropolitana de Santiago', + 'TA' => 'Tarapacá', + 'VS' => 'Valparaíso', ); $administrative_areas['CN'] = array( '34' => t('Anhui Sheng'), @@ -155,55 +178,55 @@ function addressfield_get_administrative_areas($country_code) { '33' => t('Zhejiang Sheng'), ); $administrative_areas['CO'] = array( - 'AMA' => t('Amazonas'), - 'ANT' => t('Antioquia'), - 'ARA' => t('Arauca'), - 'ATL' => t('Atlántico'), - 'BOL' => t('Bolívar'), - 'BOY' => t('Boyacá'), - 'CAL' => t('Caldas'), - 'CAQ' => t('Caquetá'), - 'CAS' => t('Casanare'), - 'CAU' => t('Cauca'), - 'CES' => t('Cesar'), - 'COR' => t('Córdoba'), - 'CUN' => t('Cundinamarca'), - 'CHO' => t('Chocó'), - 'GUA' => t('Guainía'), - 'GUV' => t('Guaviare'), - 'HUI' => t('Huila'), - 'LAG' => t('La Guajira'), - 'MAG' => t('Magdalena'), - 'MET' => t('Meta'), - 'NAR' => t('Nariño'), - 'NSA' => t('Norte de Santander'), - 'PUT' => t('Putumayo'), - 'QUI' => t('Quindío'), - 'RIS' => t('Risaralda'), - 'SAP' => t('San Andrés, Providencia y Santa Catalina'), - 'SAN' => t('Santander'), - 'SUC' => t('Sucre'), - 'TOL' => t('Tolima'), - 'VAC' => t('Valle del Cauca'), - 'VAU' => t('Vaupés'), - 'VID' => t('Vichada'), + 'AMA' => 'Amazonas', + 'ANT' => 'Antioquia', + 'ARA' => 'Arauca', + 'ATL' => 'Atlántico', + 'BOL' => 'Bolívar', + 'BOY' => 'Boyacá', + 'CAL' => 'Caldas', + 'CAQ' => 'Caquetá', + 'CAS' => 'Casanare', + 'CAU' => 'Cauca', + 'CES' => 'Cesar', + 'COR' => 'Córdoba', + 'CUN' => 'Cundinamarca', + 'CHO' => 'Chocó', + 'GUA' => 'Guainía', + 'GUV' => 'Guaviare', + 'HUI' => 'Huila', + 'LAG' => 'La Guajira', + 'MAG' => 'Magdalena', + 'MET' => 'Meta', + 'NAR' => 'Nariño', + 'NSA' => 'Norte de Santander', + 'PUT' => 'Putumayo', + 'QUI' => 'Quindío', + 'RIS' => 'Risaralda', + 'SAP' => 'San Andrés, Providencia y Santa Catalina', + 'SAN' => 'Santander', + 'SUC' => 'Sucre', + 'TOL' => 'Tolima', + 'VAC' => 'Valle del Cauca', + 'VAU' => 'Vaupés', + 'VID' => 'Vichada', ); $administrative_areas['EE'] = array( - '37' => t('Harjumaa'), - '39' => t('Hiiumaa'), - '44' => t('Ida-Virumaa'), - '49' => t('Jõgevamaa'), - '51' => t('Järvamaa'), - '57' => t('Läänemaa'), - '59' => t('Lääne-Virumaa'), - '65' => t('Põlvamaa'), - '67' => t('Pärnumaa'), - '70' => t('Raplamaa'), - '74' => t('Saaremaa'), - '78' => t('Tartumaa'), - '82' => t('Valgamaa'), - '84' => t('Viljandimaa'), - '86' => t('Võrumaa'), + '37' => 'Harjumaa', + '39' => 'Hiiumaa', + '44' => 'Ida-Virumaa', + '49' => 'Jõgevamaa', + '51' => 'Järvamaa', + '57' => 'Läänemaa', + '59' => 'Lääne-Virumaa', + '65' => 'Põlvamaa', + '67' => 'Pärnumaa', + '70' => 'Raplamaa', + '74' => 'Saaremaa', + '78' => 'Tartumaa', + '82' => 'Valgamaa', + '84' => 'Viljandimaa', + '86' => 'Võrumaa', ); $administrative_areas['EG'] = array( 'ALX' => t('Alexandria'), @@ -235,58 +258,58 @@ function addressfield_get_administrative_areas($country_code) { 'LX' => t('Luxor'), ); $administrative_areas['ES'] = array( - 'C' => t("A Coruña"), - 'VI' => t('Alava'), - 'AB' => t('Albacete'), - 'A' => t('Alicante'), - 'AL' => t("Almería"), - 'O' => t('Asturias'), - 'AV' => t("Ávila"), - 'BA' => t('Badajoz'), - 'PM' => t('Baleares'), - 'B' => t('Barcelona'), - 'BU' => t('Burgos'), - 'CC' => t("Cáceres"), - 'CA' => t("Cádiz"), - 'S' => t('Cantabria'), - 'CS' => t("Castellón"), - 'CE' => t('Ceuta'), - 'CR' => t('Ciudad Real'), - 'CO' => t("Córdoba"), - 'CU' => t('Cuenca'), - 'GI' => t('Gerona'), - 'GR' => t('Granada'), - 'GU' => t('Guadalajara'), - 'SS' => t("Guipúzcoa"), - 'H' => t('Huelva'), - 'HU' => t('Huesca'), - 'J' => t("Jaén"), - 'LO' => t('La Rioja'), - 'GC' => t('Las Palmas'), - 'LE' => t("León"), - 'L' => t("Lérida"), - 'LU' => t('Lugo'), - 'M' => t('Madrid'), - 'MA' => t("Málaga"), - 'ML' => t('Melilla'), - 'MU' => t('Murcia'), - 'NA' => t('Navarra'), - 'OR' => t('Ourense'), - 'P' => t('Palencia'), - 'PO' => t('Pontevedra'), - 'SA' => t('Salamanca'), - 'TF' => t('Santa Cruz de Tenerife'), - 'SG' => t('Segovia'), - 'SE' => t('Sevilla'), - 'SO' => t('Soria'), - 'T' => t('Tarragona'), - 'TE' => t('Teruel'), - 'TO' => t('Toledo'), - 'V' => t('Valencia'), - 'VA' => t('Valladolid'), - 'BI' => t('Vizcaya'), - 'ZA' => t('Zamora'), - 'Z' => t('Zaragoza'), + 'C' => "A Coruña", + 'VI' => 'Alava', + 'AB' => 'Albacete', + 'A' => 'Alicante', + 'AL' => "Almería", + 'O' => 'Asturias', + 'AV' => "Ávila", + 'BA' => 'Badajoz', + 'PM' => 'Baleares', + 'B' => 'Barcelona', + 'BU' => 'Burgos', + 'CC' => "Cáceres", + 'CA' => "Cádiz", + 'S' => 'Cantabria', + 'CS' => "Castellón", + 'CE' => 'Ceuta', + 'CR' => 'Ciudad Real', + 'CO' => "Córdoba", + 'CU' => 'Cuenca', + 'GI' => 'Girona', + 'GR' => 'Granada', + 'GU' => 'Guadalajara', + 'SS' => "Guipúzcoa", + 'H' => 'Huelva', + 'HU' => 'Huesca', + 'J' => "Jaén", + 'LO' => 'La Rioja', + 'GC' => 'Las Palmas', + 'LE' => "León", + 'L' => "Lleida", + 'LU' => 'Lugo', + 'M' => 'Madrid', + 'MA' => "Málaga", + 'ML' => 'Melilla', + 'MU' => 'Murcia', + 'NA' => 'Navarra', + 'OR' => 'Ourense', + 'P' => 'Palencia', + 'PO' => 'Pontevedra', + 'SA' => 'Salamanca', + 'TF' => 'Santa Cruz de Tenerife', + 'SG' => 'Segovia', + 'SE' => 'Sevilla', + 'SO' => 'Soria', + 'T' => 'Tarragona', + 'TE' => 'Teruel', + 'TO' => 'Toledo', + 'V' => 'Valencia', + 'VA' => 'Valladolid', + 'BI' => 'Vizcaya', + 'ZA' => 'Zamora', + 'Z' => 'Zaragoza', ); $administrative_areas['HK'] = array( // HK subdivisions have no ISO codes assigned. @@ -330,57 +353,57 @@ function addressfield_get_administrative_areas($country_code) { 'SU' => t('Sumatera Utara'), ); $administrative_areas['IE'] = array( - 'CW' => t('Co Carlow'), - 'CN' => t('Co Cavan'), - 'CE' => t('Co Clare'), - 'CO' => t('Co Cork'), - 'DL' => t('Co Donegal'), - 'D' => t('Co Dublin'), - 'D1' => t('Dublin 1'), - 'D2' => t('Dublin 2'), - 'D3' => t('Dublin 3'), - 'D4' => t('Dublin 4'), - 'D5' => t('Dublin 5'), - 'D6' => t('Dublin 6'), - 'D6W' => t('Dublin 6w'), - 'D7' => t('Dublin 7'), - 'D8' => t('Dublin 8'), - 'D9' => t('Dublin 9'), - 'D10' => t('Dublin 10'), - 'D11' => t('Dublin 11'), - 'D12' => t('Dublin 12'), - 'D13' => t('Dublin 13'), - 'D14' => t('Dublin 14'), - 'D15' => t('Dublin 15'), - 'D16' => t('Dublin 16'), - 'D17' => t('Dublin 17'), - 'D18' => t('Dublin 18'), - 'D19' => t('Dublin 19'), - 'D20' => t('Dublin 20'), - 'D21' => t('Dublin 21'), - 'D22' => t('Dublin 22'), - 'D23' => t('Dublin 23'), - 'D24' => t('Dublin 24'), - 'G' => t('Co Galway'), - 'KY' => t('Co Kerry'), - 'KE' => t('Co Kildare'), - 'KK' => t('Co Kilkenny'), - 'LS' => t('Co Laois'), - 'LM' => t('Co Leitrim'), - 'LK' => t('Co Limerick'), - 'LD' => t('Co Longford'), - 'LH' => t('Co Louth'), - 'MO' => t('Co Mayo'), - 'MH' => t('Co Meath'), - 'MN' => t('Co Monaghan'), - 'OY' => t('Co Offaly'), - 'RN' => t('Co Roscommon'), - 'SO' => t('Co Sligo'), - 'TA' => t('Co Tipperary'), - 'WD' => t('Co Waterford'), - 'WH' => t('Co Westmeath'), - 'WX' => t('Co Wexford'), - 'WW' => t('Co Wicklow'), + 'CW' => 'Co Carlow', + 'CN' => 'Co Cavan', + 'CE' => 'Co Clare', + 'CO' => 'Co Cork', + 'DL' => 'Co Donegal', + 'D' => 'Co Dublin', + 'D1' => 'Dublin 1', + 'D2' => 'Dublin 2', + 'D3' => 'Dublin 3', + 'D4' => 'Dublin 4', + 'D5' => 'Dublin 5', + 'D6' => 'Dublin 6', + 'D6W' => 'Dublin 6w', + 'D7' => 'Dublin 7', + 'D8' => 'Dublin 8', + 'D9' => 'Dublin 9', + 'D10' => 'Dublin 10', + 'D11' => 'Dublin 11', + 'D12' => 'Dublin 12', + 'D13' => 'Dublin 13', + 'D14' => 'Dublin 14', + 'D15' => 'Dublin 15', + 'D16' => 'Dublin 16', + 'D17' => 'Dublin 17', + 'D18' => 'Dublin 18', + 'D19' => 'Dublin 19', + 'D20' => 'Dublin 20', + 'D21' => 'Dublin 21', + 'D22' => 'Dublin 22', + 'D23' => 'Dublin 23', + 'D24' => 'Dublin 24', + 'G' => 'Co Galway', + 'KY' => 'Co Kerry', + 'KE' => 'Co Kildare', + 'KK' => 'Co Kilkenny', + 'LS' => 'Co Laois', + 'LM' => 'Co Leitrim', + 'LK' => 'Co Limerick', + 'LD' => 'Co Longford', + 'LH' => 'Co Louth', + 'MO' => 'Co Mayo', + 'MH' => 'Co Meath', + 'MN' => 'Co Monaghan', + 'OY' => 'Co Offaly', + 'RN' => 'Co Roscommon', + 'SO' => 'Co Sligo', + 'TA' => 'Co Tipperary', + 'WD' => 'Co Waterford', + 'WH' => 'Co Westmeath', + 'WX' => 'Co Wexford', + 'WW' => 'Co Wicklow', ); $administrative_areas['IN'] = array( 'AP' => t('Andhra Pradesh'), @@ -388,8 +411,6 @@ function addressfield_get_administrative_areas($country_code) { 'AS' => t('Assam'), 'BR' => t('Bihar'), 'CT' => t('Chhattisgarh'), - 'DD' => t('Daman & Diu'), - 'DN' => t('Dadra & Nagar Haveli'), 'GA' => t('Goa'), 'GJ' => t('Gujarat'), 'HP' => t('Himachal Pradesh'), @@ -424,116 +445,116 @@ function addressfield_get_administrative_areas($country_code) { 'PY' => t('Puducherry'), ); $administrative_areas['IT'] = array( - 'AG' => t('Agrigento'), - 'AL' => t('Alessandria'), - 'AN' => t('Ancona'), - 'AO' => t('Aosta'), - 'AP' => t('Ascoli Piceno'), - 'AQ' => t("L'Aquila"), - 'AR' => t('Arezzo'), - 'AT' => t('Asti'), - 'AV' => t('Avellino'), - 'BA' => t('Bari'), - 'BG' => t('Bergamo'), - 'BI' => t('Biella'), - 'BL' => t('Belluno'), - 'BN' => t('Benevento'), - 'BO' => t('Bologna'), - 'BR' => t('Brindisi'), - 'BS' => t('Brescia'), - 'BT' => t('Barletta-Andria-Trani'), - 'BZ' => t('Bolzano/Bozen'), - 'CA' => t('Cagliari'), - 'CB' => t('Campobasso'), - 'CE' => t('Caserta'), - 'CH' => t('Chieti'), - 'CI' => t('Carbonia-Iglesias'), - 'CL' => t('Caltanissetta'), - 'CN' => t('Cuneo'), - 'CO' => t('Como'), - 'CR' => t('Cremona'), - 'CS' => t('Cosenza'), - 'CT' => t('Catania'), - 'CZ' => t('Catanzaro'), - 'EN' => t('Enna'), - 'FC' => t('Forlì-Cesena'), - 'FE' => t('Ferrara'), - 'FG' => t('Foggia'), - 'FI' => t('Firenze'), - 'FM' => t('Fermo'), - 'FR' => t('Frosinone'), - 'GE' => t('Genova'), - 'GO' => t('Gorizia'), - 'GR' => t('Grosseto'), - 'IM' => t('Imperia'), - 'IS' => t('Isernia'), - 'KR' => t('Crotone'), - 'LC' => t('Lecco'), - 'LE' => t('Lecce'), - 'LI' => t('Livorno'), - 'LO' => t('Lodi'), - 'LT' => t('Latina'), - 'LU' => t('Lucca'), - 'MB' => t('Monza e Brianza'), - 'MC' => t('Macerata'), - 'ME' => t('Messina'), - 'MI' => t('Milano'), - 'MN' => t('Mantova'), - 'MO' => t('Modena'), - 'MS' => t('Massa-Carrara'), - 'MT' => t('Matera'), - 'NA' => t('Napoli'), - 'NO' => t('Novara'), - 'NU' => t('Nuoro'), - 'OG' => t('Ogliastra'), - 'OR' => t('Oristano'), - 'OT' => t('Olbia-Tempio'), - 'PA' => t('Palermo'), - 'PC' => t('Piacenza'), - 'PD' => t('Padova'), - 'PE' => t('Pescara'), - 'PG' => t('Perugia'), - 'PI' => t('Pisa'), - 'PN' => t('Pordenone'), - 'PO' => t('Prato'), - 'PR' => t('Parma'), - 'PT' => t('Pistoia'), - 'PU' => t('Pesaro e Urbino'), - 'PV' => t('Pavia'), - 'PZ' => t('Potenza'), - 'RA' => t('Ravenna'), - 'RC' => t('Reggio Calabria'), - 'RE' => t('Reggio Emilia'), - 'RG' => t('Ragusa'), - 'RI' => t('Rieti'), - 'RM' => t('Roma'), - 'RN' => t('Rimini'), - 'RO' => t('Rovigo'), - 'SA' => t('Salerno'), - 'SI' => t('Siena'), - 'SO' => t('Sondrio'), - 'SP' => t('La Spezia'), - 'SR' => t('Siracusa'), - 'SS' => t('Sassari'), - 'SV' => t('Savona'), - 'TA' => t('Taranto'), - 'TE' => t('Teramo'), - 'TN' => t('Trento'), - 'TO' => t('Torino'), - 'TP' => t('Trapani'), - 'TR' => t('Terni'), - 'TS' => t('Trieste'), - 'TV' => t('Treviso'), - 'UD' => t('Udine'), - 'VA' => t('Varese'), - 'VB' => t('Verbano-Cusio-Ossola'), - 'VC' => t('Vercelli'), - 'VE' => t('Venezia'), - 'VI' => t('Vicenza'), - 'VR' => t('Verona'), - 'VS' => t('Medio Campidano'), - 'VT' => t('Viterbo'), - 'VV' => t('Vibo Valentia'), + 'AG' => 'Agrigento', + 'AL' => 'Alessandria', + 'AN' => 'Ancona', + 'AO' => 'Aosta', + 'AP' => 'Ascoli Piceno', + 'AQ' => "L'Aquila", + 'AR' => 'Arezzo', + 'AT' => 'Asti', + 'AV' => 'Avellino', + 'BA' => 'Bari', + 'BG' => 'Bergamo', + 'BI' => 'Biella', + 'BL' => 'Belluno', + 'BN' => 'Benevento', + 'BO' => 'Bologna', + 'BR' => 'Brindisi', + 'BS' => 'Brescia', + 'BT' => 'Barletta-Andria-Trani', + 'BZ' => 'Bolzano/Bozen', + 'CA' => 'Cagliari', + 'CB' => 'Campobasso', + 'CE' => 'Caserta', + 'CH' => 'Chieti', + 'CI' => 'Carbonia-Iglesias', + 'CL' => 'Caltanissetta', + 'CN' => 'Cuneo', + 'CO' => 'Como', + 'CR' => 'Cremona', + 'CS' => 'Cosenza', + 'CT' => 'Catania', + 'CZ' => 'Catanzaro', + 'EN' => 'Enna', + 'FC' => 'Forlì-Cesena', + 'FE' => 'Ferrara', + 'FG' => 'Foggia', + 'FI' => 'Firenze', + 'FM' => 'Fermo', + 'FR' => 'Frosinone', + 'GE' => 'Genova', + 'GO' => 'Gorizia', + 'GR' => 'Grosseto', + 'IM' => 'Imperia', + 'IS' => 'Isernia', + 'KR' => 'Crotone', + 'LC' => 'Lecco', + 'LE' => 'Lecce', + 'LI' => 'Livorno', + 'LO' => 'Lodi', + 'LT' => 'Latina', + 'LU' => 'Lucca', + 'MB' => 'Monza e Brianza', + 'MC' => 'Macerata', + 'ME' => 'Messina', + 'MI' => 'Milano', + 'MN' => 'Mantova', + 'MO' => 'Modena', + 'MS' => 'Massa-Carrara', + 'MT' => 'Matera', + 'NA' => 'Napoli', + 'NO' => 'Novara', + 'NU' => 'Nuoro', + 'OG' => 'Ogliastra', + 'OR' => 'Oristano', + 'OT' => 'Olbia-Tempio', + 'PA' => 'Palermo', + 'PC' => 'Piacenza', + 'PD' => 'Padova', + 'PE' => 'Pescara', + 'PG' => 'Perugia', + 'PI' => 'Pisa', + 'PN' => 'Pordenone', + 'PO' => 'Prato', + 'PR' => 'Parma', + 'PT' => 'Pistoia', + 'PU' => 'Pesaro e Urbino', + 'PV' => 'Pavia', + 'PZ' => 'Potenza', + 'RA' => 'Ravenna', + 'RC' => 'Reggio Calabria', + 'RE' => 'Reggio Emilia', + 'RG' => 'Ragusa', + 'RI' => 'Rieti', + 'RM' => 'Roma', + 'RN' => 'Rimini', + 'RO' => 'Rovigo', + 'SA' => 'Salerno', + 'SI' => 'Siena', + 'SO' => 'Sondrio', + 'SP' => 'La Spezia', + 'SR' => 'Siracusa', + 'SS' => 'Sassari', + 'SV' => 'Savona', + 'TA' => 'Taranto', + 'TE' => 'Teramo', + 'TN' => 'Trento', + 'TO' => 'Torino', + 'TP' => 'Trapani', + 'TR' => 'Terni', + 'TS' => 'Trieste', + 'TV' => 'Treviso', + 'UD' => 'Udine', + 'VA' => 'Varese', + 'VB' => 'Verbano-Cusio-Ossola', + 'VC' => 'Vercelli', + 'VE' => 'Venezia', + 'VI' => 'Vicenza', + 'VR' => 'Verona', + 'VS' => 'Medio Campidano', + 'VT' => 'Viterbo', + 'VV' => 'Vibo Valentia', ); $administrative_areas['JM'] = array( '13' => 'Clarendon', @@ -638,38 +659,38 @@ function addressfield_get_administrative_areas($country_code) { 'ZHA' => t('Zhambyl region'), ); $administrative_areas['MX'] = array( - 'AGU' => t('Aguascalientes'), - 'BCN' => t('Baja California'), - 'BCS' => t('Baja California Sur'), - 'CAM' => t('Campeche'), - 'COA' => t('Coahuila'), - 'COL' => t('Colima'), - 'CHP' => t('Chiapas'), - 'CHH' => t('Chihuahua'), - 'DIF' => t('Distrito Federal'), - 'DUG' => t('Durango'), - 'MEX' => t('Estado de México'), - 'GUA' => t('Guanajuato'), - 'GRO' => t('Guerrero'), - 'HID' => t('Hidalgo'), - 'JAL' => t('Jalisco'), - 'MIC' => t('Michoacán'), - 'MOR' => t('Morelos'), - 'NAY' => t('Nayarit'), - 'NLE' => t('Nuevo León'), - 'OAX' => t('Oaxaca'), - 'PUE' => t('Puebla'), - 'QUE' => t('Queretaro'), - 'ROO' => t('Quintana Roo'), - 'SLP' => t('San Luis Potosí'), - 'SIN' => t('Sinaloa'), - 'SON' => t('Sonora'), - 'TAB' => t('Tabasco'), - 'TAM' => t('Tamaulipas'), - 'TLA' => t('Tlaxcala'), - 'VER' => t('Veracruz'), - 'YUC' => t('Yucatán'), - 'ZAC' => t('Zacatecas'), + 'AGU' => 'Aguascalientes', + 'BCN' => 'Baja California', + 'BCS' => 'Baja California Sur', + 'CAM' => 'Campeche', + 'COA' => 'Coahuila', + 'COL' => 'Colima', + 'CHP' => 'Chiapas', + 'CHH' => 'Chihuahua', + 'DIF' => 'Distrito Federal', + 'DUG' => 'Durango', + 'MEX' => 'Estado de México', + 'GUA' => 'Guanajuato', + 'GRO' => 'Guerrero', + 'HID' => 'Hidalgo', + 'JAL' => 'Jalisco', + 'MIC' => 'Michoacán', + 'MOR' => 'Morelos', + 'NAY' => 'Nayarit', + 'NLE' => 'Nuevo León', + 'OAX' => 'Oaxaca', + 'PUE' => 'Puebla', + 'QUE' => 'Queretaro', + 'ROO' => 'Quintana Roo', + 'SLP' => 'San Luis Potosí', + 'SIN' => 'Sinaloa', + 'SON' => 'Sonora', + 'TAB' => 'Tabasco', + 'TAM' => 'Tamaulipas', + 'TLA' => 'Tlaxcala', + 'VER' => 'Veracruz', + 'YUC' => 'Yucatán', + 'ZAC' => 'Zacatecas', ); $administrative_areas['MY'] = array( '01' => t('Johor'), @@ -690,31 +711,31 @@ function addressfield_get_administrative_areas($country_code) { '11' => t('Terengganu'), ); $administrative_areas['PE'] = array( - 'AMA' => t('Amazonas'), - 'ANC' => t('Ancash'), - 'APU' => t('Apurimac'), - 'ARE' => t('Arequipa'), - 'AYA' => t('Ayacucho'), - 'CAJ' => t('Cajamarca'), - 'CAL' => t('Callao'), - 'CUS' => t('Cusco'), - 'HUV' => t('Huancavelica'), - 'HUC' => t('Huanuco'), - 'ICA' => t('Ica'), - 'JUN' => t('Junin'), - 'LAL' => t('La Libertad'), - 'LAM' => t('Lambayeque'), - 'LIM' => t('Lima'), - 'LOR' => t('Loreto'), - 'MDD' => t('Madre de Dios'), - 'MOQ' => t('Moquegua'), - 'PAS' => t('Pasco'), - 'PIU' => t('Piura'), - 'PUN' => t('Puno'), - 'SAM' => t('San Martin'), - 'TAC' => t('Tacna'), - 'TUM' => t('Tumbes'), - 'UCA' => t('Ucayali'), + 'AMA' => 'Amazonas', + 'ANC' => 'Ancash', + 'APU' => 'Apurimac', + 'ARE' => 'Arequipa', + 'AYA' => 'Ayacucho', + 'CAJ' => 'Cajamarca', + 'CAL' => 'Callao', + 'CUS' => 'Cusco', + 'HUV' => 'Huancavelica', + 'HUC' => 'Huanuco', + 'ICA' => 'Ica', + 'JUN' => 'Junin', + 'LAL' => 'La Libertad', + 'LAM' => 'Lambayeque', + 'LIM' => 'Lima', + 'LOR' => 'Loreto', + 'MDD' => 'Madre de Dios', + 'MOQ' => 'Moquegua', + 'PAS' => 'Pasco', + 'PIU' => 'Piura', + 'PUN' => 'Puno', + 'SAM' => 'San Martin', + 'TAC' => 'Tacna', + 'TUM' => 'Tumbes', + 'UCA' => 'Ucayali', ); $administrative_areas['RU'] = array( 'MOW' => t('Moskva'), @@ -918,7 +939,6 @@ function addressfield_get_administrative_areas($country_code) { '21' => t('Zakarpats\'ka oblast'), '23' => t('Zaporiz\'ka oblast'), '26' => t('Ivano-Frankivs\'ka oblast'), - '30' => t('Kyiv city'), '30' => t('Kiev Oblast'), '35' => t('Kirovohrads\'ka oblast'), '09' => t('Luhans\'ka oblast'), @@ -1003,35 +1023,32 @@ function addressfield_get_administrative_areas($country_code) { 'VI' => t('Virgin Islands'), ); $administrative_areas['VE'] = array( - 'Z' => t('Amazonas'), - 'B' => t('Anzoátegui'), - 'C' => t('Apure'), - 'D' => t('Aragua'), - 'E' => t('Barinas'), - 'F' => t('Bolívar'), - 'G' => t('Carabobo'), - 'H' => t('Cojedes'), - 'Y' => t('Delta Amacuro'), - 'W' => t('Dependencias Federales'), - 'A' => t('Distrito Federal'), - 'I' => t('Falcón'), - 'J' => t('Guárico'), - 'K' => t('Lara'), - 'L' => t('Mérida'), - 'M' => t('Miranda'), - 'N' => t('Monagas'), - 'O' => t('Nueva Esparta'), - 'P' => t('Portuguesa'), - 'R' => t('Sucre'), - 'S' => t('Táchira'), - 'T' => t('Trujillo'), - 'X' => t('Vargas'), - 'U' => t('Yaracuy'), - 'V' => t('Zulia'), + 'Z' => 'Amazonas', + 'B' => 'Anzoátegui', + 'C' => 'Apure', + 'D' => 'Aragua', + 'E' => 'Barinas', + 'F' => 'Bolívar', + 'G' => 'Carabobo', + 'H' => 'Cojedes', + 'Y' => 'Delta Amacuro', + 'W' => 'Dependencias Federales', + 'A' => 'Distrito Federal', + 'I' => 'Falcón', + 'J' => 'Guárico', + 'K' => 'Lara', + 'L' => 'Mérida', + 'M' => 'Miranda', + 'N' => 'Monagas', + 'O' => 'Nueva Esparta', + 'P' => 'Portuguesa', + 'R' => 'Sucre', + 'S' => 'Táchira', + 'T' => 'Trujillo', + 'X' => 'Vargas', + 'U' => 'Yaracuy', + 'V' => 'Zulia', ); - // Allow other modules to alter the administrative areas. - drupal_alter('addressfield_administrative_areas', $administrative_areas); - - return isset($administrative_areas[$country_code]) ? $administrative_areas[$country_code] : null; + return $administrative_areas; } diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.devel_generate.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.devel_generate.inc index e14c8885..2eb9c41a 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.devel_generate.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.devel_generate.inc @@ -43,7 +43,6 @@ function _addressfield_sample_addresses() { $fields = array(); if ($handle = @fopen("$filepath/addresses.txt",'r')) { if (is_resource($handle)) { - $addresses = array(); while (($buffer = fgets($handle)) !== false) { list($country, $administrative_area, $sub_administrative_area, $locality, $dependent_locality, $postal_code, $thoroughfare, $premise, $sub_premise) = explode("\t", $buffer); $fields[] = array( diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.feeds.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.feeds.inc index 2b8537ad..062b221f 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.feeds.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.feeds.inc @@ -6,10 +6,10 @@ */ /** - * Implements hook_feeds_node_processor_targets_alter(). + * Implements hook_feeds_processor_targets_alter(). */ -function addressfield_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { - foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { +function addressfield_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { + foreach (field_info_instances($entity_type, $bundle) as $name => $instance) { $info = field_info_field($name); if ($info['type'] == 'addressfield') { foreach ($info['columns'] as $sub_field => $schema_info) { @@ -34,7 +34,7 @@ function addressfield_feeds_processor_targets_alter(&$targets, $entity_type, $bu * An entity object, for instance a node object. * @param $target * A string identifying the target on the node. - * @param $value + * @param $values * The value to populate the target with. */ function addressfield_set_target($source, $entity, $target, $values) { diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.info b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.info index 991621bb..89e95157 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.info +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.info @@ -6,12 +6,13 @@ package = Fields dependencies[] = ctools files[] = addressfield.migrate.inc +files[] = views/addressfield_views_handler_field_administrative_area.inc files[] = views/addressfield_views_handler_field_country.inc files[] = views/addressfield_views_handler_filter_country.inc -; Information added by Drupal.org packaging script on 2015-04-23 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2015-10-07 +version = "7.x-1.2" core = "7.x" project = "addressfield" -datestamp = "1429819382" +datestamp = "1444254070" diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.migrate.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.migrate.inc index 6e74d63f..cc98911c 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.migrate.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.migrate.inc @@ -152,7 +152,7 @@ class MigrateAddressFieldHandler extends MigrateFieldHandler { if ($value) { if (isset($field_info['columns'][$column_key])) { - // Store the data in a seperate column. + // Store the data in a separate column. $result[$column_key] = $value; } else { diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.module b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.module index db7e0236..25b277dc 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.module +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/addressfield.module @@ -534,7 +534,9 @@ function addressfield_field_widget_form(&$form, &$form_state, $field, $instance, // $form_state['values'] is empty because of #limit_validation_errors, so // $form_state['input'] needs to be used instead. $parents = array_merge($element['#field_parents'], array($element['#field_name'], $langcode, $delta)); - $input_address = drupal_array_get_nested_value($form_state['input'], $parents); + if (!empty($form_state['input'])) { + $input_address = drupal_array_get_nested_value($form_state['input'], $parents); + } if (!empty($input_address)) { $address = $input_address; } @@ -728,8 +730,6 @@ function addressfield_field_formatter_settings_summary($field, $instance, $view_ $display = $instance['display'][$view_mode]; $settings = $display['settings']; - $summary = ''; - if ($settings['use_widget_handlers']) { return t('Use widget configuration'); } diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/example/addressfield_example.info b/profiles/commerce_kickstart/modules/contrib/addressfield/example/addressfield_example.info index c7120e3b..b0f19829 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/example/addressfield_example.info +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/example/addressfield_example.info @@ -7,9 +7,9 @@ hidden = TRUE dependencies[] = ctools dependencies[] = addressfield -; Information added by Drupal.org packaging script on 2015-04-23 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2015-10-07 +version = "7.x-1.2" core = "7.x" project = "addressfield" -datestamp = "1429819382" +datestamp = "1444254070" diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/example/plugins/format/address-ch-example.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/example/plugins/format/address-ch-example.inc index db3a98e2..4f92daa5 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/example/plugins/format/address-ch-example.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/example/plugins/format/address-ch-example.inc @@ -3552,8 +3552,6 @@ function addressfield_form_ch_postal_code_validation($element, &$form_state, &$f if (!empty($element['#value']) && (isset($data[$element['#value']]))) { // Get the base #parents for this address form. $base_parents = array_slice($element['#parents'], 0, -1); - $base_array_parents = array_slice($element['#array_parents'], 0, -2); - $city = $data[$element['#value']]; // Set the new values in the form. diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address-optional.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address-optional.inc index 201e40ea..bb4408be 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address-optional.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address-optional.inc @@ -7,7 +7,7 @@ */ $plugin = array( - 'title' => t('Make all fields optional (Not recommended)'), + 'title' => t('Make all fields optional (No validation - unsuitable for postal purposes)'), 'format callback' => 'addressfield_format_address_optional', 'type' => 'address', 'weight' => 100, diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address.inc index 0523ffe1..e9b801f1 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/address.inc @@ -83,7 +83,7 @@ function addressfield_format_address_generate(&$format, $address, $context = arr '#tag' => 'div', '#attributes' => array( 'class' => array('dependent-locality'), - 'autocomplete' => '"address-level3', + 'autocomplete' => 'address-level3', ), // Most formats place this field in its own row. '#suffix' => $clearfix, @@ -97,7 +97,7 @@ function addressfield_format_address_generate(&$format, $address, $context = arr '#prefix' => ' ', '#attributes' => array( 'class' => array('locality'), - 'autocomplete' => '"address-level2', + 'autocomplete' => 'address-level2', ), ); $format['locality_block']['administrative_area'] = array( diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/organisation.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/organisation.inc index 5cef00e3..3cec0f7f 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/organisation.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/plugins/format/organisation.inc @@ -20,7 +20,7 @@ $plugin = array( function addressfield_format_organisation_generate(&$format, $address) { $format['organisation_block'] = array( '#type' => 'addressfield_container', - '#attributes' => array('class' => array('addressfield-container-inline', 'name-block')), + '#attributes' => array('class' => array('addressfield-container-inline', 'organisation-block')), '#weight' => -50, // The addressfield is considered empty without a country, hide all fields // until one is selected. diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield.views.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield.views.inc index 9be1637e..f059da34 100644 --- a/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield.views.inc @@ -26,7 +26,7 @@ function addressfield_field_views_data($field) { // Only expose these components as Views field handlers. $implemented = array( 'country' => 'addressfield_views_handler_field_country', - 'administrative_area' => 'views_handler_field', + 'administrative_area' => 'addressfield_views_handler_field_administrative_area', 'sub_administrative_area' => 'views_handler_field', 'dependent_locality' => 'views_handler_field', 'locality' => 'views_handler_field', diff --git a/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield_views_handler_field_administrative_area.inc b/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield_views_handler_field_administrative_area.inc new file mode 100644 index 00000000..daeae662 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/addressfield/views/addressfield_views_handler_field_administrative_area.inc @@ -0,0 +1,48 @@ +country_alias = $this->query->add_field($this->table_alias, $this->definition['field_name'] . '_country'); + } + + function option_definition() { + $options = parent::option_definition(); + $options['display_name'] = array('default' => TRUE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $form['display_name'] = array( + '#type' => 'checkbox', + '#title' => t('Display the name of administrative area instead of the code.'), + '#default_value' => $this->options['display_name'], + ); + } + + function get_value($values, $field = NULL) { + $value = parent::get_value($values, $field); + + // If we have a value for the field, look for the administrative area name in the + // Address Field options list array if specified. + if (!empty($value) && !empty($this->options['display_name'])) { + module_load_include('inc', 'addressfield', 'addressfield.administrative_areas'); + $country = $values->{$this->country_alias}; + $areas = addressfield_get_administrative_areas($country); + + if (!empty($areas[$value])) { + $value = $areas[$value]; + } + } + + return $value; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/admin_views/admin_views.info b/profiles/commerce_kickstart/modules/contrib/admin_views/admin_views.info index 3bce29f5..2dc00a58 100644 --- a/profiles/commerce_kickstart/modules/contrib/admin_views/admin_views.info +++ b/profiles/commerce_kickstart/modules/contrib/admin_views/admin_views.info @@ -10,9 +10,9 @@ files[] = plugins/views_plugin_access_menu.inc files[] = tests/admin_views.test -; Information added by Drupal.org packaging script on 2015-07-08 -version = "7.x-1.5" +; Information added by Drupal.org packaging script on 2016-08-02 +version = "7.x-1.6" core = "7.x" project = "admin_views" -datestamp = "1436376676" +datestamp = "1470165840" diff --git a/profiles/commerce_kickstart/modules/contrib/admin_views/plugins/views_plugin_access_menu.inc b/profiles/commerce_kickstart/modules/contrib/admin_views/plugins/views_plugin_access_menu.inc index 82c9ceaa..79a2e93f 100644 --- a/profiles/commerce_kickstart/modules/contrib/admin_views/plugins/views_plugin_access_menu.inc +++ b/profiles/commerce_kickstart/modules/contrib/admin_views/plugins/views_plugin_access_menu.inc @@ -24,6 +24,11 @@ class views_plugin_access_menu extends views_plugin_access { // view itself. The 'current display' could be set to something else, like // default. $path = $this->display->handler->get_option('path'); + + if (empty($path)) { + return FALSE; + } + $item = menu_get_item($path); // If we are on the original router path, the menu system has checked access already. diff --git a/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views.test b/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views.test index 727bded3..22d4e2bd 100644 --- a/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views.test +++ b/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views.test @@ -199,6 +199,7 @@ class AdminViewsPageDisplayTestCase extends AdminViewsWebTestCase { * Returns a test page view with a path under "admin/content". */ protected function normalPageView() { + views_include('view'); $view = new view(); $view->name = 'admin_views_test_normal'; $view->description = ''; @@ -283,5 +284,17 @@ class AdminViewsAccessHandlerTestCase extends AdminViewsWebTestCase { // The next item in the AJAX data will be the insert command containing the // rendered view. $this->assertTrue(empty($response_data[1])); + + // Test the access again with the default display. + $params['views_display_id'] = 'default'; + + $response_data = $this->drupalGetAJAX('views/ajax', array('query' => $params)); + + $this->assertResponse(200); + // Check no views settings are returned. + $this->assertTrue(empty($response_data[0]['settings']['views'])); + // The next item in the AJAX data will be the insert command containing the + // rendered view. + $this->assertTrue(empty($response_data[1])); } } diff --git a/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views_test/admin_views_test.info b/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views_test/admin_views_test.info index 80f2f406..ff8fdfc8 100644 --- a/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views_test/admin_views_test.info +++ b/profiles/commerce_kickstart/modules/contrib/admin_views/tests/admin_views_test/admin_views_test.info @@ -6,9 +6,9 @@ hidden = TRUE dependencies[] admin_views -; Information added by Drupal.org packaging script on 2015-07-08 -version = "7.x-1.5" +; Information added by Drupal.org packaging script on 2016-08-02 +version = "7.x-1.6" core = "7.x" project = "admin_views" -datestamp = "1436376676" +datestamp = "1470165840" diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/chosen/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/chosen/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/README.txt b/profiles/commerce_kickstart/modules/contrib/chosen/README.txt index 658c4e0c..619533e2 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/chosen/README.txt @@ -1,13 +1,17 @@ -- SUMMARY -- -Chosen uses the Chosen jQuery plugin to make your +elements more user-friendly. -- INSTALLATION -- - 1. Download the Chosen jQuery plugin (http://harvesthq.github.io/chosen/ version 1.1.0 is recommended) and extract the file under sites/all/libraries. + 1. Download the Chosen jQuery plugin + (http://harvesthq.github.io/chosen/ version 1.1.0 is recommended) + and extract the file under sites/all/libraries. 2. Download and enable the module. - 3. Configure at Administer > Configuration > User interface > Chosen (requires administer site configuration permission) + 3. Configure at Administer > Configuration > + User interface > Chosen (requires administer site configuration permission) -- INSTALLATION VIA DRUSH -- @@ -29,4 +33,5 @@ There are accessibility problems with the main library as identified here: How to exlude a select field from becoming a chosen select. - go to the configuration page and add your field using the jquery "not" operator to the textarea with the comma seperated values. - For date fields this could look like: select:not([name*='day'],[name*='year'],[name*='month']) + For date fields this could look like: + select:not([name*='day'],[name*='year'],[name*='month']) diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.admin.inc b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.admin.inc index 188c1730..8fbbf800 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.admin.inc @@ -13,7 +13,10 @@ function chosen_admin_settings($form, &$form_state) { $chosen_path = chosen_get_chosen_path(); if (!$chosen_path) { drupal_set_message(t('The library could not be detected. You need to download the !chosen and extract the entire contents of the archive into the %path directory on your server.', - array('!chosen' => l(t('Chosen JavaScript file'), CHOSEN_WEBSITE_URL), '%path' => 'sites/all/libraries') + array( + '!chosen' => l(t('Chosen JavaScript file'), CHOSEN_WEBSITE_URL), + '%path' => 'sites/all/libraries', + ) ), 'error'); return; } @@ -46,10 +49,9 @@ function chosen_admin_settings($form, &$form_state) { '#type' => 'textfield', '#title' => t('Minimum width of widget'), '#field_suffix' => 'px', - '#required' => TRUE, '#size' => 3, - '#default_value' => variable_get('chosen_minimum_width', 200), - '#description' => t('The minimum width of the Chosen widget.'), + '#default_value' => variable_get('chosen_minimum_width', ''), + '#description' => t('The minimum width of the Chosen widget. Leave blank to have chosen determine this.'), ); $form['chosen_jquery_selector'] = array( @@ -61,7 +63,7 @@ function chosen_admin_settings($form, &$form_state) { $form['options'] = array( '#type' => 'fieldset', - '#title' => t('Chosen options'), + '#title' => t('Chosen general options'), ); $form['options']['chosen_search_contains'] = array( '#type' => 'checkbox', @@ -76,11 +78,36 @@ function chosen_admin_settings($form, &$form_state) { '#default_value' => variable_get('chosen_disable_search', FALSE), '#description' => t('Enable or disable the search box in the results list to filter out possible options.'), ); - $form['options']['chosen_use_theme'] = array( + + $form['theme_options'] = array( + '#type' => 'fieldset', + '#title' => t('Chosen per theme options'), + ); + + $form['theme_options']['chosen_disabled_themes'] = array( + '#type' => 'checkboxes', + '#title' => t('Disable the default Chosen theme for the following themes'), + '#options' => _chosen_enabled_themes_options(), + '#default_value' => variable_get('chosen_disabled_themes', array()), + '#description' => t('Enable or disable the default Chosen CSS file. Select a theme if it contains custom styles for Chosen replacements.'), + ); + + $form['options']['chosen_include'] = array( + '#type' => 'radios', + '#title' => t('Use chosen for admin pages and/or front end pages'), + '#options' => array( + CHOSEN_INCLUDE_EVERYWHERE => t('Include Chosen on every page'), + CHOSEN_INCLUDE_ADMIN => t('Include Chosen only on admin pages'), + CHOSEN_INCLUDE_NO_ADMIN => t('Include Chosen only on front end pages'), + ), + '#default_value' => variable_get('chosen_include', CHOSEN_INCLUDE_EVERYWHERE), + ); + + $form['options']['chosen_allow_single_deselect'] = array( '#type' => 'checkbox', - '#title' => t('Use the default chosen theme'), - '#default_value' => variable_get('chosen_use_theme', TRUE), - '#description' => t('Enable or disable the default chosen CSS file. Disable this option if your theme contains custom styles for Chosen replacements.'), + '#title' => t('Allow deselect on single selects'), + '#default_value' => variable_get('chosen_allow_single_deselect', FALSE), + '#description' => t("Enable or disable option deselection for single select fields. This will only work if the first option has blank text."), ); $form['strings'] = array( @@ -108,3 +135,22 @@ function chosen_admin_settings($form, &$form_state) { return system_settings_form($form); } + +/** + * Helper function to get options for enabled themes. + */ +function _chosen_enabled_themes_options() { + $options = array(); + + // Get the list of themes. + $themes = list_themes(); + + foreach ($themes as $theme) { + // Only create options for enabled themes. + if ($theme->status) { + $options[$theme->name] = $theme->info['name']; + } + } + + return $options; +} diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.info b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.info index a019e969..d77d4c1d 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.info +++ b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.info @@ -4,10 +4,9 @@ package = User interface configure = admin/config/user-interface/chosen core = 7.x - -; Information added by drush on 2015-08-20 -version = "7.x-2.0-beta4+1-dev" +; Information added by Drupal.org packaging script on 2017-03-23 +version = "7.x-2.1" core = "7.x" project = "chosen" -datestamp = "1440110594" +datestamp = "1490260386" diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.install b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.install index 71ec7933..2faec729 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.install +++ b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.install @@ -12,13 +12,15 @@ function chosen_requirements($phase) { module_load_include('module', 'chosen'); $t = get_t(); $chosen_path = chosen_get_chosen_path(); - switch($phase) { + switch ($phase) { case 'runtime': if (!$chosen_path) { $requirements['chosen_js'] = array( 'title' => $t('Chosen JavaScript file'), 'severity' => REQUIREMENT_ERROR, - 'description' => $t('You need to download the !chosen and extract the entire contents of the archive into the %path directory on your server.', array('!chosen' => l($t('Chosen JavaScript file'), CHOSEN_WEBSITE_URL), '%path' => 'sites/all/libraries')), + 'description' => $t('You need to download the !chosen and extract the entire contents of the archive into the %path directory on your server.', array( + '!chosen' => l($t('Chosen JavaScript file'), CHOSEN_WEBSITE_URL), + '%path' => 'sites/all/libraries')), 'value' => $t('Not Installed'), ); } @@ -29,8 +31,8 @@ function chosen_requirements($phase) { 'value' => $t('Installed'), ); } - break; - } + break; + } return $requirements; } @@ -47,12 +49,16 @@ function chosen_uninstall() { variable_del('chosen_placeholder_text_multiple'); variable_del('chosen_placeholder_text_single'); variable_del('chosen_no_results_text'); - variable_del('chosen_use_theme'); + variable_del('chosen_disabled_themes'); variable_del('chosen_disable_search'); variable_del('chosen_disable_search_threshold'); + variable_del('chosen_allow_single_deselect'); + variable_del('chosen_include'); } /** + * Implements hook_update_N(). + * * Transfer the old chosen minimum value to the new chosen minimum single and * chosen minimum multiple variables. */ @@ -81,7 +87,10 @@ function chosen_update_7202() { } /** - * Update any option_select widgets with an empty Chosen setting to the 'No preference' Chosen setting. + * Implements hook_update_N(). + * + * Update any option_select widgets with an empty Chosen setting + * to the 'No preference' Chosen setting. */ function chosen_update_7203() { $field_names = db_query("SELECT field_name FROM {field_config_instance} WHERE data LIKE :widget", array(':widget' => '%' . db_like('options_select') . '%' . db_like('apply_chosen') . '%'))->fetchCol(); @@ -97,3 +106,34 @@ function chosen_update_7203() { } } } + +/** + * Implements hook_update_N(). + * + * Set the 'Disable the default Chosen theme' Chosen setting to the opposite + * of the current general'Use the default chosen theme' + * Chosen setting for all enabled themes. + */ +function chosen_update_7204() { + module_load_include('inc', 'chosen', 'chosen.admin'); + + $chosen_use_theme_setting = variable_get('chosen_use_theme', TRUE); + + // We now chose to disable the default Chosen CSS instead of deselecting + // the 'Use the default chosen theme' option box. + if (!$chosen_use_theme_setting) { + $themes = system_list('theme'); + + foreach ($themes as $theme) { + // Only create options for enabled themes. + if ($theme->status) { + $options[$theme->name] = $theme->info['name']; + } + } + + variable_set('chosen_disabled_themes', array_keys($options)); + } + + // Delete the old variable. + variable_del('chosen_use_theme'); +} diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.js b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.js index c7731c22..e0a64061 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.js +++ b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.js @@ -1,3 +1,8 @@ +/** + * @file + * Attaches behaviors for the Chosen module. + */ + (function($) { Drupal.behaviors.chosen = { attach: function(context, settings) { @@ -12,11 +17,13 @@ // The width default option is considered the minimum width, so this // must be evaluated for every option. - if ($(element).width() < settings.chosen.minimum_width) { - options.width = settings.chosen.minimum_width + 'px'; - } - else { - options.width = $(element).width() + 'px'; + if (settings.chosen.minimum_width > 0) { + if ($(element).width() < settings.chosen.minimum_width) { + options.width = settings.chosen.minimum_width + 'px'; + } + else { + options.width = $(element).width() + 'px'; + } } // Some field widgets have cardinality, so we must respect that. @@ -41,7 +48,7 @@ // - WYSIWYG elements // - Tabledrag weights // - Elements that have opted-out of Chosen - // - Elements already processed by Chosen + // - Elements already processed by Chosen. .not('#field-ui-field-overview-form select, #field-ui-display-overview-form select, .wysiwyg, .draggable select[name$="[weight]"], .draggable select[name$="[position]"], .chosen-disable, .chosen-processed') .filter(function() { // Filter out select widgets that do not meet the minimum number of diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.module b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.module index e54378dc..b65c2bad 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/chosen.module +++ b/profiles/commerce_kickstart/modules/contrib/chosen/chosen.module @@ -8,9 +8,26 @@ */ /** - * Define chosen library url + * Define chosen library url. */ define('CHOSEN_WEBSITE_URL', 'http://harvesthq.github.io/chosen'); +define('CHOSEN_INCLUDE_ADMIN', 0); +define('CHOSEN_INCLUDE_NO_ADMIN', 1); +define('CHOSEN_INCLUDE_EVERYWHERE', 2); + +/** + * Implements hook_help(). + */ +function chosen_help($path, $arg) { + switch ($path) { + case 'admin/help#chosen': + $output = '

        ' . t('About') . '

        '; + $output .= '

        ' . t('Chosen uses the Chosen jQuery plugin to make your < select > elements more user-friendly.') . '

        '; + $output .= '

        ' . t('Usage') . '

        '; + $output .= '

        ' . t('Configure at: admin/config/user-interface/chosen', array('@structure_types' => base_path() . 'admin/config/user-interface/chosen')) . '

        '; + return $output; + } +} /** * Implements hook_menu(). @@ -89,6 +106,8 @@ function chosen_field_widget_form_alter(&$element, &$form_state, $context) { * Implements hook_library(). */ function chosen_library() { + global $theme; + $library_path = chosen_get_chosen_path(); $info['chosen'] = array( @@ -96,16 +115,25 @@ function chosen_library() { 'website' => CHOSEN_WEBSITE_URL, 'version' => '1.1.0', 'js' => array( - $library_path . '/chosen.jquery.min.js' => array('group' => 'JS_LIBRARY'), + $library_path . '/chosen.jquery.min.js' => array(), ), ); - if (variable_get('chosen_use_theme', TRUE)) { - $info['chosen']['css'] = array($library_path . '/chosen.css' => array()); + + $css_disabled_themes = variable_get('chosen_disabled_themes', array()); + + // Only add the Chosen CSS if it is not disabled for the active theme. + if (!in_array($theme, $css_disabled_themes, TRUE)) { + $css = $library_path . '/chosen.css'; + if (!file_exists($css)) { + $css = $library_path . '/chosen.min.css'; + } + $info['chosen']['css'] = array($css => array()); } // All the settings that are actually passed through into the chosen() // function are contained in this array. $options = array( + 'allow_single_deselect' => (bool) variable_get('chosen_allow_single_deselect', FALSE), 'disable_search' => (bool) variable_get('chosen_disable_search', FALSE), 'disable_search_threshold' => (int) variable_get('chosen_disable_search_threshold', 0), 'search_contains' => (bool) variable_get('chosen_search_contains', FALSE), @@ -131,7 +159,7 @@ function chosen_library() { 'selector' => variable_get('chosen_jquery_selector', 'select:visible'), 'minimum_single' => (int) variable_get('chosen_minimum_single', 20), 'minimum_multiple' => (int) variable_get('chosen_minimum_multiple', 20), - 'minimum_width' => (int) variable_get('chosen_minimum_width', 200), + 'minimum_width' => (int) variable_get('chosen_minimum_width', ''), 'options' => $options, ), ), @@ -169,6 +197,15 @@ function chosen_element_info_alter(&$info) { * Render API callback: Apply Chosen to a select element. */ function chosen_pre_render_select($element) { + // Exclude chosen from theme other than admin. + global $theme, $language; + + $is_admin = path_is_admin(current_path()) || current_path() == 'system/ajax' || $theme == variable_get('admin_theme'); + $chosen_include = variable_get('chosen_include', CHOSEN_INCLUDE_EVERYWHERE); + if ($chosen_include != CHOSEN_INCLUDE_EVERYWHERE && $is_admin == $chosen_include) { + return $element; + } + // If the #chosen FAPI property is set, then add the appropriate class. if (isset($element['#chosen'])) { if (!empty($element['#chosen'])) { @@ -197,12 +234,20 @@ function chosen_pre_render_select($element) { } if (isset($element['#field_name']) && !empty($element['#multiple'])) { + // Remove '_none' from multi-select options. + unset($element['#options']['_none']); + $field = field_info_field($element['#field_name']); if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && $field['cardinality'] > 1) { $element['#attributes']['data-cardinality'] = $field['cardinality']; } } + // Right to Left Support. + if ($language->direction == 1) { + $element['#attributes']['class'][] = 'chosen-rtl'; + } + $element['#attached']['library'][] = array('chosen', 'drupal.chosen'); return $element; } @@ -252,7 +297,7 @@ function chosen_element_apply_property_recursive(array &$element, $chosen_value /** * Get the location of the chosen library. * - * @return + * @return mixed * The location of the library, or FALSE if the library isn't installed. */ function chosen_get_chosen_path() { diff --git a/profiles/commerce_kickstart/modules/contrib/chosen/drush/chosen.drush.inc b/profiles/commerce_kickstart/modules/contrib/chosen/drush/chosen.drush.inc index 84b20244..1d46c754 100644 --- a/profiles/commerce_kickstart/modules/contrib/chosen/drush/chosen.drush.inc +++ b/profiles/commerce_kickstart/modules/contrib/chosen/drush/chosen.drush.inc @@ -2,7 +2,7 @@ /** * @file - * drush integration for chosen. + * Drush integration for chosen. */ /** @@ -11,7 +11,7 @@ define('CHOSEN_DOWNLOAD_URI', 'https://github.com/harvesthq/chosen/releases/download/v1.1.0/chosen_v1.1.0.zip'); /** - * Implementation of hook_drush_command(). + * Implements hook_drush_command(). * * In this hook, you specify which commands your * drush module makes available, what it does and @@ -21,9 +21,6 @@ define('CHOSEN_DOWNLOAD_URI', 'https://github.com/harvesthq/chosen/releases/down * you define menu hooks. * * See `drush topic docs-commands` for a list of recognized keys. - * - * @return - * An associative array describing your command(s). */ function chosen_drush_command() { $items = array(); @@ -32,7 +29,8 @@ function chosen_drush_command() { $items['chosen-plugin'] = array( 'callback' => 'drush_chosen_plugin', 'description' => dt('Download and install the Chosen plugin.'), - 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. + // No bootstrap. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'arguments' => array( 'path' => dt('Optional. A path where to install the Chosen plugin. If omitted Drush will use the default location.'), ), @@ -43,16 +41,10 @@ function chosen_drush_command() { } /** - * Implementation of hook_drush_help(). + * Implements hook_drush_help(). * * This function is called whenever a drush user calls * 'drush help '. - * - * @param - * A string with the help section (prepend with 'drush:'). - * - * @return - * A string with the help text for your command. */ function chosen_drush_help($section) { switch ($section) { @@ -86,7 +78,7 @@ function drush_chosen_plugin() { // Download the zip archive. if ($filepath = drush_download_file(CHOSEN_DOWNLOAD_URI)) { $filename = basename($filepath); - $dirname = basename($filepath, '.zip'); + $dirname = basename($filepath, '.zip'); // Remove any existing Chosen plugin directory. if (is_dir($dirname) || is_dir('chosen')) { diff --git a/profiles/commerce_kickstart/modules/contrib/cloud_zoom/cloud_zoom.info b/profiles/commerce_kickstart/modules/contrib/cloud_zoom/cloud_zoom.info index 9da40877..e14dcc5c 100644 --- a/profiles/commerce_kickstart/modules/contrib/cloud_zoom/cloud_zoom.info +++ b/profiles/commerce_kickstart/modules/contrib/cloud_zoom/cloud_zoom.info @@ -7,10 +7,7 @@ dependencies[] = image files[] = cloud_zoom.module - -; Information added by drush on 2015-08-20 +; Information added by drush on 2012-10-17 version = "7.x-1.1+1-dev" -core = "7.x" project = "cloud_zoom" -datestamp = "1440110594" - +datestamp = "1350465577" \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/colorbox/PATCHES.txt deleted file mode 100644 index 42e65dfc..00000000 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/PATCHES.txt +++ /dev/null @@ -1,4 +0,0 @@ -The following patches have been applied to this project: -- https://www.drupal.org/files/issues/plugin_version_detection-2360375-9.patch - -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/README.txt b/profiles/commerce_kickstart/modules/contrib/colorbox/README.txt index a5c217e2..31cb8901 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/README.txt @@ -89,7 +89,7 @@ Make any CSS adjustments to your "colorbox_mycolorbox.css" file. Load images from custom links in a Colorbox: -------------------------------------------- -Add the class "colorbox" to the link and point the src to the image +Add the class "colorbox" to the link and point its href attribute to the image you want to display in the Colorbox. diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox-insert-image.tpl.php b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox-insert-image.tpl.php index 623db20e..6144fc30 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox-insert-image.tpl.php +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox-insert-image.tpl.php @@ -27,4 +27,6 @@ * use the file's name. i.e. __title_or_filename__. */ ?> -width="" height="" alt="__alt__" title="__title__" class="" /> + + width="" height="" alt="__alt__" title="__title__" class="" /> + diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.admin.inc b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.admin.inc index 002c50bb..2122650f 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.admin.inc @@ -145,6 +145,12 @@ function colorbox_admin_settings() { '#default_value' => variable_get('colorbox_overlayclose', 1), '#description' => t('Enable closing Colorbox by clicking on the background overlay.'), ); + $form['colorbox_custom_settings']['colorbox_returnfocus'] = array( + '#type' => 'checkbox', + '#title' => t('Return focus'), + '#default_value' => variable_get('colorbox_returnfocus', 1), + '#description' => t('Return focus when Colorbox exits to the element it was launched from.'), + ); $form['colorbox_custom_settings']['colorbox_maxwidth'] = array( '#type' => 'textfield', '#title' => t('Max width'), @@ -250,19 +256,26 @@ function colorbox_admin_settings() { '#collapsible' => TRUE, '#collapsed' => TRUE, ); + $form['colorbox_advanced_settings']['colorbox_unique_token'] = array( + '#type' => 'radios', + '#title' => t('Unique per-request gallery token'), + '#options' => array(1 => t('On'), 0 => t('Off')), + '#default_value' => variable_get('colorbox_unique_token', 1), + '#description' => t('If On (default), Colorbox will add a unique per-request token to the gallery id to avoid images being added manually to galleries. The token was added as a security fix but some see the old behavoiur as an feature and this settings makes it possible to remove the token.'), + ); $form['colorbox_advanced_settings']['colorbox_mobile_detect'] = array( '#type' => 'radios', '#title' => t('Mobile detection'), '#options' => array(1 => t('On'), 0 => t('Off')), '#default_value' => variable_get('colorbox_mobile_detect', 1), - '#description' => t('If on (default) Colorbox will not be active for devices with a the max width set below.'), + '#description' => t('If On (default), Colorbox will not be active for devices with the max width set below.'), ); $form['colorbox_advanced_settings']['colorbox_mobile_device_width'] = array( '#type' => 'textfield', - '#title' => t('Device with'), + '#title' => t('Device width'), '#default_value' => variable_get('colorbox_mobile_device_width', '480px'), '#size' => 30, - '#description' => t('Set the mobile device max with. Default: 480px.'), + '#description' => t('Set the mobile device max width. Default: 480px.'), '#states' => array( 'visible' => array( ':input[name="colorbox_mobile_detect"]' => array('value' => '1'), diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.api.php b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.api.php index 83ead10d..9dbf4cd4 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.api.php +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.api.php @@ -10,11 +10,11 @@ * * Implements hook_colorbox_settings_alter(). * - * @param $settings + * @param array $settings * An associative array of Colorbox settings. See the * @link http://colorpowered.com/colorbox/ Colorbox documentation @endlink * for the full list of supported parameters. - * @param $style + * @param string $style * The name of the active style plugin. If $style is 'none', no Colorbox * theme will be loaded. */ @@ -27,3 +27,18 @@ function hook_colorbox_settings_alter(&$settings, &$style) { $style = 'mystyle'; } } + +/** + * Allows to override activation of Colorbox for the current URL. + * + * @param bool $active + * A boolean indicating whether colorbox should be active for the current + * URL or not. + */ +function hook_colorbox_active_alter(&$active) { + $path = drupal_get_path_alias($_GET['q']); + if (drupal_match_path($path, 'admin/config/colorbox_test')) { + // Enable colorbox for this URL. + $active = TRUE; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.info b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.info index 3bc9eb32..e72aad31 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.info +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.info @@ -1,14 +1,14 @@ name = Colorbox description = A light-weight, customizable lightbox plugin for jQuery 1.4.3+. -dependencies[] = libraries (2.x) +dependencies[] = libraries (>=2.x) core = 7.x configure = admin/config/media/colorbox files[] = views/colorbox_handler_field_colorbox.inc -; Information added by Drupal.org packaging script on 2014-06-25 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2017-04-04 +version = "7.x-2.13" core = "7.x" project = "colorbox" -datestamp = "1403692729" +datestamp = "1491291489" diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.module b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.module index ffcddb04..4d16400d 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.module +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.module @@ -6,9 +6,9 @@ */ /** - * The default path to the Colorbox directory. + * The minimum required version of the Colorbox plugin. */ -define('COLORBOX_MIN_PLUGIN_VERSION', '1.3.21.1'); +define('COLORBOX_MIN_PLUGIN_VERSION', '1.6.1'); /** @@ -44,7 +44,7 @@ function colorbox_theme() { 'node' => NULL, // Left for legacy support. 'field' => array(), 'display_settings' => array(), - 'delta' => null, + 'delta' => NULL, ), 'file' => 'colorbox.theme.inc', ), @@ -56,8 +56,8 @@ function colorbox_theme() { */ function colorbox_init() { // Do not load colorbox during the Drupal installation process, e.g. if part - // of installation profiles. - if (!drupal_installation_attempted()) { + // of installation profiles. Only add the JavaScript and CSS on specified paths. + if (!drupal_installation_attempted() && _colorbox_active()) { _colorbox_doheader(); } } @@ -132,7 +132,7 @@ function colorbox_menu() { /** * Check if Colorbox should be active for the current URL. * - * @return + * @return bool * TRUE if Colorbox should be active for the current page. */ function _colorbox_active() { @@ -142,7 +142,7 @@ function _colorbox_active() { return FALSE; } - // Code from the block_list funtion in block.module. + // Code from the block_list function in block.module. $path = drupal_get_path_alias($_GET['q']); $colorbox_pages = variable_get('colorbox_pages', "admin*\nimagebrowser*\nimg_assist*\nimce*\nnode/add/*\nnode/*/edit\nprint/*\nprintpdf/*\nsystem/ajax\nsystem/ajax/*"); // Compare with the internal and path alias (if any). @@ -152,6 +152,9 @@ function _colorbox_active() { } $page_match = variable_get('colorbox_visibility', 0) == 0 ? !$page_match : $page_match; + // Allow other modules to change the state of colorbox for the current URL. + drupal_alter('colorbox_active', $page_match); + return $page_match; } @@ -163,11 +166,8 @@ function _colorbox_doheader() { if ($already_added) { return; // Don't add the JavaScript and CSS multiple times. } - if (!_colorbox_active()) { - return; // Don't add the JavaScript and CSS on specified paths. - } - // Insert options and translated strings as javascript settings. + // Insert options and translated strings as JavaScript settings. if (variable_get('colorbox_custom_settings_activate', 0)) { $js_settings = array( 'transition' => variable_get('colorbox_transition_type', 'elastic'), @@ -178,11 +178,12 @@ function _colorbox_doheader() { 'slideshowSpeed' => variable_get('colorbox_slideshowspeed', 2500), 'slideshowStart' => variable_get('colorbox_text_start', 'start slideshow'), 'slideshowStop' => variable_get('colorbox_text_stop', 'stop slideshow'), - 'current' => variable_get('colorbox_text_current', '{current} of {total}'), - 'previous' => variable_get('colorbox_text_previous', '« Prev'), - 'next' => variable_get('colorbox_text_next', 'Next »'), - 'close' => variable_get('colorbox_text_close', 'Close'), + 'current' => strip_tags(variable_get('colorbox_text_current', '{current} of {total}')), + 'previous' => strip_tags(variable_get('colorbox_text_previous', '« Prev')), + 'next' => strip_tags(variable_get('colorbox_text_next', 'Next »')), + 'close' => strip_tags(variable_get('colorbox_text_close', 'Close')), 'overlayClose' => variable_get('colorbox_overlayclose', 1) ? TRUE : FALSE, + 'returnFocus' => variable_get('colorbox_returnfocus', 1) ? TRUE : FALSE, 'maxWidth' => variable_get('colorbox_maxwidth', '98%'), 'maxHeight' => variable_get('colorbox_maxheight', '98%'), 'initialWidth' => variable_get('colorbox_initialwidth', '300'), @@ -219,7 +220,9 @@ function _colorbox_doheader() { // Add and initialise the Colorbox plugin. $variant = variable_get('colorbox_compression_type', 'minified'); - libraries_load('colorbox', $variant); + if (module_exists('libraries')) { + libraries_load('colorbox', $variant); + } drupal_add_js($path . '/js/colorbox.js'); // Add JS and CSS based on selected style. @@ -347,7 +350,7 @@ function colorbox_field_formatter_settings_form($field, $instance, $view_mode, $ '#type' => 'select', '#default_value' => $settings['colorbox_caption'], '#options' => $caption, - '#description' => t('Automatic will use the first none empty value of the title, the alt text and the content title.'), + '#description' => t('Automatic will use the first non-empty value of the title, the alt text and the content title.'), ); $element['colorbox_caption_custom'] = array( '#title' => t('Custom caption'), @@ -362,11 +365,18 @@ function colorbox_field_formatter_settings_form($field, $instance, $view_mode, $ // Allow users to hide or set a custom recursion limit. // The module token_tweaks sets a global recursion limit that can not be bypassed. if (module_exists('token') && $recursion_limit = min(variable_get('token_tree_recursion_limit', 3), variable_get('colorbox_token_recursion_limit', 3))) { + // File entities do not have $field, only $instance. + if (!empty($field)) { + $token_types = array_merge(array_keys($field['bundles']), array('file')); + } + else { + $token_types = array($instance['entity_type'], 'file'); + } $element['colorbox_token'] = array( '#type' => 'fieldset', '#title' => t('Replacement patterns'), '#theme' => 'token_tree', - '#token_types' => array($instance['entity_type'], 'file'), + '#token_types' => $token_types, '#recursion_limit' => $recursion_limit, '#dialog' => TRUE, '#states' => array( @@ -490,6 +500,8 @@ function colorbox_field_formatter_view($entity_type, $entity, $field, $instance, /** * Implements hook_insert_styles(). + * + * @return array */ function colorbox_insert_styles() { $insert_styles = array(); @@ -512,7 +524,7 @@ function colorbox_insert_content($item, $style, $widget) { /** * Machine names normally need to be unique but that does not apply to galleries. * - * @return + * @return false * Always FALSE */ function colorbox_gallery_exists() { diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.theme.inc b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.theme.inc index a30f91e2..cd4a2375 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/colorbox.theme.inc @@ -8,15 +8,19 @@ /** * Returns HTML for an Colorbox image field formatter. * - * @param $variables + * @param array $variables * An associative array containing: * - item: An array of image data. * - image_style: An optional image style. * - path: An array containing the link 'path' and link 'options'. * + * @return string + * An HTML string representing the themed output. + * * @ingroup themeable */ function theme_colorbox_image_formatter($variables) { + static $gallery_token = NULL; $item = $variables['item']; $entity_type = $variables['entity_type']; $entity = $variables['entity']; @@ -43,10 +47,26 @@ function theme_colorbox_image_formatter($variables) { $image['attributes'] = $item['attributes']; } + // Allow image attributes to be overridden. + if (isset($variables['item']['override']['attributes'])) { + foreach (array('width', 'height', 'alt', 'title') as $key) { + if (isset($variables['item']['override']['attributes'][$key])) { + $image[$key] = $variables['item']['override']['attributes'][$key]; + unset($variables['item']['override']['attributes'][$key]); + } + } + if (isset($image['attributes'])) { + $image['attributes'] = $variables['item']['override']['attributes'] + $image['attributes']; + } + else { + $image['attributes'] = $variables['item']['override']['attributes']; + } + } + $entity_title = entity_label($entity_type, $entity); switch ($settings['colorbox_caption']) { - case 'auto': + case 'auto': // If the title is empty use alt or the entity title in that order. if (!empty($image['title'])) { $caption = $image['title']; @@ -61,18 +81,23 @@ function theme_colorbox_image_formatter($variables) { $caption = ''; } break; + case 'title': $caption = $image['title']; break; + case 'alt': $caption = $image['alt']; break; + case 'node_title': $caption = $entity_title; break; + case 'custom': $caption = token_replace($settings['colorbox_caption_custom'], array($entity_type => $entity, 'file' => (object) $item), array('clear' => TRUE)); break; + default: $caption = ''; } @@ -91,22 +116,37 @@ function theme_colorbox_image_formatter($variables) { case 'post': $gallery_id = 'gallery-' . $entity_id; break; + case 'page': $gallery_id = 'gallery-all'; break; + case 'field_post': $gallery_id = 'gallery-' . $entity_id . '-' . $field['field_name']; break; + case 'field_page': $gallery_id = 'gallery-' . $field['field_name']; break; + case 'custom': $gallery_id = $settings['colorbox_gallery_custom']; break; + default: $gallery_id = ''; } + // If gallery id is not empty add unique per-request token to avoid images being added manually to galleries. + if (!empty($gallery_id) && variable_get('colorbox_unique_token', 1)) { + // Check if gallery token has already been set, we need to reuse the token for the whole request. + if (is_null($gallery_token)) { + // We use a short token since randomness is not critical. + $gallery_token = drupal_random_key(8); + } + $gallery_id = $gallery_id . '-' . $gallery_token; + } + if ($style_name = $settings['colorbox_image_style']) { $path = image_style_url($style_name, $image['path']); } @@ -120,13 +160,16 @@ function theme_colorbox_image_formatter($variables) { /** * Returns HTML for an image using a specific Colorbox image style. * - * @param $variables + * @param array $variables * An associative array containing: * - image: image item as array. * - path: The path of the image that should be displayed in the Colorbox. * - title: The title text that will be used as a caption in the Colorbox. * - gid: Gallery id for Colorbox image grouping. * + * @return string + * An HTML string containing a link to the given path. + * * @ingroup themeable */ function theme_colorbox_imagefield($variables) { @@ -149,9 +192,9 @@ function theme_colorbox_imagefield($variables) { 'attributes' => array( 'title' => $variables['title'], 'class' => $class, - 'rel' => $variables['gid'], + 'data-colorbox-gallery' => $variables['gid'], + 'data-cbox-img-attrs' => '{"title": "' . $variables['image']['title'] . '", "alt": "' . $variables['image']['alt'] . '"}', ), - 'language' => array('language' => NULL), ); return l($image, $options['path'], $options); @@ -159,6 +202,8 @@ function theme_colorbox_imagefield($variables) { /** * Preprocess variables for the colorbox-insert-image.tpl.php file. + * + * @param array $variables */ function template_preprocess_colorbox_insert_image(&$variables) { $item = $variables['item']; @@ -206,6 +251,7 @@ function template_preprocess_colorbox_insert_image(&$variables) { case 2: $variables['gallery_id'] = 'gallery-all'; break; + case 3: $variables['gallery_id'] = ''; break; diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/drush/colorbox.drush.inc b/profiles/commerce_kickstart/modules/contrib/colorbox/drush/colorbox.drush.inc index e45c40fe..56df55c5 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/drush/colorbox.drush.inc +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/drush/colorbox.drush.inc @@ -23,7 +23,7 @@ define('COLORBOX_DOWNLOAD_PREFIX', 'colorbox-'); * * See `drush topic docs-commands` for a list of recognized keys. * - * @return + * @return array * An associative array describing your command(s). */ function colorbox_drush_command() { @@ -49,10 +49,10 @@ function colorbox_drush_command() { * This function is called whenever a drush user calls * 'drush help ' * - * @param + * @param string $section * A string with the help section (prepend with 'drush:') * - * @return + * @return string * A string with the help text for your command. */ function colorbox_drush_help($section) { @@ -63,14 +63,14 @@ function colorbox_drush_help($section) { } /** - * Implements drush_MODULE_post_pm_enable(). + * Implements drush_MODULE_pre_pm_enable(). */ -// function drush_colorbox_post_pm_enable() { -// $modules = func_get_args(); -// if (in_array('colorbox', $modules)) { -// drush_colorbox_plugin(); -// } -// } +function drush_colorbox_pre_pm_enable() { + $modules = drush_get_context('PM_ENABLE_MODULES'); + if (in_array('colorbox', $modules) && !drush_get_option('skip')) { + drush_colorbox_plugin(); + } +} /** * Command to download the Colorbox plugin. @@ -90,39 +90,45 @@ function drush_colorbox_plugin() { drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice'); } - // Set the directory to the download location. - $olddir = getcwd(); - chdir($path); - - // Download the zip archive - if ($filepath = drush_download_file(COLORBOX_DOWNLOAD_URI)) { - $filename = basename($filepath); - $dirname = COLORBOX_DOWNLOAD_PREFIX . basename($filepath, '.zip'); - - // Remove any existing Colorbox plugin directory - if (is_dir($dirname) || is_dir('colorbox')) { - drush_delete_dir($dirname, TRUE); - drush_delete_dir('colorbox', TRUE); - drush_log(dt('A existing Colorbox plugin was deleted from @path', array('@path' => $path)), 'notice'); + // Download colorbox plugin only if path is writable. + if (is_writable($path)) { + // Set the directory to the download location. + $olddir = getcwd(); + chdir($path); + + // Download the zip archive + if ($filepath = drush_download_file(COLORBOX_DOWNLOAD_URI)) { + $filename = basename($filepath); + $dirname = COLORBOX_DOWNLOAD_PREFIX . basename($filepath, '.zip'); + + // Remove any existing Colorbox plugin directory. + if (is_dir($dirname) || is_dir('colorbox')) { + drush_delete_dir($dirname, TRUE); + drush_delete_dir('colorbox', TRUE); + drush_log(dt('A existing Colorbox plugin was deleted from @path', array('@path' => $path)), 'notice'); + } + + // Decompress the zip archive + drush_tarball_extract($filename); + + // Change the directory name to "colorbox" if needed. + if ($dirname != 'colorbox') { + drush_move_dir($dirname, 'colorbox', TRUE); + $dirname = 'colorbox'; + } } - // Decompress the zip archive - drush_tarball_extract($filename); - - // Change the directory name to "colorbox" if needed. - if ($dirname != 'colorbox') { - drush_move_dir($dirname, 'colorbox', TRUE); - $dirname = 'colorbox'; + if (is_dir($dirname)) { + drush_log(dt('Colorbox plugin has been installed in @path', array('@path' => $path)), 'success'); + } + else { + drush_log(dt('Drush was unable to install the Colorbox plugin to @path', array('@path' => $path)), 'error'); } - } - if (is_dir($dirname)) { - drush_log(dt('Colorbox plugin has been installed in @path', array('@path' => $path)), 'success'); + // Set working directory back to the previous working directory. + chdir($olddir); } else { - drush_log(dt('Drush was unable to install the Colorbox plugin to @path', array('@path' => $path)), 'error'); + drush_log(dt('Drush was unable to install the Colorbox plugin because @path is not writable. If you enable the colorbox module before you install the plugin library, you may find that colorbox does not work until you reinstall the colorbox module.', array('@path' => $path)), 'warning'); } - - // Set working directory back to the previous working directory. - chdir($olddir); } diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox.js b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox.js index ace202ef..567ad381 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox.js @@ -1,8 +1,13 @@ +/** + * @file + * Colorbox module init js. + */ + (function ($) { Drupal.behaviors.initColorbox = { attach: function (context, settings) { - if (!$.isFunction($.colorbox)) { + if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') { return; } @@ -14,6 +19,16 @@ Drupal.behaviors.initColorbox = { } } + // Use "data-colorbox-gallery" if set otherwise use "rel". + settings.colorbox.rel = function () { + if ($(this).data('colorbox-gallery')) { + return $(this).data('colorbox-gallery'); + } + else { + return $(this).attr('rel'); + } + }; + $('.colorbox', context) .once('init-colorbox') .colorbox(settings.colorbox); diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_admin_settings.js b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_admin_settings.js index 46608f81..79de0833 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_admin_settings.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_admin_settings.js @@ -1,3 +1,8 @@ +/** + * @file + * Colorbox module admin settings js. + */ + (function ($) { Drupal.behaviors.initColorboxAdminSettings = { diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_inline.js b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_inline.js index f515036e..c06c588a 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_inline.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_inline.js @@ -1,10 +1,24 @@ +/** + * @file + * Colorbox module inline js. + */ + (function ($) { Drupal.behaviors.initColorboxInline = { attach: function (context, settings) { - if (!$.isFunction($.colorbox)) { + if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') { return; } + + if (settings.colorbox.mobiledetect && window.matchMedia) { + // Disable Colorbox for small screens. + var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")"); + if (mq.matches) { + return; + } + } + $.urlParam = function(name, url){ if (name == 'fragment') { var results = new RegExp('(#[^&#]*)').exec(url); diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_load.js b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_load.js index e63043ed..bf2e924b 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_load.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/js/colorbox_load.js @@ -1,14 +1,27 @@ +/** + * @file + * Colorbox module load js. + */ (function ($) { Drupal.behaviors.initColorboxLoad = { attach: function (context, settings) { - if (!$.isFunction($.colorbox)) { + if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') { return; } + + if (settings.colorbox.mobiledetect && window.matchMedia) { + // Disable Colorbox for small screens. + var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")"); + if (mq.matches) { + return; + } + } + $.urlParams = function (url) { var p = {}, e, - a = /\+/g, // Regex for replacing addition symbol with a space + a = /\+/g, // Regex for replacing addition symbol with a space. r = /([^&=]+)=?([^&]*)/g, d = function (s) { return decodeURIComponent(s.replace(a, ' ')); }, q = url.split('?'); diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/styles/default/colorbox_style.js b/profiles/commerce_kickstart/modules/contrib/colorbox/styles/default/colorbox_style.js index 47875ffd..d3e3c56f 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/styles/default/colorbox_style.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/styles/default/colorbox_style.js @@ -1,3 +1,8 @@ +/** + * @file + * Colorbox module style js. + */ + (function ($) { Drupal.behaviors.initColorboxDefaultStyle = { diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/styles/plain/colorbox_style.js b/profiles/commerce_kickstart/modules/contrib/colorbox/styles/plain/colorbox_style.js index 19d8e66e..df6b5d6c 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/styles/plain/colorbox_style.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/styles/plain/colorbox_style.js @@ -1,3 +1,8 @@ +/** + * @file + * Colorbox module style js. + */ + (function ($) { Drupal.behaviors.initColorboxPlainStyle = { diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/styles/stockholmsyndrome/colorbox_style.js b/profiles/commerce_kickstart/modules/contrib/colorbox/styles/stockholmsyndrome/colorbox_style.js index db3ab3ef..450c3536 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/styles/stockholmsyndrome/colorbox_style.js +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/styles/stockholmsyndrome/colorbox_style.js @@ -1,3 +1,8 @@ +/** + * @file + * Colorbox module style js. + */ + (function ($) { Drupal.behaviors.initColorboxStockholmsyndromeStyle = { diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox.views.inc b/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox.views.inc index 40f90a51..2731d7b8 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox.views.inc @@ -6,7 +6,7 @@ */ /** - * Implementation of hook_views_data() + * Implements hook_views_data() */ function colorbox_views_data() { diff --git a/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox_handler_field_colorbox.inc b/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox_handler_field_colorbox.inc index 27a20452..49b6af78 100644 --- a/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox_handler_field_colorbox.inc +++ b/profiles/commerce_kickstart/modules/contrib/colorbox/views/colorbox_handler_field_colorbox.inc @@ -67,7 +67,7 @@ If you would like to have the characters %5B and %5D please use the html entity $patterns .= theme('item_list', array( 'items' => $items, - 'type' => $type + 'type' => $type, )); } } @@ -162,10 +162,15 @@ If you would like to have the characters %5B and %5D please use the html entity $tokens = $this->get_render_tokens($this->options['alter']); $popup = filter_xss_admin($this->options['popup']); $caption = filter_xss_admin($this->options['caption']); - $gallery = drupal_html_class($this->options['custom_gid']); + $gallery = filter_xss_admin($this->options['custom_gid']); $popup = strtr($popup, $tokens); $caption = strtr($caption, $tokens); - $gallery = strtr($gallery, $tokens); + $gallery = drupal_html_class(strtr($gallery, $tokens)); + + // Return nothing if popup is empty. + if (empty($popup)) { + return; + } $width = $this->options['width'] ? $this->options['width'] : ''; $height = $this->options['height'] ? $this->options['height'] : ''; @@ -178,11 +183,11 @@ If you would like to have the characters %5B and %5D please use the html entity 'width' => $width, 'height' => $height, 'title' => $caption, - 'inline' => 'true' + 'inline' => 'true', ), 'attributes' => array( 'class' => array('colorbox-inline'), - 'rel' => $gallery_id + 'rel' => $gallery_id, ) ); // Remove any parameters that aren't set. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/.gitignore b/profiles/commerce_kickstart/modules/contrib/commerce/.gitignore index ef95a08c..aa17f57a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/.gitignore +++ b/profiles/commerce_kickstart/modules/contrib/commerce/.gitignore @@ -4,3 +4,5 @@ *.kpf *.swp *.swo +.idea* + diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/.travis.yml b/profiles/commerce_kickstart/modules/contrib/commerce/.travis.yml new file mode 100644 index 00000000..1386faaa --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce/.travis.yml @@ -0,0 +1,67 @@ +language: php +sudo: false + +php: + # Use >= 5.4 since it has built-in webserver. + - 5.4 + - 5.5 + - 5.6 + - 7 + # @todo Impossible to use HHVM since it will not have "$PHP INI" and tests will be failed every time. + +services: + - mysql + +matrix: + fast_finish: true + allow_failures: + - php: 7 + +install: + # Now we are in the directory with repository sources. Go back + # to "vendor" directory, where "commerce" will be located. + - cd .. + + # Set environment variables. + - PHPINI="$HOME/.phpenv/versions/$(phpenv version-name)/etc/php.ini" + - DRUPALDIR="$(pwd)/drupal" + - DRUPALHOST="127.0.0.1:1349" + + # Populate Composer packages. + - export PATH="$HOME/.composer/vendor/bin:$PATH" + + # Install Drush. + - composer global require --prefer-source --no-interaction drush/drush:8.* + + # Download Drupal. + - drush dl drupal-7 --drupal-project-rename=$(basename $DRUPALDIR) + + # Move "commerce" module to Drupal. + - mv commerce/ $DRUPALDIR/sites/all/modules/ + + # Disable Sendmail. + - echo sendmail_path=$(which true) >> $PHPINI + # Increase the MySQL connection timeout on the PHP end. + - echo "mysql.connect_timeout=3000" >> $PHPINI + - echo "default_socket_timeout=3000" >> $PHPINI + + # Increase the MySQL server timetout and packet size. + - mysql -uroot -e "SET GLOBAL wait_timeout = 36000;" + - mysql -uroot -e "SET GLOBAL max_allowed_packet = 33554432;" + +before_script: + - cd $DRUPALDIR + # Install Drupal. + - drush si -y minimal --db-url=mysql://root:@127.0.0.1/drupal --account-name=admin --account-pass=admin --site-mail=admin@example.com --site-name=Commerce + # Enabled set of modules. + - drush en -y commerce_cart commerce_customer_ui commerce_product_ui commerce_line_item_ui commerce_order_ui commerce_payment commerce_payment_example commerce_tax_ui simpletest + # Start HTTP server. + - drush runserver $DRUPALHOST > /dev/null 2>&1 & + +script: + - php scripts/run-tests.sh --verbose --color --concurrency 4 --php $(which php) --url http://$DRUPALHOST "Drupal Commerce" | tee /tmp/test.txt + # Exit with the inverted value, because if there are no fails found, it will exit with 1 and for us + # that is a good thing so invert it to 0. Travis has some issues with the exclamation mark in front + # so we have to fiddle a bit. + # Also make the grep case insensitive and fail on run-tests.sh regular fails as well on fatal errors. + - exit $(! egrep -i "([1-9]+ fail)|([1-9]+ exception)|(error)" /tmp/test.txt > /dev/null 2>&1) $? diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/commerce.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/commerce.api.php index d13ad3d1..2c44df6c 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/commerce.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/commerce.api.php @@ -177,3 +177,18 @@ function hook_commerce_entity_access_condition_alter() { function hook_commerce_entity_create_alter($entity_type, $entity) { // No example. } + +/** + * Allows modules to alter the default currency. + * + * @param $currency_code + * The default currency code. + * + * @see commerce_default_currency() + */ +function hook_commerce_default_currency_alter(&$currency_code) { + global $language; + if (isset($language->language) && $language->language == 'en-US') { + $currency_code = 'USD'; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/commerce.info b/profiles/commerce_kickstart/modules/contrib/commerce/commerce.info index cefd6004..7aef00a4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/commerce.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/commerce.info @@ -12,9 +12,9 @@ files[] = tests/commerce_base.test ; Central Entity Controller. files[] = includes/commerce.controller.inc -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/commerce.module b/profiles/commerce_kickstart/modules/contrib/commerce/commerce.module old mode 100644 new mode 100755 index 7214e13b..fcf003d4 --- a/profiles/commerce_kickstart/modules/contrib/commerce/commerce.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/commerce.module @@ -470,7 +470,9 @@ function commerce_i18n_string($name, $default, $options = array()) { * Returns the currency code of the site's default currency. */ function commerce_default_currency() { - return variable_get('commerce_default_currency', 'USD'); + $currency_code = variable_get('commerce_default_currency', 'USD'); + drupal_alter('commerce_default_currency', $currency_code); + return $currency_code; } /** @@ -527,7 +529,7 @@ function commerce_currencies($enabled = FALSE, $reset = FALSE) { 'thousands_separator' => ',', 'decimal_separator' => '.', 'symbol_placement' => 'hidden', - 'symbol_spacer' => ' ', + 'symbol_spacer' => ' ', 'code_placement' => 'after', 'code_spacer' => ' ', 'format_callback' => '', @@ -950,7 +952,7 @@ function commerce_simplexml_add_children(&$simplexml_element, $child_data) { } else { // Otherwise add the child element with its simple value. - $simplexml_element->addChild("$key", "$value"); + $simplexml_element->addChild("$key", htmlspecialchars($value, ENT_QUOTES, 'UTF-8', FALSE)); } } } @@ -987,6 +989,7 @@ function commerce_entity_access($op, $entity, $account, $entity_type) { return (bool) $query ->addTag($entity_info['access arguments']['access tag']) ->addMetaData('account', $account) + ->addMetaData('entity', $entity) ->condition($entity_info['entity keys']['id'], $entity->{$entity_info['entity keys']['id']}) ->range(0, 1) ->execute() @@ -1239,6 +1242,7 @@ function commerce_entity_access_query_alter($query, $entity_type, $base_table = $context = array( 'account' => $account, + 'entity' => $query->getMetaData('entity'), 'entity_type' => $entity_type, 'base_table' => $base_table ); @@ -1350,3 +1354,19 @@ function commerce_preprocess_entity(&$variables) { $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id; } } + +/** + * Determines whether or not the given entity object was loaded directly from + * the database to represent the unchanged version of the entity. + * + * @param string $entity_type + * The type of $entity; for example, 'commerce_order'. + * @param object $entity + * The entity to check. + * + * @return bool + * TRUE if the entity is "unchanged", FALSE otherwise. + */ +function commerce_entity_is_unchanged($entity_type, $entity) { + return entity_get_controller($entity_type)->isUnchanged($entity); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/commerce_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/commerce_ui.info index 1c25f666..0fb91d2f 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/commerce_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/commerce_ui.info @@ -4,9 +4,9 @@ package = Commerce dependencies[] = commerce core = 7.x -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.controller.inc b/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.controller.inc index 1cdd6052..0bd97833 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.controller.inc @@ -7,7 +7,32 @@ * A full fork of Entity API's controller, with support for revisions. */ -class DrupalCommerceEntityController extends DrupalDefaultEntityController implements EntityAPIControllerInterface { + +/** + * Interface for the default Drupal Commerce entity controller. + */ +interface DrupalCommerceEntityControllerInterface extends EntityAPIControllerInterface { + + /** + * Determines whether or not the given entity object was loaded directly from + * the database to represent the unchanged version of the entity. + * + * @param object $entity + * The entity to check. + * + * @return bool + * TRUE if the entity is "unchanged", FALSE otherwise. + */ + public function isUnchanged($entity); + +} + +/** + * Default implementation of DrupalCommerceEntityControllerInterface. + * + * Provides base entity controller for Drupal Commerce entities. + */ +class DrupalCommerceEntityController extends DrupalDefaultEntityController implements DrupalCommerceEntityControllerInterface { /** * Stores our transaction object, necessary for pessimistic locking to work. @@ -20,6 +45,12 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple */ protected $lockedEntities = array(); + /** + * Stores the ids of unchanged entities, necessary for knowing if we're + * dealing with unchanged entities before acting on them. + */ + protected $unchangedEntities = array(); + /** * Override of DrupalDefaultEntityController::buildQuery(). * @@ -68,6 +99,13 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple $this->releaseLock(); } + /** + * Implements DrupalCommerceEntityControllerInterface::isUnchanged(). + */ + public function isUnchanged($entity) { + return isset($this->unchangedEntities[$entity->{$this->idKey}]); + } + /** * Checks the list of tracked locked entities, and if it's empty, commits * the transaction in order to remove the acquired locks. @@ -184,10 +222,12 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple // value already set on the entity will be replaced with the entity as // saved. This will allow any original entity comparisons in the current // save process to react to the most recently saved version of the entity. - if (!empty($entity->{$this->idKey})) { + if (!empty($entity->{$this->idKey}) && !isset($entity->original)) { + $this->unchangedEntities[$entity->{$this->idKey}] = TRUE; // In order to properly work in case of name changes, load the original // entity using the id key if it is available. $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey}); + unset($this->unchangedEntities[$entity->{$this->idKey}]); } $this->invoke('presave', $entity); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.currency.inc b/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.currency.inc index e0a7de44..933d9292 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.currency.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/includes/commerce.currency.inc @@ -3,6 +3,7 @@ /** * @file * Defines the default currency list of active currencies from ISO 4217. + * The currency symbols and formatting rules are provided by CLDR. */ @@ -13,824 +14,1070 @@ function commerce_commerce_currency_info() { return array( 'AED' => array( 'code' => 'AED', - 'symbol' => 'د.إ', - 'name' => t('United Arab Emirates Dirham'), 'numeric_code' => '784', - 'code_placement' => 'before', + 'symbol' => 'د.إ.‏', + 'name' => t('United Arab Emirates Dirham'), 'minor_unit' => t('Fils'), 'major_unit' => t('Dirham'), ), 'AFN' => array( 'code' => 'AFN', - 'symbol' => 'Af', - 'name' => t('Afghan Afghani'), - 'decimals' => 0, 'numeric_code' => '971', + 'symbol' => '؋', + 'name' => t('Afghan Afghani'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Pul'), 'major_unit' => t('Afghani'), + 'decimals' => 0, + ), + 'ALL' => array( + 'code' => 'ALL', + 'numeric_code' => '008', + 'symbol' => 'Lekë', + 'name' => t('Albanian Lek'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', + 'major_unit' => t('Lek'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', + ), + 'AMD' => array( + 'code' => 'AMD', + 'numeric_code' => '051', + 'symbol' => '֏', + 'name' => t('Armenian Dram'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'major_unit' => t('Dram'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'ANG' => array( 'code' => 'ANG', - 'symbol' => 'NAf.', - 'name' => t('Netherlands Antillean Guilder'), 'numeric_code' => '532', + 'symbol' => 'ANG', + 'name' => t('Netherlands Antillean Guilder'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Guilder'), ), 'AOA' => array( 'code' => 'AOA', + 'numeric_code' => '973', 'symbol' => 'Kz', 'name' => t('Angolan Kwanza'), - 'numeric_code' => '973', + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Cêntimo'), 'major_unit' => t('Kwanza'), - ), - 'ARM' => array( - 'code' => 'ARM', - 'symbol' => 'm$n', - 'name' => t('Argentine Peso Moneda Nacional'), - 'minor_unit' => t('Centavos'), - 'major_unit' => t('Peso'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'ARS' => array( 'code' => 'ARS', - 'symbol' => 'AR$', - 'name' => t('Argentine Peso'), 'numeric_code' => '032', + 'symbol' => '$', + 'name' => t('Argentine Peso'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'AUD' => array( 'code' => 'AUD', + 'numeric_code' => '036', 'symbol' => '$', 'name' => t('Australian Dollar'), - 'numeric_code' => '036', 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'AWG' => array( 'code' => 'AWG', + 'numeric_code' => '533', 'symbol' => 'Afl.', 'name' => t('Aruban Florin'), - 'numeric_code' => '533', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Guilder'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'AZN' => array( 'code' => 'AZN', - 'symbol' => 'man.', - 'name' => t('Azerbaijanian Manat'), + 'numeric_code' => '944', + 'symbol' => '₼', + 'name' => t('Azerbaijani Manat'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Qəpik'), 'major_unit' => t('New Manat'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'BAM' => array( 'code' => 'BAM', + 'numeric_code' => '977', 'symbol' => 'KM', 'name' => t('Bosnia-Herzegovina Convertible Mark'), - 'numeric_code' => '977', + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Fening'), 'major_unit' => t('Convertible Marka'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'BBD' => array( 'code' => 'BBD', - 'symbol' => 'Bds$', - 'name' => t('Barbadian Dollar'), 'numeric_code' => '052', + 'symbol' => '$', + 'name' => t('Barbadian Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'BDT' => array( 'code' => 'BDT', - 'symbol' => 'Tk', - 'name' => t('Bangladeshi Taka'), 'numeric_code' => '050', + 'symbol' => '৳', + 'name' => t('Bangladeshi Taka'), + 'symbol_placement' => 'after', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Paisa'), 'major_unit' => t('Taka'), ), - 'BGN' => array( - 'code' => 'BGN', - 'symbol' => 'лв', - 'name' => t('Bulgarian lev'), - 'thousands_separator' => ' ', - 'decimal_separator' => ',', - 'symbol_placement' => 'after', - 'code_placement' => 'hidden', - 'numeric_code' => '975', - 'minor_unit' => t('Stotinka'), - 'major_unit' => t('Lev'), + 'BGN' => array( + 'code' => 'BGN', + 'numeric_code' => '975', + 'symbol' => 'лв.', + 'name' => t('Bulgarian Lev'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', + 'minor_unit' => t('Stotinka'), + 'major_unit' => t('Lev'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'BHD' => array( 'code' => 'BHD', - 'symbol' => 'BD', - 'name' => t('Bahraini Dinar'), - 'decimals' => 3, 'numeric_code' => '048', + 'symbol' => 'د.ب.‏', + 'name' => t('Bahraini Dinar'), 'minor_unit' => t('Fils'), 'major_unit' => t('Dinar'), + 'decimals' => 3, ), 'BIF' => array( 'code' => 'BIF', + 'numeric_code' => '108', 'symbol' => 'FBu', 'name' => t('Burundian Franc'), - 'decimals' => 0, - 'numeric_code' => '108', + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'BMD' => array( 'code' => 'BMD', - 'symbol' => 'BD$', - 'name' => t('Bermudan Dollar'), 'numeric_code' => '060', + 'symbol' => '$', + 'name' => t('Bermudan Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'BND' => array( 'code' => 'BND', - 'symbol' => 'BN$', - 'name' => t('Brunei Dollar'), 'numeric_code' => '096', + 'symbol' => '$', + 'name' => t('Brunei Dollar'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Sen'), 'major_unit' => t('Dollar'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'BOB' => array( 'code' => 'BOB', + 'numeric_code' => '068', 'symbol' => 'Bs', 'name' => t('Bolivian Boliviano'), - 'numeric_code' => '068', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Bolivianos'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'BRL' => array( 'code' => 'BRL', + 'numeric_code' => '986', 'symbol' => 'R$', 'name' => t('Brazilian Real'), - 'numeric_code' => '986', 'symbol_placement' => 'before', 'code_placement' => 'hidden', - 'thousands_separator' => '.', - 'decimal_separator' => ',', 'minor_unit' => t('Centavo'), 'major_unit' => t('Reais'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'BSD' => array( 'code' => 'BSD', - 'symbol' => 'BS$', - 'name' => t('Bahamian Dollar'), 'numeric_code' => '044', + 'symbol' => '$', + 'name' => t('Bahamian Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'BTN' => array( 'code' => 'BTN', + 'numeric_code' => '064', 'symbol' => 'Nu.', 'name' => t('Bhutanese Ngultrum'), - 'numeric_code' => '064', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Chetrum'), 'major_unit' => t('Ngultrum'), ), 'BWP' => array( 'code' => 'BWP', - 'symbol' => 'BWP', - 'name' => t('Botswanan Pula'), 'numeric_code' => '072', + 'symbol' => 'P', + 'name' => t('Botswanan Pula'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Thebe'), 'major_unit' => t('Pulas'), ), - 'BYR' => array( - 'code' => 'BYR', - 'symbol' => 'руб.', - 'name' => t('Belarusian ruble'), - 'numeric_code' => '974', + 'BYN' => array( + 'code' => 'BYN', + 'numeric_code' => '933', + 'symbol' => 'Br', + 'name' => t('Belarusian Ruble'), 'symbol_placement' => 'after', 'code_placement' => 'hidden', - 'decimals' => 0, - 'thousands_separator' => ' ', + 'minor_unit' => t('Kopek'), 'major_unit' => t('Ruble'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'BZD' => array( 'code' => 'BZD', - 'symbol' => 'BZ$', - 'name' => t('Belize Dollar'), 'numeric_code' => '084', + 'symbol' => '$', + 'name' => t('Belize Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'CAD' => array( 'code' => 'CAD', - 'symbol' => 'CA$', - 'name' => t('Canadian Dollar'), 'numeric_code' => '124', + 'symbol' => '$', + 'name' => t('Canadian Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'CDF' => array( 'code' => 'CDF', - 'symbol' => 'CDF', - 'name' => t('Congolese Franc'), 'numeric_code' => '976', + 'symbol' => 'FC', + 'name' => t('Congolese Franc'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'CHF' => array( 'code' => 'CHF', - 'symbol' => 'Fr.', - 'name' => t('Swiss Franc'), - 'rounding_step' => '0.05', 'numeric_code' => '756', - 'thousands_separator' => "'", + 'symbol' => 'CHF', + 'name' => t('Swiss Franc'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Rappen'), 'major_unit' => t('Franc'), + 'thousands_separator' => '’', + 'rounding_step' => '0.05', ), 'CLP' => array( 'code' => 'CLP', - 'symbol' => 'CL$', - 'name' => t('Chilean Peso'), - 'decimals' => 0, 'numeric_code' => '152', + 'symbol' => '$', + 'name' => t('Chilean Peso'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'CNY' => array( 'code' => 'CNY', - 'symbol' => '¥', - 'name' => t('Chinese Yuan Renminbi'), 'numeric_code' => '156', + 'symbol' => '¥', + 'name' => t('Chinese Yuan'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', - 'thousands_separator' => '', 'minor_unit' => t('Fen'), 'major_unit' => t('Yuan'), ), 'COP' => array( 'code' => 'COP', + 'numeric_code' => '170', 'symbol' => '$', 'name' => t('Colombian Peso'), - 'decimals' => 0, - 'numeric_code' => '170', 'symbol_placement' => 'before', 'code_placement' => 'hidden', - 'thousands_separator' => '.', - 'decimal_separator' => ',', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'CRC' => array( 'code' => 'CRC', - 'symbol' => '¢', - 'name' => t('Costa Rican Colón'), - 'decimals' => 0, 'numeric_code' => '188', + 'symbol' => '₡', + 'name' => t('Costa Rican Colón'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Céntimo'), 'major_unit' => t('Colón'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'CUC' => array( 'code' => 'CUC', - 'symbol' => 'CUC$', + 'numeric_code' => '931', + 'symbol' => 'CUC', 'name' => t('Cuban Convertible Peso'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), ), 'CUP' => array( 'code' => 'CUP', - 'symbol' => 'CU$', - 'name' => t('Cuban Peso'), 'numeric_code' => '192', + 'symbol' => '$', + 'name' => t('Cuban Peso'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), ), 'CVE' => array( 'code' => 'CVE', - 'symbol' => 'CV$', - 'name' => t('Cape Verdean Escudo'), 'numeric_code' => '132', + 'symbol' => '​', + 'name' => t('Cape Verdean Escudo'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Escudo'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'CZK' => array( 'code' => 'CZK', - 'symbol' => 'Kč', - 'name' => t('Czech Republic Koruna'), 'numeric_code' => '203', - 'thousands_separator' => ' ', - 'decimal_separator' => ',', + 'symbol' => 'Kč', + 'name' => t('Czech Koruna'), 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Haléř'), 'major_unit' => t('Koruna'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'DJF' => array( 'code' => 'DJF', - 'symbol' => 'Fdj', - 'name' => t('Djiboutian Franc'), 'numeric_code' => '262', - 'decimals' => 0, + 'symbol' => 'DJF', + 'name' => t('Djiboutian Franc'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, ), 'DKK' => array( 'code' => 'DKK', + 'numeric_code' => '208', 'symbol' => 'kr.', 'name' => t('Danish Krone'), - 'numeric_code' => '208', - 'thousands_separator' => ' ', - 'decimal_separator' => ',', 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Øre'), 'major_unit' => t('Kroner'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'DOP' => array( 'code' => 'DOP', + 'numeric_code' => '214', 'symbol' => 'RD$', 'name' => t('Dominican Peso'), - 'numeric_code' => '214', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), ), 'DZD' => array( 'code' => 'DZD', - 'symbol' => 'DA', - 'name' => t('Algerian Dinar'), 'numeric_code' => '012', + 'symbol' => 'DZD', + 'name' => t('Algerian Dinar'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Santeem'), 'major_unit' => t('Dinar'), ), - 'EEK' => array( - 'code' => 'EEK', - 'symbol' => 'Ekr', - 'name' => t('Estonian Kroon'), - 'thousands_separator' => ' ', - 'decimal_separator' => ',', - 'numeric_code' => '233', - 'minor_unit' => t('Sent'), - 'major_unit' => t('Krooni'), - ), 'EGP' => array( 'code' => 'EGP', - 'symbol' => 'EG£', - 'name' => t('Egyptian Pound'), 'numeric_code' => '818', + 'symbol' => 'ج.م.‏', + 'name' => t('Egyptian Pound'), 'minor_unit' => t('Piastr'), 'major_unit' => t('Pound'), ), 'ERN' => array( 'code' => 'ERN', + 'numeric_code' => '232', 'symbol' => 'Nfk', 'name' => t('Eritrean Nakfa'), - 'numeric_code' => '232', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Nakfa'), ), 'ETB' => array( 'code' => 'ETB', - 'symbol' => 'Br', - 'name' => t('Ethiopian Birr'), 'numeric_code' => '230', + 'symbol' => 'ETB', + 'name' => t('Ethiopian Birr'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Santim'), 'major_unit' => t('Birr'), ), 'EUR' => array( 'code' => 'EUR', + 'numeric_code' => '978', 'symbol' => '€', 'name' => t('Euro'), - 'thousands_separator' => ' ', - 'decimal_separator' => ',', 'symbol_placement' => 'after', 'code_placement' => 'hidden', - 'numeric_code' => '978', 'minor_unit' => t('Cent'), 'major_unit' => t('Euro'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'FJD' => array( 'code' => 'FJD', - 'symbol' => 'FJ$', - 'name' => t('Fijian Dollar'), 'numeric_code' => '242', + 'symbol' => '$', + 'name' => t('Fijian Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'FKP' => array( 'code' => 'FKP', - 'symbol' => 'FK£', - 'name' => t('Falkland Islands Pound'), 'numeric_code' => '238', + 'symbol' => '£', + 'name' => t('Falkland Islands Pound'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Penny'), 'major_unit' => t('Pound'), ), 'GBP' => array( 'code' => 'GBP', - 'symbol' => '£', - 'name' => t('British Pound Sterling'), 'numeric_code' => '826', + 'symbol' => '£', + 'name' => t('British Pound'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Penny'), 'major_unit' => t('Pound'), ), + 'GEL' => array( + 'code' => 'GEL', + 'numeric_code' => '981', + 'symbol' => '₾', + 'name' => t('Georgian Lari'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', + 'minor_unit' => t('Tetri'), + 'major_unit' => t('Lari'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', + ), 'GHS' => array( 'code' => 'GHS', + 'numeric_code' => '936', 'symbol' => 'GH₵', 'name' => t('Ghanaian Cedi'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Pesewa'), 'major_unit' => t('Cedi'), ), 'GIP' => array( 'code' => 'GIP', - 'symbol' => 'GI£', - 'name' => t('Gibraltar Pound'), 'numeric_code' => '292', - 'minor_unit' => t('Penny'), - 'major_unit' => t('Pound'), - ), + 'symbol' => '£', + 'name' => t('Gibraltar Pound'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', + 'minor_unit' => t('Penny'), + 'major_unit' => t('Pound'), + ), 'GMD' => array( 'code' => 'GMD', - 'symbol' => 'GMD', - 'name' => t('Gambian Dalasi'), 'numeric_code' => '270', + 'symbol' => 'D', + 'name' => t('Gambian Dalasi'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Butut'), 'major_unit' => t('Dalasis'), ), 'GNF' => array( 'code' => 'GNF', + 'numeric_code' => '324', 'symbol' => 'FG', 'name' => t('Guinean Franc'), - 'decimals' => 0, - 'numeric_code' => '324', + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'GTQ' => array( 'code' => 'GTQ', - 'symbol' => 'GTQ', - 'name' => t('Guatemalan Quetzal'), 'numeric_code' => '320', + 'symbol' => 'Q', + 'name' => t('Guatemalan Quetzal'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Quetzales'), ), 'GYD' => array( 'code' => 'GYD', - 'symbol' => 'GY$', - 'name' => t('Guyanaese Dollar'), - 'decimals' => 0, 'numeric_code' => '328', + 'symbol' => '$', + 'name' => t('Guyanaese Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), + 'decimals' => 0, ), 'HKD' => array( 'code' => 'HKD', + 'numeric_code' => '344', 'symbol' => 'HK$', 'name' => t('Hong Kong Dollar'), - 'numeric_code' => '344', 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'HNL' => array( 'code' => 'HNL', - 'symbol' => 'HNL', - 'name' => t('Honduran Lempira'), 'numeric_code' => '340', + 'symbol' => 'L', + 'name' => t('Honduran Lempira'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Lempiras'), ), 'HRK' => array( 'code' => 'HRK', + 'numeric_code' => '191', 'symbol' => 'kn', 'name' => t('Croatian Kuna'), - 'numeric_code' => '191', + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Lipa'), 'major_unit' => t('Kuna'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'HTG' => array( 'code' => 'HTG', + 'numeric_code' => '332', 'symbol' => 'HTG', 'name' => t('Haitian Gourde'), - 'numeric_code' => '332', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Gourde'), ), 'HUF' => array( 'code' => 'HUF', + 'numeric_code' => '348', 'symbol' => 'Ft', 'name' => t('Hungarian Forint'), - 'numeric_code' => '348', - 'decimal_separator' => ',', - 'thousands_separator' => ' ', - 'decimals' => 0, 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'major_unit' => t('Forint'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'IDR' => array( 'code' => 'IDR', + 'numeric_code' => '360', 'symbol' => 'Rp', 'name' => t('Indonesian Rupiah'), - 'decimals' => 0, - 'numeric_code' => '360', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Sen'), 'major_unit' => t('Rupiahs'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'ILS' => array( 'code' => 'ILS', + 'numeric_code' => '376', 'symbol' => '₪', 'name' => t('Israeli New Shekel'), - 'numeric_code' => '376', - 'symbol_placement' => 'before', + 'symbol_placement' => 'after', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Agora'), 'major_unit' => t('New Shekels'), ), 'INR' => array( 'code' => 'INR', - 'symbol' => 'Rs', - 'name' => t('Indian Rupee'), 'numeric_code' => '356', + 'symbol' => '₹', + 'name' => t('Indian Rupee'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Paisa'), 'major_unit' => t('Rupee'), ), + 'IQD' => array( + 'code' => 'IQD', + 'numeric_code' => '368', + 'symbol' => 'د.ع.‏', + 'name' => t('Iraqi Dinar'), + 'minor_unit' => t('Fils'), + 'major_unit' => t('Dinar'), + 'decimals' => 0, + ), 'IRR' => array( 'code' => 'IRR', + 'numeric_code' => '364', 'symbol' => 'ریال', 'name' => t('Iranian Rial'), - 'decimals' => 0, - 'numeric_code' => '364', - 'symbol_placement' => 'after', + 'symbol_placement' => 'before', + 'symbol_spacer' => ',', 'code_placement' => 'hidden', 'minor_unit' => t('Rial'), 'major_unit' => t('Toman'), + 'decimals' => 0, ), 'ISK' => array( 'code' => 'ISK', - 'symbol' => 'Ikr', - 'name' => t('Icelandic Króna'), - 'decimals' => 0, - 'thousands_separator' => ' ', 'numeric_code' => '352', + 'symbol' => 'ISK', + 'name' => t('Icelandic Króna'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Eyrir'), 'major_unit' => t('Kronur'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'JMD' => array( 'code' => 'JMD', - 'symbol' => 'J$', - 'name' => t('Jamaican Dollar'), 'numeric_code' => '388', + 'symbol' => '$', + 'name' => t('Jamaican Dollar'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'JOD' => array( 'code' => 'JOD', - 'symbol' => 'JD', - 'name' => t('Jordanian Dinar'), - 'decimals' => 3, 'numeric_code' => '400', + 'symbol' => 'د.أ.‏', + 'name' => t('Jordanian Dinar'), 'minor_unit' => t('Piastr'), 'major_unit' => t('Dinar'), + 'decimals' => 3, ), 'JPY' => array( 'code' => 'JPY', - 'symbol' => '¥', - 'name' => t('Japanese Yen'), - 'decimals' => 0, 'numeric_code' => '392', + 'symbol' => '¥', + 'name' => t('Japanese Yen'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Sen'), 'major_unit' => t('Yen'), + 'decimals' => 0, ), 'KES' => array( 'code' => 'KES', + 'numeric_code' => '404', 'symbol' => 'Ksh', 'name' => t('Kenyan Shilling'), - 'numeric_code' => '404', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Shilling'), ), 'KGS' => array( 'code' => 'KGS', - 'code_placement' => 'hidden', + 'numeric_code' => '417', 'symbol' => 'сом', + 'name' => t('Kyrgystani Som'), 'symbol_placement' => 'after', - 'name' => t('Kyrgyzstani Som'), - 'numeric_code' => '417', - 'thousands_separator' => '', - 'major_unit' => t('Som'), + 'code_placement' => 'hidden', 'minor_unit' => t('Tyiyn'), + 'major_unit' => t('Som'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', + ), + 'KHR' => array( + 'code' => 'KHR', + 'numeric_code' => '116', + 'symbol' => '៛', + 'name' => t('Cambodian Riel'), + 'symbol_placement' => 'after', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', + 'minor_unit' => t('Sen'), + 'major_unit' => t('Riel'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'KMF' => array( 'code' => 'KMF', - 'symbol' => 'CF', - 'name' => t('Comorian Franc'), - 'decimals' => 0, 'numeric_code' => '174', + 'symbol' => 'ف.ج.ق.‏', + 'name' => t('Comorian Franc'), 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, + ), + 'KPW' => array( + 'code' => 'KPW', + 'numeric_code' => '408', + 'symbol' => 'KPW', + 'name' => t('North Korean Won'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'minor_unit' => t('Chon'), + 'major_unit' => t('Won'), + 'decimals' => 0, ), 'KRW' => array( 'code' => 'KRW', + 'numeric_code' => '410', 'symbol' => '₩', 'name' => t('South Korean Won'), - 'decimals' => 0, - 'numeric_code' => '410', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Jeon'), 'major_unit' => t('Won'), + 'decimals' => 0, ), 'KWD' => array( 'code' => 'KWD', - 'symbol' => 'KD', - 'name' => t('Kuwaiti Dinar'), - 'decimals' => 3, 'numeric_code' => '414', + 'symbol' => 'د.ك.‏', + 'name' => t('Kuwaiti Dinar'), 'minor_unit' => t('Fils'), 'major_unit' => t('Dinar'), + 'decimals' => 3, ), 'KYD' => array( 'code' => 'KYD', - 'symbol' => 'KY$', - 'name' => t('Cayman Islands Dollar'), 'numeric_code' => '136', + 'symbol' => '$', + 'name' => t('Cayman Islands Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'KZT' => array( 'code' => 'KZT', - 'symbol' => 'тг.', - 'name' => t('Kazakhstani tenge'), 'numeric_code' => '398', - 'thousands_separator' => ' ', - 'decimal_separator' => ',', + 'symbol' => '₸', + 'name' => t('Kazakhstani Tenge'), 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Tiyn'), 'major_unit' => t('Tenge'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'LAK' => array( 'code' => 'LAK', - 'symbol' => '₭N', - 'name' => t('Laotian Kip'), - 'decimals' => 0, 'numeric_code' => '418', + 'symbol' => '₭', + 'name' => t('Laotian Kip'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Att'), 'major_unit' => t('Kips'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'LBP' => array( 'code' => 'LBP', - 'symbol' => 'LB£', - 'name' => t('Lebanese Pound'), - 'decimals' => 0, 'numeric_code' => '422', + 'symbol' => 'ل.ل.‏', + 'name' => t('Lebanese Pound'), 'minor_unit' => t('Piastre'), 'major_unit' => t('Pound'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'LKR' => array( 'code' => 'LKR', - 'symbol' => 'SLRs', - 'name' => t('Sri Lanka Rupee'), 'numeric_code' => '144', + 'symbol' => 'රු.', + 'name' => t('Sri Lankan Rupee'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Rupee'), ), 'LRD' => array( 'code' => 'LRD', - 'symbol' => 'L$', - 'name' => t('Liberian Dollar'), 'numeric_code' => '430', + 'symbol' => '$', + 'name' => t('Liberian Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'LSL' => array( 'code' => 'LSL', + 'numeric_code' => '426', 'symbol' => 'LSL', 'name' => t('Lesotho Loti'), - 'numeric_code' => '426', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Sente'), 'major_unit' => t('Loti'), ), - 'LTL' => array( - 'code' => 'LTL', - 'symbol' => 'Lt', - 'name' => t('Lithuanian Litas'), - 'numeric_code' => '440', - 'minor_unit' => t('Centas'), - 'major_unit' => t('Litai'), - ), - 'LVL' => array( - 'code' => 'LVL', - 'symbol' => 'Ls', - 'name' => t('Latvian Lats'), - 'numeric_code' => '428', - 'minor_unit' => t('Santims'), - 'major_unit' => t('Lati'), - ), 'LYD' => array( 'code' => 'LYD', - 'symbol' => 'LD', - 'name' => t('Libyan Dinar'), - 'decimals' => 3, 'numeric_code' => '434', + 'symbol' => 'د.ل.‏', + 'name' => t('Libyan Dinar'), 'minor_unit' => t('Dirham'), 'major_unit' => t('Dinar'), + 'decimals' => 3, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'MAD' => array( 'code' => 'MAD', - 'symbol' => ' Dhs', - 'name' => t('Moroccan Dirham'), 'numeric_code' => '504', - 'symbol_placement' => 'after', - 'code_placement' => 'hidden', + 'symbol' => 'د.م.‏', + 'name' => t('Moroccan Dirham'), 'minor_unit' => t('Santimat'), 'major_unit' => t('Dirhams'), ), 'MDL' => array( 'code' => 'MDL', - 'symbol' => 'MDL', - 'name' => t('Moldovan leu'), - 'symbol_placement' => 'after', 'numeric_code' => '498', + 'symbol' => 'L', + 'name' => t('Moldovan Leu'), + 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('bani'), 'major_unit' => t('Lei'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', + ), + 'MGA' => array( + 'code' => 'MGA', + 'numeric_code' => '969', + 'symbol' => 'Ar', + 'name' => t('Malagasy Ariary'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'major_unit' => t('Ariary'), + 'decimals' => 0, ), 'MKD' => array( 'code' => 'MKD', + 'numeric_code' => '807', 'symbol' => 'ден', - 'name' => t('Macedonian denar'), + 'name' => t('Macedonian Denar'), 'symbol_placement' => 'after', - 'numeric_code' => '807', 'code_placement' => 'hidden', 'minor_unit' => t('Deni'), 'major_unit' => t('Denari'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'MMK' => array( 'code' => 'MMK', - 'symbol' => 'MMK', - 'name' => t('Myanma Kyat'), - 'decimals' => 0, 'numeric_code' => '104', + 'symbol' => 'K', + 'name' => t('Myanmar Kyat'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Pya'), 'major_unit' => t('Kyat'), + 'decimals' => 0, ), 'MNT' => array( 'code' => 'MNT', + 'numeric_code' => '496', 'symbol' => '₮', 'name' => t('Mongolian Tugrik'), - 'decimals' => 0, - 'numeric_code' => '496', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Möngö'), 'major_unit' => t('Tugriks'), + 'decimals' => 0, ), 'MOP' => array( 'code' => 'MOP', + 'numeric_code' => '446', 'symbol' => 'MOP$', 'name' => t('Macanese Pataca'), - 'numeric_code' => '446', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Avo'), 'major_unit' => t('Pataca'), ), 'MRO' => array( 'code' => 'MRO', - 'symbol' => 'UM', - 'name' => t('Mauritanian Ouguiya'), - 'decimals' => 0, 'numeric_code' => '478', + 'symbol' => 'أ.م.‏', + 'name' => t('Mauritanian Ouguiya'), 'minor_unit' => t('Khoums'), 'major_unit' => t('Ouguiya'), - ), - 'MTP' => array( - 'code' => 'MTP', - 'symbol' => 'MT£', - 'name' => t('Maltese Pound'), - 'minor_unit' => t('Shilling'), - 'major_unit' => t('Pound'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'MUR' => array( 'code' => 'MUR', - 'symbol' => 'MURs', - 'name' => t('Mauritian Rupee'), - 'decimals' => 0, 'numeric_code' => '480', + 'symbol' => 'Rs', + 'name' => t('Mauritian Rupee'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Rupee'), + 'decimals' => 0, + 'thousands_separator' => ' ', + ), + 'MVR' => array( + 'code' => 'MVR', + 'numeric_code' => '462', + 'symbol' => 'MVR', + 'name' => t('Maldivian Rufiyaa'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'minor_unit' => t('Laari'), + 'major_unit' => t('Rufiyaa'), + ), + 'MWK' => array( + 'code' => 'MWK', + 'numeric_code' => '454', + 'symbol' => 'MK', + 'name' => t('Malawian Kwacha'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'minor_unit' => t('Tambala'), + 'major_unit' => t('Kwacha'), ), 'MXN' => array( 'code' => 'MXN', + 'numeric_code' => '484', 'symbol' => '$', 'name' => t('Mexican Peso'), - 'numeric_code' => '484', 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), ), 'MYR' => array( 'code' => 'MYR', + 'numeric_code' => '458', 'symbol' => 'RM', 'name' => t('Malaysian Ringgit'), - 'numeric_code' => '458', 'symbol_placement' => 'before', 'code_placement' => 'hidden', 'minor_unit' => t('Sen'), @@ -838,485 +1085,648 @@ function commerce_commerce_currency_info() { ), 'MZN' => array( 'code' => 'MZN', + 'numeric_code' => '943', 'symbol' => 'MTn', 'name' => t('Mozambican Metical'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Metical'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'NAD' => array( 'code' => 'NAD', - 'symbol' => 'N$', - 'name' => t('Namibian Dollar'), 'numeric_code' => '516', + 'symbol' => '$', + 'name' => t('Namibian Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'NGN' => array( 'code' => 'NGN', + 'numeric_code' => '566', 'symbol' => '₦', 'name' => t('Nigerian Naira'), - 'numeric_code' => '566', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Kobo'), 'major_unit' => t('Naira'), ), 'NIO' => array( 'code' => 'NIO', - 'symbol' => 'C$', - 'name' => t('Nicaraguan Cordoba Oro'), 'numeric_code' => '558', + 'symbol' => 'C$', + 'name' => t('Nicaraguan Córdoba'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Cordoba'), ), 'NOK' => array( 'code' => 'NOK', - 'symbol' => 'Nkr', - 'name' => t('Norwegian Krone'), - 'thousands_separator' => ' ', - 'decimal_separator' => ',', 'numeric_code' => '578', + 'symbol' => 'NOK', + 'name' => t('Norwegian Krone'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Øre'), 'major_unit' => t('Krone'), ), 'NPR' => array( 'code' => 'NPR', - 'symbol' => 'NPRs', - 'name' => t('Nepalese Rupee'), 'numeric_code' => '524', + 'symbol' => 'नेरू', + 'name' => t('Nepalese Rupee'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Paisa'), 'major_unit' => t('Rupee'), ), 'NZD' => array( 'code' => 'NZD', + 'numeric_code' => '554', 'symbol' => '$', 'name' => t('New Zealand Dollar'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', - 'numeric_code' => '554', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), + 'OMR' => array( + 'code' => 'OMR', + 'numeric_code' => '512', + 'symbol' => 'ر.ع.‏', + 'name' => t('Omani Rial'), + 'minor_unit' => t('Baisa'), + 'major_unit' => t('Rial'), + 'decimals' => 3, + ), 'PAB' => array( 'code' => 'PAB', + 'numeric_code' => '590', 'symbol' => 'B/.', 'name' => t('Panamanian Balboa'), - 'numeric_code' => '590', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Centésimo'), 'major_unit' => t('Balboa'), ), 'PEN' => array( 'code' => 'PEN', - 'symbol' => 'S/.', - 'name' => t('Peruvian Nuevo Sol'), 'numeric_code' => '604', + 'symbol' => 'S/', + 'name' => t('Peruvian Sol'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Céntimo'), 'major_unit' => t('Nuevos Sole'), ), 'PGK' => array( 'code' => 'PGK', + 'numeric_code' => '598', 'symbol' => 'PGK', 'name' => t('Papua New Guinean Kina'), - 'numeric_code' => '598', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Toea'), 'major_unit' => t('Kina '), ), 'PHP' => array( 'code' => 'PHP', + 'numeric_code' => '608', 'symbol' => '₱', 'name' => t('Philippine Peso'), - 'numeric_code' => '608', + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Centavo'), 'major_unit' => t('Peso'), ), 'PKR' => array( 'code' => 'PKR', - 'symbol' => 'PKRs', - 'name' => t('Pakistani Rupee'), - 'decimals' => 0, 'numeric_code' => '586', + 'symbol' => 'Rs', + 'name' => t('Pakistani Rupee'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Paisa'), 'major_unit' => t('Rupee'), + 'decimals' => 0, ), 'PLN' => array( 'code' => 'PLN', - 'symbol' => 'zł', - 'name' => t('Polish Złoty'), - 'decimal_separator' => ',', - 'thousands_separator' => ' ', 'numeric_code' => '985', + 'symbol' => 'zł', + 'name' => t('Polish Zloty'), 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Grosz'), 'major_unit' => t('Złotych'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'PYG' => array( 'code' => 'PYG', - 'symbol' => '₲', - 'name' => t('Paraguayan Guarani'), - 'decimals' => 0, 'numeric_code' => '600', + 'symbol' => 'PYG', + 'name' => t('Paraguayan Guarani'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Céntimo'), 'major_unit' => t('Guarani'), + 'decimals' => 0, ), 'QAR' => array( 'code' => 'QAR', - 'symbol' => 'QR', - 'name' => t('Qatari Rial'), 'numeric_code' => '634', + 'symbol' => 'ر.ق.‏', + 'name' => t('Qatari Rial'), 'minor_unit' => t('Dirham'), 'major_unit' => t('Rial'), ), - 'RHD' => array( - 'code' => 'RHD', - 'symbol' => 'RH$', - 'name' => t('Rhodesian Dollar'), - 'minor_unit' => t('Cent'), - 'major_unit' => t('Dollar'), - ), 'RON' => array( 'code' => 'RON', + 'numeric_code' => '946', 'symbol' => 'RON', 'name' => t('Romanian Leu'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Ban'), 'major_unit' => t('Leu'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'RSD' => array( 'code' => 'RSD', - 'symbol' => 'din.', + 'numeric_code' => '941', + 'symbol' => 'RSD', 'name' => t('Serbian Dinar'), - 'decimals' => 0, + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Para'), 'major_unit' => t('Dinars'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'RUB' => array( 'code' => 'RUB', - 'symbol' => 'руб.', - 'name' => t('Russian Ruble'), - 'thousands_separator' => ' ', - 'decimal_separator' => ',', 'numeric_code' => '643', + 'symbol' => '₽', + 'name' => t('Russian Ruble'), 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Kopek'), 'major_unit' => t('Ruble'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', + ), + 'RWF' => array( + 'code' => 'RWF', + 'numeric_code' => '646', + 'symbol' => 'RF', + 'name' => t('Rwandan Franc'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'minor_unit' => t('Centime'), + 'major_unit' => t('Franc'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'SAR' => array( 'code' => 'SAR', - 'symbol' => 'SR', - 'name' => t('Saudi Riyal'), 'numeric_code' => '682', + 'symbol' => 'ر.س.‏', + 'name' => t('Saudi Riyal'), 'minor_unit' => t('Hallallah'), 'major_unit' => t('Riyals'), ), 'SBD' => array( 'code' => 'SBD', - 'symbol' => 'SI$', - 'name' => t('Solomon Islands Dollar'), 'numeric_code' => '090', + 'symbol' => '$', + 'name' => t('Solomon Islands Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'SCR' => array( 'code' => 'SCR', - 'symbol' => 'SRe', - 'name' => t('Seychellois Rupee'), 'numeric_code' => '690', + 'symbol' => 'SCR', + 'name' => t('Seychellois Rupee'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Rupee'), ), - 'SDD' => array( - 'code' => 'SDD', - 'symbol' => 'LSd', - 'name' => t('Old Sudanese Dinar'), - 'numeric_code' => '736', - 'minor_unit' => t('None'), - 'major_unit' => t('Dinar'), + 'SDG' => array( + 'code' => 'SDG', + 'numeric_code' => '938', + 'symbol' => 'ج.س.', + 'name' => t('Sudanese Pound'), + 'minor_unit' => t('Piastre'), + 'major_unit' => t('Pound'), ), 'SEK' => array( 'code' => 'SEK', + 'numeric_code' => '752', 'symbol' => 'kr', 'name' => t('Swedish Krona'), - 'numeric_code' => '752', - 'thousands_separator' => ' ', - 'decimal_separator' => ',', 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Öre'), 'major_unit' => t('Kronor'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'SGD' => array( 'code' => 'SGD', - 'symbol' => 'S$', - 'name' => t('Singapore Dollar'), 'numeric_code' => '702', + 'symbol' => '$', + 'name' => t('Singapore Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'SHP' => array( 'code' => 'SHP', - 'symbol' => 'SH£', - 'name' => t('Saint Helena Pound'), 'numeric_code' => '654', + 'symbol' => 'SHP', + 'name' => t('St. Helena Pound'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Penny'), 'major_unit' => t('Pound'), ), 'SLL' => array( 'code' => 'SLL', - 'symbol' => 'Le', - 'name' => t('Sierra Leonean Leone'), - 'decimals' => 0, 'numeric_code' => '694', + 'symbol' => 'SLL', + 'name' => t('Sierra Leonean Leone'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Leone'), + 'decimals' => 0, ), 'SOS' => array( 'code' => 'SOS', - 'symbol' => 'Ssh', - 'name' => t('Somali Shilling'), - 'decimals' => 0, 'numeric_code' => '706', + 'symbol' => 'S', + 'name' => t('Somali Shilling'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Shilling'), + 'decimals' => 0, ), 'SRD' => array( 'code' => 'SRD', - 'symbol' => 'SR$', + 'numeric_code' => '968', + 'symbol' => '$', 'name' => t('Surinamese Dollar'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), - 'SRG' => array( - 'code' => 'SRG', - 'symbol' => 'Sf', - 'name' => t('Suriname Guilder'), - 'numeric_code' => '740', - 'minor_unit' => t('Cent'), - 'major_unit' => t('Guilder'), + 'SSP' => array( + 'code' => 'SSP', + 'numeric_code' => '728', + 'symbol' => '£', + 'name' => t('South Sudanese Pound'), + 'minor_unit' => t('Piaster'), + 'major_unit' => t('Pound'), ), 'STD' => array( 'code' => 'STD', - 'symbol' => 'Db', - 'name' => t('São Tomé and Príncipe Dobra'), - 'decimals' => 0, 'numeric_code' => '678', + 'symbol' => 'Db', + 'name' => t('São Tomé & Príncipe Dobra'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Cêntimo'), 'major_unit' => t('Dobra'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'SYP' => array( 'code' => 'SYP', - 'symbol' => 'SY£', - 'name' => t('Syrian Pound'), - 'decimals' => 0, 'numeric_code' => '760', + 'symbol' => 'ل.س.‏', + 'name' => t('Syrian Pound'), 'minor_unit' => t('Piastre'), 'major_unit' => t('Pound'), + 'decimals' => 0, ), 'SZL' => array( 'code' => 'SZL', - 'symbol' => 'SZL', - 'name' => t('Swazi Lilangeni'), 'numeric_code' => '748', + 'symbol' => 'E', + 'name' => t('Swazi Lilangeni'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Lilangeni'), ), 'THB' => array( 'code' => 'THB', - 'symbol' => '฿', - 'name' => t('Thai Baht'), 'numeric_code' => '764', + 'symbol' => 'THB', + 'name' => t('Thai Baht'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Satang'), 'major_unit' => t('Baht'), ), + 'TJS' => array( + 'code' => 'TJS', + 'numeric_code' => '972', + 'symbol' => 'TJS', + 'name' => t('Tajikistani Somoni'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', + 'minor_unit' => t('Diram'), + 'major_unit' => t('Somoni'), + ), + 'TMT' => array( + 'code' => 'TMT', + 'numeric_code' => '934', + 'symbol' => 'TMT', + 'name' => t('Turkmenistani Manat'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', + 'minor_unit' => t('Tenge'), + 'major_unit' => t('Manat'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', + ), 'TND' => array( 'code' => 'TND', - 'symbol' => 'DT', - 'name' => t('Tunisian Dinar'), - 'decimals' => 3, 'numeric_code' => '788', + 'symbol' => 'TND', + 'name' => t('Tunisian Dinar'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Millime'), 'major_unit' => t('Dinar'), + 'decimals' => 3, ), 'TOP' => array( 'code' => 'TOP', + 'numeric_code' => '776', 'symbol' => 'T$', 'name' => t('Tongan Paʻanga'), - 'numeric_code' => '776', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Senit'), 'major_unit' => t('Paʻanga'), ), 'TRY' => array( 'code' => 'TRY', - 'symbol' => 'TL', - 'name' => t('Turkish Lira'), 'numeric_code' => '949', - 'thousands_separator' => '.', - 'decimal_separator' => ',', - 'symbol_placement' => 'after', - 'code_placement' => '', + 'symbol' => '₺', + 'name' => t('Turkish Lira'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Kurus'), 'major_unit' => t('Lira'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'TTD' => array( 'code' => 'TTD', - 'symbol' => 'TT$', - 'name' => t('Trinidad and Tobago Dollar'), 'numeric_code' => '780', + 'symbol' => '$', + 'name' => t('Trinidad & Tobago Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'TWD' => array( 'code' => 'TWD', - 'symbol' => 'NT$', - 'name' => t('New Taiwan Dollar'), 'numeric_code' => '901', + 'symbol' => '$', + 'name' => t('New Taiwan Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('New Dollar'), ), 'TZS' => array( 'code' => 'TZS', + 'numeric_code' => '834', 'symbol' => 'TSh', 'name' => t('Tanzanian Shilling'), - 'decimals' => 0, - 'numeric_code' => '834', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Senti'), 'major_unit' => t('Shilling'), + 'decimals' => 0, ), 'UAH' => array( 'code' => 'UAH', - 'symbol' => 'грн.', - 'name' => t('Ukrainian Hryvnia'), 'numeric_code' => '980', - 'thousands_separator' => '', - 'decimal_separator' => '.', + 'symbol' => '₴', + 'name' => t('Ukrainian Hryvnia'), 'symbol_placement' => 'after', 'code_placement' => 'hidden', 'minor_unit' => t('Kopiyka'), 'major_unit' => t('Hryvnia'), + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'UGX' => array( 'code' => 'UGX', + 'numeric_code' => '800', 'symbol' => 'USh', 'name' => t('Ugandan Shilling'), - 'decimals' => 0, - 'numeric_code' => '800', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Shilling'), + 'decimals' => 0, ), 'USD' => array( 'code' => 'USD', - 'symbol' => '$', - 'name' => t('United States Dollar'), 'numeric_code' => '840', + 'symbol' => '$', + 'name' => t('US Dollar'), 'symbol_placement' => 'before', + 'symbol_spacer' => '', 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'UYU' => array( 'code' => 'UYU', - 'symbol' => '$U', - 'name' => t('Uruguayan Peso'), 'numeric_code' => '858', + 'symbol' => '$', + 'name' => t('Uruguayan Peso'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centésimo'), 'major_unit' => t('Peso'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', + ), + 'UZS' => array( + 'code' => 'UZS', + 'numeric_code' => '860', + 'symbol' => 'soʻm', + 'name' => t('Uzbekistani Som'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', + 'minor_unit' => t('Tiyin'), + 'major_unit' => t('Som'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'VEF' => array( 'code' => 'VEF', - 'symbol' => 'Bs.F.', - 'name' => t('Venezuelan Bolívar Fuerte'), + 'numeric_code' => '937', + 'symbol' => 'Bs.', + 'name' => t('Venezuelan Bolívar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Céntimo'), 'major_unit' => t('Bolivares Fuerte'), + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'VND' => array( 'code' => 'VND', - 'symbol' => 'đ', + 'numeric_code' => '704', + 'symbol' => '₫', 'name' => t('Vietnamese Dong'), - 'decimals' => 0, - 'thousands_separator' => '.', - 'symbol_placement' => 'after', - 'symbol_spacer' => '', + 'symbol_placement' => 'before', 'code_placement' => 'hidden', - 'numeric_code' => '704', 'minor_unit' => t('Hà'), 'major_unit' => t('Dong'), + 'decimals' => 0, + 'thousands_separator' => '.', + 'decimal_separator' => ',', ), 'VUV' => array( 'code' => 'VUV', - 'symbol' => 'VT', - 'name' => t('Vanuatu Vatu'), - 'decimals' => 0, 'numeric_code' => '548', + 'symbol' => 'VUV', + 'name' => t('Vanuatu Vatu'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'major_unit' => t('Vatu'), + 'decimals' => 0, ), 'WST' => array( 'code' => 'WST', - 'symbol' => 'WS$', - 'name' => t('Samoan Tala'), 'numeric_code' => '882', + 'symbol' => 'WST', + 'name' => t('Samoan Tala'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Sene'), 'major_unit' => t('Tala'), ), 'XAF' => array( 'code' => 'XAF', - 'symbol' => 'FCFA', - 'name' => t('CFA Franc BEAC'), - 'decimals' => 0, 'numeric_code' => '950', + 'symbol' => 'FCFA', + 'name' => t('Central African CFA Franc'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'XCD' => array( 'code' => 'XCD', - 'symbol' => 'EC$', - 'name' => t('East Caribbean Dollar'), 'numeric_code' => '951', + 'symbol' => '$', + 'name' => t('East Caribbean Dollar'), + 'symbol_placement' => 'before', + 'symbol_spacer' => '', + 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Dollar'), ), 'XOF' => array( 'code' => 'XOF', - 'symbol' => 'CFA', - 'name' => t('CFA Franc BCEAO'), - 'decimals' => 0, 'numeric_code' => '952', + 'symbol' => 'CFA', + 'name' => t('West African CFA Franc'), + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, ), 'XPF' => array( 'code' => 'XPF', - 'symbol' => 'CFPF', - 'name' => t('CFP Franc'), - 'decimals' => 0, 'numeric_code' => '953', + 'symbol' => 'FCFP', + 'name' => t('CFP Franc'), + 'symbol_placement' => 'after', + 'code_placement' => 'hidden', 'minor_unit' => t('Centime'), 'major_unit' => t('Franc'), + 'decimals' => 0, + 'thousands_separator' => ' ', + 'decimal_separator' => ',', ), 'YER' => array( 'code' => 'YER', - 'symbol' => 'YR', - 'name' => t('Yemeni Rial'), - 'decimals' => 0, 'numeric_code' => '886', + 'symbol' => 'ر.ي.‏', + 'name' => t('Yemeni Rial'), 'minor_unit' => t('Fils'), 'major_unit' => t('Rial'), + 'decimals' => 0, ), 'ZAR' => array( 'code' => 'ZAR', - 'symbol' => 'R', - 'name' => t('South African Rand'), 'numeric_code' => '710', + 'symbol' => 'ZAR', + 'name' => t('South African Rand'), 'symbol_placement' => 'before', 'code_placement' => 'hidden', 'minor_unit' => t('Cent'), 'major_unit' => t('Rand'), ), - 'ZMK' => array( - 'code' => 'ZMK', - 'symbol' => 'ZK', + 'ZMW' => array( + 'code' => 'ZMW', + 'numeric_code' => '967', + 'symbol' => 'K', 'name' => t('Zambian Kwacha'), - 'decimals' => 0, - 'numeric_code' => '894', + 'symbol_placement' => 'before', + 'code_placement' => 'hidden', 'minor_unit' => t('Ngwee'), 'major_unit' => t('Kwacha'), ), diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.api.php index 4943583c..41a7f88e 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.api.php @@ -100,6 +100,20 @@ function hook_commerce_cart_order_convert($order_wrapper, $account) { // No example. } +/** + * Allows modules to perform processing on a shopping cart order prior to the + * logic in commerce_cart_order_refresh() taking place. + * + * @param $order_wrapper + * The entity metadata wrapper for the order about to be refreshed. + * + * @see commerce_cart_order_refresh() + * @see entity_metadata_wrapper() + */ +function hook_commerce_cart_order_pre_refresh($order_wrapper) { + // No example. +} + /** * Allows modules to perform additional processing to refresh an individual line * item on a shopping cart order. @@ -203,7 +217,7 @@ function hook_commerce_cart_attributes_refresh_alter(&$commands, $form, $form_st * A clone of the line item being added to the cart. Since this is a clone, * changes made to it will not propagate up to the Add to Cart process. */ -function hook_commerce_cart_product_comparison_properties_alter(&$comparison_properties) { +function hook_commerce_cart_product_comparison_properties_alter(&$comparison_properties, $line_item) { // Force separate line items when the same product is added to the cart from // different display paths. $comparison_properties[] = 'commerce_display_path'; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.info index e1d37029..b0d42542 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.info @@ -15,15 +15,19 @@ core = 7.x ; Views handlers files[] = includes/views/handlers/commerce_cart_handler_field_add_to_cart_form.inc +files[] = includes/views/handlers/commerce_cart_handler_field_edit_attributes.inc files[] = includes/views/handlers/commerce_cart_plugin_argument_default_current_cart_order_id.inc files[] = includes/views/handlers/commerce_cart_handler_area_empty_text.inc ; Simple tests files[] = tests/commerce_cart.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Cart provider Interface. +files[] = plugins/cart_provider/interface.inc + +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.install b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.install index 48aeb064..1ec92870 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.install @@ -43,3 +43,11 @@ function commerce_cart_update_7102() { variable_set('commerce_cart_refresh_frequency', 0); return t('New order settings have been added to let you reduce the frequency of the shopping cart refresh. This update set it to occur continuously as it has been, but we recommend you implement some delay unless you have a unique product pricing situation that demands pricing updates every time a shopping cart is loaded.'); } + +/** + * Rebuild registry to ensure autoloading for cart provider plugins. + */ +function commerce_cart_update_7103() { + registry_rebuild(); + return t('Registry has been rebuilt'); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.module index 944a1b44..e1bb9e46 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/commerce_cart.module @@ -8,6 +8,9 @@ * special considerations to associate it with a user and */ +// Define a constant for the default cart provider. +define('COMMERCE_CART_DEFAULT_PROVIDER', 'session'); + // Define constants for the shopping cart refresh modes. define('COMMERCE_CART_REFRESH_ALWAYS', 'always'); define('COMMERCE_CART_REFRESH_OWNER_ONLY', 'owner_only'); @@ -334,7 +337,7 @@ function commerce_cart_line_item_delete_form_submit($form, &$form_state) { $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $form_state['line_items'][$line_item_id]); // If the deleted line item is a product... - if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { + if (in_array($line_item_wrapper->getBundle(), commerce_product_line_item_types())) { $title = $line_item_wrapper->commerce_product->title->value(); } else { @@ -359,6 +362,19 @@ function commerce_cart_form_commerce_order_settings_form_alter(&$form, &$form_st '#weight' => 10, ); + $cart_providers = array(); + foreach (commerce_cart_get_providers() as $key => $cart_provider) { + $cart_providers[$key] = $cart_provider['title']; + } + + $form['commerce_cart_provider'] = array( + '#title' => t('Cart provider'), + '#type' => 'select', + '#options' => $cart_providers, + '#default_value' => variable_get('commerce_cart_provider', COMMERCE_CART_DEFAULT_PROVIDER), + '#access' => count($cart_providers) > 1, + ); + // Add a fieldset for settings pertaining to the shopping cart refresh. $form['cart_refresh'] = array( '#type' => 'fieldset', @@ -651,9 +667,6 @@ function commerce_cart_block_view($delta) { // Prepare the display of the default Shopping Cart block. if ($delta == 'cart') { - // Default to an empty cart block message. - $content = theme('commerce_cart_empty_block'); - // First check to ensure there are products in the shopping cart. if ($order = commerce_cart_order_load($user->uid)) { $wrapper = entity_metadata_wrapper('commerce_order', $order); @@ -671,6 +684,11 @@ function commerce_cart_block_view($delta) { } } + if (empty($content)) { + // Default to an empty cart block message. + $content = theme('commerce_cart_empty_block'); + } + return array('subject' => t('Shopping cart'), 'content' => $content); } } @@ -747,35 +765,37 @@ function commerce_cart_commerce_order_load($orders) { // Refresh only if this order object represents the latest revision of a // shopping cart order, it hasn't been refreshed already in this request // and it meets the criteria in the shopping cart refresh settings. - if (!isset($refreshed[$order->order_id]) && - commerce_cart_order_is_cart($order) && - commerce_order_is_latest_revision($order) && - commerce_cart_order_can_refresh($order)) { - // Update the last cart refresh timestamp and record the order's current - // changed timestamp to detect if the order is actually updated. - $order->data['last_cart_refresh'] = REQUEST_TIME; - - $unchanged_data = $order->data; - $last_changed = $order->changed; - - // Refresh the order and add its ID to the refreshed array. - $refreshed[$order->order_id] = TRUE; - commerce_cart_order_refresh($order); - - // If order wasn't updated during the refresh, we need to manually update - // the last cart refresh timestamp in the database. - if ($order->changed == $last_changed) { - db_update('commerce_order') - ->fields(array('data' => serialize($unchanged_data))) - ->condition('order_id', $order->order_id) - ->execute(); - - db_update('commerce_order_revision') - ->fields(array('data' => serialize($unchanged_data))) - ->condition('order_id', $order->order_id) - ->condition('revision_id', $order->revision_id) - ->execute(); - } + if (isset($refreshed[$order->order_id]) || + commerce_entity_is_unchanged('commerce_order', $order) || + !commerce_cart_order_is_cart($order) || + !commerce_order_is_latest_revision($order) || + !commerce_cart_order_can_refresh($order)) { + continue; + } + // Update the last cart refresh timestamp and record the order's current + // changed timestamp to detect if the order is actually updated. + $order->data['last_cart_refresh'] = REQUEST_TIME; + + $unchanged_data = $order->data; + $last_changed = $order->changed; + + // Refresh the order and add its ID to the refreshed array. + $refreshed[$order->order_id] = TRUE; + commerce_cart_order_refresh($order); + + // If order wasn't updated during the refresh, we need to manually update + // the last cart refresh timestamp in the database. + if ($order->changed == $last_changed) { + db_update('commerce_order') + ->fields(array('data' => serialize($unchanged_data))) + ->condition('order_id', $order->order_id) + ->execute(); + + db_update('commerce_order_revision') + ->fields(array('data' => serialize($unchanged_data))) + ->condition('order_id', $order->order_id) + ->condition('revision_id', $order->revision_id) + ->execute(); } } } @@ -1065,6 +1085,9 @@ function commerce_cart_order_convert($order, $account) { function commerce_cart_order_refresh($order) { $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + // Allow other modules to act on the order prior to the refresh logic. + module_invoke_all('commerce_cart_order_pre_refresh', $order); + // Loop over every line item on the order... $line_item_changed = FALSE; @@ -1097,9 +1120,9 @@ function commerce_cart_order_refresh($order) { // current quantity and display URI information. commerce_product_line_item_populate($cloned_line_item, $product); - // Process the unit price through Rules so it reflects the user's actual - // current purchase price. - rules_invoke_event('commerce_product_calculate_sell_price', $cloned_line_item); + // Process the unit price through the sell price calculation callback + // so it reflects the user's actual current purchase price. + commerce_product_pricing_invoke($cloned_line_item); } // Allow other modules to alter line items on a shopping cart refresh. @@ -1130,9 +1153,8 @@ function commerce_cart_order_refresh($order) { $data['components'] = $current_data['components']; $line_item_wrapper->commerce_unit_price->data = $data; - // Save the updated line item and clear the entity cache. + // Save the updated line item. commerce_line_item_save($line_item_wrapper->value()); - entity_get_controller('commerce_line_item')->resetCache(array($line_item_wrapper->line_item_id->value())); $line_item_changed = TRUE; } @@ -1183,8 +1205,9 @@ function commerce_cart_get_properties($data = FALSE, array $options, $name) { * An array of applicable cart order IDs or an empty array if none exist. */ function commerce_cart_order_session_order_ids($completed = FALSE) { - $key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders'; - return empty($_SESSION[$key]) ? array() : $_SESSION[$key]; + if ($cart_provider = commerce_cart_get_provider()) { + return $cart_provider->cartOrderIds($completed); + } } /** @@ -1197,13 +1220,8 @@ function commerce_cart_order_session_order_ids($completed = FALSE) { * completed orders array instead of the active cart orders array. */ function commerce_cart_order_session_save($order_id, $completed = FALSE) { - $key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders'; - - if (empty($_SESSION[$key])) { - $_SESSION[$key] = array($order_id); - } - elseif (!in_array($order_id, $_SESSION[$key])) { - $_SESSION[$key][] = $order_id; + if ($cart_provider = commerce_cart_get_provider()) { + $cart_provider->cartSave($order_id, $completed); } } @@ -1222,16 +1240,11 @@ function commerce_cart_order_session_save($order_id, $completed = FALSE) { * or if the specified order ID exists in the session. */ function commerce_cart_order_session_exists($order_id = NULL, $completed = FALSE) { - $key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders'; - - // If an order was specified, look for it in the array. - if (!empty($order_id)) { - return !empty($_SESSION[$key]) && in_array($order_id, $_SESSION[$key]); - } - else { - // Otherwise look for any value. - return !empty($_SESSION[$key]); + if ($cart_provider = commerce_cart_get_provider()) { + return $cart_provider->cartExists($order_id, $completed); } + + return FALSE; } /** @@ -1245,15 +1258,8 @@ function commerce_cart_order_session_exists($order_id = NULL, $completed = FALSE * completed orders array instead of the active cart orders array. */ function commerce_cart_order_session_delete($order_id = NULL, $completed = FALSE) { - $key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders'; - - if (!empty($_SESSION[$key])) { - if (!empty($order_id)) { - $_SESSION[$key] = array_diff($_SESSION[$key], array($order_id)); - } - else { - unset($_SESSION[$key]); - } + if ($cart_provider = commerce_cart_get_provider()) { + $cart_provider->cartDelete($order_id, $completed); } } @@ -1265,6 +1271,7 @@ function commerce_cart_order_session_delete($order_id = NULL, $completed = FALSE * @param $line_item * An unsaved product line item to be added to the cart with the following data * on the line item being used to determine how to add the product to the cart: + * - $line_item->commerce_unit_price: a non-NULL default unit price. * - $line_item->commerce_product: reference to the product to add to the cart. * - $line_item->quantity: quantity of this product to add to the cart. * - $line_item->data: data array that is saved with the line item if the line @@ -1317,7 +1324,7 @@ function commerce_cart_product_add($uid, $line_item, $combine = TRUE) { $quantity = $line_item->quantity; // Invoke the product prepare event with the shopping cart order. - rules_invoke_all('commerce_cart_product_prepare', $order, $product, $line_item->quantity); + rules_invoke_all('commerce_cart_product_prepare', $order, $product, $quantity); // Determine if the product already exists on the order and increment its // quantity instead of adding a new line if it does. @@ -1339,7 +1346,8 @@ function commerce_cart_product_add($uid, $line_item, $combine = TRUE) { // Allow other modules to specify what properties should be compared when // determining whether or not to combine line items. - drupal_alter('commerce_cart_product_comparison_properties', $comparison_properties, clone($line_item)); + $line_item_clone = clone($line_item); + drupal_alter('commerce_cart_product_comparison_properties', $comparison_properties, $line_item_clone); // Loop over each line item on the order. foreach ($order_wrapper->commerce_line_items as $delta => $matching_line_item_wrapper) { @@ -1382,10 +1390,6 @@ function commerce_cart_product_add($uid, $line_item, $combine = TRUE) { commerce_line_item_save($matching_line_item); - // Clear the line item cache so the updated quantity will be available to - // the ensuing load instead of the original quantity as loaded above. - entity_get_controller('commerce_line_item')->resetCache(array($matching_line_item->line_item_id)); - // Update the line item variable for use in the invocation and return value. $line_item = $matching_line_item; } @@ -1699,6 +1703,105 @@ function commerce_cart_forms($form_id, $args) { return $forms; } +/** + * Returns an array of products matching a set of attribute values. + * + * @param $products + * An array of product objects. + * @param $form_state + * The $form_state from an Add to Cart form. + */ +function _commerce_cart_matching_products($products, &$form_state) { + $matching_products = array(); + + // If the form state contains attribute data... + if (!empty($form_state['values']['attributes'])) { + $changed_attribute = NULL; + $attributes = (array) array_diff_key($form_state['values']['attributes'], array('product_select' => '')); + $unchanged_attributes = $form_state['values']['unchanged_attributes']; + + // Get the changed attribute. + foreach ($attributes as $attribute_name => $attribute_value) { + // If this is the attribute widget that was changed... + if ($attribute_value != $unchanged_attributes[$attribute_name]) { + // Store the field name. + $changed_attribute = $attribute_name; + + // Clear the input for the "Select a product" widget now if it + // exists on the form since we know an attribute was changed. + unset($form_state['input']['attributes']['product_select']); + + // We found the changed attribute so we're done. + break; + } + } + + // If we found a changed attribute and it's not already in the first + // position of the array. + if (!empty($changed_attribute) && array_search($changed_attribute, array_keys($attributes))) { + // Move the changed attribute to the first position of the array so it's + // handled first. + $attributes = array($changed_attribute => $attributes[$changed_attribute]) + $attributes; + } + + // Create a list of all products associated with each attribute for + // filtering. + $attributes_products = array_fill_keys(array_keys($attributes), array()); + foreach ($products as $product_id => $product) { + $product_wrapper = entity_metadata_wrapper('commerce_product', $product); + + // Store which attributes this product is associated with. + foreach ($attributes as $attribute_name => $attribute_value) { + if ($product_wrapper->{$attribute_name}->raw() == $attribute_value) { + $attributes_products[$attribute_name][$product_id] = $product; + } + } + } + + // Filter out products starting with those not associated with the changed + // attribute and proceeding with those not associated with other attributes + // in the order the attributes appear in the form. + $matching_products = array_shift($attributes_products); + foreach ($attributes_products as $attribute_name => $attribute_products) { + $filtered_products = array_intersect_key($matching_products, $attribute_products); + + // If all products have been filtered out, use the set of products prior + // to filtering on the current attribute. + if (!empty($filtered_products)) { + $matching_products = $filtered_products; + } + else { + break; + } + } + + // Only accept the first matching product and subsequent matching products + // with identical attribute values. + if (count($matching_products) > 1) { + $default_product = reset($matching_products); + unset($matching_products[key($matching_products)]); + $default_product_wrapper = entity_metadata_wrapper('commerce_product', $default_product); + $default_products[$default_product->product_id] = $default_product; + + foreach ($matching_products as $matching_product_id => $matching_product) { + $matching_product_wrapper = entity_metadata_wrapper('commerce_product', $matching_product); + + foreach ($attributes as $attribute_name => $attribute_value) { + if ($matching_product_wrapper->{$attribute_name}->raw() != $default_product_wrapper->{$attribute_name}->raw()) { + continue 2; + } + } + + $default_products[$matching_product_id] = $matching_product; + } + + $matching_products = $default_products; + } + } + + return $matching_products; +} + /** * Builds an Add to Cart form for a set of products. * @@ -1773,6 +1876,9 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q // from causing the AJAX refresh not to work. $form['#attributes']['class'][] = drupal_html_class(commerce_cart_add_to_cart_form_id($product_ids)); + // Disable autocomplete on Add to Cart form elements. + $form['#attributes']['autocomplete'] = 'off'; + // Store the customer uid in the form so other modules can override with a // selection widget if necessary. $form['uid'] = array( @@ -1815,17 +1921,7 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q $same_type = TRUE; $type = ''; - // Find the default product so we know how to set default options on the - // various Add to Cart form widgets and an array of any matching product - // based on attribute selections so we can add a selection widget. - $matching_products = array(); - $default_product = NULL; - $attribute_names = array(); - $unchanged_attributes = array(); - foreach ($products as $product_id => $product) { - $product_wrapper = entity_metadata_wrapper('commerce_product', $product); - // Store the first product type. if (empty($type)) { $type = $product->type; @@ -1836,71 +1932,14 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q if ($product->type != $type) { $same_type = FALSE; } - - // If the form state contains a set of attribute data, use it to try - // and determine the default product. - $changed_attribute = NULL; - - if (!empty($form_state['values']['attributes'])) { - $match = TRUE; - - // Set an array of checked attributes for later comparison against the - // default matching product. - if (empty($attribute_names)) { - $attribute_names = (array) array_diff_key($form_state['values']['attributes'], array('product_select' => '')); - $unchanged_attributes = $form_state['values']['unchanged_attributes']; - } - - foreach ($attribute_names as $key => $value) { - // If this is the attribute widget that was changed... - if ($value != $unchanged_attributes[$key]) { - // Store the field name. - $changed_attribute = $key; - - // Clear the input for the "Select a product" widget now if it - // exists on the form since we know an attribute was changed. - unset($form_state['input']['attributes']['product_select']); - } - - // If a field name has been stored and we've moved past it to - // compare the next attribute field... - if (!empty($changed_attribute) && $changed_attribute != $key) { - // Wipe subsequent values from the form state so the attribute - // widgets can use the default values from the new default product. - unset($form_state['input']['attributes'][$key]); - - // Don't accept this as a matching product. - continue; - } - - if ($product_wrapper->{$key}->raw() != $value) { - $match = FALSE; - } - } - - // If the changed field name has already been stored, only accept the - // first matching product by ignoring the rest that would match. An - // exception is granted for additional matching products that share - // the exact same attribute values as the first. - if ($match && !empty($changed_attribute) && !empty($matching_products)) { - reset($matching_products); - $matching_product = $matching_products[key($matching_products)]; - $matching_product_wrapper = entity_metadata_wrapper('commerce_product', $matching_product); - - foreach ($attribute_names as $key => $value) { - if ($product_wrapper->{$key}->raw() != $matching_product_wrapper->{$key}->raw()) { - $match = FALSE; - } - } - } - - if ($match) { - $matching_products[$product_id] = $product; - } - } } - // Set the default product now if it isn't already set. + // Find an array of matching products based on attribute selections so we + // can find the default product and add a selection widget if multiple + // matching products are found. + $matching_products = _commerce_cart_matching_products($products, $form_state); + + // Set the default product. if (empty($matching_products)) { // If a product ID value was passed in, use that product if it exists. if (!empty($form_state['values']['product_id']) && @@ -1935,15 +1974,23 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q // Wrap the default product for later use. $default_product_wrapper = entity_metadata_wrapper('commerce_product', $default_product); + // Remove any attribute input values from the form state that aren't + // associated with the default product so that attribute widgets can use + // the default values. + if (!empty($form_state['input']['attributes'])) { + foreach ($form_state['input']['attributes'] as $attribute_name => $attribute_value) { + if (empty($default_product_wrapper->{$attribute_name}) || $default_product_wrapper->{$attribute_name}->raw() != $attribute_value) { + unset($form_state['input']['attributes'][$attribute_name]); + } + } + } + $form_state['default_product'] = $default_product; // If all the products are of the same type... if ($same_type) { // Loop through all the field instances on that product type. foreach (field_info_instances('commerce_product', $type) as $field_name => $instance) { - // A field qualifies if it is single value, required and uses a widget - // with a definite set of options. For the sake of simplicity, this is - // currently restricted to fields defined by the options module. $field = field_info_field($field_name); // If the instance is of a field type that is eligible to function as @@ -2022,7 +2069,7 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q continue; } - if (isset($form['attributes'][$used_field_name]['#default_value'])) { + if (array_key_exists('#default_value', $form['attributes'][$used_field_name])) { if ($product_wrapper->{$used_field_name}->raw() != $form['attributes'][$used_field_name]['#default_value']) { continue 2; } @@ -2132,7 +2179,7 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q } } - // If the products referenced were of different types or did not posess + // If the products referenced were of different types or did not possess // any qualifying attribute fields... if (!$same_type || empty($qualifying_fields)) { // For a single product form, just add the hidden product_id field. @@ -2336,15 +2383,12 @@ function commerce_cart_add_to_cart_form_attributes_refresh($form, $form_state) { $replacement_class, ); - // Theme the product extra field to $element. - $variables = array( - $product_extra_field_name => $product->{$product_extra_field_name}, - 'label' => $product_extra_field['label'] . ':', - 'product' => $product, - ); - + // Build the product extra field to $element. $element = array( - '#markup' => theme($product_extra_field['theme'], $variables), + '#theme' => $product_extra_field['theme'], + '#' . $product_extra_field_name => $product->{$product_extra_field_name}, + '#label' => $product_extra_field['label'] . ':', + '#product' => $product, '#attached' => array( 'css' => array(drupal_get_path('module', 'commerce_product') . '/theme/commerce_product.theme.css'), ), @@ -2368,6 +2412,31 @@ function commerce_cart_add_to_cart_form_attributes_refresh($form, $form_state) { return array('#type' => 'ajax', '#commands' => $commands); } +/** + * Ajax callback: returns AJAX commands when an attribute widget is changed on + * the Views powered shopping cart form. + */ +function commerce_cart_add_to_cart_views_form_refresh($form, $form_state) { + $commands[] = array(); + + // Extract the View from the form's arguments and derive its DOM ID class. + $view = $form_state['build_info']['args'][0]; + $dom_id_class = drupal_html_class('view-dom-id-' . $view->dom_id); + + // Unset the form related variables from the $_POST array. Otherwise, when we + // rebuild the View, the Views form will fetch these values to rebuild the + // form state and resubmit the form. + foreach (array('form_build_id', 'form_token', 'form_id') as $key) { + unset($_POST[$key]); + } + + // Render afresh the the output of the View and return it for replacement. + $output = commerce_embed_view($view->name, $view->current_display, $view->args, $view->override_url); + $commands[] = ajax_command_replace('.' . $dom_id_class, $output); + + return array('#type' => 'ajax', '#commands' => $commands); +} + /** * Form submit handler: add the selected product to the cart. */ @@ -2404,9 +2473,9 @@ function commerce_cart_add_to_cart_form_submit($form, &$form_state) { // Add field data to the line item. field_attach_submit('commerce_line_item', $line_item, $form['line_item_fields'], $form_state); - // Process the unit price through Rules so it reflects the user's actual - // purchase price. - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); + // Process the unit price through the sell price calculation callback + // so it reflects the user's actual current purchase price. + commerce_product_pricing_invoke($line_item); // Only attempt an Add to Cart if the line item has a valid unit price. $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); @@ -2553,7 +2622,8 @@ function commerce_cart_field_formatter_settings_summary($field, $instance, $view ); if (count(commerce_product_line_item_types()) > 1) { - $summary[] = t('Add to Cart line item type: @type', array('@type' => commerce_line_item_type_get_name($settings['line_item_type']))); + $type = !empty($settings['line_item_type']) ? $settings['line_item_type'] : 'product'; + $summary[] = t('Add to Cart line item type: @type', array('@type' => commerce_line_item_type_get_name($type))); } } @@ -2743,3 +2813,49 @@ function commerce_cart_i18n_string_list_field_alter(&$strings, $type = NULL, $ob ); } } + +/** + * Implements hook_ctools_plugin_type(). + */ +function commerce_cart_ctools_plugin_type() { + $plugins['cart_provider'] = array( + 'classes' => array('class'), + ); + return $plugins; +} + +/** + * Implements hook_ctools_plugin_directory(). + */ +function commerce_cart_ctools_plugin_directory($module, $plugin) { + if ($module == 'commerce_cart') { + return 'plugins/' . $plugin; + } +} + +/** + * Returns the instantiated cart provider. + */ +function commerce_cart_get_provider($provider = NULL) { + ctools_include('plugins'); + if (empty($provider)) { + $provider = variable_get('commerce_cart_provider', COMMERCE_CART_DEFAULT_PROVIDER); + } + + // Return NULL if the class could not be found. + if (!$class = ctools_plugin_load_class('commerce_cart', 'cart_provider', $provider, 'class')) { + return NULL; + } + + return new $class(); +} + +/** + * Returns the cart providers. + */ +function commerce_cart_get_providers() { + ctools_include('plugins'); + $plugins = ctools_get_plugins('commerce_cart', 'cart_provider'); + uasort($plugins, 'ctools_plugin_sort'); + return $plugins; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/commerce_cart.pages.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/commerce_cart.pages.inc index 207f6bd7..5466baea 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/commerce_cart.pages.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/commerce_cart.pages.inc @@ -34,9 +34,6 @@ function commerce_cart_checkout_router() { function commerce_cart_view() { global $user; - // Default to displaying an empty message. - $content = theme('commerce_cart_empty_page'); - // First check to make sure we have a valid order. if ($order = commerce_cart_order_load($user->uid)) { $wrapper = entity_metadata_wrapper('commerce_order', $order); @@ -49,5 +46,10 @@ function commerce_cart_view() { } } + if (empty($content)) { + // Default to displaying an empty message. + $content = theme('commerce_cart_empty_page'); + } + return $content; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views.inc index 4ced3f01..8d4b9094 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views.inc @@ -29,6 +29,15 @@ function commerce_cart_views_data_alter(&$data) { 'handler' => 'commerce_cart_handler_area_empty_text', ), ); + + // Add an edit handler for cart line item attributes. + $data['commerce_line_item']['edit_attributes'] = array( + 'field' => array( + 'title' => t('Attribute field widgets'), + 'help' => t('Renders attribute field widgets for customers to edit from the View.'), + 'handler' => 'commerce_cart_handler_field_edit_attributes', + ), + ); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views_default.inc index 28054506..66e70bab 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views_default.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/commerce_cart.views_default.inc @@ -482,6 +482,57 @@ function commerce_cart_views_default_views_alter(&$views) { $handler->display->display_options['empty']['text']['field'] = 'area'; $handler->display->display_options['empty']['text']['content'] = 'There are currently no shopping cart orders.'; $handler->display->display_options['empty']['text']['format'] = 'plain_text'; + $handler->display->display_options['defaults']['fields'] = FALSE; + /* Field: Commerce Order: Order number */ + $handler->display->display_options['fields']['order_number']['id'] = 'order_number'; + $handler->display->display_options['fields']['order_number']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['order_number']['field'] = 'order_number'; + $handler->display->display_options['fields']['order_number']['link_to_order'] = 'admin'; + /* Field: Commerce Order: Updated date */ + $handler->display->display_options['fields']['changed']['id'] = 'changed'; + $handler->display->display_options['fields']['changed']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['changed']['field'] = 'changed'; + $handler->display->display_options['fields']['changed']['label'] = 'Updated'; + $handler->display->display_options['fields']['changed']['date_format'] = 'medium'; + /* Field: Commerce Customer profile: Address */ + $handler->display->display_options['fields']['commerce_customer_address']['id'] = 'commerce_customer_address'; + $handler->display->display_options['fields']['commerce_customer_address']['table'] = 'field_data_commerce_customer_address'; + $handler->display->display_options['fields']['commerce_customer_address']['field'] = 'commerce_customer_address'; + $handler->display->display_options['fields']['commerce_customer_address']['relationship'] = 'commerce_customer_billing_profile_id'; + $handler->display->display_options['fields']['commerce_customer_address']['label'] = 'Name'; + $handler->display->display_options['fields']['commerce_customer_address']['empty'] = '-'; + $handler->display->display_options['fields']['commerce_customer_address']['hide_alter_empty'] = FALSE; + $handler->display->display_options['fields']['commerce_customer_address']['click_sort_column'] = 'country'; + $handler->display->display_options['fields']['commerce_customer_address']['settings'] = array( + 'use_widget_handlers' => 0, + 'format_handlers' => array( + 'name-oneline' => 'name-oneline', + ), + ); + /* Field: User: Name */ + $handler->display->display_options['fields']['name']['id'] = 'name'; + $handler->display->display_options['fields']['name']['table'] = 'users'; + $handler->display->display_options['fields']['name']['field'] = 'name'; + $handler->display->display_options['fields']['name']['relationship'] = 'uid'; + $handler->display->display_options['fields']['name']['label'] = 'User'; + /* Field: Commerce Order: Order total */ + $handler->display->display_options['fields']['commerce_order_total']['id'] = 'commerce_order_total'; + $handler->display->display_options['fields']['commerce_order_total']['table'] = 'field_data_commerce_order_total'; + $handler->display->display_options['fields']['commerce_order_total']['field'] = 'commerce_order_total'; + $handler->display->display_options['fields']['commerce_order_total']['label'] = 'Total'; + $handler->display->display_options['fields']['commerce_order_total']['click_sort_column'] = 'amount'; + $handler->display->display_options['fields']['commerce_order_total']['settings'] = array( + 'calculation' => FALSE, + ); + /* Field: Commerce Order: Order status */ + $handler->display->display_options['fields']['status']['id'] = 'status'; + $handler->display->display_options['fields']['status']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['status']['field'] = 'status'; + /* Field: Commerce Order: Operations links */ + $handler->display->display_options['fields']['operations']['id'] = 'operations'; + $handler->display->display_options['fields']['operations']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['operations']['field'] = 'operations'; + $handler->display->display_options['fields']['operations']['label'] = 'Operations'; $handler->display->display_options['defaults']['filter_groups'] = FALSE; $handler->display->display_options['defaults']['filters'] = FALSE; /* Filter criterion: Commerce Order: Order state */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/handlers/commerce_cart_handler_field_edit_attributes.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/handlers/commerce_cart_handler_field_edit_attributes.inc new file mode 100644 index 00000000..692f5520 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/includes/views/handlers/commerce_cart_handler_field_edit_attributes.inc @@ -0,0 +1,133 @@ +additional_fields['line_item_id'] = 'line_item_id'; + + // Set real_field in order to make it generate a field_alias. + $this->real_field = 'line_item_id'; + } + + /** + * Overrides views_handler_field::render(). + */ + function render($values) { + // Render the field as a placeholder for eventual Views form replacement. + return ''; + } + + /** + * Returns the form which replaces the placeholder from render(). + */ + function views_form(&$form, &$form_state) { + // The view is empty, abort. + if (empty($this->view->result)) { + return; + } + + $form[$this->options['id']] = array( + '#tree' => TRUE, + ); + + // At this point, the query has already been run, so we can access the + // results in order to get the base key value (for example, nid for nodes). + foreach ($this->view->result as $row_id => $row) { + // Load the line item and generate a form ID based on its context data. + $line_item = commerce_line_item_load($this->get_value($row, 'line_item_id')); + $product_ids = commerce_cart_add_to_cart_form_product_ids($line_item); + $form_id = commerce_cart_add_to_cart_form_id($product_ids); + + // Ensure the current line item is a product line item. + if (!in_array($line_item->type, commerce_product_line_item_types())) { + continue; + } + + // Fetch the Add to Cart form and put the attributes section into this + // field's part of the form if it exists. + $subform_state = $form_state; + $subform_state['build_info'] = array( + 'form_id' => $form_id, + 'base_form_id' => 'commerce_cart_add_to_cart_form', + 'args' => array($line_item), + ); + + // If the form has been submitted, copy the attributes values from the + // edit_attributes sub-array to the top level of the submitted values + // array so the selected product can be properly matched when the Add to + // Cart form builds itself. + if (!empty($form_state['values']['edit_attributes'][$row_id])) { + $subform_state['values'] += $form_state['values']['edit_attributes'][$row_id]; + } + + $subform = array(); + $subform = commerce_cart_add_to_cart_form($subform, $subform_state, $line_item); + + // If an Ajax refresh resulted in an updated product ID, update the line + // item accordingly and rebuild the subform. Note that for line items + // that group products such that a product select list is required, we + // have to fetch the current product ID differently than if it is stored + // in a hidden form field. + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + + if (!empty($subform['product_id'])) { + $product_id = NULL; + + if (in_array($subform['product_id']['#type'], array('hidden', 'value'))) { + $product_id = $subform['product_id']['#value']; + } + else { + $product_id = $subform['product_id']['#default_value']; + } + + if (isset($product_id) && $product_id != $line_item_wrapper->commerce_product->raw()) { + $line_item_wrapper->commerce_product = $product_id; + commerce_line_item_save($line_item); + commerce_cart_order_refresh($line_item->order_id); + + // Rebuild the subform array. + $subform_state = $form_state; + $subform_state['build_info'] = array( + 'form_id' => $form_id, + 'base_form_id' => 'commerce_cart_add_to_cart_form', + 'args' => array($line_item), + ); + + $subform = array(); + $subform = commerce_cart_add_to_cart_form($subform, $subform_state, $line_item); + } + } + + // Initialize the element representing the attribute fields in the form. + $form[$this->options['id']][$row_id] = array(); + $element = &$form[$this->options['id']][$row_id]; + $element['#line_item'] = $line_item->line_item_id; + + foreach (array('attributes', 'unchanged_attributes', 'product_id') as $name) { + if (!empty($subform[$name])) { + $element[$name] = $subform[$name]; + + if (!empty($element[$name]['#ajax']['callback'])) { + $element[$name]['#ajax']['callback'] = 'commerce_cart_add_to_cart_views_form_refresh'; + } + + foreach (element_children($element[$name]) as $key) { + if (!empty($element[$name][$key]['#ajax']['callback'])) { + $element[$name][$key]['#ajax']['callback'] = 'commerce_cart_add_to_cart_views_form_refresh'; + } + } + } + } + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/plugins/cart_provider/CommerceCartProviderSession.class.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/plugins/cart_provider/CommerceCartProviderSession.class.php new file mode 100644 index 00000000..60bc120c --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/plugins/cart_provider/CommerceCartProviderSession.class.php @@ -0,0 +1,65 @@ + t('Session'), + 'class' => 'CommerceCartProviderSession', + 'weight' => -10, +); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/tests/commerce_cart.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/tests/commerce_cart.test index 723fa1fb..da87246d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/tests/commerce_cart.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/tests/commerce_cart.test @@ -132,7 +132,7 @@ class CommerceCartTestCaseSimpleProduct extends CommerceCartTestCase { $this->assertTrue(commerce_cart_order_is_cart($order), t('User has currently an order in cart status.')); // Get the products out of the order and store them in an array. foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) { - if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { + if (in_array($line_item_wrapper->getBundle(), commerce_product_line_item_types())) { $product = $line_item_wrapper->commerce_product->value(); $products[$product->product_id]= $product; } @@ -302,7 +302,7 @@ class CommerceCartTestCaseMultiProducts extends CommerceCartTestCase { // Get the products out of the order and store them in an array. foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) { - if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { + if (in_array($line_item_wrapper->getBundle(), commerce_product_line_item_types())) { $product = $line_item_wrapper->commerce_product->value(); $products_in_cart[$product->product_id]= $product; } @@ -509,7 +509,8 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart')); // Get the order just created. - $order_anonymous = reset(commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE)); + $orders = commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE); + $order_anonymous = reset($orders); // Reset the cache as we don't want to keep the lock. entity_get_controller('commerce_order')->resetCache(); @@ -529,7 +530,8 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase $this->drupalPost('user', array('name' => $this->store_customer->name, 'pass' => $this->store_customer->pass_raw), t('Log in')); // Get the order for user just logged in. - $order_authenticated = reset(commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid, 'status' => 'cart'), TRUE)); + $orders = commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid, 'status' => 'cart'), TRUE); + $order_authenticated = reset($orders); // Reset the cache as we don't want to keep the lock. entity_get_controller('commerce_order')->resetCache(); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/theme/buttons.png b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/theme/buttons.png index 5ef49fd5..88bd11b9 100644 Binary files a/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/theme/buttons.png and b/profiles/commerce_kickstart/modules/contrib/commerce/modules/cart/theme/buttons.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.api.php index 86431b6e..1e0d1a7d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.api.php @@ -219,6 +219,8 @@ function hook_commerce_checkout_page_info_alter(&$checkout_pages) { * if not specified, defaults to the title * - page: the page_id of the checkout page the pane should appear on by * default; defaults to ‘checkout’ + * - fieldset: boolean that defines if the pane should render as a fieldset + * or container element. * - locked: boolean indicating that the pane cannot be moved from the * specified checkout page. * - collapsible: boolean indicating whether or not the checkout pane’s fieldset @@ -292,3 +294,21 @@ function hook_commerce_checkout_pane_info() { function hook_commerce_checkout_pane_info_alter(&$checkout_panes) { $checkout_panes['billing']['weight'] = -6; } + +/** + * Allows modules to define the first page for a new order entering checkout. + * + * The default first checkout page is whatever the first page is after the + * checkout pages array is sorted by weight. This hook allows modules to provide + * an alternate page ID or default page ID without having to build the checkout + * page and pane info arrays, providing a noticeable performance improvement. + * + * If more than one module implements this hook, the first returned value will + * be used. + * + * @return string + * The new first checkout page ID. + */ +function hook_commerce_checkout_first_checkout_page() { + return 'checkout'; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.info index 76b7f895..a6f3d40f 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.info @@ -12,9 +12,9 @@ configure = admin/commerce/config/checkout ; Simple tests files[] = tests/commerce_checkout.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.js b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.js index cbe6ab40..414ac5b6 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.js +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.js @@ -7,7 +7,7 @@ Drupal.behaviors.commerceCheckout = { attach: function (context, settings) { // When the buttons to move from page to page in the checkout process are - // clicked we disable them so they are not accidently clicked twice. + // clicked we disable them so they are not accidentally clicked twice. $('input.checkout-continue:not(.checkout-processed)', context).addClass('checkout-processed').click(function() { var $this = $(this); $this.clone().insertAfter(this).attr('disabled', true).next().removeClass('element-invisible'); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.module index 210b4087..4b701a5d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.module @@ -128,6 +128,9 @@ function commerce_checkout_hook_info() { 'commerce_checkout_pane_info_alter' => array( 'group' => 'commerce', ), + 'commerce_checkout_first_checkout_page' => array( + 'group' => 'commerce', + ), 'commerce_checkout_router' => array( 'group' => 'commerce', ), @@ -279,7 +282,7 @@ function commerce_checkout_line_item_views_form_submit($form, &$form_state) { commerce_order_save($form_state['order']); // Redirect to the checkout page if specified. - if ($form_state['triggering_element']['#value'] == $form['actions']['checkout']['#value']) { + if ($form_state['triggering_element']['#value'] === $form['actions']['checkout']['#value']) { $form_state['redirect'] = 'checkout/' . $order->order_id; } } @@ -475,10 +478,22 @@ function commerce_checkout_pages() { } /** - * Returns the page ID of the first checkout page sorted by weight. + * Returns the page ID of the first checkout page to be used. */ function commerce_checkout_first_checkout_page() { - return key(commerce_checkout_pages()); + // See if any modules return a page ID first. + $page_id = module_invoke_all('commerce_checkout_first_checkout_page'); + + // If we received a return value from the hook, use the first item in the + // return array as the page ID. + if (!empty($page_id)) { + return reset($page_id); + } + + // Otherwise fallback to the first page defined in checkout form. + $keys = commerce_checkout_pages(); + reset($keys); + return key($keys); } /** @@ -700,7 +715,7 @@ function commerce_checkout_pane_callback($checkout_pane, $callback) { // If the specified callback function exists, return it. if (!empty($checkout_pane['callbacks'][$callback]) && - function_exists($checkout_pane['callbacks'][$callback])) { + is_callable($checkout_pane['callbacks'][$callback])) { return $checkout_pane['callbacks'][$callback]; } @@ -734,12 +749,20 @@ function commerce_checkout_access($order, $account = NULL) { return FALSE; } } - elseif (empty($_SESSION['commerce_cart_completed_orders']) || - !in_array($order->order_id, $_SESSION['commerce_cart_completed_orders'])) { - // And then return FALSE if the anonymous user's session doesn't specify - // this order ID. - if (empty($_SESSION['commerce_cart_orders']) || !in_array($order->order_id, $_SESSION['commerce_cart_orders'])) { - return FALSE; + else { + // There's no choice but to check the current user's cart + // using the commerce_cart module. + if (module_exists('commerce_cart')) { + $cart_order_ids = commerce_cart_order_session_order_ids(); + $completed_order_ids = commerce_cart_order_session_order_ids(TRUE); + if (empty($completed_order_ids) || + !in_array($order->order_id, $completed_order_ids)) { + // And then return FALSE if the anonymous user's session doesn't specify + // this order ID. + if (empty($cart_order_ids) || !in_array($order->order_id, $cart_order_ids)) { + return FALSE; + } + } } } @@ -936,6 +959,14 @@ function commerce_checkout_order_can_checkout($order) { * Completes the checkout process for the given order. */ function commerce_checkout_complete($order) { + // An order could complete the checkout process more than once for a variety + // of reasons, including administrators making changes. We want to keep the + // timestamp from when the order was originally placed, so we don't update it + // if it is already set. + if (empty($order->placed)) { + $order->placed = REQUEST_TIME; + } + rules_invoke_all('commerce_checkout_complete', $order); } @@ -993,7 +1024,7 @@ function commerce_checkout_completion_message_default() { $format = 'filtered_html'; } else { - // Otherwise select a fallback format and use a plaint text default value. + // Otherwise select a fallback format and use a plain text default value. $value = 'Your order number is [commerce-order:order-number]. You can view your order on your account page when logged in.'; $format = filter_fallback_format(); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.rules_defaults.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.rules_defaults.inc index a835c9ff..66fcc76c 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.rules_defaults.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/commerce_checkout.rules_defaults.inc @@ -22,7 +22,7 @@ function commerce_checkout_default_rules_configuration() { $rule->label = t('Set the order created date to the checkout completion date'); $rule->tags = array('Commerce Checkout'); - $rule->active = variable_get('enable_commerce_checkout_order_created_date_update', TRUE); + $rule->active = variable_get('enable_commerce_checkout_order_created_date_update', FALSE); $rule ->event('commerce_checkout_complete') diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/images/status-active.gif b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/images/status-active.gif index 207e95c3..892865f6 100644 Binary files a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/images/status-active.gif and b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/images/status-active.gif differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/includes/commerce_checkout.pages.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/includes/commerce_checkout.pages.inc index bbb2a06d..3d5d50f2 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/includes/commerce_checkout.pages.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/includes/commerce_checkout.pages.inc @@ -10,6 +10,14 @@ * Redirects invalid checkout attempts or displays the checkout form if valid. */ function commerce_checkout_router($order, $checkout_page = NULL) { + // Ensure this function is routing the latest revision of the given order in + // case some hook invoked before this page callback is executed updated some + // value on the order that would impact routing. + if (FALSE && !commerce_order_is_latest_revision($order)) { + entity_get_controller('commerce_order')->resetCache(array($order->order_id)); + $order = commerce_order_load($order->order_id); + } + $checkout_pages = commerce_checkout_pages(); // If no checkout page is specified, default to the first one. @@ -74,6 +82,10 @@ function commerce_checkout_form($form, &$form_state, $order, $checkout_page) { $form['#attached']['css'][] = drupal_get_path('module', 'commerce_checkout') .'/theme/commerce_checkout.theme.css'; $form['#attached']['js'][] = drupal_get_path('module', 'commerce_checkout') . '/commerce_checkout.js'; + // Refresh the order object in case it has been altered outside the checkout + // process so cache_form has a stale version. + $order = commerce_order_load($order->order_id); + $form_state['order'] = $order; $form_state['checkout_page'] = $checkout_page; $form_state['account'] = clone($user); @@ -106,6 +118,11 @@ function commerce_checkout_form($form, &$form_state, $order, $checkout_page) { // Generate the pane form. $pane_form = $callback($form, $form_state, $checkout_pane, $order); + // Refresh the order object in case the checkout pane altered it outside + // of the passed reference. + $order = commerce_order_load($order->order_id); + $form_state['order'] = $order; + // Combine the messages that were created during this pane's validation or // submit process with any that were created during the pane generation // and merge them into the session's current messages array. @@ -263,10 +280,10 @@ function _commerce_checkout_set_validated(&$element, $imploded_parents) { /** * Validate handler for the continue button of the checkout form. * - * This function calls the validation function of each pane, followed by - * the submit function if the validation succeeded. As long as one pane - * fails validation, we then ask for the form to be rebuilt. Once all the panes - * are happy, we move on to the next page. + * This function calls the validation function of each pane, followed by the + * submit function if the validation succeeded. As long as one pane fails + * validation, we then ask for the form to be rebuilt. Once all the panes are + * happy, we move on to the next page. */ function commerce_checkout_form_validate($form, &$form_state) { $checkout_page = $form_state['checkout_page']; @@ -278,15 +295,24 @@ function commerce_checkout_form_validate($form, &$form_state) { $previous_messages = drupal_get_messages(); // Load any pre-existing validation errors for the elements. + $form_errors = form_get_errors(); $errors = array(); - foreach ((array) form_get_errors() as $element_path => $error) { + // If we found any errors at all on the page, go ahead and mark the form as + // failing validation. This will ensure that even form elements on the page + // that aren't part of a checkout pane trigger validation failure. + $form_validate = TRUE; + + if (!empty($form_errors)) { + $form_validate = FALSE; + } + + foreach ((array) $form_errors as $element_path => $error) { list($pane_id, ) = explode('][', $element_path, 2); $errors[$pane_id][$element_path] = $error; } // Loop through the enabled checkout panes for the current page. - $form_validate = TRUE; foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) { $validate = TRUE; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/tests/commerce_checkout.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/tests/commerce_checkout.test index 20a99a24..2d5ff38d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/tests/commerce_checkout.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/checkout/tests/commerce_checkout.test @@ -76,7 +76,8 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase { $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart')); // Get the order for the anonymous user. - $this->order = reset(commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE)); + $orders = commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE); + $this->order = reset($orders); // Reset the cache as we don't want to keep the lock. entity_get_controller('commerce_order')->resetCache(); } @@ -523,4 +524,33 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase { $this->assertCheckoutPageAccessible($this->order, 'complete'); } + /** + * Test programmatic checkout completion. + */ + function testCommerceCheckoutProgrammaticCheckout() { + // Log in as normal user. + $this->drupalLogin($this->store_customer); + + // Order creation, in cart status. + $this->order = $this->createDummyOrder($this->store_customer->uid); + + commerce_checkout_complete($this->order); + + // Ensure the "placed" property is set when the order completes checkout. + $this->assertTrue($this->order->placed > 0, 'commerce_checkout_complete() added the placed date to the order'); + + // Ensure the "placed" property is not updated when the order completes + // checkout more than once, such as when simulating checkout via the admin + // pages. + // If commerce_checkout_complete does not work as expected and does update + // the value, we wouldn't know because in the context of this test it would + // be set to REQUEST_TIME which is the current value as well. REQUEST_TIME + // is a constant and we cannot change it either. We therefore manually set + // the value to a different non-zero value and check if that value is + // changed. + $this->order->placed = 1; + commerce_checkout_complete($this->order); + $this->assertEqual($this->order->placed, 1, 'commerce_checkout_complete() did not update the already placed date of the order'); + } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.info index fc979946..3af05d04 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.info @@ -21,9 +21,9 @@ files[] = includes/views/handlers/commerce_customer_handler_filter_customer_prof ; Simple tests ; files[] = tests/commerce_customer.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.install b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.install index 50951e65..18aa2cf4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.install @@ -198,7 +198,7 @@ function commerce_customer_update_7000() { 'administer customer profiles' => 'administer commerce_customer_profile entities', 'access customer profiles' => 'view any commerce_customer_profile entity', ); - $entity_info = entity_get_info('commerce_product'); + $entity_info = entity_get_info('commerce_customer_profile'); foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { $map['create ' . $bundle_name . ' customer profiles'] = 'create commerce_customer_profile entities of bundle ' . $bundle_name; $map['edit any ' . $bundle_name . ' customer profile'] = 'edit any commerce_customer_profile entity of bundle ' . $bundle_name; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.module index 32a11cf5..5a822491 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer.module @@ -96,7 +96,7 @@ function commerce_customer_profile_label($profile) { // Make sure we get a valid label callback. $callback = $profile_type['label_callback']; - if (!function_exists($callback)) { + if (!is_callable($callback)) { $callback = 'commerce_customer_profile_default_label'; } @@ -424,39 +424,6 @@ function commerce_customer_commerce_checkout_pane_info() { return $checkout_panes; } -/** - * Implements hook_field_views_data(). - */ -function commerce_customer_field_views_data($field) { - $data = field_views_field_default_views_data($field); - - // Build an array of bundles the customer profile reference field appears on. - $bundles = array(); - - foreach ($field['bundles'] as $entity => $entity_bundles) { - $bundles[] = $entity . ' (' . implode(', ', $entity_bundles) . ')'; - } - - $replacements = array('!field_name' => $field['field_name'], '@bundles' => implode(', ', $bundles)); - - foreach ($data as $table_name => $table_data) { - foreach ($table_data as $field_name => $field_data) { - if (isset($field_data['filter']['field_name']) && $field_name != 'delta') { - $data[$table_name][$field_name]['relationship'] = array( - 'title' => t('Referenced customer profile'), - 'label' => t('Customer profile referenced by !field_name', $replacements), - 'help' => t('Relate this entity to the customer profile referenced by its !field_name value.', $replacements) . '
        ' . t('Appears in: @bundles.', $replacements), - 'base' => 'commerce_customer_profile', - 'base field' => 'profile_id', - 'handler' => 'views_handler_relationship', - ); - } - } - } - - return $data; -} - /** * Implements hook_field_delete_instance(). */ @@ -691,10 +658,10 @@ function commerce_customer_profile_can_delete($profile) { * @param $profile_id * The ID of the customer profile to delete. * @param $entity_context - * An optional entity context array that specifies the entity throgh whose - * customer profile reference field the given profiles are being deleted: - * - entity_type: the type of entity - * - entity_id: the unique ID of the entity + * An optional entity context array that specifies the entity through whose + * customer profile reference field the given profile is being deleted: + * - entity_type: The type of entity. + * - entity_id: The unique ID of the entity. * * @return * TRUE on success, FALSE otherwise. @@ -709,10 +676,10 @@ function commerce_customer_profile_delete($profile_id, $entity_context = array() * @param $profile_ids * An array of customer profile IDs to delete. * @param $entity_context - * An optional entity context array that specifies the entity throgh whose + * An optional entity context array that specifies the entity through whose * customer profile reference field the given profiles are being deleted: - * - entity_type: the type of entity - * - entity_id: the unique ID of the entity + * - entity_type: The type of entity. + * - entity_id: The unique ID of the entity. * * @return * TRUE on success, FALSE otherwise. @@ -908,13 +875,6 @@ function commerce_customer_field_formatter_info() { function commerce_customer_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { $result = array(); - // Collect the list of customer profile IDs. - $profile_ids = array(); - - foreach ($items as $delta => $item) { - $profile_ids[] = $item['profile_id']; - } - switch ($display['type']) { case 'commerce_customer_profile_reference_display': foreach ($items as $delta => $item) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer_ui.info index bc4a30d7..e1fce6fb 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/commerce_customer_ui.info @@ -12,9 +12,9 @@ configure = admin/commerce/customer-profiles/types ; Simple tests files[] = tests/commerce_customer_ui.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer.checkout_pane.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer.checkout_pane.inc index 67e03e2b..8cbecdf0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer.checkout_pane.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer.checkout_pane.inc @@ -297,9 +297,8 @@ function commerce_customer_profile_pane_checkout_form_submit($form, &$form_state commerce_customer_profile_save($profile); // Store the profile ID for the related field as specified on the settings form. - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - if ($field_name = variable_get('commerce_' . $checkout_pane['pane_id'] . '_field', '')) { + if (!empty($field_name)) { + $wrapper = entity_metadata_wrapper('commerce_order', $order); $wrapper->{$field_name} = $profile; } else { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer_profile.controller.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer_profile.controller.inc index 9ae2ce9a..4eaf9e92 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer_profile.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/commerce_customer_profile.controller.inc @@ -145,6 +145,7 @@ class CommerceCustomerProfileEntityController extends DrupalCommerceEntityContro public function delete($profile_ids, DatabaseTransaction $transaction = NULL, $entity_context = array()) { if (!empty($profile_ids)) { $profiles = $this->load($profile_ids, array()); + $profile_ids_to_remove = array(); // Ensure the customer profiles can actually be deleted. foreach ((array) $profiles as $profile_id => $profile) { @@ -156,6 +157,7 @@ class CommerceCustomerProfileEntityController extends DrupalCommerceEntityContro // If the profile cannot be deleted, remove it from the profiles array. if (!commerce_customer_profile_can_delete($profile)) { unset($profiles[$profile_id]); + $profile_ids_to_remove[] = $profile_id; } } @@ -164,6 +166,7 @@ class CommerceCustomerProfileEntityController extends DrupalCommerceEntityContro return FALSE; } + $profile_ids = array_diff($profile_ids, $profile_ids_to_remove); parent::delete($profile_ids, $transaction); return TRUE; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/views/commerce_customer.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/views/commerce_customer.views.inc index ba5d35ca..a156923c 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/views/commerce_customer.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/includes/views/commerce_customer.views.inc @@ -263,3 +263,36 @@ function commerce_customer_views_data() { return $data; } + +/** + * Implements hook_field_views_data(). + */ +function commerce_customer_field_views_data($field) { + $data = field_views_field_default_views_data($field); + + // Build an array of bundles the customer profile reference field appears on. + $bundles = array(); + + foreach ($field['bundles'] as $entity => $entity_bundles) { + $bundles[] = $entity . ' (' . implode(', ', $entity_bundles) . ')'; + } + + $replacements = array('!field_name' => $field['field_name'], '@bundles' => implode(', ', $bundles)); + + foreach ($data as $table_name => $table_data) { + foreach ($table_data as $field_name => $field_data) { + if (isset($field_data['filter']['field_name']) && $field_name != 'delta') { + $data[$table_name][$field_name]['relationship'] = array( + 'title' => t('Referenced customer profile'), + 'label' => t('Customer profile referenced by !field_name', $replacements), + 'help' => t('Relate this entity to the customer profile referenced by its !field_name value.', $replacements) . '
        ' . t('Appears in: @bundles.', $replacements), + 'base' => 'commerce_customer_profile', + 'base field' => 'profile_id', + 'handler' => 'views_handler_relationship', + ); + } + } + } + + return $data; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_profile_dummy_type.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_profile_dummy_type.info index df612763..4d72ecb0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_profile_dummy_type.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_profile_dummy_type.info @@ -5,9 +5,9 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_ui.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_ui.test index b6056192..c2db3655 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_ui.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/customer/tests/commerce_customer_ui.test @@ -118,7 +118,9 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->assertTrue($this->url = url('admin/commerce/customer-profiles/add/billing', array('absolute => TRUE'))); // Get the default values for the address. - $address = addressfield_default_values(); + $field = field_info_field('commerce_customer_address'); + $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', 'billing'); + $address = addressfield_default_values($field, $instance); // Check the integrity of the add form. $this->pass(t('Test the integrity of the add customer profile form:')); @@ -263,7 +265,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { } // Load the profile and check if the field is filled. - $profile = commerce_customer_profile_load(reset($this->loadCustomerProfile($conditions))->profile_id); + $profiles = $this->loadCustomerProfile($conditions); + $profile = commerce_customer_profile_load(reset($profiles)->profile_id); $this->assertTrue($profile->{'field_' . $edit['fields[_add_new_field][field_name]']}[LANGUAGE_NONE][0]['value'] == $field_value, t('The extra field !field created for the customer profile exists and it has the correct value: !value', array('!field' => $edit['fields[_add_new_field][field_name]'], '!value' => $field_value))); } @@ -305,7 +308,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->assertFieldByName('name', NULL, t('Name field is present and empty')); // Check at database level. - $profile = reset(commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE)); + $profiles = commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE); + $profile = reset($profiles); $this->assertTrue($profile->commerce_customer_address[LANGUAGE_NONE][0]['name_line'] == $edit['commerce_customer_address[und][0][name_line]'], t('\'Name line\' field has been correctly modified in the customer profile')); $this->assertTrue($profile->commerce_customer_address[LANGUAGE_NONE][0]['locality'] == $edit['commerce_customer_address[und][0][locality]'], t('\'Locality\' field has been correctly modified in the customer profile')); $this->assertTrue($profile->uid == 0, t('Profile owner is now anonymous user')); @@ -326,7 +330,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->drupalGet('admin/commerce/customer-profiles'); $this->assertText(t('Disabled'), t('\'Disabled\' text for the profile appears in the profile listing page')); - $profile = reset(commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE)); + $profiles = commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE); + $profile = reset($profiles); $this->assertTrue($profile->status == 0, t('Profile status is Disabled')); } @@ -364,7 +369,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->assertText(t('No customer profiles have been created yet.'), t('\'No customer profiles have been created yet\' message is displayed')); // Check at database level. - $profile = reset(commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE)); + $profiles = commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE); + $profile = reset($profiles); $this->assertTrue(empty($profile), t('Profile can\'t be loaded from database after deleting it')); } @@ -413,7 +419,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->drupalLogin($this->store_admin); // Check the customer profile at database level. - $order = reset(commerce_order_load_multiple(array($order->order_id), array(), TRUE)); + $orders = commerce_order_load_multiple(array($order->order_id), array(), TRUE); + $order = reset($orders); $order_wrapper = entity_metadata_wrapper('commerce_order', $order); $profile = $order_wrapper->commerce_customer_billing->value(); $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); @@ -466,7 +473,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { 'value' => $element, ); } - $profile = commerce_customer_profile_load(reset($this->loadCustomerProfile($conditions))->profile_id); + $profiles = $this->loadCustomerProfile($conditions); + $profile = commerce_customer_profile_load(reset($profiles)->profile_id); $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); $this->assertFalse(empty($profile), t('Profile has been created in database')); foreach ($address_info as $name => $info) { @@ -498,7 +506,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->assertTrue($edit['commerce_customer_billing[und][profiles][0][commerce_customer_address][und][0][name_line]'], t('\'Name line\' text is present with the correct value: !value', array('!value' => $edit['commerce_customer_billing[und][profiles][0][commerce_customer_address][und][0][name_line]']))); // Check the customer profile at database level. - $profile = reset(commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE)); + $profiles = commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE); + $profile = reset($profiles); $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); $this->assertEqual($profile_wrapper->commerce_customer_address->name_line->value(), $edit['commerce_customer_billing[und][profiles][0][commerce_customer_address][und][0][name_line]'], t('\'Name line\' property value !value match', array('!value' => $edit['commerce_customer_billing[und][profiles][0][commerce_customer_address][und][0][name_line]']))); $this->assertEqual($profile_wrapper->commerce_customer_address->locality->value(), $edit['commerce_customer_billing[und][profiles][0][commerce_customer_address][und][0][locality]'], t('\'Locality\' property value !value match', array('!value' => $edit['commerce_customer_billing[und][profiles][0][commerce_customer_address][und][0][locality]']))); @@ -523,10 +532,32 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->assertNoText($profile_wrapper->commerce_customer_address->name_line->value(), t('\'Name line\' for the profile is not present in the customer profiles listing')); // Check the customer profile has been deleted at database level. - $profile = reset(commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE)); + $profiles = commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE); + $profile = reset($profiles); $this->assertTrue(empty($profile), t('Profile has been delete from database')); } + /** + * Delete multiple profiles with and without orders attached. + */ + public function testCommerceCustomerDeleteProfilesWithOrderReference() { + // Create 2 new customer profiles. + $profile_with_order = $this->createDummyCustomerProfile('billing', $this->store_customer->uid); + $this->createDummyOrder($this->store_customer->uid, array(), 'pending', $profile_with_order->profile_id); + $profile_without_order = $this->createDummyCustomerProfile('billing', $this->store_customer->uid); + + $profile_ids = array($profile_with_order->profile_id, $profile_without_order->profile_id); + + // Delete the profiles we can. + commerce_customer_profile_delete_multiple($profile_ids); + + // Check the customer profile has been deleted at database level. + $profiles = commerce_customer_profile_load_multiple($profile_ids, array(), TRUE); + + $this->assertTrue(!empty($profiles[$profile_with_order->profile_id]), t('Profile with order has not been delete from database')); + $this->assertTrue(empty($profiles[$profile_without_order->profile_id]), t('Profile without order has been delete from database')); + } + /** * Create a custom profile type form an helper module and test it. */ @@ -613,7 +644,8 @@ class CommerceCustomerUITest extends CommerceBaseTestCase { $this->drupalPost(NULL, $info, t('Continue to next step')); // Check the customer profile at database level. - $order = reset(commerce_order_load_multiple(array($order->order_id), array(), TRUE)); + $orders = commerce_order_load_multiple(array($order->order_id), array(), TRUE); + $order = reset($orders); $order_wrapper = entity_metadata_wrapper('commerce_order', $order); // Extract the address field value from the billing profile. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.info index 13fb72ec..6e8ce9f3 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.info @@ -15,6 +15,7 @@ files[] = includes/views/handlers/commerce_line_item_handler_area_line_item_summ files[] = includes/views/handlers/commerce_line_item_handler_argument_line_item_line_item_id.inc files[] = includes/views/handlers/commerce_line_item_handler_field_line_item_title.inc files[] = includes/views/handlers/commerce_line_item_handler_field_line_item_type.inc +files[] = includes/views/handlers/commerce_line_item_handler_filter_line_item_is_referenced.inc files[] = includes/views/handlers/commerce_line_item_handler_filter_line_item_type.inc files[] = includes/views/handlers/commerce_line_item_handler_field_edit_quantity.inc files[] = includes/views/handlers/commerce_line_item_handler_field_edit_delete.inc @@ -22,9 +23,9 @@ files[] = includes/views/handlers/commerce_line_item_handler_field_edit_delete.i ; Simple tests ; files[] = tests/commerce_line_item.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.module index 22276de2..33843502 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item.module @@ -105,9 +105,11 @@ function commerce_line_item_hook_info() { */ function commerce_line_item_form_alter(&$form, &$form_state, $form_id) { $line_item_form = FALSE; + // Is this a views form? if (strpos($form_id, 'views_form_') === 0) { $view = $form_state['build_info']['args'][0]; + // Does the view contain one of the line item edit fields? foreach ($view->field as $field_name => $field) { if ($field instanceof commerce_line_item_handler_field_edit_delete || $field instanceof commerce_line_item_handler_field_edit_quantity) { @@ -115,10 +117,12 @@ function commerce_line_item_form_alter(&$form, &$form_state, $form_id) { } } } + // This is not the form we are looking for. if (!$line_item_form) { return; } + // Require the existence of an order_id argument (and its value). if (empty($view->argument['order_id']) || empty($view->argument['order_id']->value)) { return; @@ -202,7 +206,7 @@ function commerce_line_item_theme() { 'render element' => 'form', ), 'commerce_line_item_summary' => array( - 'variables' => array('quantity_raw' => NULL, 'quantity_label' => NULL, 'quantity' => NULL, 'total_raw' => NULL, 'total' => NULL, 'links' => NULL, 'view' => NULL), + 'variables' => array('quantity_raw' => NULL, 'quantity_label' => NULL, 'quantity' => NULL, 'total_raw' => NULL, 'total_label' => NULL, 'total' => NULL, 'links' => NULL, 'view' => NULL), 'path' => drupal_get_path('module', 'commerce_line_item') . '/theme', 'template' => 'commerce-line-item-summary', ), @@ -374,11 +378,21 @@ function commerce_line_item_field_attach_delete($entity_type, $entity) { if ($wrapper->{$field_name} instanceof EntityListWrapper) { foreach ($wrapper->{$field_name} as $delta => $line_item_wrapper) { - $referenced_line_item_ids[] = $line_item_wrapper->line_item_id->value(); + try { + $referenced_line_item_ids[] = $line_item_wrapper->line_item_id->value(); + } + catch (EntityMetadataWrapperException $e) { + // Do nothing, this was a bad reference value. + } } } elseif (!is_null($wrapper->{$field_name}->value())) { - $referenced_line_item_ids[] = $wrapper->{$field_name}->line_item_id->value(); + try { + $referenced_line_item_ids[] = $wrapper->{$field_name}->line_item_id->value(); + } + catch (EntityMetadataWrapperException $e) { + // Do nothing, this was a bad reference value. + } } // Loop over each line item referenced on this field... @@ -541,14 +555,14 @@ function commerce_line_item_type_to_arg($type) { * - add_form_validate * - add_form_submit * - * @return + * @return string * A string containing the name of the callback function or FALSE if it could - * not be found. + * not be found. */ function commerce_line_item_type_callback($line_item_type, $callback) { // If the specified callback function exists, return it. if (!empty($line_item_type['callbacks'][$callback]) && - function_exists($line_item_type['callbacks'][$callback])) { + is_callable($line_item_type['callbacks'][$callback])) { return $line_item_type['callbacks'][$callback]; } @@ -620,12 +634,14 @@ function commerce_line_item_load_multiple($line_item_ids = array(), $conditions * * @param $line_item_id * The ID of the line item to delete. + * @param boolean $skip_order_save + * TRUE to skip saving the order after deleting the line item. * * @return * TRUE on success, FALSE otherwise. */ -function commerce_line_item_delete($line_item_id) { - return commerce_line_item_delete_multiple(array($line_item_id)); +function commerce_line_item_delete($line_item_id, $skip_order_save = FALSE) { + return commerce_line_item_delete_multiple(array($line_item_id), $skip_order_save); } /** @@ -633,12 +649,14 @@ function commerce_line_item_delete($line_item_id) { * * @param $line_item_ids * An array of line item IDs to delete. + * @param boolean $skip_order_save + * TRUE to skip saving the order after deleting the line item. * * @return * TRUE on success, FALSE otherwise. */ -function commerce_line_item_delete_multiple($line_item_ids) { - return entity_get_controller('commerce_line_item')->delete($line_item_ids); +function commerce_line_item_delete_multiple($line_item_ids, $skip_order_save = FALSE) { + return entity_get_controller('commerce_line_item')->delete($line_item_ids, NULL, $skip_order_save); } /** @@ -995,7 +1013,12 @@ function commerce_line_item_field_widget_form(&$form, &$form_state, $field, $ins } // Load the line items for temporary storage in the form array. - $line_items = commerce_line_item_load_multiple($line_item_ids); + if(!empty($line_item_ids)) { + $line_items = commerce_line_item_load_multiple($line_item_ids); + } + else { + $line_items = array(); + } // Update the base form element array to use the proper theme and validate // functions and to include header information for the line item table. @@ -1038,8 +1061,7 @@ function commerce_line_item_field_widget_form(&$form, &$form_state, $field, $ins // Unset the title and description and add it to the line item form. $language = $widget_form['commerce_unit_price']['#language']; - unset($widget_form['commerce_unit_price'][$language][0]['amount']['#title']); - unset($widget_form['commerce_unit_price'][$language][0]['amount']['#description']); + $widget_form['commerce_unit_price'][$language][0]['amount']['#title_display'] = 'invisible'; $element['line_items'][$line_item_id]['commerce_unit_price'] = $widget_form['commerce_unit_price']; $quantity = round($line_item->quantity); @@ -1152,14 +1174,18 @@ function theme_commerce_line_item_manager($variables) { // Add each line item to the table. foreach (element_children($form['line_items']) as $line_item_id) { - $row = array( - drupal_render($form['line_items'][$line_item_id]['remove']), - drupal_render($form['line_items'][$line_item_id]['title']), - drupal_render($form['line_items'][$line_item_id]['line_item_label']), - drupal_render($form['line_items'][$line_item_id]['commerce_unit_price']), - drupal_render($form['line_items'][$line_item_id]['quantity']), - drupal_render($form['line_items'][$line_item_id]['commerce_total']), - ); + $row = array(); + + // Loop over all child elements in the line item's form array. + foreach (element_children($form['line_items'][$line_item_id]) as $item) { + // Do not attempt to render value elements into the row. + if (!empty($form['line_items'][$line_item_id][$item]['#type']) && + $form['line_items'][$line_item_id][$item]['#type'] == 'value') { + continue; + } + + $row[] = drupal_render($form['line_items'][$line_item_id][$item]); + } $rows[] = $row; } @@ -1245,7 +1271,7 @@ function commerce_line_item_manager_validate($element, &$form_state, $form) { // If the "Add line item" button was clicked, store the line item type in the // $form_state for the rebuild of the $form array. if (!empty($form_state['triggering_element'])) { - if ($form_state['triggering_element']['#value'] == t('Add line item')) { + if ($form_state['triggering_element']['#value'] === t('Add line item')) { $form_state['line_item_add'] = $element['actions']['line_item_type']['#value']; $form_state['rebuild'] = TRUE; } @@ -1427,18 +1453,42 @@ function commerce_line_items_quantity($line_items, $types = array()) { $quantity = 0; foreach ($line_items as $line_item) { - if (!$line_item instanceof EntityMetadataWrapper) { - $line_item = entity_metadata_wrapper('commerce_line_item', $line_item); + if ($line_item instanceof EntityMetadataWrapper) { + $line_item = $line_item->value(); } - if (empty($types) || in_array($line_item->type->value(), $types)) { - $quantity += $line_item->quantity->value(); + if (empty($types) || in_array($line_item->type, $types)) { + $quantity += $line_item->quantity; } } return $quantity; } +/** + * Returns the total quantity of a group of line items identified by ID. + * + * @param int[] $line_item_ids + * An array of line item IDs whose quantities should be summed and returned. + * @param string[] $types + * An array of line item types to filter by before counting. + * + * @return int + * The quantity of the line items. + */ +function commerce_line_items_quantity_by_id($line_item_ids, $types = array()) { + if (empty($line_item_ids)) { + return 0; + } + + if (empty($types)) { + return db_query("SELECT SUM(quantity) FROM {commerce_line_item} WHERE line_item_id IN(:line_item_ids)", array(':line_item_ids' => $line_item_ids))->fetchField(); + } + else { + return db_query("SELECT SUM(quantity) FROM {commerce_line_item} WHERE line_item_id IN(:line_item_ids) AND type IN (:types)", array(':line_item_ids' => $line_item_ids, ':types' => $types))->fetchField(); + } +} + /** * Returns the total price amount and currency of an array of line items. * @@ -1503,7 +1553,7 @@ function commerce_line_items_total($line_items, $types = array()) { $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item_wrapper); } - if (empty($types) || in_array($line_item_wrapper->type->value(), $types)) { + if (empty($types) || in_array($line_item_wrapper->getBundle(), $types)) { $total += commerce_currency_convert( $line_item_wrapper->commerce_total->amount->value(), $line_item_wrapper->commerce_total->currency_code->value(), diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item_ui.info index cb1c2f1c..339dd4a0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/commerce_line_item_ui.info @@ -9,9 +9,9 @@ dependencies[] = views core = 7.x configure = admin/commerce/config/line-items -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/commerce_line_item.controller.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/commerce_line_item.controller.inc index fdc47fe2..84f0e81c 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/commerce_line_item.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/commerce_line_item.controller.inc @@ -124,8 +124,12 @@ class CommerceLineItemEntityController extends DrupalCommerceEntityController { * @param $transaction * An optional transaction object to pass thru. If passed the caller is * responsible for rolling back the transaction if something goes wrong. + * @param boolean $skip_order_save + * TRUE to skip saving the order after deleting the line item. If saving the + * order is skipped, the caller is responsible for saving the order to + * ensure changes to its line item reference field value are saved. */ - public function delete($line_item_ids, DatabaseTransaction $transaction = NULL) { + public function delete($line_item_ids, DatabaseTransaction $transaction = NULL, $skip_order_save = FALSE) { $line_items = $line_item_ids ? $this->load($line_item_ids) : FALSE; if (!$line_items) { @@ -139,9 +143,53 @@ class CommerceLineItemEntityController extends DrupalCommerceEntityController { } try { - // First attempt to delete references for the given line items. + // First attempt to delete references to each line item, building a list + // of modified entities to be saved once each after references to all line + // items have been deleted. + $reference_fields = commerce_info_fields('commerce_line_item_reference'); + $modified_entities = array(); + foreach ($line_items as $line_item_id => $line_item) { - commerce_line_item_delete_references($line_item); + // Check the data in every line item reference field. + foreach ($reference_fields as $field_name => $field) { + // Query for any entity referencing the deleted line item in this field. + $query = new EntityFieldQuery(); + $query->fieldCondition($field_name, 'line_item_id', $line_item->line_item_id, '='); + $result = $query->execute(); + + // If no results were returned, continue to next field. + if (empty($result)) { + continue; + } + + // Loop over results for each type of entity returned. + foreach ($result as $entity_type => $data) { + // Load the entities of the current type. + $entities = entity_load($entity_type, array_keys($data)); + + // Loop over each entity and remove the reference to the deleted line item. + foreach ($entities as $entity_id => $entity) { + // If we have this entity already, get the reference, otherwise + // store the new entity to save changes later. + if (!isset($modified_entities[$entity_type . ':' . $entity_id])) { + $modified_entities[$entity_type . ':' . $entity_id] = array( + 'entity_type' => $entity_type, + 'entity' => $entity, + ); + } + + commerce_entity_reference_delete($entity, $field_name, 'line_item_id', $line_item->line_item_id); + } + } + } + } + + // Save the entities that were modified through deleted references. + foreach ($modified_entities as $entity_data) { + if ($skip_order_save && $entity_data['entity_type'] == 'commerce_order') { + continue; + } + entity_save($entity_data['entity_type'], $entity_data['entity']); } return parent::delete($line_item_ids, $transaction); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/commerce_line_item.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/commerce_line_item.views.inc index c5bca1fa..11c02915 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/commerce_line_item.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/commerce_line_item.views.inc @@ -63,6 +63,15 @@ function commerce_line_item_views_data() { // TODO: Expose the display view build mode. + // Defines a filter that ensures line items are referenced by orders. + $data['commerce_line_item']['line_item_is_referenced'] = array( + 'title' => t('Line item is referenced by its order'), + 'help' => t('Ensures a line item in the result set is referenced by the order it indicates in its order_id property.'), + 'filter' => array( + 'handler' => 'commerce_line_item_handler_filter_line_item_is_referenced', + ), + ); + // Expose the type-dependent line item title. $data['commerce_line_item']['line_item_title'] = array( 'field' => array( diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_field_edit_quantity.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_field_edit_quantity.inc index be9fe55f..c8eee3b4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_field_edit_quantity.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_field_edit_quantity.inc @@ -101,9 +101,12 @@ class commerce_line_item_handler_field_edit_quantity extends views_handler_field } // Process the deletes first. - foreach ($deleted_line_items as $line_item_id) { + if (!empty($deleted_line_items)) { $order = commerce_order_load($form_state['order']->order_id); - commerce_cart_order_product_line_item_delete($order, $line_item_id); + foreach ($deleted_line_items as $line_item_id) { + $order = commerce_cart_order_product_line_item_delete($order, $line_item_id, TRUE); + } + commerce_order_save($order); } // Then process the quantity updates. @@ -112,7 +115,6 @@ class commerce_line_item_handler_field_edit_quantity extends views_handler_field $line_item = commerce_line_item_load($line_item_id); $line_item->quantity = $quantity; commerce_line_item_save($line_item); - entity_get_controller('commerce_line_item')->resetCache(array($line_item->line_item_id)); } } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_filter_line_item_is_referenced.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_filter_line_item_is_referenced.inc new file mode 100644 index 00000000..4ebe5d9c --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/line_item/includes/views/handlers/commerce_line_item_handler_filter_line_item_is_referenced.inc @@ -0,0 +1,71 @@ +operator == 'NOT IN') { + return 'NOT'; + } + + return ''; + } + + function option_definition() { + $options = parent::option_definition(); + $options['operator'] = array('default' => 'IN'); + return $options; + } + + function operator_options($which = 'title') { + $options = array(); + $options['IN'] = t('Line item is referenced'); + $options['NOT IN'] = t('Line item is NOT referenced'); + return $options; + } + + /** + * Adds a subquery to the WHERE clause that ensures the order_id referenced by + * the line item is present in the comerce_line_items field's table for the + * given line_item_id. + */ + function query() { + // Ensure we have a valid operator for the subquery. + if (empty($this->operator)) { + $this->operator = 'IN'; + } + + // Ensure the commerce_line_items field exists and fetch its table name. + $field_info = field_info_field('commerce_line_items'); + + if (empty($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) { + return; + } + + $field_table = key($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT']); + $field_value_column = $field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT'][$field_table]['line_item_id']; + + $this->table_alias = $this->query->ensure_table($this->view->base_table, $this->relationship); + + // Build SQL where snippet using a subquery on the field table for the + // required default line item reference field on orders. + $snippet = $this->table_alias . '.line_item_id ' . $this->operator . ' (SELECT ' . $field_value_column . ' FROM {' . $field_table . '} WHERE deleted = 0 AND entity_id != 0 AND entity_id = ' . $this->table_alias . '.order_id)'; + + $this->query->add_where_expression($this->options['group'], $snippet); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.api.php index 969a9bb1..5184dcb3 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.api.php @@ -178,3 +178,17 @@ function hook_commerce_order_uri($order) { function hook_commerce_order_presave($order) { // No example. } + +/** + * Respond to order deletion. + * + * The order will still exist and allows you to take an action before it is + * completely removed. + * + * @param $order + * The order object that is being deleted. + */ +function hook_commerce_order_delete($order) { + commerce_cart_order_session_delete($order->order_id); + commerce_cart_order_session_delete($order->order_id, TRUE); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.drush.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.drush.inc new file mode 100644 index 00000000..e5969e37 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.drush.inc @@ -0,0 +1,107 @@ + 'Set the order placed timestamp to match the created timestamp.', + 'examples' => array( + 'drush commerce-order-update-placed-timestamp' => dt('Update the placed timestamp, Defaults to 1000 orders per batch run.'), + 'drush coupt 500' => dt('Update the placed timestamp, 500 orders per batch run.'), + ), + 'arguments' => array( + 'limit' => dt("The number of orders to update per batch run. Defaults to 1000."), + ), + 'aliases' => array('coupt'), + ); + + return $items; +} + +/** + * Update the placed timestamp for existing placed orders. + + * @param integer $limit + * Number of orders to update per batch run. + */ +function drush_commerce_order_update_placed_timestamp($limit = 1000) { + $pending_statuses = commerce_order_statuses(array('state' => 'pending')); + $completed_statuses = commerce_order_statuses(array('state' => 'completed')); + $statuses = array_merge(array_keys($pending_statuses), array_keys($completed_statuses)); + $orders_count = db_select('commerce_order') + ->condition('placed', 0) + ->condition('status', $statuses, 'IN') + ->countQuery() + ->execute() + ->fetchField(); + + if ($orders_count <= 0) { + drush_log(dt('There are currently no pending or completed orders that should have their placed timestamp updated.'), 'error'); + } + drush_log(dt('@count orders are about to be processed.', array('@count' => $orders_count))); + + $batch = array( + 'title' => dt('Updating orders'), + 'progress_message' => dt('Completed about @percentage% of the order update operation.'), + 'operations' => array( + array('_commerce_order_update_placed_timestamp_batch_callback', array($statuses, $limit, $orders_count)), + ), + 'finished' => '_commerce_order_update_placed_timestamp_finished', + ); + batch_set($batch); + drush_backend_batch_process(); +} + +/** + * Batch API callback: Update the placed timestamp. + * + * @param array $statuses + * An array containing pending/completed order statuses. + * @param integer $limit + * Number of orders to update per batch run. + * @param integer $orders_count + * Total number orders to update. + * @param $context + * An array (or object implementing ArrayAccess) containing the batch context. + */ +function _commerce_order_update_placed_timestamp_batch_callback($statuses, $limit, $orders_count, &$context) { + // Persistent data among batch runs. + if (!isset($context['sandbox']['limit'])) { + $context['sandbox']['limit'] = ceil($orders_count / $limit); + $context['sandbox']['progress'] = 0; + } + // Persistent data for results. + if (!isset($context['results']['updated_count'])) { + $context['results']['updated_count'] = 0; + } + $rows_affected = db_query('UPDATE {commerce_order} SET placed = created WHERE placed = 0 AND status IN (:statuses) LIMIT ' . $limit, array(':statuses' => $statuses), array('return' => Database::RETURN_AFFECTED)); + $context['results']['updated_count'] += $rows_affected; + $context['sandbox']['progress']++; + $context['message'] = dt('@progress orders processed out of @count.', array('@progress' => $context['results']['updated_count'], '@count' => $orders_count)); + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['limit']; +} + +/** + * Batch API finishing callback for the placed timestamp update. + * + * @param boolean $success + * Whether the batch finished successfully. + * @param array $results + * Detailed information about the result. + */ +function _commerce_order_update_placed_timestamp_finished($success, $results) { + if ($success) { + drush_log(dt('The placed timestamp was successfully updated for @count orders.', array('@count' => $results['updated_count'])), 'success'); + } + else { + drush_log(dt('There was an error while updating the placed timestamp for orders.'), 'error'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info index 1b9fe2f8..d0e2ae45 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info @@ -36,9 +36,9 @@ files[] = includes/views/handlers/commerce_order_plugin_argument_validate_user.i files[] = tests/commerce_order.rules.test files[] = tests/commerce_order.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info.inc index 884cf9f8..bce6eb3a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.info.inc @@ -60,6 +60,14 @@ function commerce_order_entity_property_info() { 'setter permission' => 'administer commerce_order entities', 'schema field' => 'changed', ); + $properties['placed'] = array( + 'type' => 'date', + 'label' => t('Date placed'), + 'description' => t('The date the order was placed.'), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer commerce_order entities', + 'schema field' => 'placed', + ); $properties['hostname'] = array( 'type' => 'text', 'label' => t('Host name'), @@ -79,8 +87,8 @@ function commerce_order_entity_property_info() { ); $properties['uid'] = array( 'type' => 'integer', - 'label' => t("Owner ID"), - 'description' => t("The unique ID of the order owner."), + 'label' => t('Owner ID'), + 'description' => t('The unique ID of the order owner.'), 'setter callback' => 'entity_property_verbatim_set', 'setter permission' => 'administer commerce_order entities', 'clear' => array('owner'), @@ -112,6 +120,12 @@ function commerce_order_entity_property_info() { 'computed' => TRUE, 'clear' => array('mail'), ); + $properties['revision'] = array( + 'label' => t('Create revision'), + 'type' => 'boolean', + 'description' => t('Whether or not saving this order creates a new revision.'), + 'setter callback' => 'entity_property_verbatim_set', + ); return $info; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.install b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.install index 4aa42ec9..69175340 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.install @@ -65,6 +65,12 @@ function commerce_order_schema() { 'not null' => TRUE, 'default' => 0, ), + 'placed' => array( + 'description' => 'The Unix timestamp when the order was placed e.g. when checkout was completed.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), 'hostname' => array( 'description' => 'The IP address that created this order.', 'type' => 'varchar', @@ -471,3 +477,31 @@ function commerce_order_update_7109() { return t('Schema for the commerce_order table has been updated.'); } + +/** + * Add the placed timestamp to {commerce_order}. + */ +function commerce_order_update_7110() { + if (!db_field_exists('commerce_order', 'placed')) { + db_add_field('commerce_order', 'placed', array( + 'description' => 'The Unix timestamp when the order was placed e.g. when checkout was completed.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + )); + + // Existing sites may have left the value for this variable undefined, and + // the current default is TRUE. We are changing its default to FALSE with + // this release so that new sites have the correct behavior i.e. the + // "created" property holds the creation time and the "placed" property + // holds the checkout completion time. To not alter the behavior in existing + // sites we set the variable to TRUE, the previous default, and site admins + // can change it when they desire. + $current_value = variable_get('enable_commerce_checkout_order_created_date_update'); + if ($current_value === NULL) { + variable_set('enable_commerce_checkout_order_created_date_update', TRUE); + } + + return t('Schema for the commerce_order table has been updated. A drush command (commerce-order-update-placed-timestamp) is available to update the placed timestamp for existing orders.'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.js b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.js index 35580e3e..eca4d5fd 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.js +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.js @@ -26,6 +26,10 @@ Drupal.behaviors.orderFieldsetSummaries = { Drupal.t('Created @date', { '@date' : $('#edit-created').val() }) : Drupal.t('New order'); + if ($('#edit-placed', context).val()) { + summary += '
        ' + Drupal.t('Placed @date', { '@date' : $('#edit-placed').val() }); + } + // Add the changed date to the summary if it's different from the created. if ($('#edit-created', context).val() != $('#edit-changed', context).val()) { summary += '
        ' + Drupal.t('Updated @date', { '@date' : $('#edit-changed').val() }); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.module index 786e0132..a8d6ab7e 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.module @@ -271,7 +271,7 @@ function commerce_order_configure_customer_profile_type($customer_profile_type, /** * Configures the customer profile reference fields attached to the default - * order type when modules defining customer profile types are enabeld after the + * order type when modules defining customer profile types are enabled after the * Order module. * * @param $modules @@ -1291,7 +1291,7 @@ function commerce_order_calculate_total($order) { $order_wrapper = entity_metadata_wrapper('commerce_order', $order); // First determine the currency to use for the order total. - $currency_code = commerce_default_currency(); + $default_currency_code = $currency_code = commerce_default_currency(); $currencies = array(); // Populate an array of how many line items on the order use each currency. @@ -1305,7 +1305,7 @@ function commerce_order_calculate_total($order) { $line_item_currency_code = $line_item_wrapper->commerce_total->currency_code->value(); - if (!in_array($line_item_currency_code, array_keys($currencies))) { + if (!isset($currencies[$line_item_currency_code])) { $currencies[$line_item_currency_code] = 1; } else { @@ -1320,9 +1320,9 @@ function commerce_order_calculate_total($order) { if (count($currencies) == 1) { $currency_code = key($currencies); } - elseif (in_array(commerce_default_currency(), array_keys($currencies))) { + elseif (isset($currencies[$default_currency_code])) { // Otherwise use the site default currency if it's in the order. - $currency_code = commerce_default_currency(); + $currency_code = $default_currency_code; } elseif (count($currencies) > 1) { // Otherwise use the first currency on the order. We do this instead of @@ -1347,13 +1347,15 @@ function commerce_order_calculate_total($order) { ); $order_wrapper->commerce_order_total->data = commerce_price_component_add($base_price, 'base_price', $base_price, TRUE); + $order_total = $order_wrapper->commerce_order_total->value(); // Then loop over each line item and add its total to the order total. $amount = 0; foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { // Convert the line item's total to the order's currency for totalling. - $component_total = commerce_price_component_total($line_item_wrapper->commerce_total->value()); + $line_item_total = $line_item_wrapper->commerce_total->value(); + $component_total = commerce_price_component_total($line_item_total); // Add the totals. $amount += commerce_currency_convert( @@ -1363,14 +1365,12 @@ function commerce_order_calculate_total($order) { ); // Combine the line item total's component prices into the order total. - $order_wrapper->commerce_order_total->data = commerce_price_components_combine( - $order_wrapper->commerce_order_total->value(), - $line_item_wrapper->commerce_total->value() - ); + $order_total['data'] = commerce_price_components_combine($order_total, $line_item_total); } - // Update the order total price field with the final total amount. + // Update the order total price field with the final total amount and data. $order_wrapper->commerce_order_total->amount = round($amount); + $order_wrapper->commerce_order_total->data = $order_total['data']; } /** @@ -1504,3 +1504,11 @@ function commerce_order_preprocess_views_view(&$vars) { $vars['footer'] = ''; } } + +/** + * Element validate handler: strip whitespaces from order related e-mail fields. + * @see commerce_order_account_pane_checkout_form() + */ +function commerce_order_mail_validate($element, &$form_state, $form) { + form_set_value($element, trim($element['#value']), $form_state); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.rules.inc index 51d56588..3a3530d4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.rules.inc @@ -274,26 +274,35 @@ function commerce_order_rules_compare_address($order, $address_field, $component * in the specified quantity. */ function commerce_order_rules_contains_product($order, $sku, $operator, $value) { - $products = array($sku => 0); + $count = 0; - // If we actually received a valid order... - if (!empty($order)) { - $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Populate the array of the quantities of the products on the order. - foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { - if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { - // Extract a product ID and quantity from the line item. - $line_item_sku = $line_item_wrapper->commerce_product->sku->value(); - $quantity = $line_item_wrapper->quantity->value(); - - // Update the product's quantity value. - if (empty($products[$line_item_sku])) { - $products[$line_item_sku] = $quantity; - } - else { - $products[$line_item_sku] += $quantity; - } + // If we got a valid order with line items... + if (!empty($order) && !empty($order->commerce_line_items)) { + // Collect all the line item IDs on the order. + $line_item_ids = array(); + + if ($items = field_get_items('commerce_order', $order, 'commerce_line_items')) { + foreach ($items as $item) { + $line_item_ids[] = $item['line_item_id']; + } + } + + // If we found valid line item IDs... + if (!empty($line_item_ids)) { + // Get the product ID matching the given SKU. + $product_id = db_query("SELECT product_id FROM {commerce_product} WHERE sku = :sku", array(':sku' => $sku))->fetchField(); + + // Build a query to find the line items matching the given SKU. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'commerce_line_item') + ->entityCondition('bundle', commerce_product_line_item_types(), 'IN') + ->entityCondition('entity_id', $line_item_ids, 'IN') + ->fieldCondition('commerce_product', 'product_id', $product_id); + $result = $query->execute(); + + if (!empty($result['commerce_line_item'])) { + $count = commerce_line_items_quantity_by_id(array_keys($result['commerce_line_item'])); } } } @@ -301,15 +310,15 @@ function commerce_order_rules_contains_product($order, $sku, $operator, $value) // Make a quantity comparison based on the operator. switch ($operator) { case '<': - return $products[$sku] < $value; + return $count < $value; case '<=': - return $products[$sku] <= $value; + return $count <= $value; case '=': - return $products[$sku] == $value; + return $count == $value; case '>=': - return $products[$sku] >= $value; + return $count >= $value; case '>': - return $products[$sku] > $value; + return $count > $value; } return FALSE; @@ -322,20 +331,62 @@ function commerce_order_rules_contains_product($order, $sku, $operator, $value) function commerce_order_rules_contains_product_type($order, $product_types, $operator, $value) { $quantity = 0; - // If we actually received a valid order... - if (!empty($order)) { - $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Look for product line items on the order whose products match the - // specified product types and increment the quantity count accordingly. - foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { - if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { - // Extract the product type from the line item. - $line_item_product_type = $line_item_wrapper->commerce_product->type->value(); - - // If the line item product type matches, update the total quantity. - if (in_array($line_item_product_type, $product_types)) { - $quantity += $line_item_wrapper->quantity->value(); + // If we got a valid order with line items... + if (!empty($order) && !empty($order->commerce_line_items)) { + // Collect all the line item IDs on the order. + $line_item_ids = array(); + + if ($items = field_get_items('commerce_order', $order, 'commerce_line_items')) { + foreach ($items as $item) { + $line_item_ids[] = $item['line_item_id']; + } + } + + // If we found valid line item IDs... + if (!empty($line_item_ids)) { + // Build a query to find the order's product line items. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'commerce_line_item') + ->entityCondition('bundle', commerce_product_line_item_types(), 'IN') + ->entityCondition('entity_id', $line_item_ids, 'IN'); + $line_item_result = $query->execute(); + + // If the query discovered product line items... + if (!empty($line_item_result['commerce_line_item'])) { + // Load the product line items. + $line_items = commerce_line_item_load_multiple(array_keys($line_item_result['commerce_line_item'])); + + // Build an array of referenced product IDs. + $product_ids_to_line_items = array(); + + foreach ($line_items as $line_item) { + if ($items = field_get_items('commerce_line_item', $line_item, 'commerce_product')) { + $item = reset($items); + $product_ids_to_line_items[$item['product_id']][] = $line_item->line_item_id; + } + } + + // If the search resulted in products to load... + if (!empty($product_ids_to_line_items)) { + // Build a query to find products in the appropriate types. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'commerce_product') + ->entityCondition('entity_id', array_keys($product_ids_to_line_items), 'IN') + ->entityCondition('bundle', $product_types, 'IN'); + $product_result = $query->execute(); + + // If the query discovered valid products... + if (!empty($product_result['commerce_product'])) { + // Loop over all valid product stubs and add their related line + // item quantities to the running total. + foreach (array_keys($product_result['commerce_product']) as $product_id) { + foreach ($product_ids_to_line_items[$product_id] as $line_item_id) { + $quantity += $line_items[$line_item_id]->quantity; + } + } + } } } } @@ -365,12 +416,20 @@ function commerce_order_rules_contains_product_type($order, $product_types, $ope function commerce_order_rules_compare_total_quantity($order, $operator, $value) { $quantity = 0; - // If we received an order, get the total quantity of products on it. - if (!empty($order)) { - $wrapper = entity_metadata_wrapper('commerce_order', $order); + // If we got a valid order with line items... + if (!empty($order) && !empty($order->commerce_line_items)) { + // Collect all the line item IDs on the order. + $line_item_ids = array(); + + if ($items = field_get_items('commerce_order', $order, 'commerce_line_items')) { + foreach ($items as $item) { + $line_item_ids[] = $item['line_item_id']; + } + } - if (!empty($wrapper->commerce_line_items)) { - $quantity = commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types()); + // If we found valid line item IDs... + if (!empty($line_item_ids)) { + $quantity = commerce_line_items_quantity_by_id($line_item_ids, commerce_product_line_item_types()); } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.tokens.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.tokens.inc index 875632d4..dc89394a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.tokens.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order.tokens.inc @@ -76,6 +76,11 @@ function commerce_order_token_info() { 'description' => t('The date the order was last updated.'), 'type' => 'date', ); + $order['placed'] = array( + 'name' => t('Date placed'), + 'description' => t('The date the order was placed i.e. checkout was completed.'), + 'type' => 'date', + ); return array( 'types' => array('commerce-order' => $type), @@ -169,6 +174,10 @@ function commerce_order_tokens($type, $tokens, array $data = array(), array $opt case 'changed': $replacements[$original] = format_date($order->changed, 'medium', '', NULL, $language_code); break; + + case 'placed': + $replacements[$original] = format_date($order->changed, 'medium', '', NULL, $language_code); + break; } } @@ -178,7 +187,7 @@ function commerce_order_tokens($type, $tokens, array $data = array(), array $opt $replacements += token_generate('user', $owner_tokens, array('user' => $owner), $options); } - foreach (array('created', 'changed') as $date) { + foreach (array('created', 'changed', 'placed') as $date) { if ($created_tokens = token_find_with_prefix($tokens, $date)) { $replacements += token_generate('date', $created_tokens, array('date' => $order->{$date}), $options); } @@ -186,4 +195,4 @@ function commerce_order_tokens($type, $tokens, array $data = array(), array $opt } return $replacements; -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.info index df9f5efa..886e0e3b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.info @@ -16,9 +16,9 @@ files[] = includes/views/handlers/commerce_order_ui_handler_area_view_order_form ; Simple tests files[] = tests/commerce_order_ui.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.module index 43db91be..6c3a964d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/commerce_order_ui.module @@ -17,7 +17,6 @@ function commerce_order_ui_menu() { 'title' => 'Create an order', 'description' => 'Create a new order.', 'page callback' => 'commerce_order_ui_order_form_wrapper', - 'page arguments' => array(commerce_order_new()), 'access callback' => 'commerce_order_access', 'access arguments' => array('create'), 'weight' => 10, @@ -27,7 +26,7 @@ function commerce_order_ui_menu() { 'title' => 'Create an order', 'description' => 'Create a new order for the specified user.', 'page callback' => 'commerce_order_ui_order_form_wrapper', - 'page arguments' => array(commerce_order_new(), 4), + 'page arguments' => array(NULL, 4), 'access callback' => 'commerce_order_access', 'access arguments' => array('create'), 'file' => 'includes/commerce_order_ui.orders.inc', @@ -284,7 +283,7 @@ function commerce_order_ui_form_commerce_order_ui_order_form_alter(&$form, &$for */ function commerce_order_ui_order_form_submit($form, &$form_state) { // Apply the redirect based on the clicked button. - if ($form_state['triggering_element']['#value'] == t('Save order', array(), array('context' => 'a drupal commerce order'))) { + if ($form_state['triggering_element']['#value'] === t('Save order', array(), array('context' => 'a drupal commerce order'))) { drupal_set_message(t('Order saved.')); $form_state['redirect'] = 'admin/commerce/orders/' . $form_state['commerce_order']->order_id . '/edit'; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.checkout_pane.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.checkout_pane.inc index 8bfb7ca4..c3792c16 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.checkout_pane.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.checkout_pane.inc @@ -67,6 +67,10 @@ function commerce_order_account_pane_checkout_form($form, &$form_state, $checkou '#title' => t('E-mail address'), '#default_value' => $order->mail, '#required' => TRUE, + '#element_validate' => array('commerce_order_mail_validate'), + '#attributes' => array( + 'autocomplete' => 'email', + ), ); if (variable_get('commerce_order_account_pane_mail_double_entry', FALSE)) { @@ -76,6 +80,7 @@ function commerce_order_account_pane_checkout_form($form, &$form_state, $checkou '#description' => t('Provide your e-mail address in both fields.'), '#default_value' => $order->mail, '#required' => TRUE, + '#element_validate' => array('commerce_order_mail_validate'), ); } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.controller.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.controller.inc index ba345a88..b056d6fb 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.controller.inc @@ -30,6 +30,7 @@ class CommerceOrderEntityController extends DrupalCommerceEntityController { 'data' => array(), 'created' => '', 'changed' => '', + 'placed' => '', 'hostname' => '', ); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.forms.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.forms.inc index f46dab59..a7a98c76 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.forms.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order.forms.inc @@ -25,10 +25,6 @@ function commerce_order_order_form($form, &$form_state, $order) { } } - if (empty($order->created)) { - $order->date = format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O'); - } - // Add the field related form elements. $form_state['commerce_order'] = $order; field_attach_form('commerce_order', $order, $form, $form_state); @@ -122,6 +118,9 @@ function commerce_order_order_form($form, &$form_state, $order) { ); // Add a log checkbox and timestamp field to a history tab. + $created_timestamp = empty($order->created) ? REQUEST_TIME : $order->created; + $placed_timestamp = empty($order->placed) ? REQUEST_TIME : $order->placed; + $form['order_history'] = array( '#type' => 'fieldset', '#title' => t('Order history', array(), array('context' => 'a drupal commerce order')), @@ -140,13 +139,20 @@ function commerce_order_order_form($form, &$form_state, $order) { '#default_value' => variable_get('commerce_order_auto_revision', TRUE), '#access' => user_access('administer commerce_order entities'), ); - $form['order_history']['date'] = array( + $form['order_history']['date_created'] = array( '#type' => 'textfield', '#title' => t('Created on'), - '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($order->date) ? date_format(date_create($order->date), 'Y-m-d H:i:s O') : format_date($order->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($order->date) ? date_format(date_create($order->date), 'O') : format_date($order->created, 'custom', 'O'))), + '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => format_date($created_timestamp, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date($created_timestamp, 'custom', 'O'))), '#maxlength' => 25, '#default_value' => !empty($order->created) ? format_date($order->created, 'custom', 'Y-m-d H:i:s O') : '', ); + $form['order_history']['date_placed'] = array( + '#type' => 'textfield', + '#title' => t('Placed on'), + '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC.', array('%time' => format_date($placed_timestamp, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date($placed_timestamp, 'custom', 'O'))), + '#maxlength' => 25, + '#default_value' => !empty($order->placed) ? format_date($order->placed, 'custom', 'Y-m-d H:i:s O') : '', + ); $form['order_history']['created'] = array( '#type' => 'hidden', '#value' => !empty($order->created) ? format_date($order->created, 'short') : '', @@ -157,6 +163,11 @@ function commerce_order_order_form($form, &$form_state, $order) { '#value' => !empty($order->changed) ? format_date($order->changed, 'short') : '', '#attributes' => array('id' => 'edit-changed'), ); + $form['order_history']['placed'] = array( + '#type' => 'hidden', + '#value' => !empty($order->placed) ? format_date($order->placed, 'short') : '', + '#attributes' => array('id' => 'edit-placed'), + ); // We add the form's #submit array to this button along with the actual submit // handler to preserve any submit handlers added by a form callback_wrapper. @@ -195,8 +206,13 @@ function commerce_order_order_form_validate($form, &$form_state) { } // Validate the "created on" field. - if (!empty($form_state['values']['date']) && strtotime($form_state['values']['date']) === FALSE) { - form_set_error('date', t('You have to specify a valid date.')); + if (!empty($form_state['values']['date_created']) && strtotime($form_state['values']['date_created']) === FALSE) { + form_set_error('date', t('You have to specify a valid order creation date.')); + } + + // Validate the "placed on" field. + if (!empty($form_state['values']['date_placed']) && strtotime($form_state['values']['date_placed']) === FALSE) { + form_set_error('date', t('You have to specify a valid order placement date.')); } // Validate the e-mail address entered. @@ -256,7 +272,10 @@ function commerce_order_order_form_submit($form, &$form_state) { $order->mail = $form_state['values']['mail']; } - $order->created = !empty($form_state['values']['date']) ? strtotime($form_state['values']['date']) : REQUEST_TIME; + $order->created = !empty($form_state['values']['date_created']) ? strtotime($form_state['values']['date_created']) : REQUEST_TIME; + if (!empty($form_state['values']['date_placed'])) { + $order->placed = strtotime($form_state['values']['date_placed']); + } // Notify field widgets. field_attach_submit('commerce_order', $order, $form, $form_state); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order_ui.orders.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order_ui.orders.inc index 3e1b96a5..21d1bde8 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order_ui.orders.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/commerce_order_ui.orders.inc @@ -38,7 +38,11 @@ function commerce_order_settings_form($form, &$form_state) { * * @see commerce_order_order_form() */ -function commerce_order_ui_order_form_wrapper($order, $account = NULL) { +function commerce_order_ui_order_form_wrapper($order = NULL, $account = NULL) { + if (empty($order)) { + $order = commerce_order_new(); + } + // Set the page title and a default customer if necessary. if (empty($order->order_id)) { drupal_set_title(t('Create an order')); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order.views.inc index e5a8b598..940de374 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order.views.inc @@ -171,7 +171,7 @@ function commerce_order_views_data() { ), ); - // Expose the created and changed timestamps. + // Expose the created, changed, and placed timestamps. $data['commerce_order']['created'] = array( 'title' => t('Created date'), 'help' => t('The date the order was created.'), @@ -310,6 +310,75 @@ function commerce_order_views_data() { ), ); + $data['commerce_order']['placed'] = array( + 'title' => t('Placed date'), + 'help' => t('The date the order was placed.'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort_date', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_date', + ), + ); + + $data['commerce_order']['placed_fulldate'] = array( + 'title' => t('Placed date'), + 'help' => t('In the form of CCYYMMDD.'), + 'argument' => array( + 'field' => 'placed', + 'handler' => 'views_handler_argument_node_created_fulldate', + ), + ); + + $data['commerce_order']['placed_year_month'] = array( + 'title' => t('Placed year + month'), + 'help' => t('In the form of YYYYMM.'), + 'argument' => array( + 'field' => 'placed', + 'handler' => 'views_handler_argument_node_created_year_month', + ), + ); + + $data['commerce_order']['placed_timestamp_year'] = array( + 'title' => t('Placed year'), + 'help' => t('In the form of YYYY.'), + 'argument' => array( + 'field' => 'placed', + 'handler' => 'views_handler_argument_node_created_year', + ), + ); + + $data['commerce_order']['placed_month'] = array( + 'title' => t('Placed month'), + 'help' => t('In the form of MM (01 - 12).'), + 'argument' => array( + 'field' => 'placed', + 'handler' => 'views_handler_argument_node_created_month', + ), + ); + + $data['commerce_order']['placed_day'] = array( + 'title' => t('Placed day'), + 'help' => t('In the form of DD (01 - 31).'), + 'argument' => array( + 'field' => 'placed', + 'handler' => 'views_handler_argument_node_created_day', + ), + ); + + $data['commerce_order']['placed_week'] = array( + 'title' => t('Placed week'), + 'help' => t('In the form of WW (01 - 53).'), + 'argument' => array( + 'field' => 'placed', + 'handler' => 'views_handler_argument_node_created_week', + ), + ); + // Expose links to operate on the order. $data['commerce_order']['view_order'] = array( 'field' => array( @@ -352,7 +421,7 @@ function commerce_order_views_data() { // Define a handler for an area used to summarize a set of line items. $data['commerce_order']['order_total'] = array( 'title' => t('Order total'), - 'help' => t('Displays the order total field formatted with its components list; requires an Order ID argument.'), + 'help' => t('Displays the order total field; requires an Order ID argument.'), 'area' => array( 'handler' => 'commerce_order_handler_area_order_total', ), diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order_ui.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order_ui.views_default.inc index ac7f698e..8183116b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order_ui.views_default.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/commerce_order_ui.views_default.inc @@ -36,6 +36,7 @@ function commerce_order_ui_views_default_views() { $handler->display->display_options['style_plugin'] = 'table'; $handler->display->display_options['style_options']['columns'] = array( 'order_number' => 'order_number', + 'placed' => 'placed', 'changed' => 'changed', 'commerce_customer_address' => 'commerce_customer_address', 'name' => 'name', @@ -52,6 +53,13 @@ function commerce_order_ui_views_default_views() { 'separator' => '', 'empty_column' => 0, ), + 'placed' => array( + 'sortable' => 1, + 'default_sort_order' => 'desc', + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), 'changed' => array( 'sortable' => 1, 'default_sort_order' => 'desc', @@ -116,6 +124,13 @@ function commerce_order_ui_views_default_views() { $handler->display->display_options['fields']['order_number']['table'] = 'commerce_order'; $handler->display->display_options['fields']['order_number']['field'] = 'order_number'; $handler->display->display_options['fields']['order_number']['link_to_order'] = 'admin'; + /* Field: Commerce Order: Placed date */ + $handler->display->display_options['fields']['placed']['id'] = 'placed'; + $handler->display->display_options['fields']['placed']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['placed']['field'] = 'placed'; + $handler->display->display_options['fields']['placed']['label'] = 'Placed'; + $handler->display->display_options['fields']['placed']['empty'] = '-'; + $handler->display->display_options['fields']['placed']['date_format'] = 'medium'; /* Field: Commerce Order: Updated date */ $handler->display->display_options['fields']['changed']['id'] = 'changed'; $handler->display->display_options['fields']['changed']['table'] = 'commerce_order'; @@ -206,6 +221,7 @@ function commerce_order_ui_views_default_views() { t('Order owner'), t('Customer profile'), t('Order number'), + t('Placed'), t('Updated'), t('Name'), t('-'), @@ -247,7 +263,7 @@ function commerce_order_ui_views_default_views() { $handler->display->display_options['style_plugin'] = 'table'; $handler->display->display_options['style_options']['columns'] = array( 'order_number' => 'order_number', - 'created' => 'created', + 'placed' => 'placed', 'changed' => 'changed', 'commerce_order_total' => 'commerce_order_total', 'status' => 'status', @@ -261,7 +277,7 @@ function commerce_order_ui_views_default_views() { 'separator' => '', 'empty_column' => 0, ), - 'created' => array( + 'placed' => array( 'sortable' => 1, 'default_sort_order' => 'desc', 'align' => '', @@ -301,15 +317,16 @@ function commerce_order_ui_views_default_views() { $handler->display->display_options['fields']['order_number']['table'] = 'commerce_order'; $handler->display->display_options['fields']['order_number']['field'] = 'order_number'; $handler->display->display_options['fields']['order_number']['link_to_order'] = 'customer'; - /* Field: Commerce Order: Created date */ - $handler->display->display_options['fields']['created']['id'] = 'created'; - $handler->display->display_options['fields']['created']['table'] = 'commerce_order'; - $handler->display->display_options['fields']['created']['field'] = 'created'; - $handler->display->display_options['fields']['created']['label'] = 'Created'; + /* Field: Commerce Order: Placed date */ + $handler->display->display_options['fields']['placed']['id'] = 'placed'; + $handler->display->display_options['fields']['placed']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['placed']['field'] = 'placed'; + $handler->display->display_options['fields']['placed']['label'] = 'Placed'; /* Field: Commerce Order: Updated date */ $handler->display->display_options['fields']['changed']['id'] = 'changed'; $handler->display->display_options['fields']['changed']['table'] = 'commerce_order'; $handler->display->display_options['fields']['changed']['field'] = 'changed'; + $handler->display->display_options['fields']['changed']['label'] = 'Updated'; /* Field: Commerce Order: Order total */ $handler->display->display_options['fields']['commerce_order_total']['id'] = 'commerce_order_total'; $handler->display->display_options['fields']['commerce_order_total']['table'] = 'field_data_commerce_order_total'; @@ -378,8 +395,8 @@ function commerce_order_ui_views_default_views() { t('last »'), t('You have not placed any orders with us yet.'), t('Order number'), - t('Created'), - t('Updated date'), + t('Placed'), + t('Updated'), t('Total'), t('Order status'), t('All'), diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc index c0d238fb..4dbb39e6 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc @@ -1,16 +1,30 @@ FALSE, + 'bool' => TRUE, + ); + + $options['label_custom'] = array( + 'default' => t('Order total'), + 'translatable' => TRUE, + ); + + $options['label_position'] = array( + 'default' => 'inline', + ); + + $options['formatter'] = array( + 'default' => 'commerce_price_formatted_components', + ); return $options; } @@ -20,8 +34,45 @@ class commerce_order_handler_area_order_total extends views_handler_area { $form['empty']['#description'] = t("Even if selected, this area handler will never render if a valid order cannot be found in the View's arguments."); - // TODO: Update with option form elements if the display formatter itself - // gets support for them. + $form['label_display'] = array( + '#type' => 'checkbox', + '#title' => t('Display a custom label for this field'), + '#description' => t('Displaying a custom label may require updates to your theme to maintain the default alignment of the field without a label.'), + '#default_value' => $this->options['label_display'], + ); + + $form['label_custom'] = array( + '#type' => 'textfield', + '#title' => t('Custom label'), + '#default_value' => $this->options['label_custom'], + '#dependency' => array( + 'edit-options-label-display' => array(1), + ), + ); + + $form['label_position'] = array( + '#type' => 'select', + '#title' => t('Label position'), + '#default_value' => $this->options['label_position'], + '#dependency' => array( + 'edit-options-label-display' => array(1), + ), + '#options' => array( + 'inline' => t('Inline'), + 'above' => t('Above'), + ), + ); + + // Load the formatter options for the commerce_order_total field. + module_load_include('inc', 'field_ui', 'field_ui.admin'); + $field = field_info_field('commerce_order_total'); + + $form['formatter'] = array( + '#type' => 'select', + '#title' => t('Formatter'), + '#options' => field_ui_formatter_options($field['type']), + '#default_value' => $this->options['formatter'], + ); } function render($empty = FALSE) { @@ -36,8 +87,8 @@ class commerce_order_handler_area_order_total extends views_handler_area { // Prepare a display settings array. $display = array( - 'label' => 'hidden', - 'type' => 'commerce_price_formatted_components', + 'label' => empty($this->options['label_display']) ? 'hidden' : $this->options['label_position'], + 'type' => $this->options['formatter'], 'settings' => array( 'calculation' => FALSE, ), @@ -45,6 +96,9 @@ class commerce_order_handler_area_order_total extends views_handler_area { // Render the order's order total field with the current display. $field = field_view_field('commerce_order', $order, 'commerce_order_total', $display); + if (!empty($this->options['label_display'])) { + $field['#title'] = $this->options['label_custom']; + } return '
        ' . drupal_render($field) . '
        '; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order.test index 1164634b..144d96a8 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order.test @@ -106,6 +106,7 @@ class CommerceOrderCRUDTestCase extends CommerceBaseTestCase { $order->mail = $this->generateEmail(); $order->order_number = $this->randomName(10); $order->hostname = $this->randomName(10); + $order->placed = REQUEST_TIME; commerce_order_save($order); $this->assertEqual(token_replace('[commerce-order:order-id]', array('commerce-order' => $order)), $order->order_id, '[commerce-order:order-id] was replaced with the order ID.'); @@ -118,6 +119,7 @@ class CommerceOrderCRUDTestCase extends CommerceBaseTestCase { $this->assertEqual(token_replace('[commerce-order:owner:uid]', array('commerce-order' => $order)), $order->uid, '[commerce-order:owner:uid] was replaced with the uid of the owner.'); $this->assertEqual(token_replace('[commerce-order:owner:name]', array('commerce-order' => $order)), check_plain(format_username($creator)), '[commerce-order:owner:name] was replaced with the name of the owner.'); $this->assertEqual(token_replace('[commerce-order:created]', array('commerce-order' => $order)), format_date($order->created, 'medium'), '[commerce-order:created] was replaced with the created date.'); + $this->assertEqual(token_replace('[commerce-order:placed]', array('commerce-order' => $order)), format_date($order->placed, 'medium'), '[commerce-order:placed] was replaced with the placed date.'); $this->assertEqual(token_replace('[commerce-order:changed]', array('commerce-order' => $order)), format_date($order->changed, 'medium'), '[commerce-order:changed] was replaced with the changed date.'); } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order_ui.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order_ui.test index 969fed63..692bbd38 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order_ui.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/order/tests/commerce_order_ui.test @@ -103,7 +103,8 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { $edit = array( 'name' => '', 'log' => $this->randomName(), - 'date' => format_date($timestamp, 'custom', 'Y-m-d H:i:s O'), + 'date_created' => format_date($timestamp, 'custom', 'Y-m-d H:i:s O'), + 'date_placed' => format_date($timestamp, 'custom', 'Y-m-d H:i:s O'), 'status' => 'completed', ); @@ -120,13 +121,15 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { $this->assertTrue($order_wrapper->uid->value() == 0, t('Order owner correctly updated')); $this->assertTrue($order->log == $edit['log'], t('Order log correctly updated')); $this->assertTrue($order_wrapper->created->value() == $timestamp, t('Order created date correctly updated')); + $this->assertTrue($order_wrapper->placed->value() == $timestamp, t('Order placed date correctly updated')); $this->assertTrue($order_wrapper->status->value() == $edit['status'], t('Order status correctly updated')); // Check if the values have been changed. Log is not checked because it // is a message for each revision. $this->pass(t('Order in screen assertions:')); $this->assertFieldById('edit-name', $edit['name'], t('Name correctly modified')); - $this->assertFieldById('edit-date', $edit['date'], t('Date changed correctly')); + $this->assertFieldById('edit-date-created', $edit['date_created'], t('Created date changed correctly')); + $this->assertFieldById('edit-date-placed', $edit['date_placed'], t('Placed date changed correctly')); $this->assertOptionSelected('edit-status', $edit['status'], t('Status changed')); } @@ -157,7 +160,7 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { // Check if the product has been added to the order. foreach (entity_metadata_wrapper('commerce_order', reset($order))->commerce_line_items as $delta => $line_item_wrapper) { - if ($line_item_wrapper->type->value() == 'product') { + if ($line_item_wrapper->getBundle() == 'product') { $product = $line_item_wrapper->commerce_product->value(); $products[$product->product_id]= $product; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.api.php index 539476b7..fe35ffe1 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.api.php @@ -142,6 +142,8 @@ function hook_commerce_payment_totals_row_info_alter(&$rows, $totals, $order) { * of the payment method. * - redirect_form: the name of the CALLBACK_commerce_payment_method_redirect_form() * of the payment method. + * - redirect_form_back: the name of the CALLBACK_commerce_payment_method_redirect_form_back() + * of the payment method. * - redirect_form_validate: the name of the CALLBACK_commerce_payment_method_redirect_form_validate() * of the payment method. * - redirect_form_submit: the name of the CALLBACK_commerce_payment_method_redirect_form_submit() @@ -458,3 +460,50 @@ function hook_commerce_payment_transaction_status_info() { return $statuses; } + +/** + * Allows you to act when a transaction is updated. + * + * If you want to perform an action when a transaction changes states, you can + * use this hook to do so. You can load the original transaction here and + * compare. + * + * @param $transaction + * The transaction being updated. + */ +function hook_commerce_payment_transaction_update($transaction) { + // No example. +} + +/** + * Allows you to act when a transaction is created. + * + * @param $transaction + * The transaction being created. + */ +function hook_commerce_payment_transaction_insert($transaction) { + // No example. +} + +/** + * Allows you to act when a transaction is being deleted. + * + * @param $transaction + * The transaction being deleted. + */ +function hook_commerce_payment_transaction_delete($transaction) { + // No example. +} + +/** + * Allows you to act when transactions are being loaded. + * + * @param $transactions + * An array of transactions indexed by transaction_id. + */ +function hook_commerce_payment_transaction_load($transactions) { + $result = db_query('SELECT transaction_id, foo FROM {mytable} WHERE transaction_ids IN (:transaction_ids)', array(':transaction_ids' => array_keys($transactions))); + foreach ($result as $record) { + $transactions[$record->transaction_id]->foo = $record->foo; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.info index ac7eb7f9..0f2dbecb 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.info @@ -28,11 +28,11 @@ files[] = includes/views/handlers/commerce_payment_handler_field_balance.inc ; Tests files[] = tests/commerce_payment.rules.test -;files[] = tests/commerce_payment.test +files[] = tests/commerce_payment_credit_card.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.install b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.install index 683126e6..c56fa89a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.install @@ -293,3 +293,19 @@ function commerce_payment_update_7102() { return t('Schema for the commerce_payment_transaction table has been updated.'); } + +/** + * Disable the payment checkout pane on the review form to preserve prior behavior. + */ +function commerce_payment_update_7103() { + if (!db_table_exists('commerce_checkout_pane')) { + return t('Checkout module is disabled, skipping update.'); + } + + db_update('commerce_checkout_pane') + ->fields(array('review' => 0)) + ->condition('pane_id', 'commerce_payment') + ->execute(); + + return t('Payment checkout pane disabled on the review form; enable it if desired in the Payment checkout pane settings form.'); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.module index 6aae93cb..af3fa38e 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.module @@ -612,12 +612,14 @@ function commerce_payment_method_get_title($title = 'title', $method = NULL) { } } - // Otherwise turn the array values into the specified title only. + // Otherwise create an array of all payment method titles. + $titles = array(); + foreach ($payment_methods as $key => $value) { - $payment_methods[$key] = $value[$title]; + $titles[$key] = $value[$title]; } - return $payment_methods; + return $titles; } /** @@ -656,7 +658,7 @@ function commerce_payment_method_callback($payment_method, $callback) { // If the specified callback function exists, return it. if (!empty($payment_method['callbacks'][$callback]) && - function_exists($payment_method['callbacks'][$callback])) { + is_callable($payment_method['callbacks'][$callback])) { return $payment_method['callbacks'][$callback]; } @@ -1160,3 +1162,75 @@ function commerce_payment_transaction_set_properties($transaction, $name, $value break; } } + +/** + * Implements hook_entity_load(). + * + * Override payment method settings on the fly. + */ +function commerce_payment_entity_load($entities, $type) { + if ($type != 'rules_config') { + return; + } + foreach ($entities as $entity) { + if (!$entity instanceof RulesTriggerableInterface) { + continue; + } + + $events = $entity->events(); + // Check for the "Select available payment methods for an order" event. + if (!in_array('commerce_payment_methods', $events)) { + continue; + } + + // Iterate over the actions to find one with the matching element ID and + // check if we have payment settings override. + foreach ($entity->actions() as $name => $action) { + if (is_callable(array($action, 'getElementName')) && strpos($action->getElementName(), 'commerce_payment_enable_') === 0) { + if (is_array($action->settings['payment_method']) && !empty($action->settings['payment_method']['settings'])) { + $instance_id = commerce_payment_method_instance_id($action->settings['payment_method']['method_id'], $entity); + $settings = variable_get($instance_id, array()); + + if (!empty($settings) && is_array($settings)) { + $action->settings['payment_method']['settings'] = array_replace_recursive($action->settings['payment_method']['settings'], $settings); + $action->processSettings(TRUE); + } + } + } + } + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Disable payment method settings form elements that are overridden. + */ +function commerce_payment_form_rules_ui_edit_element_alter(&$form, &$form_state) { + if (!isset($form['parameter']['payment_method'])) { + return; + } + // Retrieve the Rule from the $form_state. + $rule = reset($form_state['build_info']['args']); + $payment_method = &$form['parameter']['payment_method']['settings']['payment_method']; + $instance_id = commerce_payment_method_instance_id($payment_method['method_id']['#value'], $rule); + $settings = variable_get($instance_id, array()); + + if (!empty($settings) && is_array($settings)) { + $display_override_message = FALSE; + + foreach (element_get_visible_children($payment_method['settings']) as $key) { + if (isset($settings[$key]) && isset($payment_method['settings'][$key])) { + $payment_method['settings'][$key]['#disabled'] = TRUE; + $payment_method['settings'][$key]['#required'] = FALSE; + $display_override_message = TRUE; + } + } + + // Display a warning message to warn the user about the settings + // that are overridden. + if ($display_override_message) { + drupal_set_message(t('Disabled fields below have been configured in code and cannot be changed here.'), 'warning'); + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.tokens.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.tokens.inc index 22461123..85130190 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.tokens.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment.tokens.inc @@ -38,6 +38,10 @@ function commerce_payment_token_info() { 'name' => t('Payment method short title'), 'description' => t('The customer oriented short title of the payment method for the payment transaction.'), ); + $transaction['payment-method-display-title'] = array( + 'name' => t('Payment method display title'), + 'description' => t('The checkout form oriented display title of the payment method for the payment transaction.'), + ); $transaction['remote-id'] = array( 'name' => t('Remote ID'), 'description' => t('The remote identifier for the payment transaction.'), @@ -115,7 +119,10 @@ function commerce_payment_token_info_alter(&$data) { 'name' => t('Payment method short title'), 'description' => t('The customer oriented short title of the payment method selected by the customer during checkout.'), ); - + $data['tokens']['commerce-order']['payment-method-display-title'] = array( + 'name' => t('Payment method display title'), + 'description' => t('The checkout form oriented display title of the payment method for the payment transaction.'), + ); } } @@ -164,6 +171,11 @@ function commerce_payment_tokens($type, $tokens, array $data = array(), array $o $replacements[$original] = $sanitize ? check_plain($title) : $title; break; + case 'payment-method-display-title': + $title = commerce_payment_method_get_title('display_title', $transaction->payment_method); + $replacements[$original] = $sanitize ? filter_xss_admin($title) : $title; + break; + case 'remote-id': $replacements[$original] = $sanitize ? check_plain($transaction->remote_id) : $transaction->remote_id; break; @@ -267,6 +279,11 @@ function commerce_payment_tokens($type, $tokens, array $data = array(), array $o $title = commerce_payment_method_get_title('short_title', $method_id); $replacements[$original] = $sanitize ? check_plain($title) : $title; break; + + case 'payment-method-display-title': + $title = commerce_payment_method_get_title('display_title', $method_id); + $replacements[$original] = $sanitize ? filter_xss_admin($title) : $title; + break; } } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment_ui.info index b1d70198..23ee9610 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/commerce_payment_ui.info @@ -12,9 +12,9 @@ configure = admin/commerce/config/payment-methods ; Simple tests files[] = tests/commerce_payment_ui.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.checkout_pane.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.checkout_pane.inc index f5a51b65..7fb5f2dd 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.checkout_pane.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.checkout_pane.inc @@ -140,9 +140,7 @@ function commerce_payment_pane_checkout_form($form, &$form_state, $checkout_pane $pane_form['payment_method']['#default_value'] = $default_value; // Add the payment method specific form elements. - $method_info = $order->payment_methods[$pane_form['payment_method']['#default_value']]; - $payment_method = commerce_payment_method_load($method_info['method_id']); - $payment_method['settings'] = $method_info['settings']; + $payment_method = commerce_payment_method_instance_load($pane_form['payment_method']['#default_value']); if ($callback = commerce_payment_method_callback($payment_method, 'submit_form')) { $pane_form['payment_details'] = $callback($payment_method, $pane_values, $checkout_pane, $order); @@ -289,6 +287,30 @@ function commerce_payment_pane_checkout_form_submit($form, &$form_state, $checko } } +/** + * Payment pane: review callback. + */ +function commerce_payment_pane_review($form, $form_state, &$checkout_pane, $order) { + // Update the checkout pane info array passed in to give it a more specific + // title in the review table. + $checkout_pane['title'] = t('Payment method'); + + // Load the payment method instance selected in the review pane to return the + // display title of a selected payment method. + if (!empty($order->data['payment_method'])) { + $payment_method = commerce_payment_method_instance_load($order->data['payment_method']); + return $payment_method['display_title']; + } + elseif (in_array(variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE), array(COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE, COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT))) { + // In the event that the order does not have a selected payment method and + // the checkout pane is configured to display a message that payment is not + // required, show a similar message here. + return t('Payment not required'); + } + + return NULL; +} + /** * Payment redirect pane: form callback. */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.credit_card.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.credit_card.inc index 48989215..a89ec384 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.credit_card.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment.credit_card.inc @@ -10,10 +10,23 @@ * incorporate into their submission form callbacks. * * @param $fields - * An array specifying the CC fields that should be included on the form; the - * card number and expiration date fields are always present. + * An associative array specifying the fields that should be included on the + * credit card form. The card number and expiration fields are always present, + * and fields whose array keys listed below aren't set will be left out of the + * credit card form: + * - type: an array identifying supported card types using the keys of the + * return array from commerce_payment_credit_card_types(). + * - owner: TRUE to include an owner name textfield. + * - start_date: TRUE to include start date select lists. + * - issue: boolean that when present enables an issue number field; if TRUE, + * makes the field required; if FALSE, makes the field optional. + * - code: text label to use for a security code / CVV textfield. + * - bank: TRUE to include a bank name textfield. * @param $default * An array of default values for the available CC fields. + * + * @return + * A credit card form array for use in another form. */ function commerce_payment_credit_card_form($fields = array(), $default = array()) { // Merge default values into the default array. @@ -225,13 +238,13 @@ function commerce_payment_credit_card_validate($details, $settings) { } // Validate the security code if present. - if (isset($details['code']) && !commerce_payment_validate_credit_card_security_code($details['number'], $details['code'])) { + if (!empty($details['code']) && !commerce_payment_validate_credit_card_security_code($details['number'], $details['code'])) { form_set_error($prefix . 'code', t('You have entered an invalid card security code.')); $valid = FALSE; } // Validate the start date if present. - if (isset($details['start_date']) && ($invalid = commerce_payment_validate_credit_card_start_date($details['exp_month'], $details['exp_year'])) !== TRUE) { + if (isset($details['start_month']) && ($invalid = commerce_payment_validate_credit_card_start_date($details['start_month'], $details['start_year'])) !== TRUE) { form_set_error($prefix . 'start_' . $invalid, t('Your have entered an invalid start date.')); $valid = FALSE; } @@ -248,9 +261,9 @@ function commerce_payment_credit_card_validate($details, $settings) { /** * Validates a credit card number using an array of approved card types. * - * @param $number + * @param int $number * The credit card number to validate. - * @param $card_types + * @param array $card_types * An array of credit card types containing any of the keys from the array * returned by commerce_payment_credit_card_types(). Only numbers determined * to be of the types specified will pass validation. This determination is @@ -259,149 +272,19 @@ function commerce_payment_credit_card_validate($details, $settings) { * * @return * FALSE if a number is not valid based on approved credit card types or the - * credit card type if it is valid and coud be determined. + * credit card type if it is valid and could be determined. * * @see http://en.wikipedia.org/wiki/Bank_card_number#Issuer_Identification_Number_.28IIN.29 * @see commerce_payment_credit_card_types() */ -function commerce_payment_validate_credit_card_type($number, $card_types) { - $strlen = strlen($number); - - // Provide a check on the first digit (and card length if applicable). - switch (substr($number, 0, 1)) { - case '3': - // American Express begins with 3 and is 15 numbers. - if ($strlen == 15 && in_array('amex', $card_types)) { - return 'amex'; - } - - // JCB begins with 3528-3589 and is 16 numbers. - if ($strlen == 16 && in_array('jcb', $card_types)) { - return 'jcb'; - } - - // Carte Blanche begins with 300-305 and is 14 numbers. - // Diners Club International begins 36 and is 14 numbers. - if ($strlen == 14) { - $initial = (int) substr($number, 0, 3); - - if ($initial >= 300 && $initial <= 305 && in_array('cb', $card_types)) { - return 'cb'; - } - - if (substr($number, 0, 2) == '36' && in_array('dci', $card_types)) { - return 'dci'; - } - } - - return FALSE; - - case '4': - $initial = (int) substr($number, 0, 4); - $return = FALSE; - - if ($strlen == 16) { - // Visa begins with 4 and is 16 numbers. - if (in_array('visa', $card_types)) { - $return = 'visa'; - } - - // Visa Electron begins with 4026, 417500, 4256, 4508, 4844, 4913, or - // 4917 and is 16 numbers. - if (in_array($initial, array(4026, 4256, 4508, 4844, 4913, 4917)) || substr($number, 0, 6) == '417500') { - $return = in_array('visaelectron', $card_types) ? 'visaelectron' : FALSE; - } - } - - // Switch begins with 4903, 4905, 4911, or 4936 and is 16, 18, or 19 - // numbers. - if (in_array($strlen, array(16, 18, 19)) && - in_array($initial, array(4903, 4905, 4911, 4936))) { - $return = in_array('switch', $card_types) ? 'switch' : FALSE; - } - - return $return; - - case '5': - // MasterCard begins with 51-55 and is 16 numbers. - // Diners Club begins with 54 or 55 and is 16 numbers. - if ($strlen == 16) { - $initial = (int) substr($number, 0, 2); - - if ($initial >= 51 && $initial <= 55 && in_array('mastercard', $card_types)) { - return 'mastercard'; - } - - if ($initial >= 54 && $initial <= 55 && in_array('dc', $card_types)) { - return 'dc'; - } - } - - // Switch begins with 564182 and is 16, 18, or 19 numbers. - if (in_array('switch', $card_types) && substr($number, 0, 6) == '564182' && - in_array($strlen, array(16, 18, 19))) { - return 'switch'; - } - - // Maestro begins with 5018, 5020, or 5038 and is 12-19 numbers. - if (in_array('maestro', $card_types) && $strlen >= 12 && $strlen <= 19 && - in_array(substr($number, 0, 4), array(5018, 5020, 5038))) { - return 'maestro'; - } - - return FALSE; - - case '6': - // Discover begins with 6011, 622126-622925, 644-649, or 65 and is 16 - // numbers. - if ($strlen == 16 && in_array('discover', $card_types)) { - if (substr($number, 0, 4) == '6011' || substr($number, 0, 2) == '65') { - return 'discover'; - } - - $initial = (int) substr($number, 0, 6); +function commerce_payment_validate_credit_card_type($number, array $card_types = array()) { + $type = CommercePaymentCreditCard::detectType($number); - if ($initial >= 622126 && $initial <= 622925) { - return 'discover'; - } - - $initial = (int) substr($number, 0, 3); - - if ($initial >= 644 && $initial <= 649) { - return 'discover'; - } - } - - // Laser begins with 6304, 6706, 6771, or 6709 and is 16-19 numbers. - $initial = (int) substr($number, 0, 4); - - if (in_array('laser', $card_types) && $strlen >= 16 && $strlen <= 19 && - in_array($initial, array(6304, 6706, 6771, 6709))) { - return 'laser'; - } - - // Maestro begins with 6304, 6759, 6761, or 6763 and is 12-19 numbers. - if (in_array('maestro', $card_types) && $strlen >= 12 && $strlen <= 19 && - in_array($initial, array(6304, 6759, 6761, 6763))) { - return 'maestro'; - } - - // Solo begins with 6334 or 6767 and is 16, 18, or 19 numbers. - if (in_array('solo', $card_types) && in_array($strlen, array(16, 18, 19)) && - in_array($initial, array(6334, 6767))) { - return 'solo'; - } - - // Switch begins with 633110, 6333, or 6759 and is 16, 18, or 19 numbers. - if (in_array('switch', $card_types) && in_array($strlen, array(16, 18, 19)) && - (in_array($initial, array(6333, 6759)) || substr($number, 0, 6) == 633110)) { - return 'switch'; - } - - return FALSE; + if (!$type || !in_array($type['id'], $card_types)) { + return FALSE; } - return FALSE; + return $type['id']; } /** @@ -416,38 +299,16 @@ function commerce_payment_validate_credit_card_type($number, $card_types) { * @see http://www.merriampark.com/anatomycc.htm */ function commerce_payment_validate_credit_card_number($number) { - // Ensure every character in the number is numeric. - if (!ctype_digit($number)) { - return FALSE; - } - - // Validate the number using the Luhn algorithm. - $total = 0; - - for ($i = 0; $i < strlen($number); $i++) { - $digit = substr($number, $i, 1); - if ((strlen($number) - $i - 1) % 2) { - $digit *= 2; - if ($digit > 9) { - $digit -= 9; - } - } - $total += $digit; - } - - if ($total % 10 != 0) { - return FALSE; - } - - return TRUE; + $type = CommercePaymentCreditCard::detectType($number); + return CommercePaymentCreditCard::validateNumber($number, $type); } /** * Validates a credit card start date. * - * @param $month + * @param int $month * The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12. - * @param $year + * @param int $year * The 4-digit numeric representation of the year, i.e. 2010. * * @return @@ -483,9 +344,9 @@ function commerce_payment_validate_credit_card_start_date($month, $year) { /** * Validates a credit card expiration date. * - * @param $month + * @param int $month * The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12. - * @param $year + * @param int $year * The 4-digit numeric representation of the year, i.e. 2010. * * @return @@ -493,20 +354,7 @@ function commerce_payment_validate_credit_card_start_date($month, $year) { * which value should receive the error. */ function commerce_payment_validate_credit_card_exp_date($month, $year) { - if ($month < 1 || $month > 12) { - return 'month'; - } - - if ($year < date('Y')) { - return 'year'; - } - elseif ($year == date('Y')) { - if ($month < date('n')) { - return 'month'; - } - } - - return TRUE; + return CommercePaymentCreditCard::validateExpirationDate($month, $year); } /** @@ -532,45 +380,271 @@ function commerce_payment_validate_credit_card_issue($issue) { * TRUE or FALSE indicating the security code's validity. */ function commerce_payment_validate_credit_card_security_code($number, $code) { - // Ensure the code is numeric. - if (!ctype_digit($code)) { - return FALSE; - } - - // Check the length based on the type of the credit card. - switch (substr($number, 0, 1)) { - case '3': - if (strlen($number) == 15) { - return strlen($code) == 4; - } - else { - return strlen($code) == 3; - } - - case '4': - case '5': - case '6': - return strlen($code) == 3; - } + $type = CommercePaymentCreditCard::detectType($number); + return CommercePaymentCreditCard::validateSecurityCode($code, $type); } /** * Returns an associative array of credit card types. + * + * Provides BC layer for CommercePaymentCreditCard::getTypeLabels(). + * + * @see CommercePaymentCreditCard::getTypeLabels() + * + * @return array + * An array keyed by card type with card type label. */ function commerce_payment_credit_card_types() { - return array( - 'visa' => t('Visa'), - 'mastercard' => t('MasterCard'), - 'amex' => t('American Express'), - 'discover' => t('Discover Card'), - 'dc' => t("Diners Club"), - 'dci' => t("Diners Club International"), - 'cb' => t("Carte Blanche"), - 'jcb' => t('JCB'), - 'maestro' => t('Maestro'), - 'visaelectron' => t('Visa Electron'), - 'laser' => t('Laser'), - 'solo' => t('Solo'), - 'switch' => t('Switch'), - ); + return CommercePaymentCreditCard::getTypeLabels(); +} + +/** + * Provides logic for listing card types and validating card details. + */ +final class CommercePaymentCreditCard { + + /** + * Gets all available credit card types. + * + * @return array + * The credit card types. + */ + public static function getTypes() { + $definitions = array( + 'visa' => array( + 'id' => 'visa', + 'label' => t('Visa'), + 'number_prefixes' => array('4'), + ), + 'mastercard' => array( + 'id' => 'mastercard', + 'label' => t('MasterCard'), + 'number_prefixes' => array('51-55', '222100-272099'), + ), + 'maestro' => array( + 'id' => 'maestro', + 'label' => t('Maestro'), + 'number_prefixes' => array( + '5018', '5020', '5038', '5612', '5893', '6304', + '6759', '6761', '6762', '6763', '0604', '6390', + ), + 'number_lengths' => array(12, 13, 14, 15, 16, 17, 18, 19), + ), + 'amex' => array( + 'id' => 'amex', + 'label' => t('American Express'), + 'number_prefixes' => array('34', '37'), + 'number_lengths' => array(15), + 'security_code_length' => 4, + ), + 'dc' => array( + 'id' => 'dc', + 'label' => t('Diners Club'), + 'number_prefixes' => array('300-305', '309', '36', '38', '39'), + 'number_lengths' => array(14), + ), + 'discover' => array( + 'id' => 'discover', + 'label' => t('Discover Card'), + 'number_prefixes' => array('6011', '622126-622925', '644-649', '65'), + 'number_lengths' => array(16, 19), + ), + 'jcb' => array( + 'id' => 'jcb', + 'label' => t('JCB'), + 'number_prefixes' => array('3528-3589'), + ), + 'unionpay' => array( + 'id' => 'unionpay', + 'label' => t('UnionPay'), + 'number_prefixes' => array('62', '88'), + 'number_lengths' => array(16, 17, 18, 19), + 'uses_luhn' => FALSE, + ), + ); + + foreach ($definitions as &$definition) { + $definition += array( + 'number_lengths' => array(16), + 'security_code_length' => 3, + 'uses_luhn' => TRUE, + ); + } + + return $definitions; + } + + /** + * Gets the labels of all available credit card types. + * + * @return array + * The labels, keyed by ID. + */ + public static function getTypeLabels() { + $type_labels = array(); + + foreach (self::getTypes() as $type) { + $type_labels[$type['id']] = $type['label']; + } + + return $type_labels; + } + + /** + * Detects the credit card type based on the number. + * + * @param string $number + * The credit card number. + * + * @return array|false + * The credit card type, or NULL if unknown. + */ + public static function detectType($number) { + if (!is_numeric($number)) { + return FALSE; + } + $types = self::getTypes(); + foreach ($types as $type) { + foreach ($type['number_prefixes'] as $prefix) { + if (self::matchPrefix($number, $prefix)) { + return $type; + } + } + } + + return FALSE; + } + + /** + * Checks whether the given credit card number matches the given prefix. + * + * @param string $number + * The credit card number. + * @param string $prefix + * The prefix to match against. Can be a single number such as '43' or a + * range such as '30-35'. + * + * @return bool + * TRUE if the credit card number matches the prefix, FALSE otherwise. + */ + public static function matchPrefix($number, $prefix) { + if (is_numeric($prefix)) { + return substr($number, 0, strlen($prefix)) == $prefix; + } + else { + list($start, $end) = explode('-', $prefix); + $number = substr($number, 0, strlen($start)); + return $number >= $start && $number <= $end; + } + } + + /** + * Validates the given credit card number. + * + * @param string $number + * The credit card number. + * @param array $type + * The credit card type. + * + * @return bool + * TRUE if the credit card number is valid, FALSE otherwise. + */ + public static function validateNumber($number, array $type) { + if (!is_numeric($number)) { + return FALSE; + } + if (!in_array(strlen($number), $type['number_lengths'])) { + return FALSE; + } + if ($type['uses_luhn'] && !self::validateLuhn($number)) { + return FALSE; + } + + return TRUE; + } + + /** + * Validates the given credit card number using the Luhn algorithm. + * + * @param string $number + * The credit card number. + * + * @return bool + * TRUE if the credit card number is valid, FALSE otherwise. + */ + public static function validateLuhn($number) { + $total = 0; + foreach (array_reverse(str_split($number)) as $i => $digit) { + $digit = $i % 2 ? $digit * 2 : $digit; + $digit = $digit > 9 ? $digit - 9 : $digit; + $total += $digit; + } + return ($total % 10 === 0); + } + + /** + * Validates the given credit card expiration date. + * + * @param string $month + * The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12. + * @param string $year + * The 4-digit numeric representation of the year, i.e. 2010. + * + * @return bool + * TRUE if the credit card expiration date is valid, FALSE otherwise. + */ + public static function validateExpirationDate($month, $year) { + if ($month < 1 || $month > 12) { + return FALSE; + } + if ($year < date('Y')) { + return FALSE; + } + elseif ($year == date('Y') && $month < date('n')) { + return FALSE; + } + + return TRUE; + } + + /** + * Calculates the unix timestamp for a credit card expiration date. + * + * @param string $month + * The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12. + * @param string $year + * The 4-digit numeric representation of the year, i.e. 2010. + * + * @return int + * The expiration date as a unix timestamp. + */ + public static function calculateExpirationTimestamp($month, $year) { + // Credit cards expire on the last day of the month. + $month_start = strtotime($year . '-' . $month . '-01'); + $last_day = date('t', $month_start); + return strtotime($year . '-' . $month . '-' . $last_day); + } + + /** + * Validates the given credit card security code. + * + * @param string $security_code + * The credit card security code. + * @param array $type + * The credit card type. + * + * @return bool + * TRUE if the credit card security code is valid, FALSE otherwise. + */ + public static function validateSecurityCode($security_code, array $type) { + if (!is_numeric($security_code)) { + return FALSE; + } + if (strlen($security_code) != $type['security_code_length']) { + return FALSE; + } + + return TRUE; + } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment_ui.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment_ui.admin.inc index 71bb1b99..0d6c508c 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment_ui.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/includes/commerce_payment_ui.admin.inc @@ -50,7 +50,7 @@ function commerce_payment_ui_admin_page() { // Loop over the actions to find the first enabled payment method. foreach ($rule->actions() as $action) { // Parse the action name into a payment method ID. - if (strpos($action->getElementName(), 'commerce_payment_enable_') === 0) { + if (method_exists($action, 'getElementName') && strpos($action->getElementName(), 'commerce_payment_enable_') === 0) { $method_id = drupal_substr($action->getElementName(), 24); // Load the payment method instance and determine availability. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/modules/commerce_payment_example.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/modules/commerce_payment_example.info index cbdce7ff..74f38f4e 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/modules/commerce_payment_example.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/modules/commerce_payment_example.info @@ -5,9 +5,9 @@ dependencies[] = commerce dependencies[] = commerce_payment core = 7.x -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_credit_card.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_credit_card.test new file mode 100644 index 00000000..72573f15 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_credit_card.test @@ -0,0 +1,122 @@ + 'Credit card detection testing', + 'description' => 'Tests credit card definitions.', + 'group' => 'Drupal Commerce', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); + } + + /** + * Tests type detection. + */ + public function testValidateNumber() { + // Structure: Number, Type, Valid. + $data = array( + // Non-numeric value. + array('invalid', NULL, FALSE), + // Invalid length. + array(41111111111111111, 'visa', FALSE), + // Fails luhn check. + array(41111111111111112, 'visa', FALSE), + // Valid numbers. + array(4111111111111111, 'visa', TRUE), + array(6759649826438453, 'maestro', TRUE), + array(3528000700000000, 'jcb', TRUE), + array(5555555555554444, 'mastercard', TRUE), + array(36700102000000, 'dc', TRUE), + array(30569309025904, 'dc', TRUE), + array(38520000023237, 'dc', TRUE), + array(6011000400000000, 'discover', TRUE), + array(6208205838887174, 'unionpay', TRUE), + array(374251018720018, 'amex', TRUE), + // Visa Electron card. + array(4917300800000000, 'visa', TRUE), + ); + + foreach ($data as $datum) { + $type = CommercePaymentCreditCard::detectType($datum[0]); + if (!$type) { + $this->assertEqual(NULL, $datum[1]); + } + else { + $this->assertEqual($type['id'], $datum[1]); + $this->assertEqual($datum[2], CommercePaymentCreditCard::validateNumber($datum[0], $type)); + } + } + } + + /** + * Tests expiration date validation. + */ + public function testValidateExpirationDate() { + $data = array( + // Invalid month. + array(0, 2020, FALSE), + array(13, 2020, FALSE), + // Invalid year. + array(10, 2012, FALSE), + // Valid month and year. + array(date('n'), date('Y'), TRUE), + ); + + foreach ($data as $datum) { + $result = CommercePaymentCreditCard::validateExpirationDate($datum[0], $datum[1]); + $this->assertEqual($datum[2], $result); + } + } + + /** + * @covers ::calculateExpirationTimestamp + */ + public function testCalculateExpirationTimestamp() { + $timestamp = CommercePaymentCreditCard::calculateExpirationTimestamp(12, 2012); + $date = date('Y-m-d H:i:s', $timestamp); + $expected_date = date('2012-12-31 00:00:00'); + $this->assertEqual($expected_date, $date); + } + + /** + * @covers ::validateSecurityCode + */ + public function testsValidateSecurityCode() { + $data = array( + // Invalid lengths. + array(1, 'visa', FALSE), + array(1111, 'visa', FALSE), + // Non-numeric inputs. + array('llama', 'visa', FALSE), + array('12.4', 'visa', FALSE), + // Valid number. + array(111, 'visa', TRUE), + ); + $types = CommercePaymentCreditCard::getTypes(); + foreach ($data as $datum) { + $result = CommercePaymentCreditCard::validateSecurityCode($datum[0], $types[$datum[1]]); + $this->assertEqual($datum[2], $result); + } + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_dummy_offsite.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_dummy_offsite.info index 3107c221..430b9128 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_dummy_offsite.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/tests/commerce_payment_dummy_offsite.info @@ -5,9 +5,9 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-failure.png b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-failure.png index 22477650..89ef1e41 100644 Binary files a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-failure.png and b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-failure.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-pending.png b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-pending.png index 412db5aa..17af15d7 100644 Binary files a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-pending.png and b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-pending.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-success.png b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-success.png index 95f8730e..24955b7c 100644 Binary files a/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-success.png and b/profiles/commerce_kickstart/modules/contrib/commerce/modules/payment/theme/icon-success.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.info index 31f11e3d..93c3b396 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.info @@ -11,9 +11,9 @@ files[] = commerce_price.rules.inc files[] = includes/views/handlers/commerce_price_handler_field_commerce_price.inc files[] = includes/views/handlers/commerce_price_handler_filter_commerce_price_amount.inc -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.rules.inc index 8168f2a2..dba3903a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/price/commerce_price.rules.inc @@ -54,7 +54,7 @@ class RulesDataUICommercePrice extends RulesDataUI implements RulesDataDirectInp $default_amount = $value['amount']; } else { - commerce_round(COMMERCE_ROUND_HALF_UP, $value['amount']); + $default_amount = commerce_round(COMMERCE_ROUND_HALF_UP, $value['amount']); } // Format the number to the proper decimal places for the textfield. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.info index b287213b..fb807648 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.info @@ -25,9 +25,9 @@ files[] = includes/commerce_product.translation_handler.inc ; Simple tests files[] = tests/commerce_product.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.module index 5a2c1863..24d92cdb 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product.module @@ -510,10 +510,20 @@ function commerce_product_load($product_id) { /** * Loads a product by SKU. + * + * @param string $sku + * The SKU to load. + * @param bool $reset + * Boolean to reset static cache. */ -function commerce_product_load_by_sku($sku) { - $products = commerce_product_load_multiple(array(), array('sku' => $sku)); - return $products ? reset($products) : FALSE; +function commerce_product_load_by_sku($sku, $reset = FALSE) { + $cache = &drupal_static(__FUNCTION__, array()); + if (!isset($cache[$sku]) || $reset) { + $products = commerce_product_load_multiple(array(), array('sku' => $sku)); + $cache[$sku] = $products ? reset($products) : FALSE; + } + + return $cache[$sku]; } /** @@ -691,7 +701,7 @@ function commerce_product_validate_sku_unique($sku, $product_id) { */ function commerce_product_validate_sku($sku) { // Do not allow commas in a SKU. - if (strpos($sku, ',')) { + if (strpos($sku, ',') !== FALSE) { return FALSE; } @@ -727,10 +737,7 @@ function commerce_product_set_properties($product, $name, $value) { * The values returned will be keyed by SKU and appear as such in the textfield, * even though the preview in the autocomplete list shows "SKU: Title". */ -function commerce_product_autocomplete($entity_type, $field_name, $bundle, $string = '') { - $field = field_info_field($field_name); - $instance = field_info_instance($entity_type, $field_name, $bundle); - +function commerce_product_autocomplete($entity_type = 'commerce_product', $field_name = NULL, $bundle = NULL, $string = '') { $matches = array(); // Extract the SKU string from the URL while preserving support for SKUs that @@ -757,11 +764,14 @@ function commerce_product_autocomplete($entity_type, $field_name, $bundle, $stri if (!empty($tag_last)) { $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : ''; + $field = field_info_field($field_name); + $instance = field_info_instance($entity_type, $field_name, $bundle); + // Determine the type of autocomplete match to use when searching for products. $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; // Get an array of matching products. - $products = commerce_product_match_products($field, $instance, $tag_last, $match, array(), 10); + $products = commerce_product_match_products($field, $instance, $tag_last, $match, array(), 10, TRUE); // Loop through the products and convert them into autocomplete output. foreach ($products as $product_id => $data) { @@ -822,7 +832,27 @@ function commerce_product_match_products($field, $instance = NULL, $string = '', )); if (!isset($results[$cid])) { - $matches = _commerce_product_match_products_standard($instance, $string, $match, $ids, $limit, $access_tag); + // If the variable commerce_product_match_using_efq has been set, then + // process the match using an EntityFieldQuery instead of db_select(). EFQ + // was made the only matching query builder in Commerce 1.10, but this + // introduced a scalability concern as it required the use of a multiple + // product load to build the return value instead of directly querying the + // title / SKU from the local database. + // Commerce 1.12 then introduced a breaking change by attempting to resolve + // the scalability issue by changing the options list limit from unlimited + // to 10 by default. With no clear path to resolve all concerns, we opted to + // make the routine alterable via configuration for Commerce 1.14. + // The recommended method to select the EFQ based callback instead of this + // one is to set the variable directly in the $conf array in your + // settings.php file. Additionally, you may need to set the product + // reference options list limit variable as explained in the comments + // inline in commerce_product_reference_options_list(). + if (variable_get('commerce_product_match_products_efq', FALSE)) { + $matches = _commerce_product_match_products_efq($instance, $string, $match, $ids, $limit, $access_tag); + } + else { + $matches = _commerce_product_match_products_standard($instance, $string, $match, $ids, $limit, $access_tag); + } // Store the results. $results[$cid] = !empty($matches) ? $matches : array(); @@ -837,6 +867,98 @@ function commerce_product_match_products($field, $instance = NULL, $string = '', * Returns an array of products matching the specific parameters. */ function _commerce_product_match_products_standard($instance, $string = '', $match = 'contains', $ids = array(), $limit = NULL, $access_tag = FALSE) { + // Build the query object with the necessary fields. + $query = db_select('commerce_product', 'cp'); + + // Add the access control tag if specified. + if ($access_tag) { + $query->addTag('commerce_product_access'); + } + + // Add a global query tag so anyone can alter this query. + $query->addTag('commerce_product_match'); + + $product_id_alias = $query->addField('cp', 'product_id'); + $product_sku_alias = $query->addField('cp', 'sku'); + $product_title_alias = $query->addField('cp', 'title'); + $product_type_alias = $query->addField('cp', 'type'); + + // Add a condition to the query to filter by matching product types. + if (!empty($instance['settings']['referenceable_types'])) { + $types = array_diff(array_values($instance['settings']['referenceable_types']), array(0, NULL)); + + // Only filter by type if some types have been specified. + if (!empty($types)) { + $query->condition('cp.type', $types, 'IN'); + } + } + + if ($string !== '') { + $args = array(); + + // Build a where clause matching on either the SKU or title. + switch ($match) { + case 'contains': + $where = '(cp.sku LIKE :sku_match OR cp.title LIKE :title_match)'; + $args['sku_match'] = '%' . $string . '%'; + $args['title_match'] = '%' . $string . '%'; + break; + + case 'starts_with': + $where = '(cp.sku LIKE :sku_match OR cp.title LIKE :title_match)'; + $args['sku_match'] = $string . '%'; + $args['title_match'] = $string . '%'; + break; + + case 'equals': + default: + $where = '(cp.sku = :sku_match OR cp.title = :title_match)'; + $args['sku_match'] = $string; + $args['title_match'] = $string; + break; + } + + $query->where($where, $args); + } + elseif ($ids) { + // Otherwise add a product_id specific condition if specified. + $query->condition($product_id_alias, $ids, 'IN', $ids); + } + + // Order the results by SKU, title, and then product type. + $query + ->orderBy($product_sku_alias) + ->orderBy($product_title_alias) + ->orderBy($product_type_alias); + + // Add a limit if specified. + if ($limit) { + $query->range(0, $limit); + } + + // Execute the query and build the results array. + $result = $query->execute(); + + $matches = array(); + + foreach ($result->fetchAll() as $product) { + $matches[$product->product_id] = array( + 'sku' => $product->sku, + 'type' => $product->type, + 'title' => $product->title, + 'rendered' => t('@sku: @title', array('@sku' => $product->sku, '@title' => $product->title)), + ); + } + + return $matches; +} + +/** + * Helper function for commerce_product_match_products(). + * + * Returns an array of products matching the specific parameters via EFQ. + */ +function _commerce_product_match_products_efq($instance, $string = '', $match = 'contains', $ids = array(), $limit = NULL, $access_tag = FALSE) { $query = new EntityFieldQuery; $query->entityCondition('entity_type', 'commerce_product'); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product_ui.info index 50ec75ee..95b18dbb 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/commerce_product_ui.info @@ -12,9 +12,9 @@ configure = admin/commerce/products/types ; Simple tests files[] = tests/commerce_product_ui.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.controller.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.controller.inc index f34ef109..51d60dab 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.controller.inc @@ -116,6 +116,9 @@ class CommerceProductEntityController extends DrupalCommerceEntityController { } } + // Reset load by SKU static cache. + drupal_static_reset('commerce_product_load_by_sku'); + return parent::save($product, $transaction); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.forms.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.forms.inc index 5cf0b07f..711c0553 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.forms.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/includes/commerce_product.forms.inc @@ -140,6 +140,9 @@ function commerce_product_product_form_validate($form, &$form_state) { form_set_error('sku', t('The SKU contains invalid tokens.')); } else { + // Trim leading and trailing whitespace from the SKU. + $sku = trim($sku); + // Ensure the proposed SKU is unique or reused only during product updates. $query = new EntityFieldQuery(); @@ -162,8 +165,8 @@ function commerce_product_product_form_validate($form, &$form_state) { form_set_error('sku', t('The SKU %sku contains invalid characters.', array('%sku' => $sku))); } - // Trim leading and trailing whitespace from the SKU. - form_set_value($form['sku'], trim($sku), $form_state); + // Update current SKU with trimmed version. + form_set_value($form['sku'], $sku, $form_state); } // Notify field widgets to validate their data. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/tests/commerce_product_crud_test.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/tests/commerce_product_crud_test.info index 5f7a3a17..a4a846ed 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/tests/commerce_product_crud_test.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product/tests/commerce_product_crud_test.info @@ -6,9 +6,9 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.api.php index f7de9045..7121298b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.api.php @@ -75,3 +75,22 @@ function hook_commerce_product_calculate_sell_price_line_item_alter($line_item) $line_item->order_id = commerce_cart_order_id($user->uid); } } + +/** + * Allow modules to alter the product line item during sell price calculation + * when the "Product pricing process" is configured to either "rules_invoke_all" + * or "module_invoke_all". + * + * @param $line_item + * The product line item used for sell price calculation. + */ +function hook_commerce_product_calculate_sell_price($line_item) { + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $product = $line_item_wrapper->commerce_product->value(); + + // If the product is not published, nullify the line item's unit price. + if (!$product->status) { + $line_item_wrapper->commerce_unit_price->amount = NULL; + return; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.info index be061aa4..5c8dbf34 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.info @@ -8,9 +8,9 @@ dependencies[] = entity dependencies[] = rules core = 7.x -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.module index b460f70f..8e4020ac 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing.module @@ -19,6 +19,9 @@ function commerce_product_pricing_hook_info() { 'commerce_product_calculate_sell_price_line_item_alter' => array( 'group' => 'commerce', ), + 'commerce_product_calculate_sell_price_line_item' => array( + 'group' => 'commerce', + ), ); return $hooks; @@ -88,9 +91,11 @@ function commerce_product_pricing_commerce_price_field_formatter_prepare_view($e } // Replace the data being displayed with data from a calculated price. - $items[$product_id] = array(); - $items[$product_id][0] = commerce_product_calculate_sell_price($product); - $items[$product_id][0]['original'] = $original; + if (!empty($original)) { + $items[$product_id] = array(); + $items[$product_id][0] = commerce_product_calculate_sell_price($product); + $items[$product_id][0]['original'] = $original; + } } } } @@ -146,8 +151,8 @@ function commerce_product_calculate_sell_price($product, $precalc = FALSE) { } } - // Pass the line item to Rules. - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); + // Invoke the callback specified via the "Product pricing process" option. + commerce_product_pricing_invoke($line_item); return entity_metadata_wrapper('commerce_line_item', $line_item)->commerce_unit_price->value(); } @@ -485,3 +490,21 @@ function commerce_product_batch_pre_calculate_sell_prices_finished($success, $re drupal_set_message($message); } + +/** + * Invokes the sell price calculation callback configured via the + * "Price calculation process" option. + * + * @param $line_item + * The product line item used for sell price calculation. + */ +function commerce_product_pricing_invoke($line_item) { + $callback = variable_get('commerce_product_pricing_callback', 'rules_invoke_event'); + + // Defaults to rules_invoke_event if the specified callback doesn't exist. + if (!function_exists($callback)) { + $callback = 'rules_invoke_event'; + } + + $callback('commerce_product_calculate_sell_price', $line_item); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing_ui.info index b1e9a335..71ef2245 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/commerce_product_pricing_ui.info @@ -10,9 +10,9 @@ dependencies[] = commerce_product_reference core = 7.x configure = admin/commerce/config/product-pricing -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/includes/commerce_product_pricing_ui.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/includes/commerce_product_pricing_ui.admin.inc index 426778c3..52392b4f 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/includes/commerce_product_pricing_ui.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_pricing/includes/commerce_product_pricing_ui.admin.inc @@ -79,6 +79,8 @@ function commerce_product_pre_calculation_settings_form($form, &$form_state) { ); } } + // Retrieve the current Product pricing callback currently configured. + $product_pricing_callback = variable_get('commerce_product_pricing_callback', 'rules_invoke_event'); $form['commerce_product_sell_price_pre_calculation'] = array( '#type' => 'radios', @@ -93,6 +95,7 @@ function commerce_product_pre_calculation_settings_form($form, &$form_state) { // 'automated_batch' => t('Manual batch pre-calculation with automated updates'), ), '#default_value' => variable_get('commerce_product_sell_price_pre_calculation', 'disabled'), + '#disabled' => $product_pricing_callback != 'rules_invoke_event', ); // If price pre-calculation is enabled, prevent options to bypass rules. @@ -137,6 +140,29 @@ function commerce_product_pre_calculation_settings_form($form, &$form_state) { } } + $form['commerce_product_pricing_callback'] = array( + '#type' => 'radios', + '#title' => t('Price calculation process'), + '#description' => t('You can only allow modules to directly engage in product pricing when pre-calculation is disabled.'), + '#options' => array( + 'rules_invoke_event' => t('Evaluate pricing rules in order'), + 'rules_invoke_all' => t('Evaluate module based pricing routines then invoke pricing rules in order'), + ), + '#states' => array( + 'visible' => array( + ':input[name="commerce_product_sell_price_pre_calculation"]' => array('value' => 'disabled'), + ), + ), + '#default_value' => $product_pricing_callback, + ); + // If the Product pricing callback configured is set to "module_invoke_all" + // disable the element and update the description. + if ($product_pricing_callback == 'module_invoke_all') { + $form['commerce_product_pricing_callback']['#options']['module_invoke_all'] = t('Only invoke module based pricing routines'); + $form['commerce_product_pricing_callback']['#description'] .= ' ' . t("This value has been set in your site's codebase and cannot be changed via this form."); + $form['commerce_product_pricing_callback']['#disabled'] = TRUE; + } + $form['submit'] = array( '#type' => 'submit', '#value' => t('Save configuration'), @@ -154,8 +180,18 @@ function commerce_product_pre_calculation_settings_form_submit($form, &$form_sta // Save variables on the form regardless of the button pressed. variable_set('commerce_product_sell_price_pre_calculation', $values['commerce_product_sell_price_pre_calculation']); - if (!empty($form_state['values']['commerce_product_sell_price_pre_calculation_rules_bypass'])) { - variable_set('commerce_product_sell_price_pre_calculation_rules_bypass', $form_state['values']['commerce_product_sell_price_pre_calculation_rules_bypass']); + if (!empty($values['commerce_product_sell_price_pre_calculation_rules_bypass'])) { + variable_set('commerce_product_sell_price_pre_calculation_rules_bypass', $values['commerce_product_sell_price_pre_calculation_rules_bypass']); + } + if (!empty($values['commerce_product_pricing_callback'])) { + if ($values['commerce_product_sell_price_pre_calculation'] == 'disabled') { + variable_set('commerce_product_pricing_callback', $values['commerce_product_pricing_callback']); + } + else { + // If the pre calculation is enabled, revert the Product pricing process + // to default. + variable_del('commerce_product_pricing_callback'); + } } // React to the management buttons if they were used to submit the form. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.info index 44e396b8..4c8fb116 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.info @@ -17,9 +17,9 @@ files[] = includes/views/handlers/commerce_product_reference_handler_filter_prod ; Simple tests files[] = tests/commerce_product_reference.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.module b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.module index c9f6ca69..23cef3e2 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/commerce_product_reference.module @@ -32,10 +32,16 @@ function commerce_product_reference_commerce_product_uri($product) { * Implements hook_field_extra_fields(). * * This implementation will define "extra fields" on node bundles with product - * reference fields to correspond with availabled fields on products. These + * reference fields to correspond with available fields on products. These * fields will then also be present in the node view. */ function commerce_product_reference_field_extra_fields() { + $extra = &drupal_static(__FUNCTION__); + + if (isset($extra)) { + return $extra; + } + $extra = array(); // Loop through the product reference fields. @@ -319,6 +325,44 @@ function commerce_product_reference_entity_view($entity, $entity_type, $view_mod return; } + // Determine if the referencing bundle specifies custom settings + // for the view mode currently displayed. + $bundle_settings = field_bundle_settings($entity_type, $bundle); + + if (isset($bundle_settings['view_modes'][$view_mode]) && !empty($bundle_settings['view_modes'][$view_mode]['custom_settings'])) { + $reference_view_mode = $view_mode; + } + else { + $reference_view_mode = 'default'; + } + // Don't try to perform the field injection by default. + $perform_field_injection = FALSE; + + // Retrieve the real extra fields settings. + $display = field_extra_fields_get_display($entity_type, $bundle, $reference_view_mode); + + if (!empty($display)) { + // Loop over the extra fields and see if at least one of the injected field + // is visible. + foreach ($display as $key => $extra_field) { + if (strpos($key, 'product:') !== 0) { + continue; + } + if (!empty($extra_field['visible'])) { + $perform_field_injection = TRUE; + break; + } + } + } + + // Stop here if we don't have any field to inject. + if (!$perform_field_injection) { + return; + } + + // Resolve the product module path for use in the loop below. + $product_module_path = drupal_get_path('module', 'commerce_product'); + // An entity metadata wrapper will be loaded on demand if it is determined // this entity has a product reference field instance attached to it. $wrapper = NULL; @@ -332,35 +376,51 @@ function commerce_product_reference_entity_view($entity, $entity_type, $view_mod $instances = field_info_instances($entity_type, $bundle); } - if (isset($instances[$field_name])) { + // If the reference field enables field injection. + if (isset($instances[$field_name]) && !empty($instances[$field_name]['settings']['field_injection'])) { + // If the field is missing a value, continue to next reference field. + if (empty($entity->{$field_name})) { + continue; + } + // Load a wrapper for the entity being viewed. if (empty($wrapper)) { - $wrapper = entity_metadata_wrapper($entity_type, $entity); + $wrapper = entity_metadata_wrapper($entity_type, $entity)->language($langcode); + } + + // Fetch the product reference field value. + $reference_field_value = $wrapper->{$field_name}->value(); + + // If this field doesn't reference any products, continue to the next one. + if (empty($reference_field_value)) { + continue; } // Find the default product based on the cardinality of the field. $product = NULL; if ($field['cardinality'] == 1) { - $product = $wrapper->{$field_name}->value(); + // The value of single value reference fields is the product object. + $product = $reference_field_value; } - elseif (count($wrapper->{$field_name}) > 0) { - $product = commerce_product_reference_default_product($wrapper->{$field_name}->value()); + else { + // Find the default from the array of all the referenced products. + $product = commerce_product_reference_default_product($reference_field_value); // If the product is disabled, attempt to find one that is active and // use that as the default product instead. if (!empty($product) && $product->status == 0) { - foreach ($wrapper->{$field_name} as $delta => $product_wrapper) { - if ($product_wrapper->status->value() != 0) { - $product = $product_wrapper->value(); + foreach ($reference_field_value as $reference_field_item) { + if (!empty($reference_field_item) && $reference_field_item->status != 0) { + $product = $reference_field_item; break; } } } } - // If we found a product and the reference field enables field injection... - if (!empty($product) && $instances[$field_name]['settings']['field_injection']) { + // If we found a product. + if (!empty($product)) { // Add the display context for these field to the product. $product->display_context = array( 'entity_type' => $entity_type, @@ -424,11 +484,12 @@ function commerce_product_reference_entity_view($entity, $entity_type, $view_mod } } + // Get the extra fields display settings for the current view mode. + $display = field_extra_fields_get_display('commerce_product', $product->type, $reference_view_mode); + // Attach "extra fields" to the bundle representing all the extra fields // currently attached to products. foreach (field_info_extra_fields('commerce_product', $product->type, 'display') as $product_extra_field_name => $product_extra_field) { - $display = field_extra_fields_get_display('commerce_product', $product->type, $reference_view_mode); - // Only include extra fields that specify a theme function and that // are visible on the current view mode. if (!empty($product_extra_field['theme']) && @@ -436,16 +497,13 @@ function commerce_product_reference_entity_view($entity, $entity_type, $view_mod // Add the product extra field to the entity's content array. $content_key = 'product:' . $product_extra_field_name; - $variables = array( - $product_extra_field_name => $product->{$product_extra_field_name}, - 'label' => $product_extra_field['label'] . ':', - 'product' => $product, - ); - $entity->content[$content_key] = array( - '#markup' => theme($product_extra_field['theme'], $variables), + '#theme' => $product_extra_field['theme'], + '#' . $product_extra_field_name => $product->{$product_extra_field_name}, + '#label' => $product_extra_field['label'] . ':', + '#product' => $product, '#attached' => array( - 'css' => array(drupal_get_path('module', 'commerce_product') . '/theme/commerce_product.theme.css'), + 'css' => array($product_module_path . '/theme/commerce_product.theme.css'), ), ); @@ -1023,7 +1081,12 @@ function commerce_product_reference_options_list($field, $instance = NULL) { $limit = (int) $field['settings']['options_list_limit']; } else { - $limit = NULL; + // If there is no such limit, then default it to NULL (i.e. no limit) unless + // a variable has been set that provides an alternate default. This is + // usually required when product matching has been configured to use an + // EntityFieldQuery instead of the standard db_select(). Refer to the inline + // comments in commerce_product_match_products() for more information. + $limit = variable_get('commerce_product_reference_default_options_list_limit', NULL); } // Loop through all product matches. @@ -1355,7 +1418,6 @@ function commerce_product_line_item_populate($line_item, $product) { // Wrap the line item and product to easily set field information. $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $product_wrapper = entity_metadata_wrapper('commerce_product', $product); // Add the product reference value to the line item for the right language. $line_item_wrapper->commerce_product = $product->product_id; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/includes/views/handlers/commerce_product_reference_handler_filter_node_is_product_display.inc b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/includes/views/handlers/commerce_product_reference_handler_filter_node_is_product_display.inc index 93571e86..8de88bcd 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/includes/views/handlers/commerce_product_reference_handler_filter_node_is_product_display.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/includes/views/handlers/commerce_product_reference_handler_filter_node_is_product_display.inc @@ -10,7 +10,18 @@ class commerce_product_reference_handler_filter_node_is_product_display extends function query() { $this->ensure_my_table(); - $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", array_keys(commerce_product_reference_node_types()), $this->value ? 'IN' : 'NOT IN'); + + $node_types = array_keys(commerce_product_reference_node_types()); + + if (!$node_types) { + // instead of: + // WHERE (( (node.type NOT IN ()) )) + // do this: + // WHERE (( (node.type NOT IN ('')) )) + $node_types = array(''); + } + + $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", $node_types, $this->value ? 'IN' : 'NOT IN'); } function ensure_table($table, $relationship) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/tests/commerce_product_reference.test b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/tests/commerce_product_reference.test index a80db2ec..27c43174 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/tests/commerce_product_reference.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/product_reference/tests/commerce_product_reference.test @@ -197,7 +197,7 @@ class CommerceProductReferenceAdminTest extends CommerceBaseTestCase { $product_title = t('@sku: @title', array('@sku' => $new_product->sku, '@title' => $new_product->title)); // Check at database level - $field_products = commerce_product_match_products($this->field_name, NULL, $new_product->sku, 'equals'); + $field_products = commerce_product_match_products($this->field, NULL, $new_product->sku, 'equals'); $this->assertFalse(empty($field_products), t('Product is in the available products of the field')); // Check if it is in the reference field for product displays. @@ -262,8 +262,9 @@ class CommerceProductReferenceAdminTest extends CommerceBaseTestCase { $this->drupalGet('admin/structure/types/manage/' . strtr($this->display_type->type, '_', '-') . '/display'); // Load all the fields that are pulled with the product and check if they // are in the display screen. - $extra_fields = commerce_product_reference_field_extra_fields(); - foreach ($extra_fields['node'][$this->display_type->type]['display'] as $display) { + $extra_fields = field_info_extra_fields('node', $this->display_type->type, 'display'); + + foreach ($extra_fields as $display) { $this->assertText($display['label'], t('Field %field_label is present in the manage display screen', array('%field_label' => $display['label']))); } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.api.php b/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.api.php index 9298c78d..14fc8c1d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.api.php @@ -101,11 +101,24 @@ function hook_commerce_tax_type_delete($tax_type, $skip_reset) { /** * Defines tax rates that may be applied to line items. * + * Modules that integrate third party tax calculation services still need to + * define tax rates that correspond to the price components they use to store + * those calculated taxes on line items. Otherwise modules that attempt to + * communicate the amount of tax on an order to other systems will not get + * accurate total tax amounts. + * + * To do this, a tax rate should be defined that may have a rate of 0 and an + * undefined tax type, but they should likely not specify a default Rules + * component, should not show in the administrative list, and should use a + * calculation callback that simply returns FALSE (unless the tax services + * supports tax calculation on a line item basis as opposed to requiring an + * entire order to return taxes). + * * @return * An array of information about available tax rates. The returned array * should be an associative array of tax rate arrays keyed by the tax rate * name. Each tax rate array can include the following keys: - * - title: the title of the tax rate + * - title: the title of the tax rate; must be defined * - display_title: a display title for the tax type suitable for presenting * to customers if necessary; defaults to the title * - description: a short description of the tax rate diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.info index e1bc6273..f4233b68 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax.info @@ -12,9 +12,9 @@ core = 7.x ; Simple tests ;files[] = tests/commerce_tax.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax_ui.info index 5315bdff..c87040a7 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce/modules/tax/commerce_tax_ui.info @@ -10,9 +10,9 @@ configure = admin/commerce/config/taxes ; Simple tests files[] = tests/commerce_tax_ui.test -; Information added by Drupal.org packaging script on 2015-01-16 -version = "7.x-1.11" +; Information added by Drupal.org packaging script on 2017-11-16 +version = "7.x-1.14" core = "7.x" project = "commerce" -datestamp = "1421426596" +datestamp = "1510870110" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce/tests/commerce_base.test b/profiles/commerce_kickstart/modules/contrib/commerce/tests/commerce_base.test index 1a7c7ee8..44155c02 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce/tests/commerce_base.test +++ b/profiles/commerce_kickstart/modules/contrib/commerce/tests/commerce_base.test @@ -11,6 +11,8 @@ */ abstract class CommerceBaseTestCase extends DrupalWebTestCase { + protected $profile = 'minimal'; + /** * Helper function to determine which modules should be enabled. Should be * used in place of standard parent::setUp('moduleA', 'moduleB') call. @@ -30,7 +32,7 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { $dependencies = array( // API 'entity', - 'entity_token', + 'entity_token', 'rules', 'addressfield', //'rules_admin', @@ -41,6 +43,8 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { 'field', 'field_ui', 'field_sql_storage', + 'text', + 'list' ); $api = array( 'commerce', @@ -107,30 +111,21 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { $sets = array($sets); } $site_admin = array( - 'administer blocks', - 'administer comments', - 'access dashboard', - 'administer filters', - 'administer image styles', - 'administer menu', 'administer content types', 'administer nodes', 'bypass node access', - 'administer url aliases', - 'administer search', 'administer modules', 'administer site configuration', 'administer themes', - 'administer software updates', 'administer actions', 'access administration pages', 'access site in maintenance mode', 'access site reports', 'block IP addresses', - 'administer taxonomy', 'administer permissions', 'administer users', 'administer rules', + 'administer fields', ); $store_admin = array( 'access administration pages', @@ -153,6 +148,7 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { 'administer payments', 'administer taxes', 'administer rules', + 'administer fields', ); $store_customer = array( 'access content', @@ -283,7 +279,7 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { $new_product->uid = $uid; $new_product->commerce_price[LANGUAGE_NONE][0]['amount'] = ($amount < 0) ? rand(2, 500) : $amount; - $new_product->commerce_price[LANGUAGE_NONE][0]['currency_code'] = 'USD'; + $new_product->commerce_price[LANGUAGE_NONE][0]['currency_code'] = $currency_code; commerce_product_save($new_product); @@ -582,33 +578,24 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { * Type of the customer profile, default billing. * @param $uid * User id that will own the profile, by default anonymous. - * @param $address_info - * Address information, associative array keyed by the field name. - * i.e. 'commerce_customer_address'. * * @return * The customer profile created or FALSE if the profile wasn't created. */ - public function createDummyCustomerProfile($type = 'billing', $uid = 0, $address_info = array()) { - variable_set('site_default_country', 'US'); + public function createDummyCustomerProfile($type = 'billing', $uid = 0) { // Initialize the profile. $profile = commerce_customer_profile_new($type, $uid); - - // Set the defaults. + // Initialize the address. + $defaults = array(); $defaults['name_line'] = $this->randomName(); - $defaults = array_merge($defaults, addressfield_default_values(), $this->generateAddressInformation()); - - // Get all the fields for the given type, by default billing. - $instances = field_info_instances('commerce_customer_profile', $type); - foreach ($instances as $name => $instance) { - $info_field = field_info_field($name); - if ($info_field['type'] == 'addressfield') { - $values = !empty($address_info[$name]) ? array_merge($defaults, $address_info[$name]) : $defaults; - $values['data'] = serialize($values['data']); - $profile->{$name}[LANGUAGE_NONE][] = $values; - } - } + $field = field_info_field('commerce_customer_address'); + $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', $type); + $values = array_merge($defaults, addressfield_default_values($field, $instance), $this->generateAddressInformation()); + $values['data'] = serialize($values['data']); + $profile->commerce_customer_address[LANGUAGE_NONE][] = $values; + // Save the dummy profile. commerce_customer_profile_save($profile); + return $profile; } @@ -695,7 +682,7 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { // Loop through the line items looking for products. foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) { // If this line item matches the product checked... - if ($line_item_wrapper->type->value() == 'product' && + if ($line_item_wrapper->getBundle() == 'product' && $line_item_wrapper->commerce_product->product_id->value() == $product->product_id) { $product_is_in_cart = TRUE; } @@ -739,68 +726,6 @@ abstract class CommerceBaseTestCase extends DrupalWebTestCase { } - -/** - * Sandbox for trying new things with tests. Eases development so only one test - * has to run at a time. Move everything to CommerceBaseTesterTestCase after it - * is functioning here. - */ -class CommerceSandboxTestCase extends CommerceBaseTestCase { - protected $site_admin; - - /** - * getInfo() returns properties that are displayed in the test selection form. - */ - public static function getInfo() { - return array( - 'name' => t('Commerce sandbox'), - 'description' => t('Sandbox for trying new things with tests. Eases development so only one test has to run at a time.'), - 'group' => t('Drupal Commerce'), - ); - } - - /** - * setUp() performs any pre-requisite tasks that need to happen. - */ - public function setUp() { - $modules = parent::setUpHelper('all'); - parent::setUp($modules); - - $this->site_admin = $this->createSiteAdmin(); - cache_clear_all(); // Just in case - } - - /** - * Sandbox for test development - */ - public function testTestTest() { - - } - - /** - * Test the createDummyCustomerProfile function. - */ - public function testTestCreateDummyCustomerProfile() { - $store_admin = $this->createStoreAdmin(); - // Create a new customer profile for the store admin. - $profile = $this->createDummyCustomerProfile('billing', $store_admin->uid); - - // Load profile reseting cache. - $profile = reset(commerce_customer_profile_load_multiple(array($profile->profile_id), array(), TRUE)); - - $this->assertFalse(empty($profile), t('Profile can be loaded from database')); - - // Login with store admin user and navigate to the profile listing page. - $this->drupalLogin($store_admin); - $this->drupalGet('admin/commerce/customer-profiles'); - - $this->assertText($profile->commerce_customer_address[LANGUAGE_NONE][0]['name_line'], t('\'Name line\' field for the profile created is present in the customer profile listing')); - $type = commerce_customer_profile_type_load($profile->type); - $this->assertText($type['name'], t('The type of the profile is informed in the profile listing page')); - } - -} - /** * Test class to test the CommerceBaseTestCase functions. All testTestFoo * functions have "testTest" in the name to indicate that they are verifying @@ -887,7 +812,7 @@ class CommerceBaseTesterTestCase extends CommerceBaseTestCase { $this->drupalLogin($this->site_admin); $this->drupalGet('admin/people/permissions'); // This will break if it isn't the second role created - $this->assertFieldChecked('edit-5-configure-store'); + $this->assertFieldChecked('edit-4-configure-store'); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.info b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.info index adcb7ca9..7fe7dddd 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.info @@ -9,9 +9,9 @@ dependencies[] = rules stylesheets[all][] = css/commerce_add_to_cart_confirmation.css scripts[] = js/commerce_add_to_cart_confirmation.js -; Information added by drupal.org packaging script on 2013-02-14 -version = "7.x-1.0-rc2" +; Information added by Drupal.org packaging script on 2017-08-22 +version = "7.x-1.0-rc3" core = "7.x" project = "commerce_add_to_cart_confirmation" -datestamp = "1360857057" +datestamp = "1503410653" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.module b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.module index 8e993c7f..a9f23625 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.module @@ -24,3 +24,58 @@ function commerce_add_to_cart_confirmation_entity_info_alter(&$entity_info) { 'custom settings' => TRUE, ); } + +/** + * Implements hook_theme(). + */ +function commerce_add_to_cart_confirmation_theme($existing, $type, $theme, $path) { + return array( + 'commerce_add_to_cart_confirmation_message' => array( + 'variables' => array( + 'view' => '', + ), + ), + ); +} + +/** + * Return an array of settings to place in the JavaScript Drupal settings array + * when the Add to Cart Confirmation message is rendered. + */ +function commerce_add_to_cart_confirmation_js_settings() { + $settings = array( + 'commerceAddToCartConfirmation' => array( + 'overlayClass' => 'commerce_add_to_cart_confirmation_overlay', + 'overlayParentSelector' => 'body', + ), + ); + + drupal_alter('commerce_add_to_cart_confirmation_js_settings', $settings); + + return $settings; +} + +/** + * Implements hook_js_alter(). + * + * Adds settings to the Drupal.settings object needed by this module's JS. + */ +function commerce_add_to_cart_confirmation_js_alter(&$javascript) { + $javascript['settings']['data'][] = commerce_add_to_cart_confirmation_js_settings(); +} + +/** + * Theme function to render the confirmation message. + */ +function theme_commerce_add_to_cart_confirmation_message($variables) { + // Build the message output. + // @todo Switch this to use a template. + $message = '
        ' . t('Item successfully added to your cart') . '
        '; + $message .= $variables['view']; + $message .= '
        '; + $message .= '
        ' . l(t('Go to cart'), 'cart') . '
        '; + $message .= ''; + $message .= '
        '; + + return '
        ' . $message . '' . t('Close') . '
        '; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.rules.inc index 96db4464..7682a877 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/commerce_add_to_cart_confirmation.rules.inc @@ -30,14 +30,7 @@ function commerce_add_to_cart_confirmation_rules_action_info() { * Rules action: displays a custom Add to Cart message. */ function commerce_add_to_cart_confirmation_rules_add_to_cart_message($line_item, $continue) { - - $message = '
        ' . t('Item successfully added to your cart') . '
        '; - $message .= '
        '; - $message .= '
        ' . l(t('Go to checkout'), 'cart') . '
        '; - $message .= '
        ' . t('Continue shopping') . '
        '; - $message .= '
        '; - $message .= views_embed_view('confirm_message_product_display', 'default', $line_item->line_item_id); - $close = t('Close'); - - drupal_set_message('
        ' . $message . '' . $close . '
        ', 'commerce-add-to-cart-confirmation'); + $view = views_embed_view('confirm_message_product_display', 'default', $line_item->line_item_id); + $message = theme('commerce_add_to_cart_confirmation_message', array('view' => $view)); + drupal_set_message($message, 'commerce-add-to-cart-confirmation'); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/css/commerce_add_to_cart_confirmation.css b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/css/commerce_add_to_cart_confirmation.css index b87d9af9..6efdf302 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/css/commerce_add_to_cart_confirmation.css +++ b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/css/commerce_add_to_cart_confirmation.css @@ -9,27 +9,25 @@ div.commerce_add_to_cart_confirmation_overlay { position: fixed; top: 0; width: 100%; - z-index: 200; + z-index: 1000; } div.messages.commerce-add-to-cart-confirmation { - background-color: white; - border-color: #6bb0cb; + background-color: #fff; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; - -moz-box-shadow: 0 2px 15px #57595a; - -webkit-box-shadow: 0 2px 15px #57595a; - box-shadow: 0 2px 15px #57595a; - filter: progid:DXImageTransform.Microsoft.Shadow(color='#57595A', direction='180', strength='5'); - color: #aeaaa5; left: 50%; - margin-left: -20% !important; /* width/2 */ + margin-left: -30% !important; /* width/2 */ overflow: hidden; padding: 0; position: absolute; width: 450px; - z-index: 201; + width: 60%; + z-index: 1001; +} +div.messages.commerce-add-to-cart-confirmation .message-inner { + padding: 0 15px 25px; } /* Close button */ div.messages.commerce-add-to-cart-confirmation .message-inner .commerce-add-to-cart-confirmation-close { @@ -43,24 +41,23 @@ div.messages.commerce-add-to-cart-confirmation .message-inner .commerce-add-to-c } /* Title */ div.messages.commerce-add-to-cart-confirmation .message-inner .added-product-title { - color: #2698f2; float: left; /* LTR */ font-size: 14px; font-weight: bold; padding: 10px 30px 10px 20px; text-align: center; text-transform: uppercase; - width: 55%; + width: 65%; } /* Button */ div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper { - background-color: #e4eef3; height: 100%; - padding: 40px 30px; + padding: 45px 4%; position: absolute; right: 0; /* LTR */ - width: 25%; + top: 0; + width: 35%; } div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper:after { clear: both; @@ -69,65 +66,57 @@ div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper:af height: 0; visibility: hidden; } -div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - color: white; +div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button a { + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; font-weight: bold; margin-bottom: 10px; padding: 10px; text-align: center; -} -div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.checkout { - background-color: #2698f2; -} -div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.continue { - background-color: #b3b3b3; + display: block; + font-weight: bold; + word-wrap: break-word; + color: #fff; } div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.continue .commerce-add-to-cart-confirmation-close { - background: none; + background: #ccc; height: auto; position: static; width: auto; } -div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.checkout:hover, -div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.continue:hover { +div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.checkout a:hover, +div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.continue a:hover { cursor: pointer; -} -div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button a { - color: white; - display: block; - font-weight: bold; - word-wrap: break-word; + background-color: #555; + text-decoration: none; } /* Product display */ div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display { clear: both; -} -div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content { padding: 15px; - position: relative; - width: 55%; + width: 65%; +} +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display:after { + clear:both; } div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .content .field-name-field-images { - position: relative; + position: absolute; + top: 0; + left: -130px; width: 130px; -} -div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .content .field-name-field-images img { - border: #d7d7d7 solid 1px; + margin: 0; + text-align: center; } div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .commerce-product-sku .commerce-product-sku-label, div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .field .views-label, div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .field .field-label { - color: #878380; float: left; /* LTR */ - font-size: 12px; font-weight: bold; padding-right: 5px; /* LTR */ } -div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .field { +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .views-field { clear: both; margin: 0; text-align: left; /* LTR */ @@ -146,7 +135,72 @@ div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-dis padding-right: 5px; /* LTR */ } div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .views-field-commerce-total .field-content { - color: #2698f2; display: inline; float: right; /* LTR */ } + +/* + * Style + */ +/* Overlay */ +div.messages.commerce-add-to-cart-confirmation { + border-color: #6bb0cb; + -moz-box-shadow: 0 2px 15px #57595a; + -webkit-box-shadow: 0 2px 15px #57595a; + box-shadow: 0 2px 15px #57595a; + filter: progid:DXImageTransform.Microsoft.Shadow(color='#57595A', direction='180', strength='5'); + color: #aeaaa5; +} +/* Close button */ +div.messages.commerce-add-to-cart-confirmation .message-inner .commerce-add-to-cart-confirmation-close { + background: url('../images/btn_add-to-cart-close.png') no-repeat 0 0; +} +/* Title */ +div.messages.commerce-add-to-cart-confirmation .message-inner .added-product-title { + color: #2698f2; +} +/* Button */ +div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper { + background-color: #e4eef3; +} +div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper .button.checkout a { + background-color: #2698f2; +} +/* Product display */ +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .content .field-name-field-images img { + border: #d7d7d7 solid 1px; +} +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .commerce-product-sku .commerce-product-sku-label, +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .field .views-label, +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .field .field-label { + color: #878380; +} +div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display .view-content .views-field-commerce-total .field-content { + color: #2698f2; +} + +/* + * @media rules + */ + +@media (max-width: 967px) { + div.messages.commerce-add-to-cart-confirmation { + width: 90%; + margin: 0 !important; + left: 5%; + } + div.messages.commerce-add-to-cart-confirmation .message-inner .added-product-title { + width: 100%; + float: none; + } + div.messages.commerce-add-to-cart-confirmation .message-inner .button-wrapper { + position: relative; + width: 100%; + padding: 15px; + clear: both; + margin-top: 25px; + } + div.messages.commerce-add-to-cart-confirmation .view-confirm-message-product-display { + width: 100%; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/js/commerce_add_to_cart_confirmation.js b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/js/commerce_add_to_cart_confirmation.js index 1755e06a..190fb4cd 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/js/commerce_add_to_cart_confirmation.js +++ b/profiles/commerce_kickstart/modules/contrib/commerce_add_to_cart_confirmation/js/commerce_add_to_cart_confirmation.js @@ -1,15 +1,29 @@ (function ($) { Drupal.behaviors.commerce_add_to_cart_confirmation_overlay = { attach:function (context, settings) { + var overlayClass = 'commerce_add_to_cart_confirmation_overlay'; + var overlayParentSelector = 'body'; + + // Determine the appropriate overlay class and parent selector based on the settings array. + if (typeof settings.commerceAddToCartConfirmation != 'undefined') { + if (typeof settings.commerceAddToCartConfirmation.overlayClass != 'undefined') { + overlayClass = settings.commerceAddToCartConfirmation.overlayClass; + } + + if (typeof settings.commerceAddToCartConfirmation.overlayParentSelector != 'undefined') { + overlayParentSelector = settings.commerceAddToCartConfirmation.overlayParentSelector; + } + } + if ($('.commerce-add-to-cart-confirmation').length > 0) { // Add the background overlay. - $('body').append("
        "); + $(overlayParentSelector).append('
        '); // Enable the close link. - $('.commerce-add-to-cart-confirmation-close').live('click touchend', function(e) { + $('.commerce-add-to-cart-confirmation-close').bind('click touchend', function(e) { e.preventDefault(); $('.commerce-add-to-cart-confirmation').remove(); - $('.commerce_add_to_cart_confirmation_overlay').remove(); + $('.' + overlayClass).remove(); }); } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/README.txt b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/README.txt index 145a7d27..595e2a3d 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/README.txt @@ -11,6 +11,7 @@ Installation * Visit the admin/commerce/config/checkout page and configure any customer profile checkout panes (such as "Billing information") to use the addressbook. * The "Addresses on File" select list should now automatically be attached to the checkout form. +* Enable "create", "edit", and "view" permissons for each profile type addressbook was enabled on. Updating from Addressbook 1.x ============================= diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.api.php b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.api.php index cc9b33a6..bce275c5 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.api.php @@ -45,3 +45,15 @@ function hook_commerce_addressbook_callback_alter(&$commands, $form, $form_state // Example. $commands[] = ajax_command_alert('It works!'); } + +/** + * Allow modules to be called when a default address is added/updated. + * + * @param $customer_profile + * The customer profile set as default. + * + * @see commerce_addressbook_set_default_profile(). + */ +function hook_commerce_addressbook_set_default($customer_profile) { + // No example. +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.info b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.info index c04f9d3d..5d277a0b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.info @@ -5,9 +5,9 @@ core = 7.x dependencies[] = commerce_customer dependencies[] = commerce_checkout -; Information added by Drupal.org packaging script on 2014-10-04 -version = "7.x-2.0-rc8" +; Information added by Drupal.org packaging script on 2015-07-28 +version = "7.x-2.0-rc9" core = "7.x" project = "commerce_addressbook" -datestamp = "1412458729" +datestamp = "1438117143" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.module b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.module index 50bc4cda..d4f43243 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.module @@ -246,7 +246,13 @@ function commerce_addressbook_entity_view($entity, $type, $view_mode, $langcode) ), ); } - $entity->content['commerce_addressbook_options'] = $links; + if ($links) { + $entity->content['commerce_addressbook_options'] = array_merge(array( + '#weight' => 100, + '#prefix' => '', + ), $links); + } } } @@ -451,9 +457,15 @@ function commerce_addressbook_saved_addresses_validate($element, &$form_state, $ else { $order_wrapper->{$field_name} = NULL; } + + // Save the changed customer profile to the order. + $order_wrapper->save(); + unset($form_state['input'][$pane_id]); - $element_key = $form[$pane_id]['commerce_customer_address'][$form[$pane_id]['commerce_customer_address']['#language']][0]['element_key']['#value']; - unset($form_state['addressfield'][$element_key]); + $element_key = isset($form[$pane_id]['commerce_customer_address'][$form[$pane_id]['commerce_customer_address']['#language']][0]['element_key']['#value']) ? $form[$pane_id]['commerce_customer_address'][$form[$pane_id]['commerce_customer_address']['#language']][0]['element_key']['#value'] : ''; + if (!empty($element_key)) { + unset($form_state['addressfield'][$element_key]); + } } } @@ -469,6 +481,10 @@ function commerce_addressbook_set_default_profile($customer_profile) { )) ->fields(array('profile_id' => $customer_profile->profile_id)) ->execute(); + + // Allow modules to react to change in customer profile default change. + module_invoke_all('commerce_addressbook_set_default', $customer_profile); + rules_invoke_event('commerce_addressbook_set_default', $customer_profile); } /** @@ -500,14 +516,13 @@ function commerce_addressbook_get_default_profile_id($uid, $type) { * checkout panes. */ function commerce_addressbook_commerce_order_presave($order) { - global $user; - if ($user->uid) { + if ($order->uid) { foreach (commerce_checkout_panes() as $pane_id => $checkout_pane) { // Only for panes for which the address book is enabled. if (variable_get('commerce_' . $pane_id . '_addressbook', FALSE)) { $type = substr($checkout_pane['pane_id'], 17); // Removes 'customer_profile_' // Does this profile type have a default? - $default_profile_id = commerce_addressbook_get_default_profile_id($user->uid, $type); + $default_profile_id = commerce_addressbook_get_default_profile_id($order->uid, $type); if ($default_profile_id) { // Is this profile stored in a field or in the data association? if (($field_name = variable_get('commerce_' . $pane_id . '_field', '')) && empty($order->{$field_name})) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.rules.inc new file mode 100644 index 00000000..dbc2cd0e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/commerce_addressbook.rules.inc @@ -0,0 +1,67 @@ + t('Customer profile default changed'), + 'group' => t('Commerce Addressbook'), + 'variables' => array( + 'commerce_customer_profile' => array( + 'type' => 'commerce_customer_profile', + 'label' => t('Customer profile'), + ), + ), + ); + + return $events; +} + +/** + * Implement hook_rules_condition_info() + */ +function commerce_addressbook_rules_condition_info() { + return array( + 'commerce_addressbook_profile_is_default' => array( + 'label' => t('Customer profile is the default'), + 'parameter' => array( + 'customer_profile' => array( + 'label' => t('Customer Profile'), + 'type' => 'commerce_customer_profile' + ), + ), + 'group' => t('Commerce Addressbook'), + 'callbacks' => array( + 'execute' => 'commerce_addressbook_rules_profile_is_default', + ), + ), + ); +} + +/** + * Rules condition: checks if profile is user's default customer profile. + * + * @param $customer_profile + * The customer profile to check. + * + * @return bool + * TRUE or FALSE if profile IDs match. + */ +function commerce_addressbook_rules_profile_is_default($customer_profile) { + return commerce_addressbook_get_default_profile_id($customer_profile->uid,$customer_profile->type) == $customer_profile->profile_id; +} + +/** + * @} + */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/commerce_addressbook.user.inc b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/commerce_addressbook.user.inc index e86e6b80..be98b058 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/commerce_addressbook.user.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/commerce_addressbook.user.inc @@ -178,7 +178,8 @@ function commerce_addressbook_profile_page($account, $profile_type) { $output .= '
        ' . $list_view->render() . '
        '; } if ($output == '') { - return '
        ' . t('Your @profile_type address book is currently empty.', array('@profile_type' => $profile_type)) . '
        '; + return '
        ' . t('Your @profile_type address book is currently empty.', + array('@profile_type' => commerce_customer_profile_type_get_name($profile_type))) . '
        '; } else { return $output; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/views/commerce_addressbook.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/views/commerce_addressbook.views_default.inc index e4a9da58..71467a1f 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/views/commerce_addressbook.views_default.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_addressbook/includes/views/commerce_addressbook.views_default.inc @@ -11,7 +11,7 @@ function commerce_addressbook_views_default_views() { $views = array(); - $view = new view; + $view = new view(); $view->name = 'commerce_addressbook'; $view->description = ''; $view->tag = 'default'; @@ -20,9 +20,10 @@ function commerce_addressbook_views_default_views() { $view->core = 7; $view->api_version = '3.0'; $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ - + /* Display: Master */ $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['use_more_always'] = FALSE; $handler->display->display_options['access']['type'] = 'none'; $handler->display->display_options['cache']['type'] = 'none'; $handler->display->display_options['query']['type'] = 'views_query'; @@ -32,45 +33,21 @@ function commerce_addressbook_views_default_views() { $handler->display->display_options['pager']['options']['items_per_page'] = '9'; $handler->display->display_options['pager']['options']['offset'] = '0'; $handler->display->display_options['pager']['options']['id'] = '0'; - $handler->display->display_options['pager']['options']['expose']['items_per_page_options_all'] = 0; $handler->display->display_options['style_plugin'] = 'grid'; $handler->display->display_options['style_options']['columns'] = '3'; - $handler->display->display_options['style_options']['fill_single_line'] = 0; + $handler->display->display_options['style_options']['fill_single_line'] = FALSE; $handler->display->display_options['row_plugin'] = 'fields'; - $handler->display->display_options['row_options']['hide_empty'] = 0; - $handler->display->display_options['row_options']['default_field_elements'] = 1; /* Relationship: Commerce Customer Profile: Owner */ $handler->display->display_options['relationships']['uid']['id'] = 'uid'; $handler->display->display_options['relationships']['uid']['table'] = 'commerce_customer_profile'; $handler->display->display_options['relationships']['uid']['field'] = 'uid'; - $handler->display->display_options['relationships']['uid']['required'] = 1; - /* Relationship: Addressbook: Profile ID */ - $handler->display->display_options['relationships']['profile_id']['id'] = 'profile_id'; - $handler->display->display_options['relationships']['profile_id']['table'] = 'commerce_addressbook_defaults'; - $handler->display->display_options['relationships']['profile_id']['field'] = 'profile_id'; - $handler->display->display_options['relationships']['profile_id']['required'] = 0; + $handler->display->display_options['relationships']['uid']['required'] = TRUE; /* Field: Commerce Customer Profile: Rendered Commerce Customer profile */ $handler->display->display_options['fields']['rendered_entity']['id'] = 'rendered_entity'; $handler->display->display_options['fields']['rendered_entity']['table'] = 'views_entity_commerce_customer_profile'; $handler->display->display_options['fields']['rendered_entity']['field'] = 'rendered_entity'; $handler->display->display_options['fields']['rendered_entity']['label'] = ''; - $handler->display->display_options['fields']['rendered_entity']['alter']['alter_text'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['make_link'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['absolute'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['external'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['replace_spaces'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['trim_whitespace'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['nl2br'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['word_boundary'] = 1; - $handler->display->display_options['fields']['rendered_entity']['alter']['ellipsis'] = 1; - $handler->display->display_options['fields']['rendered_entity']['alter']['strip_tags'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['trim'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['html'] = 0; $handler->display->display_options['fields']['rendered_entity']['element_label_colon'] = FALSE; - $handler->display->display_options['fields']['rendered_entity']['element_default_classes'] = 1; - $handler->display->display_options['fields']['rendered_entity']['hide_empty'] = 0; - $handler->display->display_options['fields']['rendered_entity']['empty_zero'] = 0; - $handler->display->display_options['fields']['rendered_entity']['hide_alter_empty'] = 1; $handler->display->display_options['fields']['rendered_entity']['link_to_entity'] = 0; $handler->display->display_options['fields']['rendered_entity']['display'] = 'view'; $handler->display->display_options['fields']['rendered_entity']['view_mode'] = 'addressbook'; @@ -81,26 +58,19 @@ function commerce_addressbook_views_default_views() { $handler->display->display_options['arguments']['uid']['relationship'] = 'uid'; $handler->display->display_options['arguments']['uid']['default_action'] = 'not found'; $handler->display->display_options['arguments']['uid']['default_argument_type'] = 'fixed'; - $handler->display->display_options['arguments']['uid']['default_argument_skip_url'] = 0; $handler->display->display_options['arguments']['uid']['summary']['number_of_records'] = '0'; $handler->display->display_options['arguments']['uid']['summary']['format'] = 'default_summary'; $handler->display->display_options['arguments']['uid']['summary_options']['items_per_page'] = '25'; - $handler->display->display_options['arguments']['uid']['break_phrase'] = 0; - $handler->display->display_options['arguments']['uid']['not'] = 0; /* Contextual filter: Commerce Customer Profile: Type */ $handler->display->display_options['arguments']['type']['id'] = 'type'; $handler->display->display_options['arguments']['type']['table'] = 'commerce_customer_profile'; $handler->display->display_options['arguments']['type']['field'] = 'type'; $handler->display->display_options['arguments']['type']['default_action'] = 'not found'; $handler->display->display_options['arguments']['type']['default_argument_type'] = 'fixed'; - $handler->display->display_options['arguments']['type']['default_argument_skip_url'] = 0; $handler->display->display_options['arguments']['type']['summary']['number_of_records'] = '0'; $handler->display->display_options['arguments']['type']['summary']['format'] = 'default_summary'; $handler->display->display_options['arguments']['type']['summary_options']['items_per_page'] = '25'; - $handler->display->display_options['arguments']['type']['glossary'] = 0; $handler->display->display_options['arguments']['type']['limit'] = '0'; - $handler->display->display_options['arguments']['type']['transform_dash'] = 0; - $handler->display->display_options['arguments']['type']['break_phrase'] = 0; /* Filter criterion: Commerce Customer Profile: Status */ $handler->display->display_options['filters']['status']['id'] = 'status'; $handler->display->display_options['filters']['status']['table'] = 'commerce_customer_profile'; @@ -111,9 +81,10 @@ function commerce_addressbook_views_default_views() { $handler->display->display_options['filters']['profile_id']['table'] = 'commerce_addressbook_defaults'; $handler->display->display_options['filters']['profile_id']['field'] = 'profile_id'; $handler->display->display_options['filters']['profile_id']['operator'] = 'empty'; + $views['commerce_addressbook'] = $view; - $view = new view; + $view = new view(); $view->name = 'commerce_addressbook_defaults'; $view->description = ''; $view->tag = 'default'; @@ -122,9 +93,10 @@ function commerce_addressbook_views_default_views() { $view->core = 7; $view->api_version = '3.0'; $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ - + /* Display: Master */ $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['use_more_always'] = FALSE; $handler->display->display_options['access']['type'] = 'none'; $handler->display->display_options['cache']['type'] = 'none'; $handler->display->display_options['query']['type'] = 'views_query'; @@ -135,40 +107,17 @@ function commerce_addressbook_views_default_views() { $handler->display->display_options['pager']['options']['offset'] = '0'; $handler->display->display_options['style_plugin'] = 'default'; $handler->display->display_options['row_plugin'] = 'fields'; - $handler->display->display_options['row_options']['hide_empty'] = 0; - $handler->display->display_options['row_options']['default_field_elements'] = 1; /* Relationship: Commerce Customer Profile: Owner */ $handler->display->display_options['relationships']['uid']['id'] = 'uid'; $handler->display->display_options['relationships']['uid']['table'] = 'commerce_customer_profile'; $handler->display->display_options['relationships']['uid']['field'] = 'uid'; - $handler->display->display_options['relationships']['uid']['required'] = 1; - /* Relationship: Addressbook: Profile ID */ - $handler->display->display_options['relationships']['profile_id']['id'] = 'profile_id'; - $handler->display->display_options['relationships']['profile_id']['table'] = 'commerce_addressbook_defaults'; - $handler->display->display_options['relationships']['profile_id']['field'] = 'profile_id'; - $handler->display->display_options['relationships']['profile_id']['required'] = 1; + $handler->display->display_options['relationships']['uid']['required'] = TRUE; /* Field: Commerce Customer Profile: Rendered Commerce Customer profile */ $handler->display->display_options['fields']['rendered_entity']['id'] = 'rendered_entity'; $handler->display->display_options['fields']['rendered_entity']['table'] = 'views_entity_commerce_customer_profile'; $handler->display->display_options['fields']['rendered_entity']['field'] = 'rendered_entity'; $handler->display->display_options['fields']['rendered_entity']['label'] = ''; - $handler->display->display_options['fields']['rendered_entity']['alter']['alter_text'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['make_link'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['absolute'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['external'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['replace_spaces'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['trim_whitespace'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['nl2br'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['word_boundary'] = 1; - $handler->display->display_options['fields']['rendered_entity']['alter']['ellipsis'] = 1; - $handler->display->display_options['fields']['rendered_entity']['alter']['strip_tags'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['trim'] = 0; - $handler->display->display_options['fields']['rendered_entity']['alter']['html'] = 0; $handler->display->display_options['fields']['rendered_entity']['element_label_colon'] = FALSE; - $handler->display->display_options['fields']['rendered_entity']['element_default_classes'] = 1; - $handler->display->display_options['fields']['rendered_entity']['hide_empty'] = 0; - $handler->display->display_options['fields']['rendered_entity']['empty_zero'] = 0; - $handler->display->display_options['fields']['rendered_entity']['hide_alter_empty'] = 1; $handler->display->display_options['fields']['rendered_entity']['link_to_entity'] = 0; $handler->display->display_options['fields']['rendered_entity']['display'] = 'view'; $handler->display->display_options['fields']['rendered_entity']['view_mode'] = 'addressbook'; @@ -179,31 +128,25 @@ function commerce_addressbook_views_default_views() { $handler->display->display_options['arguments']['uid']['relationship'] = 'uid'; $handler->display->display_options['arguments']['uid']['default_action'] = 'not found'; $handler->display->display_options['arguments']['uid']['default_argument_type'] = 'fixed'; - $handler->display->display_options['arguments']['uid']['default_argument_skip_url'] = 0; $handler->display->display_options['arguments']['uid']['summary']['number_of_records'] = '0'; $handler->display->display_options['arguments']['uid']['summary']['format'] = 'default_summary'; $handler->display->display_options['arguments']['uid']['summary_options']['items_per_page'] = '25'; - $handler->display->display_options['arguments']['uid']['break_phrase'] = 0; - $handler->display->display_options['arguments']['uid']['not'] = 0; /* Contextual filter: Commerce Customer Profile: Type */ $handler->display->display_options['arguments']['type']['id'] = 'type'; $handler->display->display_options['arguments']['type']['table'] = 'commerce_customer_profile'; $handler->display->display_options['arguments']['type']['field'] = 'type'; $handler->display->display_options['arguments']['type']['default_action'] = 'not found'; $handler->display->display_options['arguments']['type']['default_argument_type'] = 'fixed'; - $handler->display->display_options['arguments']['type']['default_argument_skip_url'] = 0; $handler->display->display_options['arguments']['type']['summary']['number_of_records'] = '0'; $handler->display->display_options['arguments']['type']['summary']['format'] = 'default_summary'; $handler->display->display_options['arguments']['type']['summary_options']['items_per_page'] = '25'; - $handler->display->display_options['arguments']['type']['glossary'] = 0; $handler->display->display_options['arguments']['type']['limit'] = '0'; - $handler->display->display_options['arguments']['type']['transform_dash'] = 0; - $handler->display->display_options['arguments']['type']['break_phrase'] = 0; /* Filter criterion: Commerce Customer Profile: Status */ $handler->display->display_options['filters']['status']['id'] = 'status'; $handler->display->display_options['filters']['status']['table'] = 'commerce_customer_profile'; $handler->display->display_options['filters']['status']['field'] = 'status'; $handler->display->display_options['filters']['status']['value'] = '1'; + $views['commerce_addressbook_defaults'] = $view; return $views; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/.gitignore b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/.gitignore new file mode 100644 index 00000000..090a1f02 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/.gitignore @@ -0,0 +1,2 @@ +.idea +.DS_Store diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/README.md b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/README.md new file mode 100644 index 00000000..13d053ce --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/README.md @@ -0,0 +1,84 @@ +Amazon Pay and Login with Amazon +------------------------- + +This module integrates Amazon Pay and Login with Amazon into Drupal and [Drupal Commerce][drupalcommerce]. + +Amazon Pay provides Amazon buyers a secure, trusted, and convenient way to log in and pay for their purchases on your site. Buyers use Amazon Pay to share their profile information (name and email address) and access the shipping and payment information stored in their Amazon account to complete their purchase. Learn more at + +* [Amazon Pay US][amazonpay_us] +* [Amazon Pay UK][amazonpay_uk] +* [Amazon Pay DE][amazonpay_de] + +[amazonpay_us]: https://payments.amazon.com +[amazonpay_uk]: https://payments.amazon.co.uk +[amazonpay_de]: https://payments.amazon.de +[drupalcommerce]: https://www.drupal.org/project/commerce + +## Requirements + +You must have [Drupal Commerce][drupalcommerce] and the Cart, Customer, and Payment submodules enabled. + +The shop owner must have an Amazon merchant account. Sign up now: +* US : https://pay.amazon.com/us/merchant?ld=SPEXUSAPA-drupal%20commerce-CP-DP-2017-Q1 +* UK : https://pay.amazon.com/uk/merchant?ld=SPEXUKAPA-drupal%20commerce-CP-DP-2017-Q1 +* DE : https://pay.amazon.com/de/merchant?ld=SPEXDEAPA-drupal%20commerce-CP-DP-2017-Q1 + +This module utilizes the [Amazon Pay PHP SDK][php-sdk] to communicate with Amazon. You must have the [Libraries][libraries] module installed in order to load the SDK properly. + +[php-sdk]: https://github.com/amzn/amazon-pay-sdk-php +[libraries]: https://www.drupal.org/project/libraries + +## Features + +The module's integration provides the following features: + +* When using the *Amazon Pay and Login with Amazon* mode, users logging in with their Amazon accounts will have an account created in Drupal. +* Ability to provide the normal checkout experience or only provide Amazon based checkout. +* Multilingual support +* Support for United States, United Kingdom, and Germany regions. + +The module's documentation can be found on Drupal.org at https://www.drupal.org/docs/7/modules/commerce-amazon-pay + +## Installation + +1. Install as you would normally install a contributed drupal module and its dependencies. See: https://drupal.org/documentation/install/modules-themes/modules-7 for further information. +2. Download the latest Amazon Pay PHP SDK from [GitHub][php-sdk] and place it in *sites/all/libraries* +3. Visit *admin/commerce/config/amazon-lpa* and enter your Merchant ID, MWS Access Key, MWS Secret key, and LWA Client ID. +4. Save the configuration, your API credentials will be validated. +5. Specify your domain as the *Allowed JavaScript Origins* +5. Add the following URLs as *Allowed Return URLs* +* https://example.com/checkout/amazon +* https://example.com/user/login/amazon?amzn=LwA +6. Add https://example.com/commerce-amazon-lpa/ipn as your *Merchant URL* in the *Integration Settings* form. + +## Frequently Asked Questions + +### Only allow users to login through Amazon + +You have the ability to disable Drupal's user registration and support registration and login solely through Login with Amazon. + +1. Visit *admin/commerce/config/amazon-lpa* +1. Ensure the **Operation mode** is set to *Amazon Pay and Login with Amazon* +1. Visit *admin/config/people/accounts* +1. Change **Who can register accounts?** to *Administrators only* + +### Using just Amazon Pay + +You can use the module to only support Amazon Pay, without Login with Amazon. + +1. Visit *admin/commerce/config/amazon-lpa* +1. Ensure the **Operation mode** is set to *Amazon Pay only* + +When entering the Amazon checkout, user's will be prompt to log in with their Amazon account before beginning. However, no Drupal account will be created. + +## Maintainers + +Current maintainer: +* Matt Glaman ([mglaman]) + +Development sponsored by **[Commerce Guys][commerceguys]**: + +Commerce Guys are the creators of and experts in Drupal Commerce, the eCommerce solution that capitalizes on the virtues and power of Drupal, the premier open-source content management system. We focus our knowledge and expertise on providing online merchants with the powerful, responsive, innovative eCommerce solutions they need to thrive. + +[mglaman]: https://www.drupal.org/u/mglaman +[commerceguys]: https://commerceguys.com/ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.api.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.api.php new file mode 100644 index 00000000..d28481b3 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.api.php @@ -0,0 +1,47 @@ + 'awaiting_auth', + 'title' => t('Awaiting Authorization'), + 'state' => 'pending', + 'weight' => -10, + ); + return $order_statuses; +} + +/** + * Implements hook_commerce_checkout_pane_info(). + */ +function commerce_amazon_lpa_commerce_checkout_pane_info() { + $checkout_panes = array(); + $checkout_panes['commerce_amazon_lpa_contract_id'] = array( + 'title' => '', + 'name' => t('Amazon order contract negotiation'), + 'base' => 'commerce_amazon_lpa_contract_id_pane', + 'file' => 'includes/commerce_amazon_lpa.checkout_pane.inc', + 'page' => 'checkout', + 'fieldset' => FALSE, + // Needs to be earlier so order contract ID is set. + 'weight' => -20, + ); + + $checkout_panes['commerce_amazon_lpa_switch_checkout'] = array( + 'title' => t('Checkout with Amazon'), + 'name' => t('Amazon order switch checkout'), + 'base' => 'commerce_amazon_lpa_switch_checkout_pane', + 'file' => 'includes/commerce_amazon_lpa.checkout_pane.inc', + 'page' => 'checkout', + 'fieldset' => FALSE, + // Needs to be earlier so order contract ID is set. + 'weight' => -30, + ); + + if (module_exists('commerce_shipping')) { + $checkout_panes['commerce_amazon_lpa_addressbook'] = array( + 'title' => t('Shipping information'), + 'name' => t('Amazon address book'), + 'base' => 'commerce_amazon_lpa_addressbook_pane', + 'file' => 'includes/commerce_amazon_lpa.checkout_pane.inc', + 'page' => 'checkout', + 'review' => TRUE, + 'weight' => 25, + ); + } + return $checkout_panes; +} + +/** + * Implements hook_commerce_checkout_pane_info_alter(). + */ +function commerce_amazon_lpa_commerce_checkout_pane_info_alter(&$checkout_panes) { + if (module_exists('commerce_shipping')) { + $checkout_panes['commerce_shipping']['callbacks']['checkout_form'] = 'commerce_amazon_lpa_commerce_shipping_pane_checkout_form'; + } +} + + +/** + * Implements hook_commerce_payment_method_info(). + * + * Defines Checkout by Amazon payment method. + */ +function commerce_amazon_lpa_commerce_payment_method_info() { + $payment_methods['commerce_amazon_login_and_pay'] = array( + 'title' => t('Amazon Pay'), + 'description' => t('Integration with Amazon Pay payment method'), + 'active' => TRUE, + 'checkout' => TRUE, + 'terminal' => FALSE, + // We mark the module as an offsite so that payment method selection + // can happen at any point during checkout. + 'offsite' => TRUE, + 'offsite_autoredirect' => FALSE, + 'file' => 'includes/commerce_amazon_lpa.payment_method.inc', + ); + return $payment_methods; +} + +/** + * Implements hook_commerce_checkout_complete(). + */ +function commerce_amazon_lpa_commerce_checkout_complete($order) { + if (!empty($order->data['commerce_amazon_lpa'])) { + $wrapper = entity_metadata_wrapper('commerce_order', $order); + + // Set the order reference and confirm it. + $api = AmazonLPA::instance(); + $data = $api->getOrderRef($wrapper); + + $order->data['commerce_amazon_lpa']['order_reference'] = $data; + + // Also ensure our shipping profile is up to date. + if (isset($data['Destination'])) { + $amazon_shipping_address = $data['Destination']['PhysicalDestination']; + commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'shipping', $amazon_shipping_address); + } + + commerce_order_save($order); + } +} + +/** + * Implements hook_commerce_order_update(). + */ +function commerce_amazon_lpa_commerce_order_presave($order) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $contract_id = $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD}->value(); + + // If this is not an Amazon order, do not react. + if (!$contract_id) { + return; + } + + $api = AmazonLPA::instance(); + + /** @var EntityDrupalWrapper $order_wrapper */ + // First check if the order status has changed. + if ($order->original->status != $order->status) { + // Cancel the Amazon order reference if the order is canceled. + if ($order->status == 'canceled') { + try { + $order_reference = $api->getOrderRef($order_wrapper); + $amazon_order_state = $order_reference['OrderReferenceStatus']['State']; + if ($amazon_order_state != 'Closed' && $amazon_order_state != 'Canceled') { + $response = $api->cancel($order_wrapper); + commerce_amazon_lpa_add_debug_log(t('Order cancel response: !debug', array('!debug' => '
        ' . print_r($response, TRUE) . '
        '))); + } + } + catch (AmazonApiException $e) { + drupal_set_message($e->getMessage(), 'error'); + } + } + + // We're in ERP mode and aren't doing any additional processing. + if (AmazonLPA::is_erp_mode()) { + return; + } + + $is_shipped_order_status = (variable_get('commerce_amazon_lpa_shipped_order_status') == $order->status); + + if ($is_shipped_order_status) { + commerce_amazon_lpa_add_debug_log('Order @number shipped, closing Amazon order reference', array( + '@number' => $order->order_id, + )); + + try { + $api->closeOrderRef($order_wrapper); + // No need to save since the event will take care of that for us. + $order->data['commerce_amazon_lpa']['order_details'] = AmazonLPA::instance()->getOrderRef($order_wrapper); + } + catch (Exception $e) { + // The order was already closed remotely. + return NULL; + } + } + + // If set to capture on shipment, do the capture if the status meets the + // configured shipping status. + $balance = commerce_payment_order_balance($order); + if (AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_SHIPMENT_CAPTURE && $is_shipped_order_status && $balance['amount'] > 0) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $transaction = commerce_amazon_lpa_get_order_authorization($order->order_id); + try { + if (!$transaction) { + throw new Exception(t('There are no open authorizations, cannot capture the order payment.')); + } + $response = $api->capture($order_wrapper, $transaction->remote_id); + + $api->processCaptureTransaction($transaction, $response); + + if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS) { + drupal_set_message(t('The capture has been completed successfully.')); + } + elseif ($transaction->status == COMMERCE_PAYMENT_STATUS_PENDING) { + drupal_set_message(t('The capture has been submitted successfully and may take a moment to process.')); + } + else { + drupal_set_message(t('The capture was not successful.'), 'error'); + } + + commerce_amazon_lpa_add_debug_log(t('Order capture response !debug', array('!debug' => '
        ' . print_r($response, TRUE) . '
        '))); + } + catch (Exception $e) { + $order->status = $order->original->status; + drupal_set_message($e->getMessage(), 'error'); + + // Something went wrong, try to refresh the authorization. + if ($transaction) { + $data = $api->getAuthorizationDetails($transaction->remote_id); + $api->processAuthorizeTransaction($transaction, $data); + } + } + } + } + +} + +/** + * Implements hook_commerce_product_type_insert(). + */ +function commerce_amazon_lpa_commerce_product_type_insert($product_type, $skip_reset) { + commerce_amazon_lpa_restricted_product_create_instance($product_type['type']); +} + +/** + * Implements hook_ENTITY_TYPE_view(). + */ +function commerce_amazon_lpa_commerce_order_view($entity, $view_mode, $langcode) { + // The US region does not support retrieval of billing information. + // There is always a stubbed profile, so we need to hide it. + if (variable_get('commerce_amazon_lpa_region', 'US') == 'US') { + if (isset($entity->commerce_customer_billing)) { + $entity->content['commerce_customer_billing']['#access'] = FALSE; + } + } + if (!commerce_amazon_lpa_order_is_shippable($entity)) { + if (isset($entity->commerce_customer_shipping)) { + $entity->content['commerce_customer_shipping']['#access'] = FALSE; + } + } +} + +/** + * Implements hook_commerce_line_item_summary_link_info(). + */ +function commerce_amazon_lpa_commerce_line_item_summary_link_info() { + $cache = &drupal_static(__FUNCTION__, array()); + + if (empty($cache)) { + // Use a text title in the Views Line item summary handler, Links option. + if (path_is_admin(current_path())) { + $title = t('Checkout by Amazon button'); + } + // Use Checkout by Amazon button in the Shopping cart block display. + else { + $order = commerce_cart_order_load($GLOBALS['user']->uid); + if ($order) { + $title = theme('commerce_amazon_payment_button__summary_link', array( + 'order_id' => $order->order_id, + 'html_id' => drupal_html_id('amazon_checkout_summary_link'), + 'button_options' => array( + 'payType' => 'Pay', + 'paySize' => 'small', + ), + )); + } + else { + $title = ''; + } + } + + $cache = array( + 'checkout_lpa' => array( + 'title' => $title, + 'attributes' => array('rel' => 'nofollow'), + 'weight' => 10, + 'html' => TRUE, + 'access' => user_access('access checkout') && !AmazonLPA::is_hidden(), + ), + ); + } + + return $cache; +} + +/** + * Implements hook_commerce_payment_order_paid_in_full(). + */ +function commerce_amazon_lpa_commerce_payment_order_paid_in_full($order, $transaction) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $contract_id = $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD}->value(); + + // If this is not an Amazon order, do not react. + if (!$contract_id) { + return; + } + + $api = AmazonLPA::instance(); + + try { + $order_reference = $api->getOrderRef($order_wrapper); + $amazon_order_state = $order_reference['OrderReferenceStatus']['State']; + if ($amazon_order_state != 'Closed') { + $response = $api->closeOrderRef($order_wrapper); + commerce_amazon_lpa_add_debug_log(t('Order closed response: !debug', array('!debug' => '
        ' . print_r($response, TRUE) . '
        '))); + } + } + catch (Exception $e) { + drupal_set_message($e->getMessage(), 'error'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.info b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.info new file mode 100644 index 00000000..b79c771e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.info @@ -0,0 +1,25 @@ +name = Commerce Login and Pay with Amazon +description = Base module which provides integration with Login and Pay with Amazon. +core = 7.x + +; Configuration path +configure = admin/commerce/config/amazon-lpa + +dependencies[] = list +dependencies[] = rules +dependencies[] = commerce_cart +dependencies[] = commerce_customer +dependencies[] = commerce_payment +dependencies[] = libraries +files[] = includes/AmazonPayResponse.php +files[] = includes/AmazonPayHttpCurl.php +files[] = includes/AmazonPayIpnHandler.php +files[] = includes/AmazonPayClient.php +files[] = includes/AmazonLPA.php + +; Information added by Drupal.org packaging script on 2017-10-12 +version = "7.x-1.3" +core = "7.x" +project = "commerce_amazon_lpa" +datestamp = "1507844960" + diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.install b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.install new file mode 100644 index 00000000..09b74af8 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.install @@ -0,0 +1,176 @@ + $library['version'], + 'severity' => REQUIREMENT_OK, + ); + } + else { + $library = libraries_detect('login-and-pay-with-amazon-sdk-php'); + if ($library['installed'] === TRUE) { + $requirements['commerce_amazon_lpa'] = array( + 'value' => $library['version'], + 'severity' => REQUIREMENT_WARNING, + 'description' => $t('Currently @version is installed, upgrade to latest version https://github.com/amzn/amazon-pay-sdk-php/releases.', array('@version' => $library['version'])), + ); + } + else { + $requirements['commerce_amazon_lpa'] = array( + 'value' => $t('Missing'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Login and Pay Amazon SDK library missing. Download the library from https://github.com/amzn/amazon-pay-sdk-php and place it in to sites/all/libraries/amazon-pay-sdk-php', array('@url' => 'https://github.com/amzn/amazon-pay-sdk-php')), + ); + } + } + $requirements['commerce_amazon_lpa']['title'] = $t('Amazon Payments PHP SDK'); + + // Check for Commerce currency. + if (in_array(commerce_default_currency(), array_values(AmazonLPA::get_region_currency_code()))) { + $requirements['commerce_amazon_lpa_currency'] = array( + 'value' => $t('Valid currency'), + 'severity' => REQUIREMENT_OK, + ); + } + else { + $requirements['commerce_amazon_lpa_currency'] = array( + 'value' => $t('Invalid default currency!'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Amazon Payments currently supports only EUR, GBP, USD as a currency.'), + ); + } + $requirements['commerce_amazon_lpa_currency']['title'] = $t('Amazon Payments currency check'); + + if (!function_exists('curl_init')) { + $requirements['commerce_amazon_lpa_curl'] += array( + 'title' => $t('cURL'), + 'value' => $t('Not found'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t("Amazon Pay and Login with Amazon requires the PHP cURL library.", array('!curl_url' => 'http://php.net/manual/en/curl.setup.php')), + ); + } + } + return $requirements; +} + +/** + * Implements hook_enable(). + */ +function commerce_amazon_lpa_enable() { + // Add Amazon Payment eligibility field to product types. + $bundles = field_info_bundles('commerce_product'); + foreach ($bundles as $name => $properties) { + commerce_amazon_lpa_restricted_product_create_instance($name); + } + + // Add Amazon Order ID field to each order bundle. + $bundles = field_info_bundles('commerce_order'); + foreach ($bundles as $name => $properties) { + $field_name = 'commerce_amazon_lpa_order_id'; + $field = field_info_field($field_name); + if (empty($field)) { + $field = array( + 'field_name' => $field_name, + 'cardinality' => 1, + 'locked' => 1, + 'type' => 'text', + 'translatable' => FALSE, + ); + field_create_field($field); + } + + $instance = field_info_instance('commerce_order', $field_name, $name); + if (empty($instance)) { + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'commerce_order', + 'bundle' => $name, + 'label' => t('Amazon Order ID'), + 'settings' => array(), + ); + + foreach (array('default', 'customer', 'administrator') as $view_mode) { + $instance['display'][$view_mode] = array( + 'label' => 'hidden', + 'type' => 'hidden', + ); + } + + field_create_instance($instance); + } + } + + // Create Amazon User ID field on users. + $bundles = field_info_bundles('user'); + foreach ($bundles as $name => $properties) { + $field_name = 'commerce_amazon_lpa_user_id'; + $field = field_info_field($field_name); + if (empty($field)) { + $field = array( + 'field_name' => $field_name, + 'cardinality' => 1, + 'locked' => 1, + 'type' => 'text', + 'translatable' => FALSE, + ); + field_create_field($field); + } + + $instance = field_info_instance('user', $field_name, $name); + if (empty($instance)) { + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'user', + 'bundle' => $name, + 'label' => t('Amazon User ID'), + 'settings' => array(), + ); + + foreach (array('default') as $view_mode) { + $instance['display'][$view_mode] = array( + 'label' => 'hidden', + 'type' => 'hidden', + ); + } + + field_create_instance($instance); + } + } +} + +/** + * Implements hook_uninstall(). + */ +function commerce_amazon_lpa_uninstall() { + $variables = array(); + foreach ($variables as $variable) { + variable_del($variable); + } + $bundles = field_info_bundles('commerce_order'); + foreach ($bundles as $name => $properties) { + $instance = field_info_instance('commerce_order', 'commerce_amazon_lpa_order_id', $name); + commerce_delete_instance($instance); + } + commerce_delete_field('commerce_amazon_lpa_order_id'); + + $bundles = field_info_bundles('user'); + foreach ($bundles as $name => $properties) { + $instance = field_info_instance('user', 'commerce_amazon_lpa_user_id', $name); + commerce_delete_instance($instance); + } + commerce_delete_field('commerce_amazon_lpa_user_id'); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.module b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.module new file mode 100644 index 00000000..a6b5a260 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.module @@ -0,0 +1,1212 @@ +' . t("This module integrates Login and Pay with Amazon with Drupal and Drupal Commerce. For detailed information read the README.md in the module's folder..") . '

        '; + } +} + +/** + * Implements hook_views_api(). + */ +function commerce_amazon_lpa_views_api() { + return array( + 'api' => 3, + ); +} + +/** + * Implements hook_libraries_info(). + */ +function commerce_amazon_lpa_libraries_info() { + return array( + 'login-and-pay-with-amazon-sdk-php' => array( + 'name' => 'Login and Pay with Amazon PHP SDK', + 'vendor url' => 'https://github.com/amzn/login-and-pay-with-amazon-sdk-php', + 'download url' => 'https://github.com/amzn/login-and-pay-with-amazon-sdk-php/releases', + 'download file url' => 'https://github.com/amzn/login-and-pay-with-amazon-sdk-php/archive/2.1.0.zip', + 'version arguments' => array( + 'file' => 'composer.json', + 'pattern' => '/"version": "(\d+\.\d+\.\d+)"/', + ), + 'files' => array( + 'php' => array( + 'PayWithAmazon/Client.php', + 'PayWithAmazon/HttpCurl.php', + 'PayWithAmazon/Interface.php', + 'PayWithAmazon/IpnHandler.php', + 'PayWithAmazon/Regions.php', + 'PayWithAmazon/ResponseParser.php', + ), + ), + ), + 'amazon-pay-sdk-php' => array( + 'name' => 'Login and Pay with Amazon PHP SDK', + 'vendor url' => 'https://github.com/amzn/amazon-pay-sdk-php', + 'download url' => 'https://github.com/amzn/amazon-pay-sdk-php/releases', + 'download file url' => 'https://github.com/amzn/amazon-pay-sdk-php/archive/3.1.0.zip', + 'version arguments' => array( + 'file' => 'composer.json', + 'pattern' => '/"version": "(\d+\.\d+\.\d+)"/', + ), + 'files' => array( + 'php' => array( + 'AmazonPay/Client.php', + 'AmazonPay/ClientInterface.php', + 'AmazonPay/HttpCurl.php', + 'AmazonPay/HttpCurlInterface.php', + 'AmazonPay/IpnHandler.php', + 'AmazonPay/IpnHandlerInterface.php', + 'AmazonPay/Regions.php', + 'AmazonPay/ResponseInterface.php', + 'AmazonPay/ResponseParser.php', + ), + ), + ), + ); +} + +/** + * Implements hook_library(). + */ +function commerce_amazon_lpa_library() { + $items = array(); + + switch (variable_get('commerce_amazon_lpa_region', 'US')) { + case 'US': + $url = (AmazonLPA::is_sandbox()) ? + 'https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js' + : + 'https://static-na.payments-amazon.com/OffAmazonPayments/us/js/Widgets.js'; + break; + + case 'UK': + $url = (AmazonLPA::is_sandbox()) ? + 'https://static-eu.payments-amazon.com/OffAmazonPayments/uk/sandbox/lpa/js/Widgets.js' + : + 'https://static-eu.payments-amazon.com/OffAmazonPayments/uk/lpa/js/Widgets.js'; + break; + + case 'DE': + $url = (AmazonLPA::is_sandbox()) ? + 'https://static-eu.payments-amazon.com/OffAmazonPayments/de/sandbox/lpa/js/Widgets.js' + : + 'https://static-eu.payments-amazon.com/OffAmazonPayments/de/lpa/js/Widgets.js'; + break; + + default: + throw new Exception(t('Region not supported for JavaScript widgets')); + } + + $items['amazon_widgets'] = array( + 'title' => t('Commerce Checkout by Amazon library'), + 'version' => '1.0', + 'js' => array( + drupal_get_path('module', 'commerce_amazon_lpa') . '/js/commerce-amazon-lpa.js' => array('type' => 'file'), + array( + 'type' => 'setting', + 'data' => array( + 'AmazonLPA' => array( + 'clientId' => variable_get('commerce_amazon_lpa_client_id', ''), + 'merchantId' => variable_get('commerce_amazon_lpa_merchant_id', ''), + 'widgetsJsUrl' => $url, + 'checkoutUrl' => url('checkout', array('absolute' => TRUE)), + 'langcode' => variable_get('commerce_amazon_lpa_langcode', AmazonLPA::get_region_langcode(variable_get('commerce_amazon_lpa_region'))), + 'buttonOptions' => array( + 'paySize' => variable_get('commerce_amazon_lpa_pay_button_size', AmazonLPA::BUTTON_SIZE_MEDIUM), + 'payStyle' => variable_get('commerce_amazon_lpa_pay_button_style', AmazonLPA::BUTTON_COLOR_GOLD), + 'loginSize' => variable_get('commerce_amazon_lpa_login_button_size', AmazonLPA::BUTTON_SIZE_MEDIUM), + 'loginStyle' => variable_get('commerce_amazon_lpa_login_button_style', AmazonLPA::BUTTON_COLOR_GOLD), + ), + 'addressBookOptions' => array( + 'displayMode' => 'edit', + ), + 'walletOptions' => array( + 'displayMode' => 'edit', + ), + 'loginOptions' => array( + 'popup' => variable_get('commerce_amazon_lpa_popup', 'popup') == 'popup', + ), + ), + ), + ), + ), + 'dependencies' => array(array('system', 'jquery')), + ); + + return $items; +} + +/** + * Implements hook_menu(). + */ +function commerce_amazon_lpa_menu() { + $items = array(); + + // Checkout by Amazon settings. + $items['admin/commerce/config/amazon-lpa'] = array( + 'title' => 'Amazon Pay and Login with Amazon', + 'description' => 'Manage Amazon Pay and Login with Amazon settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_amazon_lpa_settings_form'), + 'file' => 'includes/commerce_amazon_lpa.admin.inc', + 'access arguments' => array('configure store'), + ); + + $items['checkout/amazon'] = array( + 'type' => MENU_CALLBACK, + 'page callback' => 'commerce_amazon_lpa_checkout', + 'access arguments' => array('access checkout'), + 'file' => 'includes/commerce_amazon_lpa.pages.inc', + ); + + // Amazon IPN callback. + $items['commerce-amazon-lpa/ipn'] = array( + 'title' => 'IPN callback', + 'page callback' => 'commerce_amazon_lpa_process_ipn', + // Allow all access since this is an API callback. + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + 'file' => 'includes/commerce_amazon_lpa.pages.inc', + ); + + $items['user/login/amazon'] = array( + 'title' => 'You are being logged in', + 'page callback' => 'commerce_amazon_lpa_login_callback', + 'access callback' => 'commerce_amazon_lpa_login_callback_access', + 'type' => MENU_CALLBACK, + 'file' => 'includes/commerce_amazon_lpa.pages.inc', + ); + + // Payment tab on orders. + $items['admin/commerce/orders/%commerce_order/payment/amazon-payments-authorize'] = array( + 'title' => 'Add authorization', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_amazon_lpa_authorize_form', 3), + 'access callback' => 'commerce_amazon_lpa_payment_transaction_access', + 'access arguments' => array(3, NULL, 'authorize'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => 10, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'file' => 'includes/commerce_amazon_lpa.admin.inc', + ); + + $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/amazon-payments-authorize'] = array( + 'title' => 'Authorize', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_amazon_lpa_authorize_form', 3, 5), + 'access callback' => 'commerce_amazon_lpa_payment_transaction_access', + 'access arguments' => array(3, 5, 'authorize'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 2, + 'file' => 'includes/commerce_amazon_lpa.admin.inc', + ); + + $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/amazon-payments-capture'] = array( + 'title' => 'Capture', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_amazon_lpa_capture_form', 3, 5), + 'access callback' => 'commerce_amazon_lpa_payment_transaction_access', + 'access arguments' => array(3, 5, 'capture'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 2, + 'file' => 'includes/commerce_amazon_lpa.admin.inc', + ); + // Add a menu item for refunding settled transactions. + $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/amazon-payments-refund'] = array( + 'title' => 'Refund', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_amazon_lpa_refund_form', 3, 5), + 'access callback' => 'commerce_amazon_lpa_payment_transaction_access', + 'access arguments' => array(3, 5, 'refund'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 4, + 'file' => 'includes/commerce_amazon_lpa.admin.inc', + ); + + // Add a menu item for refunding settled transactions. + $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/amazon-payments-close'] = array( + 'title' => 'Close', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('commerce_amazon_lpa_close_form', 3, 5), + 'access callback' => 'commerce_amazon_lpa_payment_transaction_access', + 'access arguments' => array(3, 5, 'close'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'weight' => 5, + 'file' => 'includes/commerce_amazon_lpa.admin.inc', + ); + + return $items; +} + +/** + * Access callback for Amazon Payments payment transaction operations. + * + * @param object $order + * The order object. + * @param object $transaction + * The transaction object. + * @param string $type + * The type of transaction action to perform. One of "capture", "refund", or + * "close". + * + * @return bool + * TRUE or FALSE, if allowed. + */ +function commerce_amazon_lpa_payment_transaction_access($order, $transaction, $type) { + if (!$transaction && $type == 'authorize') { + $balance = commerce_payment_order_balance($order); + if ($balance['amount'] > 0) { + $authorization = commerce_amazon_lpa_get_order_authorization($order->order_id); + return empty($authorization); + } + else { + return FALSE; + } + } + + $amazon_transaction_type = $transaction->data['commerce_amazon_lpa']['transaction_type']; + switch ($type) { + case 'authorize': + // If it's not pending, then we cannot touch it. + if ($transaction->status != COMMERCE_PAYMENT_STATUS_PENDING || $amazon_transaction_type != 'authorization') { + return FALSE; + } + + return (empty($transaction->remote_id) && empty($transaction->remote_status)); + + case 'capture': + // If it's not pending, then we cannot touch it. + if ($transaction->status != COMMERCE_PAYMENT_STATUS_PENDING || $amazon_transaction_type != 'authorization') { + return FALSE; + } + + // We have to wait for IPN to mark an authorization open. + if ($transaction->remote_status != 'Open') { + return FALSE; + } + + if (!empty($transaction->data['commerce_amazon_lpa']['environment']) && $transaction->data['commerce_amazon_lpa']['environment'] == AmazonLPA::ENV_LIVE) { + $acceptable_days = 30; + } + else { + $acceptable_days = 2; + } + // If we're out of time, this will be false. + return ($transaction->created + 86400 * $acceptable_days) > time(); + + case 'refund': + // Only valid, open captures can be refunded. + return ($amazon_transaction_type == 'capture' + && $transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS + && $transaction->remote_status == 'Completed'); + + case 'close': + // We can only close pending authorizations. + return isset($transaction->data['commerce_amazon_lpa']['auth_reference_id']) + && $transaction->status == COMMERCE_PAYMENT_STATUS_PENDING; + + default: + return FALSE; + } +} + +/** + * Implements hook_permission(). + */ +function commerce_amazon_lpa_permission() { + $permissions = array( + 'access login and pay with amazon' => array( + 'title' => t('Login and Pay with Amazon access'), + 'description' => t('Set Login and Pay with Amazon (button) availabilty.'), + ), + 'access access login and pay with debug' => array( + 'title' => t('Access the Login and Pay with Amazon debug log'), + ), + ); + + return $permissions; +} + +/** + * Implements hook_form_alter(). + * + * Move hook_form_alter() to the end of the list. + * Needed for checkout button on cart page. + */ +function commerce_amazon_lpa_module_implements_alter(&$implementations, $hook) { + if ($hook == 'form_alter') { + $group = $implementations['commerce_amazon_lpa']; + unset($implementations['commerce_amazon_lpa']); + $implementations['commerce_amazon_lpa'] = $group; + } +} + +/** + * Implements hook_theme(). + */ +function commerce_amazon_lpa_theme($existing, $type, $theme, $path) { + return array( + 'commerce_amazon_login_button' => array( + 'variables' => array( + 'html_id' => 'AmazonLoginButton', + 'button_parameter' => 'LwA', + ), + 'file' => 'commerce_amazon_lpa.theme.inc', + ), + 'commerce_amazon_payment_button' => array( + 'variables' => array('order_id' => NULL, 'html_id' => 'AmazonPaymentButton'), + 'file' => 'commerce_amazon_lpa.theme.inc', + ), + 'commerce_amazon_payment_button__summary_link' => array( + 'base hook' => 'commerce_amazon_payment_button', + 'file' => 'commerce_amazon_lpa.theme.inc', + ), + 'commerce_amazon_addressbook_widget' => array( + 'variables' => array('html_id' => NULL, 'order_reference_id' => ''), + 'file' => 'commerce_amazon_lpa.theme.inc', + ), + 'commerce_amazon_wallet_widget' => array( + 'variables' => array('html_id' => 'walletWidgetDiv', 'order_reference_id' => ''), + 'file' => 'commerce_amazon_lpa.theme.inc', + ), + ); +} + +/** + * Implements hook_form_alter(). + */ +function commerce_amazon_lpa_form_alter(&$form, &$form_state, $form_id) { + if (!AmazonLPA::is_configured() || AmazonLPA::is_hidden()) { + // Back out if Amazon LPA not configured. + return; + } + + if (strpos($form_id, 'views_form_commerce_cart_form_') === 0) { + $form['actions']['amazon_lpa'] = array( + '#markup' => theme('commerce_amazon_payment_button', array( + 'order_id' => $form_state['order']->order_id, + 'html_id' => 'amazon_lpa_cart_pay', + )), + ); + if (commerce_amazon_lpa_order_has_restricted_products($form_state['order'])) { + $form['actions']['amazon_lpa']['#access'] = FALSE; + $form['actions']['amazon_lpa']['#suffix'] = t('Your order contains a product which cannot be paid for through Amazon Payments'); + } + if (variable_get('commerce_amazon_lpa_checkout_strategy', AmazonLPA::STRATEGY_NORMAL) == AmazonLPA::STRATEGY_AMAZON) { + $form['actions']['checkout']['#access'] = FALSE; + } + else { + $form['actions']['checkout']['#submit'][] = 'commerce_amazon_lpa_checkout_form_cancel_submit'; + } + } + + // The following items react on altering the checkout form to bring in a + // parallel checkout process with Amazon. We cannot use form_FORM_ID_alter + // because panes may not be on their default pages. + if (strpos($form_id, 'commerce_checkout_form_') === FALSE || !isset($form_state['order']->data['commerce_amazon_lpa'])) { + // Since this involves a lot of logic, exit early. + return; + } + + if (commerce_amazon_lpa_order_has_restricted_products($form_state['order'])) { + drupal_set_message(t('Your order contains a product which cannot be paid for through Amazon Payments')); + return; + } + + // Logic on if this is an Amazon order. + $api = AmazonLPA::instance(); + + $order_wrapper = entity_metadata_wrapper('commerce_order', $form_state['order']); + $contract_id = $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD}->value(); + + // Send the order ID to JavaScript. + $form['#attached']['js'][] = array( + 'data' => array( + 'AmazonLPA' => array( + 'orderId' => $form_state['order']->order_id, + 'orderReferenceId' => ($contract_id) ? $contract_id : NULL, + 'isShippable' => commerce_amazon_lpa_order_is_shippable($form_state['order']), + ), + ), + 'type' => 'setting', + ); + + // Review table modification. + if (isset($form['checkout_review'])) { + // User hasn't been able to select billing yet. + unset($form['checkout_review']['review']['#data']['customer_profile_billing']); + unset($form['checkout_review']['review']['#data']['customer_profile_shipping']); + } + + // Modify billing and shipping profile types. + $profile_types = array('billing', 'shipping'); + foreach ($profile_types as $profile_type) { + $name = 'customer_profile_' . $profile_type; + // First check to see if the profile's form is available. Exit early. + if (!isset($form[$name])) { + continue; + } + + // Remove copy feature. + if (isset($form[$name]['commerce_customer_profile_copy'])) { + unset($form[$name]['commerce_customer_profile_copy']); + } + foreach (element_children($form[$name]) as $child_key) { + $form[$name][$child_key]['#access'] = FALSE; + } + $form[$name]['#access'] = FALSE; + } + + // Modify payment form. + if (isset($form['commerce_payment'])) { + $form['commerce_payment']['payment_method']['#access'] = FALSE; + $form['commerce_payment']['payment_method']['#value'] = 'commerce_amazon_login_and_pay|commerce_payment_commerce_amazon_login_and_pay'; + $form['commerce_payment']['payment_method']['#default_value'] = 'commerce_amazon_login_and_pay|commerce_payment_commerce_amazon_login_and_pay'; + // There isn't a way to tell Commerce Payment to use a default payment + // gateway, so we rebuild the details form here. + $payment_method = commerce_payment_method_load('commerce_amazon_login_and_pay'); + $callback = commerce_payment_method_callback($payment_method, 'submit_form'); + if (is_callable($callback)) { + $form['commerce_payment']['payment_details'] = $callback($payment_method, array(), array(), $form_state['order']); + } + } + + // Hide the back button when the user has an invalid payment method. + if ($form_state['checkout_page']['page_id'] == 'review') { + if ($api->getOrderReferenceId($order_wrapper)) { + $amazon_order_reference = $api->getOrderRef($order_wrapper); + if ($amazon_order_reference['OrderReferenceStatus']['State'] == 'Suspended') { + $form['buttons']['back']['#access'] = FALSE; + } + } + } +} + +/** + * Removes Login and Pay with Amazon from a canceled checkout order. + * + * @param array $form + * Form array. + * @param array $form_state + * Form state. + */ +function commerce_amazon_lpa_checkout_form_cancel_submit($form, &$form_state) { + unset($form_state['order']->data['commerce_amazon_lpa']); + commerce_order_save($form_state['order']); +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function commerce_amazon_lpa_form_user_login_alter(&$form, &$form_state, $form_id) { + if (AmazonLPA::get_operation_mode() == AmazonLPA::OPERATION_MODE_LOGIN_AND_PAY) { + $form['amazon_lpa'] = array( + '#weight' => 200, + '#theme' => 'commerce_amazon_login_button', + ); + } +} + +/** + * Access callback for Amazon Login. + * + * Validates Login and Pay is configured and proper cookies exist. + * + * @return bool + * TRUE or FALSE if allowed. + */ +function commerce_amazon_lpa_login_callback_access() { + + if (!AmazonLPA::is_configured()) { + return FALSE; + } + + $popup = variable_get('commerce_amazon_lpa_popup', 'popup'); + if ($popup == 'popup') { + return (isset($_COOKIE['amazon_Login_accessToken'])); + } + elseif ($popup == 'redirect') { + return TRUE; + } + else { + return FALSE; + } +} + +/** + * Logs an authenticated Amazon user in and registers authmap. + */ +function commerce_amazon_lpa_external_login() { + $api = new AmazonLPA(); + + try { + $api_user = $api->getUserInfo(); + // Check if this user exists and is not an external user. + $existing = user_load_by_mail($api_user['email']); + if ($existing && !user_external_load($api_user['email'])) { + // Add authmap. + user_set_authmaps($existing, array('authname_commerce_amazon_lpa' => $api_user['email'])); + watchdog('commerce_amazon_lpa', 'Auth map added for %name.', array('%name' => $existing->name)); + // Log user in. + $form_state['uid'] = $existing->uid; + + rules_invoke_all('commerce_amazon_lpa_registration', $existing); + + user_login_submit(array(), $form_state); + } + else { + // Register or log the user in. + user_external_login_register($api_user['email'], 'commerce_amazon_lpa'); + watchdog('commerce_amazon_lpa', 'User %name registered via Amazon login.', array('%name' => $GLOBALS['user']->name)); + + rules_invoke_all('commerce_amazon_lpa_registration', $GLOBALS['user']); + } + } + catch (Exception $e) { + watchdog('commerce_amazon_lpa', 'Unable to authenticate user due to bad access token', array(), WATCHDOG_ERROR); + } +} + +/** + * Implements hook_user_logout(). + * + * @see user_cookie_delete() + */ +function commerce_amazon_lpa_user_logout($account) { + setcookie('amazon_Login_accessToken', '', REQUEST_TIME - 3600, '/'); + setcookie('amazon_Login_state_cache', '', REQUEST_TIME - 3600, '/'); +} + +/** + * Implements hook_user_presave(). + */ +function commerce_amazon_lpa_user_presave(&$edit, $account, $category) { + $api = new AmazonLPA(); + // user_external_login_register passes anonymous user to $account. + if (!isset($edit['mail']) && $amazon_user_info = $api->getUserInfo()) { + $edit['mail'] = $edit['name']; + $edit['commerce_amazon_lpa_user_id'][LANGUAGE_NONE][0]['value'] = $amazon_user_info['user_id']; + } +} + +/** + * Implements hook_cron(). + */ +function commerce_amazon_lpa_cron() { + // If in ERP mode, do not run. + if (AmazonLPA::is_erp_mode()) { + return; + } + + $api = AmazonLPA::instance(); + + // Query pending payment transactions. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'commerce_payment_transaction') + ->propertyCondition('payment_method', 'commerce_amazon_login_and_pay') + ->propertyCondition('status', COMMERCE_PAYMENT_STATUS_PENDING); + $results = $query->execute(); + if (!empty($results)) { + $transactions = entity_load('commerce_payment_transaction', array_keys($results['commerce_payment_transaction'])); + foreach ($transactions as $transaction) { + try { + $type = $transaction->data['commerce_amazon_lpa']['transaction_type']; + + if ($type == 'authorization') { + $data = $api->getAuthorizationDetails($transaction->remote_id); + $api->processAuthorizeTransaction($transaction, $data); + } + elseif ($type == 'capture') { + $data = $api->getCaptureDetails($transaction->remote_id); + $api->processCaptureTransaction($transaction, $data); + } + elseif ($type == 'refund') { + $data = $api->getRefundDetails($transaction->remote_id); + $api->processRefundTransaction($transaction, $data); + } + } + catch (Exception $e) { + watchdog_exception('commerce_amazon_lpa', $e, $e->getMessage()); + watchdog('commerce_amazon_lpa', 'Failed to process @transaction', array('@transaction' => $transaction->remote_id), WATCHDOG_ERROR); + } + } + } +} + + +/** + * Converts an Amazon address into a Commerce Customer profile. + * + * @param object $order + * The order to attach the profile to. The generated profile will be attached. + * @param string $profile_type + * The profile type to create. Usually "billing" or "shipping". + * @param array $amazon_address + * The Amazon address data structure included with the AuthorizationDetails. + * + * @return object + * The created customer profile. + * + * @throws \EntityMetadataWrapperException + * A wrapper exception. + */ +function commerce_amazon_lpa_amazon_address_to_customer_profile($order, $profile_type, $amazon_address) { + $profile_field = 'commerce_customer_' . $profile_type; + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + + if (!empty($order->{$profile_field})) { + $customer_profile = $order_wrapper->{$profile_field}->value(); + } + else { + $customer_profile = commerce_customer_profile_new($profile_type, $order->uid); + // Save the new customer profile. + commerce_customer_profile_save($customer_profile); + } + $address_state = NULL; + if (!empty($amazon_address['State'])) { + $address_state = $amazon_address['State']; + } + elseif (!empty($amazon_address['StateOrProvinceCode'])) { + $address_state = $amazon_address['StateOrProvinceCode']; + } + elseif (!empty($amazon_address['StateOrRegion'])) { + $address_state = $amazon_address['StateOrRegion']; + } + + $address_thoroughfare = !empty($amazon_address['AddressLine1']) ? $amazon_address['AddressLine1'] : ''; + $address_premise = !empty($amazon_address['AddressLine2']) ? $amazon_address['AddressLine2'] : ''; + + $customer_profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $customer_profile); + $customer_profile_wrapper->commerce_customer_address->name_line = isset($amazon_address['Name']) ? $amazon_address['Name'] : ''; + $customer_profile_wrapper->commerce_customer_address->country = $amazon_address['CountryCode']; + $customer_profile_wrapper->commerce_customer_address->locality = isset($amazon_address['City']) ? $amazon_address['City'] : ''; + $customer_profile_wrapper->commerce_customer_address->administrative_area = $address_state; + $customer_profile_wrapper->commerce_customer_address->postal_code = isset($amazon_address['PostalCode']) ? $amazon_address['PostalCode'] : ''; + $customer_profile_wrapper->commerce_customer_address->thoroughfare = $address_thoroughfare; + $customer_profile_wrapper->commerce_customer_address->premise = $address_premise; + $customer_profile->commerce_customer_address[LANGUAGE_NONE][0]['data'] = serialize($amazon_address); + // Save the customer profile. + commerce_customer_profile_save($customer_profile); + + // If Addressbook is enabled, make sure this is the default. + // This really only matters on instances like CK2 that display the information + // directly to the user and they may see the original partial profile before + // the order is confirmed. + if (module_exists('commerce_addressbook')) { + commerce_addressbook_set_default_profile($customer_profile); + } + + $order_wrapper->{$profile_field} = $customer_profile->profile_id; + + return $customer_profile; +} + +/** + * Return an entity object based on an Amazon Reference ID. + * + * @param string $id + * The ID Amazon assigns an order. + * + * @return mixed + * The order, if found, NULL otherwise. + */ +function commerce_amazon_lpa_order_from_amazon_reference_id($id) { + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'commerce_order', '=') + ->fieldCondition(AmazonLPA::REFERENCE_ID_FIELD, 'value', $id); + $result = $query->execute(); + + if (empty($result)) { + return NULL; + } + else { + $entity_stub = reset($result['commerce_order']); + return commerce_order_load($entity_stub->order_id); + } +} + +/** + * Sets the status and remote status of a transaction from Amazon state. + * + * @param object $transaction + * The transaction to set the state on. + * @param array $state + * The status that comes from Amazon. + * + * @throws \Exception + * Exception if there was an invalid state. + */ +function commerce_amazon_lpa_payment_state_to_status($transaction, array $state) { + $transaction->remote_status = $state['State']; + + switch ($state['State']) { + case 'Completed': + $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + break; + + case 'Declined': + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + break; + + case 'Open': + $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING; + break; + + case 'Pending': + $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING; + break; + + case 'Closed': + if ($state['ReasonCode'] == 'MaxCapturesProcessed') { + $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + } + else { + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + } + break; + + default: + throw new Exception(t('Unexpected payment object state (@state) returned from Login and Pay with Amazon API', array( + '@state' => $state, + ))); + } +} + +/** + * Writes a debug log message. + * + * @param string $message + * The message parameter to send to watchdog(). + * @param array $variables + * The variables parameter to send to watchdog(). + * @param int $type + * The watchdog severity, defaults to WATCHDOG_DEBUG. + */ +function commerce_amazon_lpa_add_debug_log($message, $variables = array(), $type = WATCHDOG_DEBUG) { + if (variable_get('commerce_amazon_lpa_log_handling', FALSE) == 1) { + watchdog('commerce_amazon_lpa', $message, $variables, $type); + } +} + +/** + * Implements hook_field_access(). + */ +function commerce_amazon_lpa_field_access($op, $field, $entity_type, $entity, $account) { + // The Amazon User ID field should only be populated when first login happens. + if ($field['field_name'] == 'commerce_amazon_lpa_user_id' && $op == 'edit') { + return FALSE; + } + + return NULL; +} + +/** + * Creates or appends a transaction message based on the returned data. + * + * @param object $transaction + * The transaction on which to create or append the message. + * @param string $type + * The type of data structure provided. Usually "authorization", "refund", or + * "capture". + * @param array $data + * The Details->*Status field from the original message. + */ +function commerce_amazon_lpa_transaction_message_update_data($transaction, $type, $data) { + $message_variables = array( + '@state_' . REQUEST_TIME => $data['State'], + ); + if (isset($data['ReasonCode'])) { + $message_variables['@reason_code_' . REQUEST_TIME] = commerce_amazon_lpa_reason_code_message($type, $data['ReasonCode']); + } + $transaction->message = '@state_' . REQUEST_TIME . (isset($data['ReasonCode']) ? ' (@reason_code_' . REQUEST_TIME . ')' : ''); + $transaction->message_variables = $message_variables; +} + +/** + * Gets the payment authorization for a given Order ID. + * + * @param int $order_id + * The order ID. + * + * @return bool|object + * FALSE if no transaction is found. Otherwise the transaction that holds the + * authorization for an order. + */ +function commerce_amazon_lpa_get_order_authorization($order_id) { + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'commerce_payment_transaction') + ->propertyCondition('order_id', $order_id) + ->propertyCondition('status', array(COMMERCE_PAYMENT_STATUS_FAILURE, COMMERCE_PAYMENT_STATUS_SUCCESS), 'NOT IN') + ->propertyOrderBy('created', 'DESC'); + $result = $query->execute(); + + if (!empty($result)) { + $transactions = commerce_payment_transaction_load_multiple(array_keys($result['commerce_payment_transaction'])); + foreach ($transactions as $transaction) { + // Check the authorization ID. + if (!empty($transaction->data['commerce_amazon_lpa']['auth_reference_id'])) { + return $transaction; + } + } + } + + return FALSE; +} + +/** + * Loads a payment transaction based on Amazon Payments remote ID. + * + * @param string $remote_id + * The remote Amazon Payments reference ID. + * + * @return bool|object + * False is transaction does not exist, other wise the payment transaction. + */ +function commerce_amazon_lpa_remote_payment_transaction_load($remote_id) { + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'commerce_payment_transaction') + ->propertyCondition('remote_id', $remote_id); + $result = $query->execute(); + + if (!empty($result)) { + $transactions = commerce_payment_transaction_load_multiple(array_keys($result['commerce_payment_transaction'])); + return reset($transactions); + } + + return FALSE; +} + +/** + * Returns a user-friendly explanation for a given reason code. + * + * @param string $kind + * One of 'authorization', 'refund', or 'capture'. + * @param null|string $code + * The code to return. NULL to return all codes. + * + * @return string + * The reason message. If an invalid message kind is provided, a blank string + * will be returned. If a code is provided, but no reason message is found + * "Unknown reason." will be returned. + */ +function commerce_amazon_lpa_reason_code_message($kind, $code = NULL) { + $codes = array( + 'authorization' => array( + 'ExpiredUnused' => t('The authorization has been in the Open state for 30 days (two days for Sandbox) and you did not submit any captures against it.'), + 'MaxCapturesProcessed' => t('You have already captured the full amount of the authorization. Amazon allows only one capture per authorization.'), + 'AmazonClosed' => t('Amazon has closed the authorization object due to problems with your account.'), + 'OrderReferenceCanceled' => t('The order reference was canceled causing all open authorizations to be canceled.'), + 'SellerClosed' => t('You have explicitly closed this authorization.'), + 'InvalidPaymentMethod' => t('There were problems with the payment method. You should contact your buyer and have them update their payment method using the Amazon Payments website.'), + 'AmazonRejected' => t('Amazon has rejected the authorization. You should retry the authorization only if the order reference is in the Open state.'), + 'ProcessingFailure' => t('Amazon could not process the transaction due to an internal processing error. You should retry the authorization only if the order reference is in the Open state.'), + 'TransactionTimedOut' => t('In asynchronous mode, indicates that the Authorize operation call was not processed within the default timeout period of 24 hours or within the time period specified by you in the TransactionTimeout request parameter. In synchronous mode, indicates that Amazon could not process your request within 8 seconds. If you are observing a high number of declines due to this reason code, try adjusting the timeout value in asynchronous mode, or consider using asynchronous mode if you are using synchronous mode. An alternate approach for handling this error in synchronous mode is to retry the transaction in asynchronous mode.'), + ), + 'capture' => array( + 'AmazonRejected' => t('Amazon has rejected the capture. You should only retry the capture if the authorization is in the Open state.'), + 'ProcessingFailure' => t('Amazon could not process the transaction due to an internal processing error. You should only retry the capture if the authorization is in the Open state. Otherwise, you should request a new authorization and then call Capture on it.'), + 'MaxAmountRefunded' => t('You have already refunded the following amounts, including any A-to-z claims and charge-backs that you were responsible for:'), + 'MaxRefundsProcessed' => t('You have already submitted 10 refunds for this Capture object.'), + 'AmazonClosed' => t("Amazon has closed the capture due to a problem with your account or with the buyer's account."), + ), + 'refund' => array( + 'AmazonRejected' => t('Amazon has rejected the refund. You should issue a refund to the buyer in an alternate manner (for example, a gift card or store credit).'), + 'ProcessingFailure' => t('Amazon could not process the transaction due to an internal processing error or because the buyer has already received a refund from an A-to-z claim or a chargeback. You should only retry the refund if the Capture object is in the Completed state. Otherwise, you should refund the buyer in an alternative way (for example, a store credit or a check).'), + ), + ); + $kind = strtolower($kind); + if (!in_array($kind, array_keys($codes))) { + return ''; + } + if ($code !== NULL) { + if (array_key_exists($code, $codes[$kind])) { + return $codes[$kind][$code]; + } + return t('Unknown reason.'); + } + return $codes[$kind]; +} + +/** + * Implements hook_form_alter(). + * + * @param $form + * The form to alter. + * @param $form_state + * The form's state array. + */ +function commerce_amazon_lpa_form_commerce_order_ui_order_form_alter(&$form, $form_state) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $form_state['commerce_order']); + $existing_reference_id = AmazonLPA::instance()->getOrderReferenceId($order_wrapper); + if ($existing_reference_id) { + foreach (array('commerce_customer_billing', 'commerce_customer_shipping') as $customer_profile_field) { + if (isset($form[$customer_profile_field])) { + $form[$customer_profile_field]['#disabled'] = TRUE; + } + } + } +} + +/** + * Implements hook_commerce_amazon_lpa_request_params_alter(). + */ +function commerce_amazon_lpa_commerce_amazon_lpa_request_params_alter(array &$params, $type, $data) { + // We must be in sandbox, and have simulation turned on to alter the params + // and set simulation messages. + $simulation_code = variable_get('commerce_amazon_lpa_simulation', '_none'); + if (!AmazonLPA::is_sandbox() || $simulation_code == '_none') { + return; + } + // Do not run simulations during IPN. + if (isset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'])) { + return; + } + + switch ($type) { + case 'authorize': + switch ($simulation_code) { + case 'Authorizations_InvalidPaymentMethod': + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"InvalidPaymentMethod", "PaymentMethodUpdateTimeInMins":1}}'; + break; + + case 'Authorizations_AmazonRejected': + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"AmazonRejected"}}'; + break; + + case 'Authorizations_AmazonClosed': + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Closed", "ReasonCode":"AmazonClosed"}} '; + break; + + case 'Authorizations_TransactionTimedOut': + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"TransactionTimedOut"}}'; + break; + + case 'Captures_Pending': + if (AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_AUTH_CAPTURE) { + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Pending"}}'; + } + break; + + case 'Captures_AmazonRejected': + if (AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_AUTH_CAPTURE) { + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"AmazonRejected"}}'; + } + break; + + case 'Captures_AmazonClosed': + if (AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_AUTH_CAPTURE) { + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Closed", "ReasonCode":"AmazonClosed"}}'; + } + break; + case 'Authorizations_ExpiredUnused': + $params['seller_authorization_note'] = '{"SandboxSimulation": {"State":"Closed", "ReasonCode":"ExpiredUnused", "ExpirationTimeInMins":1}}'; + break; + } + break; + + case 'cancel_order_reference': + break; + + case 'capture': + switch ($simulation_code) { + case 'Captures_Pending': + $params['seller_capture_note'] = '{"SandboxSimulation": {"State":"Pending"}}'; + break; + + case 'Captures_AmazonRejected': + $params['seller_capture_note'] = '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"AmazonRejected"}}'; + break; + + case 'Captures_AmazonClosed': + $params['seller_capture_note'] = '{"SandboxSimulation": {"State":"Closed", "ReasonCode":"AmazonClosed"}}'; + break; + } + break; + + case 'close_authorization': + break; + + case 'close_order_reference': + switch ($simulation_code) { + case 'OrderReference_AmazonClosed': + $params['closure_reason'] = '{"SandboxSimulation": {"State":"Closed", "ReasonCode":"AmazonClosed"}}'; + break; + } + break; + + case 'confirm_order_reference': + break; + + case 'get_capture_details': + break; + + case 'get_order_reference': + break; + + case 'refund': + switch ($simulation_code) { + case 'Refund_AmazonRejected': + $params['seller_refund_note'] = '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"AmazonRejected"}}'; + break; + } + break; + + case 'set_order_reference': + break; + } +} + +/** + * Creates a required, locked instance of an "Excluded from Amazon" flag. + * + * @param $bundle + * The bundle name of the entity the field instance will be attached to. + * @param $weight + * The default weight of the field instance widget and display. + * @param $display + * An array of default display data used for the entity's current view modes. + */ +function commerce_amazon_lpa_restricted_product_create_instance($bundle, $weight = 0, $display = array()) { + $field_name = 'commerce_amazon_lpa_restricted'; + $entity_type = 'commerce_product'; + + // Look for or add the specified field to the requested entity bundle. + commerce_activate_field($field_name); + field_cache_clear(); + + $field = field_info_field($field_name); + $instance = field_info_instance($entity_type, $field_name, $bundle); + + if (empty($field)) { + $field = array( + 'field_name' => $field_name, + 'type' => 'list_boolean', + 'cardinality' => 1, + 'entity_types' => array($entity_type), + 'translatable' => FALSE, + ); + $field = field_create_field($field); + } + + if (empty($instance)) { + $instance = array( + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + + 'label' => t('Not available via Amazon Payments'), + 'settings' => array(), + 'widget' => array( + 'type' => 'options_onoff', + 'weight' => $weight, + 'settings' => array( + 'display_label' => 1, + ), + ), + + 'display' => array(), + ); + + $entity_info = entity_get_info($entity_type); + + // Spoof the default view mode and node teaser so its display type is set. + $entity_info['view modes'] += array( + 'default' => array(), + 'node_teaser' => array(), + ); + + foreach ($entity_info['view modes'] as $view_mode => $data) { + $instance['display'][$view_mode] = $display + array( + 'label' => 'hidden', + 'type' => 'hidden', + 'weight' => $weight, + ); + } + + field_create_instance($instance); + } +} + +/** + * Checks if an order has a restricted item, preventing Amazon Payments. + * @param $order + * @return bool + */ +function commerce_amazon_lpa_order_has_restricted_products($order) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + foreach ($order_wrapper->commerce_line_items as $commerce_line_item) { + if (isset($commerce_line_item->commerce_product)) { + if (isset($commerce_line_item->commerce_product->commerce_amazon_lpa_restricted)) { + if ($commerce_line_item->commerce_product->commerce_amazon_lpa_restricted->value()) { + return TRUE; + } + } + } + } + + return FALSE; +} + +/** + * Checks if an order is shippable via Amazon Payments. + * @param $order + * @return bool + */ +function commerce_amazon_lpa_order_is_shippable($order) { + $is_shippable = FALSE; + + if (module_exists('commerce_shipping')) { + $digital_products = variable_get('commerce_amazon_lpa_digital_product_types', array()); + + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + foreach ($order_wrapper->commerce_line_items as $commerce_line_item) { + if (isset($commerce_line_item->commerce_product)) { + // If the product bundle is not marked as digital, it is shippable. + // Return immediately so further checks do not negate this flag. + if (!in_array($commerce_line_item->commerce_product->getBundle(), $digital_products)) { + return TRUE; + } + } + } + } + + return $is_shippable; +} + +/** + * Overrides the Commerce Shipping checkout form pane callback. + */ +function commerce_amazon_lpa_commerce_shipping_pane_checkout_form($form, &$form_state, $checkout_pane, $order) { + if (commerce_amazon_lpa_order_is_shippable($order)) { + return commerce_shipping_pane_checkout_form($form, $form_state, $checkout_pane, $order); + } + else { + $checkout_page = $form_state['checkout_page']; + // If there is another checkout page... + if ($checkout_page['next_page']) { + // Update the order status to reflect the next checkout page. + $order = commerce_order_status_update($order, 'checkout_' . $checkout_page['next_page'], FALSE, NULL, t('Order was marked as digital and skipped shipping calculations.')); + + // If it happens to be the complete page, process completion now. + if ($checkout_page['next_page'] == 'complete') { + commerce_checkout_complete($order); + } + + // Redirect to the next checkout page. + drupal_goto(commerce_checkout_order_uri($order)); + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.rules.inc new file mode 100644 index 00000000..9f1d5508 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.rules.inc @@ -0,0 +1,144 @@ + 'a drupal commerce order')), TRUE), + entity_rules_events_variables('commerce_payment_transaction', t('Last completed transaction'), TRUE), + array('data' => array('type' => 'struct', 'label' => t('Data returned by Amazon'))) + ); + + $events['commerce_amazon_lpa_nonsync_auth_soft_decline'] = array( + 'label' => t('When an Amazon Payments transaction has a soft decline'), + 'group' => t('Commerce Amazon LPA'), + 'variables' => $variables, + 'access callback' => 'commerce_order_rules_access', + ); + + $events['commerce_amazon_lpa_nonsync_auth_hard_decline'] = array( + 'label' => t('When an Amazon Payments transaction has a hard decline'), + 'group' => t('Commerce Amazon LPA'), + 'variables' => $variables, + 'access callback' => 'commerce_order_rules_access', + ); + + $events['commerce_amazon_lpa_authentication'] = array( + 'label' => t('A user authenticated an existing account with Amazon Login and Pay'), + 'group' => t('Commerce Amazon LPA'), + 'variables' => array( + 'user' => array('type' => 'user', 'label' => t('The user logging in with Amazon LPA')), + ), + 'access callback' => 'commerce_order_rules_access', + ); + + $events['commerce_amazon_lpa_registration'] = array( + 'label' => t('A user registered a new account with Amazon Login and Pay'), + 'group' => t('Commerce Amazon LPA'), + 'variables' => array( + 'user' => array('type' => 'user', 'label' => t('The user logging in with Amazon LPA')), + ), + 'access callback' => 'commerce_order_rules_access', + ); + return $events; +} + +/** + * Implements hook_rules_condition_info(). + */ +function commerce_amazon_lpa_rules_condition_info() { + return array( + 'commerce_amazon_lpa_is_amazon_order' => array( + 'label' => t('Is an Amazon Payments order'), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + 'description' => t('The order to be checked.'), + ), + ), + 'group' => t('Commerce Amazon Login & Pay'), + 'callbacks' => array( + 'execute' => 'commerce_amazon_lpa_is_amazon_order', + ), + ), + ); +} + +/** + * Condition callback for commerce_amazon_lpa_is_amazon_order + * + * @param $order + * The order to check. + * + * @return bool + * Returns TRUE if the order is for Amazon Payments. + */ +function commerce_amazon_lpa_is_amazon_order($order) { + return !empty($order->data['commerce_amazon_lpa']); +} + +/** + * Implements hook_rules_action_info(). + */ +function commerce_amazon_lpa_rules_action_info() { + return array( + 'commerce_amazon_lpa_set_order_status' => array( + 'label' => t('Set the status based on Amazon order reference status.'), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order to update'), + ), + ), + 'group' => t('Commerce Amazon Login & Pay'), + 'callbacks' => array( + 'execute' => 'commerce_amazon_lpa_set_order_status', + ), + ), + ); +} + +/** + * Rules action: updates an order's status using the Order API. + */ +function commerce_amazon_lpa_set_order_status($order, $name) { + $data = $order->data['commerce_amazon_lpa']['order_reference']; + if (AmazonLPA::get_authorization_mode() == AmazonLPA::AUTH_MANUAL) { + commerce_order_status_update($order, 'awaiting_auth', FALSE, TRUE, t('Amazon set to manual authorization, order placed in "Awaiting Authorization" status.')); + } + elseif (!empty($order->data['commerce_amazon_lpa_set_as_auth'])) { + $authorized_order_status = variable_get('commerce_amazon_lpa_auth_order_status', 'pending'); + unset($order->data['commerce_amazon_lpa_set_as_auth']); + commerce_order_status_update($order, $authorized_order_status, FALSE, TRUE, t('Payment with Amazon was authorized, moving to proper status.')); + } + // Check if the order has been paid in full (sync+immediate capture) and + // mark the order closed properly. + if ($data['OrderReferenceStatus']['State'] == 'Closed') { + commerce_order_status_update($order, 'completed', TRUE, TRUE, t('Checkout set to sync and immediate capture, marking order as completed.')); + } +} + +/** + * Implements hook_default_rules_configuration_alter(). + */ +function commerce_amazon_lpa_default_rules_configuration_alter(&$configs) { + if (isset($configs['commerce_payment_commerce_amazon_login_and_pay'])) { + /** @var RulesReactionRule $rule */ + $rule =& $configs['commerce_payment_commerce_amazon_login_and_pay']; + $rule->condition(rules_condition('commerce_amazon_lpa_is_amazon_order')); + } +} + +/** + * @} + */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.rules_defaults.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.rules_defaults.inc new file mode 100644 index 00000000..cc489b1a --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.rules_defaults.inc @@ -0,0 +1,72 @@ + array('AmazonLPA' => $settings), + 'type' => 'setting', + ); + $render['#access'] = AmazonLPA::is_configured() && !AmazonLPA::is_hidden(); +} + +/** + * Login button theme function. + * + * @param array $variables + * Variables array. + * + * @return string + * Login button HTML string. + */ +function theme_commerce_amazon_login_button($variables) { + $attributes = array( + 'id' => $variables['html_id'], + 'title' => t('Login with Amazon'), + 'data-url' => url('user/login/amazon', array('absolute' => TRUE, 'query' => array('amzn' => $variables['button_parameter']))), + 'data-pay-type' => isset($variables['button_options']['payType']) ? $variables['button_options']['loginType'] : 'LwA', + 'data-pay-size' => variable_get('commerce_amazon_lpa_login_button_size', AmazonLPA::BUTTON_SIZE_MEDIUM), + 'data-pay-style' => variable_get('commerce_amazon_lpa_login_button_style', AmazonLPA::BUTTON_COLOR_GOLD), + ); + + $render['#markup'] = '
        '; + _commerce_amazon_lpa_render_helper($render, array( + 'callbacks' => array( + array( + 'callback' => 'Drupal.AmazonLPA.LoginButton', + 'param' => $variables['html_id'], + ), + ), + )); + return render($render); +} + +/** + * Payment button theme function. + * + * @param array $variables + * Variables array. + * + * @return string + * Payment button HTML string. + */ +function theme_commerce_amazon_payment_button($variables) { + $attributes = array( + 'id' => $variables['html_id'], + 'title' => t('Amazon Payments makes shopping on our website even easier by using the payment information in your Amazon account.'), + 'data-pay-type' => isset($variables['button_options']['payType']) ? $variables['button_options']['payType'] : 'PwA', + 'data-pay-size' => variable_get('commerce_amazon_lpa_pay_button_size', AmazonLPA::BUTTON_SIZE_MEDIUM), + 'data-checkout-url' => url('checkout/amazon', array('absolute' => TRUE)), + ); + + $render['#markup'] = '
        '; + + _commerce_amazon_lpa_render_helper($render, array( + 'callbacks' => array( + array( + 'callback' => 'Drupal.AmazonLPA.PaymentButton', + 'param' => $variables['html_id'], + ), + ), + )); + return render($render); +} + +/** + * Payment button summary link theme function. + * + * @param array $variables + * Variables array. + * + * @return string + * Summary link HTML string. + */ +function theme_commerce_amazon_payment_button__summary_link($variables) { + $attributes = array( + 'id' => $variables['html_id'], + 'title' => t('Amazon Payments makes shopping on our website even easier by using the payment information in your Amazon account.'), + 'data-pay-type' => isset($variables['button_options']['payType']) ? $variables['button_options']['payType'] : NULL, + 'data-pay-size' => 'small', + 'data-checkout-url' => url('checkout/amazon', array('absolute' => TRUE)), + ); + + $render['#markup'] = '
'; + + _commerce_amazon_lpa_render_helper($render, array( + 'callbacks' => array( + array( + 'callback' => 'Drupal.AmazonLPA.PaymentButton', + 'param' => $variables['html_id'], + ), + ), + )); + + return render($render); +} + +/** + * Address book widget theme function. + * + * @param array $variables + * Variables array. + * + * @return string + * Address book widget HTML string. + */ +function theme_commerce_amazon_addressbook_widget($variables) { + $element_id = drupal_html_id($variables['html_id']); + + $addressbook_size = explode('x', variable_get('commerce_amazon_lpa_addressbook_size')) + array('', '250'); + $addressbook_width = ($addressbook_size[0]) ? $addressbook_size[0] . 'px' : '100%'; + $attributes = array( + 'id' => $element_id, + 'style' => array( + 'max-width: 100%;', + 'width: ' . $addressbook_width . ';', + 'height: ' . $addressbook_size[1] . 'px;', + 'display: inline-block;', + ), + ); + + $render['#markup'] = ''; + + _commerce_amazon_lpa_render_helper($render, array( + 'addressBookOptions' => array( + 'displayMode' => (!isset($variables['display_mode'])) ? 'edit' : $variables['display_mode'], + ), + 'callbacks' => array( + array( + 'callback' => 'Drupal.AmazonLPA.AddressBookWidget', + 'param' => $element_id, + ), + ), + )); + + return render($render); +} + +/** + * Wallet widget theme function. + * + * @param array $variables + * Variables array. + * + * @return string + * Themed wallet widget HTML. + */ +function theme_commerce_amazon_wallet_widget($variables) { + $element_id = drupal_html_id($variables['html_id']); + + $wallet_size = explode('x', variable_get('commerce_amazon_lpa_wallet_size')) + array('', '250'); + $wallet_width = ($wallet_size[0]) ? $wallet_size[0] . 'px' : '100%'; + $attributes = array( + 'id' => $element_id, + 'style' => array( + 'max-width: 100%;', + 'width: ' . $wallet_width . ';', + 'height: ' . $wallet_size[1] . 'px;', + 'display: inline-block;', + ), + ); + + $render['#markup'] = ''; + + _commerce_amazon_lpa_render_helper($render, array( + 'walletOptions' => array( + 'displayMode' => (!isset($variables['display_mode'])) ? 'edit' : $variables['display_mode'], + ), + 'orderReferenceId' => $variables['order_reference_id'], + 'callbacks' => array( + array( + 'callback' => 'Drupal.AmazonLPA.WalletWidget', + 'param' => $element_id, + ), + ), + )); + + return render($render); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.tokens.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.tokens.inc new file mode 100644 index 00000000..5e344fcc --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.tokens.inc @@ -0,0 +1,46 @@ + t('Amazon Pay'), + 'description' => t('Tokens related to Amazon Pay.'), + ); + + $tokens = array(); + $tokens['account-link'] = array( + 'name' => t('Amazon account link'), + 'description' => t('Provides a link to the Amazon customer for their account, based on your configured region.'), + ); + + return array( + 'types' => array('amazon-pay' => $type), + 'tokens' => array('amazon-pay' => $tokens), + ); +} + +/** + * Implements hook_tokens(). + */ +function commerce_amazon_lpa_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + if ($type == 'amazon-pay' || $type == 'amazon_pay') { + foreach ($tokens as $name => $original) { + switch ($name) { + case 'account-link': + $region = variable_get('commerce_amazon_lpa_region', 'US'); + $langcode = str_replace('-', '_', variable_get('commerce_amazon_lpa_langcode', AmazonLPA::get_region_langcode($region))); + $replacements[$original] = sprintf('https://pay.amazon.com/%s/jr/your-account/orders?language=%s', strtolower($region), $langcode); + break; + } + } + } + return $replacements; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.views_default.inc new file mode 100644 index 00000000..69394d2d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/commerce_amazon_lpa.views_default.inc @@ -0,0 +1,134 @@ +name = 'commerce_amazon_lpa_pending_authorizations'; + $view->description = ''; + $view->tag = 'Amazon Payments'; + $view->base_table = 'commerce_payment_transaction'; + $view->human_name = 'Expiring Authorizations'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'Expiring Authorizations'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'administer commerce_order entities'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['pager']['options']['items_per_page'] = '20'; + $handler->display->display_options['style_plugin'] = 'table'; + /* Header: Global: Text area */ + $handler->display->display_options['header']['area']['id'] = 'area'; + $handler->display->display_options['header']['area']['table'] = 'views'; + $handler->display->display_options['header']['area']['field'] = 'area'; + $handler->display->display_options['header']['area']['label'] = 'Information'; + $handler->display->display_options['header']['area']['empty'] = TRUE; + $handler->display->display_options['header']['area']['content'] = 'This list displays all orders which have expiring payment authorizations. Authorizations expire 30 days after they are created. You must capture payment before it expires in order to receive your funds.'; + $handler->display->display_options['header']['area']['format'] = 'filtered_html'; + /* No results behavior: Global: Unfiltered text */ + $handler->display->display_options['empty']['area_text_custom']['id'] = 'area_text_custom'; + $handler->display->display_options['empty']['area_text_custom']['table'] = 'views'; + $handler->display->display_options['empty']['area_text_custom']['field'] = 'area_text_custom'; + $handler->display->display_options['empty']['area_text_custom']['empty'] = TRUE; + $handler->display->display_options['empty']['area_text_custom']['content'] = '

You have no expiring authorizations.

'; + /* Field: Time ago */ + $handler->display->display_options['fields']['created_1']['id'] = 'created_1'; + $handler->display->display_options['fields']['created_1']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['fields']['created_1']['field'] = 'created'; + $handler->display->display_options['fields']['created_1']['ui_name'] = 'Time ago'; + $handler->display->display_options['fields']['created_1']['label'] = ''; + $handler->display->display_options['fields']['created_1']['exclude'] = TRUE; + $handler->display->display_options['fields']['created_1']['element_label_colon'] = FALSE; + $handler->display->display_options['fields']['created_1']['date_format'] = 'time ago'; + $handler->display->display_options['fields']['created_1']['custom_date_format'] = '2'; + $handler->display->display_options['fields']['created_1']['second_date_format'] = 'long'; + /* Field: Commerce Payment Transaction: Amount */ + $handler->display->display_options['fields']['amount']['id'] = 'amount'; + $handler->display->display_options['fields']['amount']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['fields']['amount']['field'] = 'amount'; + /* Field: Commerce Payment Transaction: Remote ID */ + $handler->display->display_options['fields']['remote_id']['id'] = 'remote_id'; + $handler->display->display_options['fields']['remote_id']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['fields']['remote_id']['field'] = 'remote_id'; + /* Field: Commerce Payment Transaction: Remote status */ + $handler->display->display_options['fields']['remote_status']['id'] = 'remote_status'; + $handler->display->display_options['fields']['remote_status']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['fields']['remote_status']['field'] = 'remote_status'; + /* Field: Commerce Payment Transaction: Created date */ + $handler->display->display_options['fields']['created']['id'] = 'created'; + $handler->display->display_options['fields']['created']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['fields']['created']['field'] = 'created'; + $handler->display->display_options['fields']['created']['alter']['alter_text'] = TRUE; + $handler->display->display_options['fields']['created']['alter']['text'] = '[created] ([created_1])'; + $handler->display->display_options['fields']['created']['date_format'] = 'custom'; + $handler->display->display_options['fields']['created']['custom_date_format'] = 'l, F jS, Y'; + $handler->display->display_options['fields']['created']['second_date_format'] = 'long'; + /* Field: Commerce Order: Link */ + $handler->display->display_options['fields']['view_order']['id'] = 'view_order'; + $handler->display->display_options['fields']['view_order']['table'] = 'commerce_order'; + $handler->display->display_options['fields']['view_order']['field'] = 'view_order'; + /* Sort criterion: Commerce Payment Transaction: Created date */ + $handler->display->display_options['sorts']['created']['id'] = 'created'; + $handler->display->display_options['sorts']['created']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['sorts']['created']['field'] = 'created'; + /* Filter criterion: Commerce Payment Transaction: Payment method */ + $handler->display->display_options['filters']['payment_method']['id'] = 'payment_method'; + $handler->display->display_options['filters']['payment_method']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['filters']['payment_method']['field'] = 'payment_method'; + $handler->display->display_options['filters']['payment_method']['value'] = array( + 'commerce_amazon_login_and_pay' => 'commerce_amazon_login_and_pay', + ); + $handler->display->display_options['filters']['payment_method']['group'] = 1; + /* Filter criterion: Commerce Payment Transaction: Remote status */ + $handler->display->display_options['filters']['remote_status']['id'] = 'remote_status'; + $handler->display->display_options['filters']['remote_status']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['filters']['remote_status']['field'] = 'remote_status'; + $handler->display->display_options['filters']['remote_status']['operator'] = 'longerthan'; + $handler->display->display_options['filters']['remote_status']['value'] = '1'; + $handler->display->display_options['filters']['remote_status']['group'] = 1; + /* Filter criterion: Commerce Payment Transaction: Status */ + $handler->display->display_options['filters']['status']['id'] = 'status'; + $handler->display->display_options['filters']['status']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['filters']['status']['field'] = 'status'; + $handler->display->display_options['filters']['status']['value'] = array( + 'pending' => 'pending', + ); + $handler->display->display_options['filters']['status']['group'] = 1; + /* Filter criterion: Commerce Payment Transaction: Created date */ + $handler->display->display_options['filters']['created']['id'] = 'created'; + $handler->display->display_options['filters']['created']['table'] = 'commerce_payment_transaction'; + $handler->display->display_options['filters']['created']['field'] = 'created'; + $handler->display->display_options['filters']['created']['operator'] = '<'; + $handler->display->display_options['filters']['created']['value']['value'] = 'now -21 days'; + $handler->display->display_options['filters']['created']['value']['type'] = 'offset'; + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'page'); + $handler->display->display_options['path'] = 'admin/commerce/orders/pending-authorizations'; + $handler->display->display_options['menu']['type'] = 'tab'; + $handler->display->display_options['menu']['title'] = 'Expiring Authorizations'; + $handler->display->display_options['menu']['description'] = 'Orders whose Amazon Payment authorizations will be expiring in the next week.'; + $handler->display->display_options['menu']['weight'] = '0'; + $handler->display->display_options['menu']['context'] = 0; + $handler->display->display_options['menu']['context_only_inline'] = 0; + + $views['commerce_amazon_lpa_pending_authorizations'] = $view; + + return $views; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonLPA.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonLPA.php new file mode 100644 index 00000000..3e9fdb2a --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonLPA.php @@ -0,0 +1,1040 @@ + 'A33DP3YE7OHVLV', + 'DE' => 'A1ZBM19RFMXA83', + 'US' => 'A294FY3QW7KJ8X', + ); + + /** + * Returns if the API is working in sandbox mode. + * + * @return bool + * Returns TRUE if sandbox, or FALSE for production. + */ + public static function is_sandbox() { + return (variable_get('commerce_amazon_lpa_environment', self::ENV_SANDBOX) == self::ENV_SANDBOX); + } + + /** + * Returns if running in ERP mode. + * + * @return bool + * Returns TRUE if in ERP mode, or FALSE for not. + */ + public static function is_erp_mode() { + return (bool) variable_get('commerce_amazon_lpa_erp_mode', 0); + } + + /** + * Checks if Login and Pay with Amazon has been configured. + * + * @return bool + * Returns TRUE if service has been configured. + */ + public static function is_configured() { + return ( + variable_get('commerce_amazon_lpa_merchant_id') !== NULL || + variable_get('commerce_amazon_lpa_access_key') !== NULL || + variable_get('commerce_amazon_lpa_secret_key') !== NULL + ); + } + + /** + * Returns if the Login and Pay with Amazon buttons have been hidden. + * + * @return bool + * Returns boolean indicating hidden status. + */ + public static function is_hidden() { + $hidden_mode = variable_get('commerce_amazon_lpa_hidden_mode'); + return ( + $GLOBALS['user']->uid != 1 && + !empty($hidden_mode) && + !user_has_role(variable_get('commerce_amazon_lpa_hidden_mode')) + ); + } + + /** + * Returns the current operation mode. + * + * @return string + * The current operation mode. + */ + public static function get_operation_mode() { + return variable_get('commerce_amazon_lpa_operation_mode', AmazonLPA::OPERATION_MODE_LOGIN_AND_PAY); + } + + /** + * Returns currency for a seller's region, or region currency map. + * + * @param null|string $region + * A region code. + * + * @return array|string|bool + * Array or single region currency code. + */ + public static function get_region_currency_code($region = NULL) { + $currencies = array( + 'DE' => 'EUR', + 'UK' => 'GBP', + 'US' => 'USD', + ); + + if ($region === NULL) { + return $currencies; + } + elseif (isset($currencies[$region])) { + return $currencies[$region]; + } + else { + return FALSE; + } + } + + /** + * Returns the language code for a region. + * + * @param null $region + * + * @return array|bool + */ + public static function get_region_langcode($region = NULL) { + $lang_codes = array( + 'DE' => 'de-DE', + 'UK' => 'en-GB', + 'US' => 'en-US', + ); + + if ($region === NULL) { + return $lang_codes; + } + elseif (isset($lang_codes[$region])) { + return $lang_codes[$region]; + } + else { + return FALSE; + } + } + + /** + * Returns the current capture mode. + * + * @return string + * The current capture mode. + */ + public static function get_capture_mode() { + return variable_get('commerce_amazon_lpa_capture_mode', 'shipment_capture'); + } + + /** + * Get the current authorization mode. + * + * @return string + * The current authorization mode. + */ + public static function get_authorization_mode() { + return variable_get('commerce_amazon_lpa_authorization_mode', self::AUTH_NONSYNC); + } + + /** + * Helper function to get a new instance. + * + * @return \AmazonLPA + * The API instance. + */ + public static function instance() { + return new self(); + } + + /** + * @var \AmazonPayClient + */ + protected $client; + + /** + * AmazonLPA constructor. + */ + public function __construct() { + if (!self::is_configured()) { + throw new Exception(t('You must configured Login and Pay with Amazon.')); + } + $this->client = new AmazonPayClient(array( + 'merchant_id' => variable_get('commerce_amazon_lpa_merchant_id'), + 'access_key' => variable_get('commerce_amazon_lpa_access_key'), + 'secret_key' => variable_get('commerce_amazon_lpa_secret_key'), + 'client_id' => variable_get('commerce_amazon_lpa_client_id'), + 'region' => variable_get('commerce_amazon_lpa_region'), + 'sandbox' => self::is_sandbox(), + )); + } + + /** + * Returns current API client. + * + * @return \AmazonPayClient + * Returns SDK client. + */ + public function getClient() { + return $this->client; + } + + /** + * Returns the current Amazon user info. + * + * @return array|mixed + * The current Amazon user information. + */ + public function getUserInfo() { + if (empty($this->user_info)) { + if (isset($_COOKIE['amazon_Login_accessToken'])) { + $access_token = $_COOKIE['amazon_Login_accessToken']; + try { + $this->user_info = $this->client->getUserInfo($access_token); + } + catch (Exception $e) { + + } + } + } + return $this->user_info; + } + + /** + * Sends API request to cancel an order. + * + * @param \EntityDrupalWrapper $order + * + * @return bool|mixed + * + * @throws \Exception + */ + public function cancel(EntityDrupalWrapper $order) { + $contract_id = $this->getOrderReferenceId($order); + $balance = commerce_payment_order_balance($order->value()); + + if (!empty($contract_id) && $balance['amount'] > 0) { + $params = array( + 'amazon_order_reference_id' => $contract_id, + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('cancel_order_reference', $params, $order); + + $response = $this->client->cancelOrderReference($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging cancel response: !debug>'), array( + '!debug' => '
' . check_plain(print_r($data, TRUE)) . '
', + )); + + if ($this->client->success) { + return $data; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to cancel @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ) + )); + } + } + + return FALSE; + } + + /** + * Captures payment on an authorization. + * + * @param \EntityDrupalWrapper $order + * @param string $authorization_id + * @param array|null $balance + * A price array structure for the balance to process. + * + * @return bool|mixed + * + * @throws \Exception + */ + public function capture(EntityDrupalWrapper $order, $authorization_id, $balance = NULL) { + $contract_id = $this->getOrderReferenceId($order); + + if (!$balance) { + $balance = commerce_payment_order_balance($order->value()); + } + + if (!empty($contract_id) && $balance['amount'] > 0) { + $params = array( + 'amazon_order_reference_id' => $contract_id, + 'amazon_authorization_id' => $authorization_id, + 'capture_amount' => commerce_currency_amount_to_decimal($balance['amount'], $balance['currency_code']), + 'currency_code' => $balance['currency_code'], + 'capture_reference_id' => 'capture_' . $order->order_id->value() . '_' . REQUEST_TIME, + 'transaction_timeout' => 0, + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('capture', $params, $order); + + $response = $this->client->capture($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging capture response: !debug>'), array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
')); + + if ($this->client->success) { + return $data['CaptureResult']['CaptureDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to capture payment for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + return FALSE; + } + + /** + * Closes an authorization with Amazon. + * + * @param \EntityDrupalWrapper $order + * @param $authorization_id + * @param string $reason + * + * @return bool|mixed + * + * @throws \Exception + */ + public function closeAuthorization(EntityDrupalWrapper $order, $authorization_id, $reason = '') { + + $params = array( + 'amazon_authorization_id' => $authorization_id, + 'closure_reason' => $reason, + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('close_authorization', $params, $order); + + $response = $this->client->closeAuthorization($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging close authorization response: !debug>'), array( + '!debug' => '
' . check_plain(print_r($data, TRUE)) . '
', + )); + + if ($this->client->success) { + return $data; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to close authorization for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * Creates an authorization transaction on an order. + * + * @param \EntityDrupalWrapper $order + * Entity metadata wrapper for a commerce order entity. + * @param bool $capture_now + * @param array|null $balance + * A price array structure for the balance to process. + * + * @return mixed + * Authorization transaction result. + * + * @throws \Exception + */ + public function authorize(EntityDrupalWrapper $order, $capture_now = FALSE, $balance = NULL) { + + if (!$balance) { + $balance = commerce_payment_order_balance($order->value()); + } + + $params = array( + 'amazon_order_reference_id' => $this->getOrderReferenceId($order), + 'authorization_amount' => commerce_currency_amount_to_decimal($balance['amount'], $balance['currency_code']), + 'currency_code' => $balance['currency_code'], + 'authorization_reference_id' => 'auth_' . $order->order_id->value() . '_' . REQUEST_TIME, + 'capture_now' => $capture_now, + ); + + if ($capture_now) { + $params['seller_authorization_note'] = check_plain(variable_get('commerce_amazon_lpa_capture_auth_statement', '')); + } + + // If using sync, set transaction timeout to 0. + if (self::get_authorization_mode() == self::AUTH_SYNC) { + $params['transaction_timeout'] = 0; + } + // Otherwise use max timeout. + else { + $params['transaction_timeout'] = variable_get('commerce_amazon_lpa_transaction_timeout', 1440); + } + + // Allow modules to modify the request params. + $this->alterRequestParams('authorize', $params, $order); + + $response = $this->client->authorize($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging authorize response: !debug'), array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
')); + + if ($this->client->success) { + return $data['AuthorizeResult']['AuthorizationDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to authorize payment for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * Processes and saves a payment transaction that is in authorization. + * + * @param object $transaction + * The transaction in authorization. + * @param array $data + * API data. + * + * @throws \Exception + * Exception. + */ + public function processAuthorizeTransaction($transaction, array $data) { + $order = commerce_order_load($transaction->order_id); + + // Amazon has at least two different ways that Buyer information is conveyed + // back to an API client. The first is that the OrderReference has a Buyer + // object that contains a name and email address (with an optional phone no.). + // The second is an AuthorizationBillingDetails object that is returned on + // some versions of the API. At the time of writing, the US version of the API + // doesn't provide this information, but the UK version does. Since we do not + // know what the API is going to return, we'll look for the + // AuthorizationBillingDetails object and, if it exists, process it. If not, + // then the Buyer information on the Order Reference will have to suffice. + // @see https://payments.amazon.com/documentation/apireference/201752660#201752450 + // @see https://payments.amazon.co.uk/developer/documentation/apireference/201752450 + $billing_address = NULL; + if (isset($data['AuthorizationBillingAddress'])) { + $billing_address = $data['AuthorizationBillingAddress']; + } + + // If we found a billing address, sync it. + if ($billing_address) { + try { + commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'billing', $billing_address); + commerce_order_save($order); + } + catch (Exception $e) { + watchdog('commerce_amazon_lpa', 'Error processing order billing information for Amazon: !error', array('!error' => '
' . print_r($data, TRUE) . '
'), WATCHDOG_ERROR); + } + } + // Otherwise just use shipping address so it isn't empty. + else { + $order_reference = $this->getOrderRef(entity_metadata_wrapper('commerce_order', $order)); + if (isset($order_reference['Destination']['PhysicalDestination'])) { + $shipping_address = $order_reference['Destination']['PhysicalDestination']; + commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'billing', $shipping_address); + commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'shipping', $shipping_address); + commerce_order_save($order); + } + } + + $transaction->remote_id = $data['AmazonAuthorizationId']; + $transaction->amount = commerce_currency_decimal_to_amount($data['AuthorizationAmount']['Amount'], $data['AuthorizationAmount']['CurrencyCode']); + $transaction->currency_code = $data['AuthorizationAmount']['CurrencyCode']; + + $transaction->data['commerce_amazon_lpa']['environment'] = variable_get('commerce_amazon_lpa_environment', self::ENV_SANDBOX); + $transaction->data['commerce_amazon_lpa']['auth_reference_id'] = $data['AuthorizationReferenceId']; + $transaction->data['commerce_amazon_lpa']['transaction_type'] = 'authorization'; + + // Capture is only pending if pre-authorized. Otherwise declined during + // validation. Check the payment object's state and update transaction + // status. + commerce_amazon_lpa_payment_state_to_status($transaction, $data['AuthorizationStatus']); + + commerce_amazon_lpa_transaction_message_update_data($transaction, 'Authorization', $data['AuthorizationStatus']); + $transaction->payload[REQUEST_TIME . '-authorization'] = $data; + + // If we did capture, set it up so that we can properly refund, etc. + if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS && $data['AuthorizationStatus']['ReasonCode'] == 'MaxCapturesProcessed') { + // Create a capture transaction. + if (isset($data['IdList']['member'])) { + $capture_id = $data['IdList']['member']; + } + else { + $capture_id = $data['IdList']['Id']; + } + $capture_details = $this->getCaptureDetails($capture_id); + $transaction->remote_id = $capture_details['AmazonCaptureId']; + $transaction->data['commerce_amazon_lpa']['capture_id'] = $capture_details['AmazonCaptureId']; + $transaction->data['commerce_amazon_lpa']['transaction_type'] = 'capture'; + // The authorization will be "Closed" but the capture will be "Completed". + // The value returned by Amazon must be overridden so that refunds will + // work. + commerce_amazon_lpa_payment_state_to_status($transaction, $capture_details['CaptureStatus']); + commerce_amazon_lpa_transaction_message_update_data($transaction, 'Capture', $capture_details['CaptureStatus']); + $transaction->payload[REQUEST_TIME . '-capture'] = $capture_details; + + // Create a revision, since we have changed the remote ID. + $transaction->revision = TRUE; + $transaction->log = t('Authorization was captured'); + } + // If we have a pending transaction, we need to make sure the order status + // matches the one configured. + if ($transaction->status == COMMERCE_PAYMENT_STATUS_PENDING && + $data['AuthorizationStatus']['State'] == 'Open' && + variable_get('commerce_amazon_lpa_auth_order_status', 'pending') != $order->status) { + $order->data['commerce_amazon_lpa_set_as_auth'] = TRUE; + } + + commerce_payment_transaction_save($transaction); + + // If the authorization is Rejected, then set the order lock on the + // order's data object to forbid any further transactions from taking + // place. + if (AmazonLPA::get_authorization_mode() == AmazonLPA::AUTH_NONSYNC) { + if ($data['AuthorizationStatus']['State'] == 'Declined') { + $reason_code = $data['AuthorizationStatus']['ReasonCode']; + if ($reason_code == 'InvalidPaymentMethod') { + rules_invoke_all('commerce_amazon_lpa_nonsync_auth_soft_decline', $order, $transaction, $data); + } + else { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD} = ''; + commerce_amazon_lpa_user_logout($order_wrapper->owner->value()); + commerce_order_status_update($order, 'cart', FALSE, TRUE, t('The payment method was rejected by Amazon.')); + + rules_invoke_all('commerce_amazon_lpa_nonsync_auth_hard_decline', $order, $transaction, $data); + } + } + } + } + + /** + * Processes and saves a payment transaction that is a capture. + * + * @param object $transaction + * The transaction in authorization. + * @param array $data + * API data. + * + * @throws \Exception + * Exception. + */ + public function processCaptureTransaction($transaction, array $data) { + $transaction->remote_id = $data['AmazonCaptureId']; + $transaction->amount = commerce_currency_decimal_to_amount($data['CaptureAmount']['Amount'], $data['CaptureAmount']['CurrencyCode']); + $transaction->currency_code = $data['CaptureAmount']['CurrencyCode']; + + $transaction->data['commerce_amazon_lpa']['environment'] = variable_get('commerce_amazon_lpa_environment', self::ENV_SANDBOX); + $transaction->data['commerce_amazon_lpa']['auth_reference_id'] = $data['AmazonCaptureId']; + $transaction->data['commerce_amazon_lpa']['transaction_type'] = 'capture'; + + commerce_amazon_lpa_payment_state_to_status($transaction, $data['CaptureStatus']); + commerce_amazon_lpa_transaction_message_update_data($transaction, 'Capture', $data['CaptureStatus']); + $transaction->payload[REQUEST_TIME . '-capture'] = $data; + + commerce_payment_transaction_save($transaction); + } + + /** + * Processes and saves a payment transaction that is a refund. + * + * @param object $transaction + * The transaction in authorization. + * @param array $data + * API data. + * + * @throws \Exception + * Exception. + */ + public function processRefundTransaction($transaction, array $data) { + $transaction->remote_id = $data['AmazonRefundId']; + $transaction->amount = commerce_currency_decimal_to_amount($data['RefundAmount']['Amount'], $data['RefundAmount']['CurrencyCode']) * -1; + $transaction->currency_code = $data['RefundAmount']['CurrencyCode']; + + $transaction->data['commerce_amazon_lpa']['environment'] = variable_get('commerce_amazon_lpa_environment', self::ENV_SANDBOX); + $transaction->data['commerce_amazon_lpa']['refund_id'] = $data['AmazonRefundId']; + $transaction->data['commerce_amazon_lpa']['transaction_type'] = 'refund'; + + commerce_amazon_lpa_payment_state_to_status($transaction, $data['RefundStatus']); + commerce_amazon_lpa_transaction_message_update_data($transaction, 'Refund', $data['RefundStatus']); + $transaction->payload[REQUEST_TIME . '-refund'] = $data; + commerce_payment_transaction_save($transaction); + } + + /** + * Confirms that an order reference has been fulfilled to Amazon Payments. + * + * Call the CloseOrderReference operation to indicate that a previously + * confirmed order reference has been fulfilled (fully or partially) and that + * you do not expect to create any new authorizations on this order reference. + * + * You can still capture funds against open authorizations on the order + * reference. + * + * @link https://payments.amazon.com/documentation/apireference/201752000 + * + * @param \EntityDrupalWrapper $order + * Entity metadata wrapper for a commerce order entity. + * + * @return mixed + * Response details. + * + * @throws \Exception + */ + public function closeOrderRef(EntityDrupalWrapper $order) { + $params = array( + 'amazon_order_reference_id' => $this->getOrderReferenceId($order), + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('close_order_reference', $params, $order); + + $response = $this->client->closeOrderReference($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging close order reference response: !debug>'), array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
')); + + if ($this->client->success) { + return $data; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to close the reference for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * Gets the order reference from Amazon Payments. + * + * The GetOrderReferenceDetails operation returns details about the Order + * Reference object and its current state. + * + * @link https://payments.amazon.com/documentation/apireference/201751970 + * + * @param \EntityDrupalWrapper $order + * Entity metadata wrapper for a commerce order entity. + * + * @return array + * Array of order reference detail information. + * + * @throws \Exception + */ + public function getOrderRef(EntityDrupalWrapper $order) { + $params = array( + 'amazon_order_reference_id' => $this->getOrderReferenceId($order), + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('get_order_reference', $params, $order); + + $response = $this->client->getOrderReferenceDetails($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging get order reference response: !debug>'), array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
')); + + if ($this->client->success) { + return $data['GetOrderReferenceDetailsResult']['OrderReferenceDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to get the order reference for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * Confirms an order reference. + * + * Call the ConfirmOrderReference operation after the order reference is free + * of constraints and all required information has been set on the order + * reference. After you call this operation, the order reference is set to + * the Open state and you can submit authorizations against the order + * reference. + * + * @link https://payments.amazon.com/documentation/apireference/201751980 + * + * @param \EntityDrupalWrapper $order + * Entity metadata wrapper for a commerce order entity. + * + * @return mixed + * Array of order reference detail information. + * + * @throws \Exception + */ + public function confirmOrderRef(EntityDrupalWrapper $order) { + $params = array( + 'amazon_order_reference_id' => $this->getOrderReferenceId($order), + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('confirm_order_reference', $params, $order); + + $response = $this->client->confirmOrderReference($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging confirm order reference response: !debug>'), array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
')); + + if ($this->client->success) { + return $data; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to confirm the order reference for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * Sets an order reference to Amazon Payments. + * + * Call the SetOrderReferenceDetails operation to specify order details such + * as the amount of the order, a description of the order, and other order + * attributes. + * + * @link https://payments.amazon.com/documentation/apireference/201751960 + * + * @param \EntityDrupalWrapper $order + * Entity metadata wrapper for a commerce order entity. + * + * @return mixed + * The order reference details response. + * + * @throws \Exception + */ + public function setOrderRef(EntityDrupalWrapper $order) { + $params = array( + 'amazon_order_reference_id' => $this->getOrderReferenceId($order), + 'amount' => $order->commerce_order_total->amount_decimal->value(), + 'currency_code' => $order->commerce_order_total->currency_code->value(), + 'seller_order_id' => $order->getIdentifier(), + 'store_name' => variable_get('site_name'), + 'platform_id' => $this->platform_ids[variable_get('commerce_amazon_lpa_region')], + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('set_order_reference', $params, $order); + + $response = $this->client->setOrderReferenceDetails($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging set order reference response: !debug>', array( + '!debug' => '
' . check_plain(print_r($data, TRUE)) . '
', + ))); + + if ($this->client->success) { + return $data['SetOrderReferenceDetailsResult']['OrderReferenceDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to set the order reference for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * @param $capture_id + * + * @return mixed + * @throws \Exception + */ + public function getCaptureDetails($capture_id) { + $params = array( + 'amazon_capture_id' => $capture_id, + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('get_capture_details', $params); + + $response = $this->client->getCaptureDetails($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging capture details response: !debug>', array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
'))); + + if ($this->client->success) { + return $data['GetCaptureDetailsResult']['CaptureDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to capture payment for @order_id: @reason', array( + '@order_id' => $capture_id, + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * @param string $authorization_id + * + * @return mixed + * @throws \Exception + */ + public function getAuthorizationDetails($authorization_id) { + $response = $this->client->getAuthorizationDetails(array( + 'amazon_authorization_id' => $authorization_id, + )); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging authorization details response: !debug>', array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
'))); + + if ($this->client->success) { + return $data['GetAuthorizationDetailsResult']['AuthorizationDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to get authorization details for @order_id', array('@order_id' => $authorization_id) + )); + } + } + + /** + * Refunds a transation for specified amount. + * + * @param $order + * @param $captureId + * @param $amount + * @param string $note + * + * @return mixed + * + * @throws \Exception + */ + public function refund(EntityDrupalWrapper $order, $captureId, $amount, $note = '') { + $balance = commerce_payment_order_balance($order->value()); + + $params = array( + 'amazon_capture_id' => $captureId, + 'refund_amount' => $amount, + 'currency_code' => $balance['currency_code'], + 'refund_reference_id' => 'refund_' . $order->order_id->value() . '_' . REQUEST_TIME, + 'seller_refund_note' => $note, + ); + + // Allow modules to modify the request params. + $this->alterRequestParams('refund', $params); + + $response = $this->client->refund($params); + $data = $response->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Debugging refund response: !debug>', array('!debug' => '
' . check_plain(print_r($data, TRUE)) . '
'))); + + if ($this->client->success) { + return $data['RefundResult']['RefundDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to refund payment for @order_id: @reason', array( + '@order_id' => $order->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $data['Error']['Code'], + '@message' => $data['Error']['Message'], + )), + ))); + } + } + + /** + * @param string $refund_id + * + * @return mixed + * @throws \Exception + */ + public function getRefundDetails($refund_id) { + $response = $this->client->getRefundDetails(array( + 'amazon_refund_id' => $refund_id, + )); + $data = $response->toArray(); + + if ($this->client->success) { + return $data['GetRefundDetailsResult']['RefundDetails']; + } + else { + throw new AmazonApiException( + $data['Error']['Code'], + $data, + t('Unable to get refund details for @order_id', array('@order_id' => $refund_id))); + } + } + + /** + * Returns the reference ID from an order. + * + * @param \EntityDrupalWrapper $order + * Entity metadata wrapper for a commerce order entity. + * + * @return mixed + */ + public function getOrderReferenceId(EntityDrupalWrapper $order) { + if (isset($order->{AmazonLPA::REFERENCE_ID_FIELD})) { + return $order->{AmazonLPA::REFERENCE_ID_FIELD}->value(); + } + return NULL; + } + + /** + * Invokes alter to allow modules to adjust API call parameters. + * + * @param $type + * @param $params + * @param $data + */ + public function alterRequestParams($type, array &$params, $data = NULL) { + drupal_alter('commerce_amazon_lpa_request_params', $params, $type, $data); + } + +} +/** + * + */ +class AmazonApiException extends Exception { + protected $errorCode; + protected $response; + + /** + * + */ + public function __construct($error_code, array $response, $message = "", $code = 0, Throwable $previous = NULL) { + parent::__construct($message, $code, $previous); + $this->errorCode = $error_code; + $this->response = $response; + } + + /** + * + */ + public function getErrorCode() { + return $this->errorCode; + } + + /** + * + */ + public function getResponse() { + return $this->response; + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayClient.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayClient.php new file mode 100644 index 00000000..631a0d8d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayClient.php @@ -0,0 +1,1492 @@ + NULL, + 'secret_key' => NULL, + 'access_key' => NULL, + 'region' => NULL, + 'currency_code' => NULL, + 'sandbox' => FALSE, + 'platform_id' => NULL, + 'cabundle_file' => NULL, + 'application_name' => NULL, + 'application_version' => NULL, + 'proxy_host' => NULL, + 'proxy_port' => -1, + 'proxy_username' => NULL, + 'proxy_password' => NULL, + 'client_id' => NULL, + 'app_id' => NULL, + 'handle_throttle' => TRUE, + ); + + private $modePath = NULL; + + protected $mwsServiceUrl = NULL; + + public $mwsServiceUrls = array( + 'eu' => 'mws-eu.amazonservices.com', + 'na' => 'mws.amazonservices.com', + 'jp' => 'mws.amazonservices.jp', + ); + + public $profileEndpointUrls = array( + 'uk' => 'amazon.co.uk', + 'us' => 'amazon.com', + 'de' => 'amazon.de', + 'jp' => 'amazon.co.jp', + ); + + public $regionMappings = array( + 'de' => 'eu', + 'uk' => 'eu', + 'us' => 'na', + 'jp' => 'jp', + ); + + /** + * Boolean variable to check if the API call was a success. + * + * @var bool + */ + public $success = FALSE; + + /** + * + */ + public function __construct($config = array()) { + $this->checkConfigKeys($config); + if (empty($this->config['region'])) { + throw new \Exception("config['region'] is a required parameter and is not set"); + } + if (!isset($this->regionMappings[strtolower($this->config['region'])])) { + throw new \Exception($this->config['region'] . ' is not a valid region'); + } + $this->modePath = strtolower($this->config['sandbox']) ? 'OffAmazonPayments_Sandbox' : 'OffAmazonPayments'; + $this->mwsEndpointUrl = $this->mwsServiceUrls[$this->regionMappings[strtolower($this->config['region'])]]; + $this->mwsServiceUrl = 'https://' . $this->mwsEndpointUrl . '/' . $this->modePath . '/' . self::MWS_VERSION; + $this->mwsEndpointPath = '/' . $this->modePath . '/' . self::MWS_VERSION; + } + + /** + * Logs message. + * + * @param $message + */ + protected function logMessage($message) { + commerce_amazon_lpa_add_debug_log($message, array(), WATCHDOG_INFO); + } + + /** + * Checks if the keys of the input configuration matches the keys in the + * config array if they match the values are taken else throws exception + * strict case match is not performed. + * + * @param $config + * + * @throws \Exception + */ + protected function checkConfigKeys($config) { + foreach ($config as $key => $value) { + if (array_key_exists($key, $this->config)) { + if (!is_array($value) && !is_bool($value) && $key !== 'proxy_password') { + $this->config[$key] = trim($value); + } + else { + $this->config[$key] = $value; + } + } + else { + throw new \Exception('Key ' . $key . ' is either not part of the configuration or has incorrect Key name. + check the config array key names to match your key names of your config array', 1); + } + } + } + + /** + * Gets the value for the key if the key exists in config. + * + * @param $name + * + * @return mixed + * + * @throws \Exception + */ + public function __get($name) { + if (array_key_exists(strtolower($name), $this->config)) { + return $this->config[strtolower($name)]; + } + else { + throw new \Exception('Key ' . $name . ' is either not a part of the configuration array config or the ' . $name . ' does not match the key name in the config array', 1); + } + } + + /** + * GetUserInfo convenience function - Returns user's profile information from + * Amazon using the access token returned by the Button widget. + * + * @see http://login.amazon.com/website Step 4 + * @param $accessToken [String] + */ + public function getUserInfo($accessToken) { + // Get the correct Profile Endpoint URL based off the country/region provided in the config['region']. + $environment = strtolower($this->config['sandbox']) ? "api.sandbox" : "api"; + $region = strtolower($this->config['region']); + $profile_endpoint = 'https://' . $environment . '.' . $this->profileEndpointUrls[$region]; + + if (empty($accessToken)) { + throw new \InvalidArgumentException('Access Token is a required parameter and is not set'); + } + + // To make sure double encoding doesn't occur decode first and encode again. + $accessToken = urldecode($accessToken); + $url = $profile_endpoint . '/auth/o2/tokeninfo?access_token=' . str_replace('%7E', '~', rawurlencode($accessToken)); + + $httpCurlRequest = new AmazonPayHttpCurl($this->config); + + $response = $httpCurlRequest->httpGet($url); + $data = json_decode($response); + + // Ensure that the Access Token matches either the supplied Client ID *or* the supplied App ID + // Web apps and Mobile apps will have different Client ID's but App ID should be the same + // As long as one of these matches, from a security perspective, we have done our due diligence. + if (($data->aud != $this->config['client_id']) && ($data->app_id != $this->config['app_id'])) { + // The access token does not belong to us. + throw new \Exception('The Access Token belongs to neither your Client ID nor App ID'); + } + + // Exchange the access token for user profile. + $url = $profile_endpoint . '/user/profile'; + $httpCurlRequest = new AmazonPayHttpCurl($this->config); + + $httpCurlRequest->setAccessToken($accessToken); + $httpCurlRequest->setHttpHeader(); + $response = $httpCurlRequest->httpGet($url); + + $userInfo = json_decode($response, TRUE); + return $userInfo; + } + + /** + * SetParametersAndPost - sets the parameters array with non empty values from the requestParameters array sent to API calls. + * If Provider Credit Details is present, values are set by setProviderCreditDetails + * If Provider Credit Reversal Details is present, values are set by setProviderCreditDetails. + */ + protected function setParametersAndPost($parameters, $fieldMappings, $requestParameters) { + /* For loop to take all the non empty parameters in the $requestParameters and add it into the $parameters array, + * if the keys are matched from $requestParameters array with the $fieldMappings array + */ + foreach ($requestParameters as $param => $value) { + + // Do not use trim on boolean values, or it will convert them to '0' or '1'. + if (!is_array($value) && !is_bool($value)) { + $value = trim($value); + } + + // Ensure that no unexpected type coercions have happened. + if ($param === 'capture_now' || $param === 'confirm_now' || $param === 'inherit_shipping_address') { + if (!is_bool($value)) { + throw new \Exception($param . ' value ' . $value . ' is of type ' . gettype($value) . ' and should be a boolean value'); + } + } + + // When checking for non-empty values, consider any boolean as non-empty. + if (array_key_exists($param, $fieldMappings) && (is_bool($value) || $value != '')) { + + if (is_array($value)) { + // If the parameter is a provider_credit_details or provider_credit_reversal_details, call the respective functions to set the values. + if ($param === 'provider_credit_details') { + $parameters = $this->setProviderCreditDetails($parameters, $value); + } + elseif ($param === 'provider_credit_reversal_details') { + $parameters = $this->setProviderCreditReversalDetails($parameters, $value); + } + + } + else { + $parameters[$fieldMappings[$param]] = $value; + } + } + } + + $parameters = $this->setDefaultValues($parameters, $fieldMappings, $requestParameters); + $responseObject = $this->calculateSignatureAndPost($parameters); + + return $responseObject; + } + + /** + * CalculateSignatureAndPost - convert the Parameters array to string and + * curl POST the parameters to MWS. + */ + protected function calculateSignatureAndPost($parameters) { + // Call the signature and Post function to perform the actions. Returns XML in array format. + $parametersString = $this->calculateSignatureAndParametersToString($parameters); + + // POST using curl the String converted Parameters. + $response = $this->invokePost($parametersString); + + // Send this response as args to ResponseParser class which will return the object of the class. + $responseObject = new AmazonPayResponse($response); + return $responseObject; + } + + /** + * If merchant_id is not set via the requestParameters array then it's taken + * from the config array. + * + * Set the platform_id if set in the config['platform_id'] array. + * + * If currency_code is set in the $requestParameters and it exists in the $fieldMappings array, strtoupper it + * else take the value from config array if set + */ + private function setDefaultValues($parameters, $fieldMappings, $requestParameters) { + if (empty($requestParameters['merchant_id'])) { + $parameters['SellerId'] = $this->config['merchant_id']; + } + + if (array_key_exists('platform_id', $fieldMappings)) { + if (empty($requestParameters['platform_id']) && !empty($this->config['platform_id'])) { + $parameters[$fieldMappings['platform_id']] = $this->config['platform_id']; + } + } + + if (array_key_exists('currency_code', $fieldMappings)) { + if (!empty($requestParameters['currency_code'])) { + $parameters[$fieldMappings['currency_code']] = strtoupper($requestParameters['currency_code']); + } + else { + $parameters[$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /** + * SetProviderCreditDetails - sets the provider credit details sent via the Capture or Authorize API calls. + * + * @param provider_id - [String] + * @param credit_amount - [String] + * + * @optional currency_code - [String] + */ + private function setProviderCreditDetails($parameters, $providerCreditInfo) { + $providerIndex = 0; + $providerString = 'ProviderCreditList.member.'; + + $fieldMappings = array( + 'provider_id' => 'ProviderId', + 'credit_amount' => 'CreditAmount.Amount', + 'currency_code' => 'CreditAmount.CurrencyCode', + ); + + foreach ($providerCreditInfo as $key => $value) { + $value = array_change_key_case($value, CASE_LOWER); + $providerIndex = $providerIndex + 1; + + foreach ($value as $param => $val) { + if (array_key_exists($param, $fieldMappings) && trim($val) != '') { + $parameters[$providerString . $providerIndex . '.' . $fieldMappings[$param]] = $val; + } + } + + // If currency code is not entered take it from the config array. + if (empty($parameters[$providerString . $providerIndex . '.' . $fieldMappings['currency_code']])) { + $parameters[$providerString . $providerIndex . '.' . $fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /** + * SetProviderCreditReversalDetails - sets the reverse provider credit details sent via the Refund API call. + * + * @param provider_id - [String] + * @param credit_amount - [String] + * + * @optional currency_code - [String] + */ + protected function setProviderCreditReversalDetails($parameters, $providerCreditInfo) { + $providerIndex = 0; + $providerString = 'ProviderCreditReversalList.member.'; + + $fieldMappings = array( + 'provider_id' => 'ProviderId', + 'credit_reversal_amount' => 'CreditReversalAmount.Amount', + 'currency_code' => 'CreditReversalAmount.CurrencyCode', + ); + + foreach ($providerCreditInfo as $key => $value) { + $value = array_change_key_case($value, CASE_LOWER); + $providerIndex = $providerIndex + 1; + + foreach ($value as $param => $val) { + if (array_key_exists($param, $fieldMappings) && trim($val) != '') { + $parameters[$providerString . $providerIndex . '.' . $fieldMappings[$param]] = $val; + } + } + + // If currency code is not entered take it from the config array. + if (empty($parameters[$providerString . $providerIndex . '.' . $fieldMappings['currency_code']])) { + $parameters[$providerString . $providerIndex . '.' . $fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /** + * GetOrderReferenceDetails API call - Returns details about the Order Reference object and its current state. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751970 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * + * @optional requestParameters['address_consent_token'] - [String] + * @optional requestParameters['access_token'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + * + * You cannot pass both address_consent_token and access_token in + * the same call or you will encounter a 400/"AmbiguousToken" error + */ + public function getOrderReferenceDetails($requestParameters = array()) { + + $parameters['Action'] = 'GetOrderReferenceDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'address_consent_token' => 'AddressConsentToken', + 'access_token' => 'AccessToken', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + return ($responseObject); + } + + /** + * SetOrderReferenceDetails API call - Sets order reference details such as the order total and a description for the order. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751960 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @param requestParameters['amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function setOrderReferenceDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'SetOrderReferenceDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * ConfirmOrderReference API call - Confirms that the order reference is free of constraints and all required information has been set on the order reference. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751980 + * * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function confirmOrderReference($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'ConfirmOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * CancelOrderReference API call - Cancels a previously confirmed order reference. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751990 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * + * @optional requestParameters['cancelation_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function cancelOrderReference($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'CancelOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'cancelation_reason' => 'CancelationReason', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * CloseOrderReference API call - Confirms that an order reference has been fulfilled (fully or partially) + * and that you do not expect to create any new authorizations on this order reference. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752000 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function closeOrderReference($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'CloseOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * CloseAuthorization API call - Closes an authorization. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752070 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] - [String] + * + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function closeAuthorization($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'CloseAuthorization'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * Authorize API call - Reserves a specified amount against the payment method(s) stored in the order reference. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752010 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @param requestParameters['authorization_amount'] [String] + * @param requestParameters['currency_code'] - [String] + * @param requestParameters['authorization_reference_id'] [String] + * + * @optional requestParameters['capture_now'] [String] + * @optional requestParameters['provider_credit_details'] - [array (array())] + * @optional requestParameters['seller_authorization_note'] [String] + * @optional requestParameters['transaction_timeout'] [String] - Defaults to 1440 minutes + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function authorize($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'Authorize'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'capture_now' => 'CaptureNow', + 'provider_credit_details' => array(), + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * GetAuthorizationDetails API call - Returns the status of a particular authorization and the total amount captured on the authorization. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752030 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function getAuthorizationDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetAuthorizationDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * Capture API call - Captures funds from an authorized payment instrument. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752040 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] - [String] + * @param requestParameters['capture_amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @param requestParameters['capture_reference_id'] - [String] + * + * @optional requestParameters['provider_credit_details'] - [array (array())] + * @optional requestParameters['seller_capture_note'] - [String] + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function capture($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'Capture'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'capture_amount' => 'CaptureAmount.Amount', + 'currency_code' => 'CaptureAmount.CurrencyCode', + 'capture_reference_id' => 'CaptureReferenceId', + 'provider_credit_details' => array(), + 'seller_capture_note' => 'SellerCaptureNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * GetCaptureDetails API call - Returns the status of a particular capture and the total amount refunded on the capture. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752060 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_capture_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function getCaptureDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetCaptureDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * Refund API call - Refunds a previously captured amount. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752080 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_capture_id'] - [String] + * @param requestParameters['refund_reference_id'] - [String] + * @param requestParameters['refund_amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * + * @optional requestParameters['provider_credit_reversal_details'] - [array(array())] + * @optional requestParameters['seller_refund_note'] [String] + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function refund($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'Refund'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'refund_reference_id' => 'RefundReferenceId', + 'refund_amount' => 'RefundAmount.Amount', + 'currency_code' => 'RefundAmount.CurrencyCode', + 'provider_credit_reversal_details' => array(), + 'seller_refund_note' => 'SellerRefundNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * GetRefundDetails API call - Returns the status of a particular refund. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752100 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_refund_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function getRefundDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetRefundDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_refund_id' => 'AmazonRefundId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * GetServiceStatus API Call - Returns the operational status of the OffAmazonPayments API section. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201752110 + * + * The GetServiceStatus operation returns the operational status of the OffAmazonPayments API + * section of Amazon Marketplace Web Service (Amazon MWS). + * Status values are GREEN, GREEN_I, YELLOW, and RED. + * + * @param requestParameters['merchant_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function getServiceStatus($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetServiceStatus'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * CreateOrderReferenceForId API Call - Creates an order reference for the given object. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751670 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['id'] - [String] + * + * @optional requestParameters['inherit_shipping_address'] [Boolean] + * @optional requestParameters['confirm_now'] - [Boolean] + * @optional Amount (required when confirm_now is set to true) [String] + * @optional requestParameters['currency_code'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function createOrderReferenceForId($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'CreateOrderReferenceForId'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'id' => 'Id', + 'id_type' => 'IdType', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'confirm_now' => 'ConfirmNow', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * GetBillingAgreementDetails API Call - Returns details about the Billing Agreement object and its current state. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751690 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * + * @optional requestParameters['address_consent_token'] - [String] + * @optional requestParameters['access_token'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + * + * You cannot pass both address_consent_token and access_token in + * the same call or you will encounter a 400/"AmbiguousToken" error + */ + public function getBillingAgreementDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetBillingAgreementDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'address_consent_token' => 'AddressConsentToken', + 'access_token' => 'AccessToken', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * SetBillingAgreementDetails API call - Sets Billing Agreement details such as a description of the agreement and other information about the seller. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751700 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @param requestParameters['amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_billing_agreement_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function setBillingAgreementDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'SetBillingAgreementDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'platform_id' => 'BillingAgreementAttributes.PlatformId', + 'seller_note' => 'BillingAgreementAttributes.SellerNote', + 'seller_billing_agreement_id' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.SellerBillingAgreementId', + 'custom_information' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.CustomInformation', + 'store_name' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.StoreName', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * ConfirmBillingAgreement API Call - Confirms that the Billing Agreement is free of constraints and all required information has been set on the Billing Agreement. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751710 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function confirmBillingAgreement($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'ConfirmBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * ValidateBillignAgreement API Call - Validates the status of the Billing Agreement object and the payment method associated with it. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751720 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function validateBillingAgreement($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'ValidateBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * AuthorizeOnBillingAgreement API call - Reserves a specified amount against the payment method(s) stored in the Billing Agreement. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751940 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @param requestParameters['authorization_reference_id'] [String] + * @param requestParameters['authorization_amount'] [String] + * @param requestParameters['currency_code'] - [String] + * + * @optional requestParameters['seller_authorization_note'] [String] + * @optional requestParameters['transaction_timeout'] - Defaults to 1440 minutes + * @optional requestParameters['capture_now'] [String] + * @optional requestParameters['soft_descriptor'] - - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['inherit_shipping_address'] [Boolean] - Defaults to true + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function authorizeOnBillingAgreement($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'AuthorizeOnBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'capture_now' => 'CaptureNow', + 'soft_descriptor' => 'SoftDescriptor', + 'seller_note' => 'SellerNote', + 'platform_id' => 'PlatformId', + 'custom_information' => 'SellerOrderAttributes.CustomInformation', + 'seller_order_id' => 'SellerOrderAttributes.SellerOrderId', + 'store_name' => 'SellerOrderAttributes.StoreName', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * CloseBillingAgreement API Call - Returns details about the Billing Agreement object and its current state. + * + * @see https://pay.amazon.com/developer/documentation/apireference/201751950 + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function closeBillingAgreement($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'CloseBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * Charge convenience method + * Performs the API calls + * 1. SetOrderReferenceDetails / SetBillingAgreementDetails + * 2. ConfirmOrderReference / ConfirmBillingAgreement + * 3. Authorize (with Capture) / AuthorizeOnBillingAgreeemnt (with Capture) + * + * @param requestParameters['merchant_id'] - [String] + * + * @param requestParameters['amazon_reference_id'] - [String] : Order Reference ID /Billing Agreement ID + * If requestParameters['amazon_reference_id'] is empty then the following is required, + * @param requestParameters['amazon_order_reference_id'] - [String] : Order Reference ID + * or, + * @param requestParameters['amazon_billing_agreement_id'] - [String] : Billing Agreement ID + * + * @param $requestParameters['charge_amount'] - [String] : Amount value to be captured + * @param requestParameters['currency_code'] - [String] : Currency Code for the Amount + * @param requestParameters['authorization_reference_id'] - [String]- Any unique string that needs to be passed + * + * @optional requestParameters['charge_note'] - [String] : Seller Note sent to the buyer + * @optional requestParameters['transaction_timeout'] - [String] : Defaults to 1440 minutes + * @optional requestParameters['charge_order_id'] - [String] : Custom Order ID provided + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function charge($requestParameters = array()) { + + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + $setParameters = $authorizeParameters = $confirmParameters = $requestParameters; + + if (!empty($requestParameters['amazon_order_reference_id'])) { + $chargeType = 'OrderReference'; + } + elseif (!empty($requestParameters['amazon_billing_agreement_id'])) { + $chargeType = 'BillingAgreement'; + + } + elseif (!empty($requestParameters['amazon_reference_id'])) { + switch (substr(strtoupper($requestParameters['amazon_reference_id']), 0, 1)) { + case 'P': + case 'S': + $chargeType = 'OrderReference'; + $setParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + $authorizeParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + $confirmParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + break; + + case 'B': + case 'C': + $chargeType = 'BillingAgreement'; + $setParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + $authorizeParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + $confirmParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + break; + + default: + throw new \Exception('Invalid Amazon Reference ID'); + } + } + else { + throw new \Exception('key amazon_order_reference_id or amazon_billing_agreement_id is null and is a required parameter'); + } + + // Set the other parameters if the values are present. + $setParameters['amount'] = !empty($requestParameters['charge_amount']) ? $requestParameters['charge_amount'] : ''; + $authorizeParameters['authorization_amount'] = !empty($requestParameters['charge_amount']) ? $requestParameters['charge_amount'] : ''; + + $setParameters['seller_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + $authorizeParameters['seller_authorization_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + $authorizeParameters['seller_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + + $setParameters['seller_order_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + $setParameters['seller_billing_agreement_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + $authorizeParameters['seller_order_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + + $authorizeParameters['capture_now'] = !empty($requestParameters['capture_now']) ? $requestParameters['capture_now'] : FALSE; + + $response = $this->makeChargeCalls($chargeType, $setParameters, $confirmParameters, $authorizeParameters); + return $response; + } + + /** + * MakeChargeCalls - makes API calls based off the charge type (OrderReference or BillingAgreement) + */ + protected function makeChargeCalls($chargeType, $setParameters, $confirmParameters, $authorizeParameters) { + switch ($chargeType) { + + case 'OrderReference': + + // Get the Order Reference details and feed the response object to the ResponseParser. + $responseObj = $this->getOrderReferenceDetails($setParameters); + + // Call the function getOrderReferenceDetailsStatus in ResponseParser.php providing it the XML response + // $oroStatus is an array containing the State of the Order Reference ID. + $oroStatus = $responseObj->getOrderReferenceDetailsStatus($responseObj->toXml()); + + if ($oroStatus['State'] === 'Draft') { + $response = $this->setOrderReferenceDetails($setParameters); + if ($this->success) { + $this->confirmOrderReference($confirmParameters); + } + } + + $responseObj = $this->getOrderReferenceDetails($setParameters); + + // Check the Order Reference Status again before making the Authorization. + $oroStatus = $responseObj->getOrderReferenceDetailsStatus($responseObj->toXml()); + + if ($oroStatus['State'] === 'Open') { + if ($this->success) { + $response = $this->authorize($authorizeParameters); + } + } + if (empty($response)) { + return NULL; + } + elseif ($oroStatus['State'] != 'Open' && $oroStatus['State'] != 'Draft') { + throw new \Exception('The Order Reference is in the ' . $oroStatus['State'] . " State. It should be in the Draft or Open State"); + } + + return $response; + + case 'BillingAgreement': + + // Get the Billing Agreement details and feed the response object to the ResponseParser. + $responseObj = $this->getBillingAgreementDetails($setParameters); + + // Call the function getBillingAgreementDetailsStatus in ResponseParser.php providing it the XML response + // $baStatus is an array containing the State of the Billing Agreement. + $baStatus = $responseObj->getBillingAgreementDetailsStatus($responseObj->toXml()); + + if ($baStatus['State'] === 'Draft') { + $response = $this->setBillingAgreementDetails($setParameters); + if ($this->success) { + $response = $this->confirmBillingAgreement($confirmParameters); + } + } + + // Check the Billing Agreement status again before making the Authorization. + $responseObj = $this->getBillingAgreementDetails($setParameters); + $baStatus = $responseObj->getBillingAgreementDetailsStatus($responseObj->toXml()); + + if ($this->success && $baStatus['State'] === 'Open') { + $response = $this->authorizeOnBillingAgreement($authorizeParameters); + } + + if (empty($response)) { + return NULL; + } + elseif ($baStatus['State'] != 'Open' && $baStatus['State'] != 'Draft') { + throw new \Exception('The Billing Agreement is in the ' . $baStatus['State'] . " State. It should be in the Draft or Open State"); + } + + return $response; + } + } + + /** + * GetProviderCreditDetails API Call - Get the details of the Provider Credit. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function getProviderCreditDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetProviderCreditDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_id' => 'AmazonProviderCreditId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * GetProviderCreditReversalDetails API Call - Get details of the Provider Credit Reversal. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_reversal_id'] - [String] + * + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function getProviderCreditReversalDetails($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'GetProviderCreditReversalDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_reversal_id' => 'AmazonProviderCreditReversalId', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * ReverseProviderCredit API Call - Reverse the Provider Credit. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_id'] - [String] + * + * @optional requestParameters['credit_reversal_reference_id'] - [String] + * @param requestParameters['credit_reversal_amount'] - [String] + * + * @optional requestParameters['currency_code'] - [String] + * @optional requestParameters['credit_reversal_note'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + public function reverseProviderCredit($requestParameters = array()) { + $parameters = array(); + $parameters['Action'] = 'ReverseProviderCredit'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_id' => 'AmazonProviderCreditId', + 'credit_reversal_reference_id' => 'CreditReversalReferenceId', + 'credit_reversal_amount' => 'CreditReversalAmount.Amount', + 'currency_code' => 'CreditReversalAmount.CurrencyCode', + 'credit_reversal_note' => 'CreditReversalNote', + 'mws_auth_token' => 'MWSAuthToken', + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /** + * Create an Array of required parameters, sort them + * Calculate signature and invoke the POST to the MWS Service URL. + * + * @param AWSAccessKeyId [String] + * @param Version [String] + * @param SignatureMethod [String] + * @param Timestamp [String] + * @param Signature [String] + */ + private function calculateSignatureAndParametersToString($parameters = array()) { + foreach ($parameters as $key => $value) { + // Ensure that no unexpected type coercions have happened. + if ($key === 'CaptureNow' || $key === 'ConfirmNow' || $key === 'InheritShippingAddress') { + if (!is_bool($value)) { + throw new \Exception($key . ' value ' . $value . ' is of type ' . gettype($value) . ' and should be a boolean value'); + } + } + + // Ensure boolean values are outputed as 'true' or 'false'. + if (is_bool($value)) { + $parameters[$key] = json_encode($value); + } + } + + $parameters['AWSAccessKeyId'] = $this->config['access_key']; + $parameters['Version'] = self::MWS_VERSION; + $parameters['SignatureMethod'] = 'HmacSHA256'; + $parameters['SignatureVersion'] = 2; + $parameters['Timestamp'] = gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time()); + uksort($parameters, 'strcmp'); + + $parameters['Signature'] = $this->signParameters($parameters); + $parameters = drupal_http_build_query($parameters); + + return $parameters; + } + + /** + * Computes RFC 2104-compliant HMAC signature for request parameters + * Implements AWS Signature, as per following spec:. + * + * If Signature Version is 0, it signs concatenated Action and Timestamp. + * + * If Signature Version is 1, it performs the following: + * + * Sorts all parameters (including SignatureVersion and excluding Signature, + * the value of which is being created), ignoring case. + * + * Iterate over the sorted list and append the parameter name (in original case) + * and then its value. It will not URL-encode the parameter values before + * constructing this string. There are no separators. + * + * If Signature Version is 2, string to sign is based on following: + * + * 1. The HTTP Request Method followed by an ASCII newline (%0A) + * 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline. + * 3. The URL encoded HTTP absolute path component of the URI + * (up to but not including the query string parameters); + * if this is empty use a forward '/'. This parameter is followed by an ASCII newline. + * 4. The concatenation of all query string components (names and values) + * as UTF-8 characters which are URL encoded as per RFC 3986 + * (hex characters MUST be uppercase), sorted using lexicographic byte ordering. + * Parameter names are separated from their values by the '=' character + * (ASCII character 61), even if the value is empty. + * Pairs of parameter and values are separated by the '&' character (ASCII code 38). + */ + private function signParameters(array $parameters) { + $signatureVersion = $parameters['SignatureVersion']; + $stringToSign = NULL; + if (2 === $signatureVersion) { + $algorithm = "HmacSHA256"; + $parameters['SignatureMethod'] = $algorithm; + $stringToSign = $this->calculateStringToSignV2($parameters); + } + else { + throw new \Exception("Invalid Signature Version specified"); + } + + return $this->sign($stringToSign, $algorithm); + } + + /** + * Calculate String to Sign for SignatureVersion 2. + * + * @param array $parameters + * request parameters. + * + * @return String to Sign + */ + protected function calculateStringToSignV2(array $parameters) { + $data = 'POST'; + $data .= "\n"; + $data .= $this->mwsEndpointUrl; + $data .= "\n"; + $data .= $this->mwsEndpointPath; + $data .= "\n"; + $data .= drupal_http_build_query($parameters); + return $data; + } + + /** + * Computes RFC 2104-compliant HMAC signature. + * + * @param $data + * @param $algorithm + * + * @return string + * + * @throws \Exception + */ + protected function sign($data, $algorithm) { + if ($algorithm === 'HmacSHA1') { + $hash = 'sha1'; + } + else { + if ($algorithm === 'HmacSHA256') { + $hash = 'sha256'; + } + else { + throw new \Exception("Non-supported signing method specified"); + } + } + + return base64_encode(hash_hmac($hash, $data, $this->config['secret_key'], TRUE)); + } + + /** + * InvokePost takes the parameters and invokes the httpPost function to POST the parameters + * Exponential retries on error 500 and 503 + * The response from the POST is an XML which is converted to Array. + */ + protected function invokePost($parameters) { + $this->success = FALSE; + + // Submit the request and read response body. + try { + $retries = 0; + do { + try { + $this->constructUserAgentHeader(); + $httpCurlRequest = new AmazonPayHttpCurl($this->config); + $response = $httpCurlRequest->httpPost($this->mwsServiceUrl, $this->userAgent, $parameters); + $curlResponseInfo = $httpCurlRequest->getCurlResponseInfo(); + $statusCode = $curlResponseInfo["http_code"]; + $this->logMessage($this->userAgent); + $response = array( + 'Status' => $statusCode, + 'ResponseBody' => $response, + ); + + $statusCode = $response['Status']; + if ($statusCode == 200) { + $shouldRetry = FALSE; + $this->success = TRUE; + } + elseif ($statusCode == 500 || $statusCode == 503) { + + $shouldRetry = TRUE; + if ($shouldRetry && strtolower($this->config['handle_throttle'])) { + $this->pauseOnRetry(++$retries, $statusCode); + } + } + else { + $shouldRetry = FALSE; + } + } + catch (\Exception $e) { + throw $e; + } + } while ($shouldRetry); + } + catch (\Exception $se) { + throw $se; + } + + return $response; + } + + /** + * Exponential sleep on failed request + * Up to three retries will occur if first reqest fails + * after 1.0 second, 2.2 seconds, and finally 7.0 seconds. + * + * @param int $retries + * + * @throws Exception if maximum number of retries has been reached + */ + protected function pauseOnRetry($retries, $status) { + if ($retries <= self::MAX_ERROR_RETRY) { + // PHP delays are in microseconds (1 million microsecond = 1 sec) + // 1st delay is (4^1) * 100000 + 600000 = 0.4 + 0.6 second = 1.0 sec + // 2nd delay is (4^2) * 100000 + 600000 = 1.6 + 0.6 second = 2.2 sec + // 3rd delay is (4^3) * 100000 + 600000 = 6.4 + 0.6 second = 7.0 sec. + $delay = (int) (pow(4, $retries) * 100000) + 600000; + usleep($delay); + } + else { + throw new \Exception('Error Code: ' . $status . PHP_EOL . 'Maximum number of retry attempts - ' . $retries . ' reached'); + } + } + + /** + * Create the User Agent Header sent with the POST request. + */ + protected function constructUserAgentHeader() { + $this->userAgent = 'drupalcommerce-sdk/1.0 ('; + if (($this->config['application_name']) || ($this->config['application_version'])) { + if ($this->config['application_name']) { + $quoted_application_name = preg_replace('/ {2,}|\s/', ' ', $this->config['application_name']); + $quoted_application_name = preg_replace('/\\\\/', '\\\\\\\\', $quoted_application_name); + $quoted_application_name = preg_replace('/\//', '\\/', $quoted_application_name); + $this->userAgent .= $quoted_application_name; + if ($this->config['application_version']) { + $this->userAgent .= '/'; + } + } + + if ($this->config['application_version']) { + $quoted_user_agent_string = preg_replace('/ {2,}|\s/', ' ', $this->config['application_version']); + $quoted_user_agent_string = preg_replace('/\\\\/', '\\\\\\\\', $quoted_user_agent_string); + $quoted_user_agent_string = preg_replace('/\\(/', '\\(', $quoted_user_agent_string); + $this->userAgent .= $quoted_user_agent_string; + } + $this->userAgent .= '; '; + } + $this->userAgent .= 'PHP/' . phpversion() . '; '; + $this->userAgent .= php_uname('s') . '/' . php_uname('m') . '/' . php_uname('r'); + $this->userAgent .= ')'; + } + + /** + * Computes RFC 2104-compliant HMAC signature. + * + * @param $stringToSign + * @param $secretKey + * + * @return string + */ + public static function getSignature($stringToSign, $secretKey) { + return base64_encode(hash_hmac('sha256', $stringToSign, $secretKey, TRUE)); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayHttpCurl.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayHttpCurl.php new file mode 100755 index 00000000..b587c2e3 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayHttpCurl.php @@ -0,0 +1,139 @@ +config = $config; + } + + /** + * Setter for boolean header to get the user info . + */ + public function setHttpHeader() { + $this->header = TRUE; + } + + /** + * Setter for Access token to get the user info . + */ + public function setAccessToken($accesstoken) { + $this->accessToken = $accesstoken; + } + + /** + * Add the common Curl Parameters to the curl handler. + + * Also checks for optional parameters if provided in the config + * config['cabundle_file'] + * config['proxy_port'] + * config['proxy_host'] + * config['proxy_username'] + * config['proxy_password']. + */ + protected function commonCurlParams($url, $userAgent) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_PORT, 443); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + + if (!is_null($this->config['cabundle_file'])) { + curl_setopt($ch, CURLOPT_CAINFO, $this->config['cabundle_file']); + } + + if (!empty($userAgent)) { + curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); + } + + if ($this->config['proxy_host'] != NULL && $this->config['proxy_port'] != -1) { + curl_setopt($ch, CURLOPT_PROXY, $this->config['proxy_host'] . ':' . $this->config['proxy_port']); + } + + if ($this->config['proxy_username'] != NULL && $this->config['proxy_password'] != NULL) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config['proxy_username'] . ':' . $this->config['proxy_password']); + } + + return $ch; + } + + /** + * Performs an HTTP POST request. + */ + public function httpPost($url, $userAgent = NULL, $parameters = NULL) { + $ch = $this->commonCurlParams($url, $userAgent); + + curl_setopt($ch, CURLOPT_POST, TRUE); + curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); + curl_setopt($ch, CURLOPT_HEADER, FALSE); + + $response = $this->execute($ch); + return $response; + } + + /** + * Performs an HTTP GET request. + */ + public function httpGet($url, $userAgent = NULL) { + $ch = $this->commonCurlParams($url, $userAgent); + + // Setting the HTTP header with the Access Token only for Getting user info. + if ($this->header) { + $this->headerArray[] = 'Authorization: bearer ' . $this->accessToken; + } + + $response = $this->execute($ch); + return $response; + } + + /** + * Execute Curl request. + */ + private function execute($ch) { + // Ensure we never send the "Expect: 100-continue" header. + $this->headerArray[] = 'Expect:'; + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headerArray); + + $response = curl_exec($ch); + if ($response === FALSE) { + $error_msg = "Unable to post request, underlying exception of " . curl_error($ch); + curl_close($ch); + throw new \Exception($error_msg); + } + else { + $this->curlResponseInfo = curl_getinfo($ch); + } + curl_close($ch); + return $response; + } + + /** + * Get the output of Curl Getinfo . + */ + public function getCurlResponseInfo() { + return $this->curlResponseInfo; + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayIpnHandler.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayIpnHandler.php new file mode 100644 index 00000000..a5532406 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayIpnHandler.php @@ -0,0 +1,437 @@ + NULL, + 'proxy_host' => NULL, + 'proxy_port' => -1, + 'proxy_username' => NULL, + 'proxy_password' => NULL, + ); + + /** + * Constructs a new AmazonPayIpnHandler object. + */ + public function __construct($headers, $body, $ipnConfig = NULL) { + $this->headers = array_change_key_case($headers, CASE_LOWER); + $this->body = $body; + + if ($ipnConfig != NULL) { + $this->checkConfigKeys($ipnConfig); + } + + // Get the list of fields that we are interested in. + $this->fields = array( + "Timestamp" => TRUE, + "Message" => TRUE, + "MessageId" => TRUE, + "Subject" => FALSE, + "TopicArn" => TRUE, + "Type" => TRUE, + ); + $this->validateHeaders(); + $this->getMessage(); + $this->checkForCorrectMessageType(); + $this->constructAndVerifySignature(); + } + + /** + * Checks config key integrity. + */ + protected function checkConfigKeys($ipnConfig) { + $ipnConfig = array_change_key_case($ipnConfig, CASE_LOWER); + $ipnConfig = $this->trimArray($ipnConfig); + + foreach ($ipnConfig as $key => $value) { + if (array_key_exists($key, $this->ipnConfig)) { + $this->ipnConfig[$key] = $value; + } + else { + throw new \Exception('Key ' . $key . ' is either not part of the configuration or has incorrect Key name. + check the ipnConfig array key names to match your key names of your config array ', 1); + } + } + } + + /** + * Helper function to log data within the Client. + */ + protected function logMessage($message) { + commerce_amazon_lpa_add_debug_log($message, array(), WATCHDOG_INFO); + } + + /** + * Sets the value for the key if the key exists in ipnConfig. + */ + public function __set($name, $value) { + if (array_key_exists(strtolower($name), $this->ipnConfig)) { + $this->ipnConfig[$name] = $value; + } + else { + throw new \Exception("Key " . $name . " is not part of the configuration", 1); + } + } + + /** + * Gets IPN config values. + */ + public function __get($name) { + if (array_key_exists(strtolower($name), $this->ipnConfig)) { + return $this->ipnConfig[$name]; + } + else { + throw new \Exception("Key " . $name . " was not found in the configuration", 1); + } + } + + /** + * Trim the input Array key values . + */ + private function trimArray($array) { + foreach ($array as $key => $value) { + $array[$key] = trim($value); + } + return $array; + } + + /** + * Validates incoming IPN headers. + */ + protected function validateHeaders() { + // Quickly check that this is a sns message. + if (!array_key_exists('x-amz-sns-message-type', $this->headers)) { + throw new \Exception("Error with message - header " . "does not contain x-amz-sns-message-type header"); + } + + if ($this->headers['x-amz-sns-message-type'] !== 'Notification') { + throw new \Exception("Error with message - header x-amz-sns-message-type is not " . "Notification, is " . $this->headers['x-amz-sns-message-type']); + } + } + + /** + * Gets the message. + */ + protected function getMessage() { + $this->snsMessage = json_decode($this->body, TRUE); + + $json_error = json_last_error(); + + if ($json_error != 0) { + $errorMsg = "Error with message - content is not in json format" . $this->getErrorMessageForJsonError($json_error) . " " . $this->snsMessage; + throw new \Exception($errorMsg); + } + } + + /** + * Convert a json error code to a descriptive error message. + * + * @param int $json_error + * The message code. + * + * @return string + * The error message. + */ + private function getErrorMessageForJsonError($json_error) { + switch ($json_error) { + case JSON_ERROR_DEPTH: + return " - maximum stack depth exceeded."; + + case JSON_ERROR_STATE_MISMATCH: + return " - invalid or malformed JSON."; + + case JSON_ERROR_CTRL_CHAR: + return " - control character error."; + + case JSON_ERROR_SYNTAX: + return " - syntax error."; + + default: + return "."; + } + } + + /** + * CheckForCorrectMessageType() + * + * Checks if the Field [Type] is set to ['Notification'] + * Gets the value for the fields marked true in the fields array + * Constructs the signature string. + */ + private function checkForCorrectMessageType() { + $type = $this->getMandatoryField("Type"); + if (strcasecmp($type, "Notification") != 0) { + throw new \Exception("Error with SNS Notification - unexpected message with Type of " . $type); + } + + if (strcmp($this->getMandatoryField("Type"), "Notification") != 0) { + throw new \Exception("Error with signature verification - unable to verify " . $this->getMandatoryField("Type") . " message"); + } + else { + + // Sort the fields into byte order based on the key name(A-Za-z) + ksort($this->fields); + + // Extract the key value pairs and sort in byte order. + $signatureFields = array(); + foreach ($this->fields as $fieldName => $mandatoryField) { + if ($mandatoryField) { + $value = $this->getMandatoryField($fieldName); + } + else { + $value = $this->getField($fieldName); + } + + if (!is_null($value)) { + array_push($signatureFields, $fieldName); + array_push($signatureFields, $value); + } + } + + /* Create the signature string - key / value in byte order + * delimited by newline character + ending with a new line character + */ + $this->signatureFields = implode("\n", $signatureFields) . "\n"; + } + } + + /** + * Ensures that the URL of the certificate is one belonging to AWS. + * + * @param string $url + * Certificate URL. + * + * @throws \Exception + */ + protected function validateUrl($url) { + $parsed = parse_url($url); + if (empty($parsed['scheme']) + || empty($parsed['host']) + || $parsed['scheme'] !== 'https' + || substr($url, -4) !== '.pem' + || !preg_match($this->defaultHostPattern, $parsed['host']) + ) { + throw new \Exception( + 'The certificate is located on an invalid domain.' + ); + } + } + + /** + * Verify that the signature is correct. + * + * @throws Exception + */ + private function constructAndVerifySignature() { + $signature = base64_decode($this->getMandatoryField("Signature")); + $certificatePath = $this->getMandatoryField("SigningCertURL"); + $this->validateUrl($certificatePath); + $this->certificate = $this->getCertificate($certificatePath); + + $result = $this->verifySignatureIsCorrectFromCertificate($signature); + if (!$result) { + throw new \Exception("Unable to match signature from remote server: signature of " . $this->getCertificate($certificatePath) . " , SigningCertURL of " . $this->getMandatoryField("SigningCertURL") . " , SignatureOf " . $this->getMandatoryField("Signature")); + } + } + + /** + * Gets the certificate from the $certificatePath using Curl. + * + * @return mixed + * The response. + */ + private function getCertificate($certificatePath) { + $httpCurlRequest = new AmazonPayHttpCurl($this->ipnConfig); + $response = $httpCurlRequest->httpGet($certificatePath); + return $response; + } + + /** + * Verify that the signature is correct for the given data and public key. + * + * @param string $signature + * The decoded signature to compare against. + */ + public function verifySignatureIsCorrectFromCertificate($signature) { + $certKey = openssl_get_publickey($this->certificate); + + if ($certKey === FALSE) { + throw new \Exception("Unable to extract public key from cert"); + } + + try { + $certInfo = openssl_x509_parse($this->certificate, TRUE); + $certSubject = $certInfo["subject"]; + + if (is_null($certSubject)) { + throw new \Exception("Error with certificate - subject cannot be found"); + } + } + catch (\Exception $ex) { + throw new \Exception("Unable to verify certificate - error with the certificate subject", NULL, $ex); + } + + if (strcmp($certSubject["CN"], $this->expectedCnName)) { + throw new \Exception("Unable to verify certificate issued by Amazon - error with certificate subject"); + } + + $result = -1; + try { + $result = openssl_verify($this->signatureFields, $signature, $certKey, OPENSSL_ALGO_SHA1); + } + catch (\Exception $ex) { + throw new \Exception("Unable to verify signature - error with the verification algorithm", NULL, $ex); + } + + return ($result > 0); + } + + /** + * Extract the mandatory field from the message and return the contents. + * + * @param string $fieldName + * Name of the field to extract. + * + * @return string + * The field contents if found. + * + * @throws \Exception + */ + protected function getMandatoryField($fieldName) { + $value = $this->getField($fieldName); + if (is_null($value)) { + throw new \Exception("Error with json message - mandatory field " . $fieldName . " cannot be found"); + } + return $value; + } + + /** + * Extract the field if present, return null if not defined. + * + * @param string $fieldName + * Name of the field to extract. + * + * @return string + * The field contents if found, NULL otherwise. + */ + protected function getField($fieldName) { + if (array_key_exists($fieldName, $this->snsMessage)) { + return $this->snsMessage[$fieldName]; + } + else { + return NULL; + } + } + + /** + * JSON decode the raw [Message] portion of the IPN . + */ + public function returnMessage() { + return json_decode($this->snsMessage['Message'], TRUE); + } + + /** + * Converts IPN [Message] field to JSON. + * + * Has child elements + * ['NotificationData'] [XML] - API call XML notification data. + * + * @return string + * The response in JSON format. + */ + public function toJson() { + $response = $this->simpleXmlObject(); + + // Merging the remaining fields with the response. + $remainingFields = $this->getRemainingIpnFields(); + $responseArray = array_merge($remainingFields, (array) $response); + + // Converting to JSON format. + $response = json_encode($responseArray); + + return $response; + } + + /** + * Converts IPN [Message] field to associative array. + * + * @return array + * The response in array format + */ + public function toArray() { + $response = $this->simpleXmlObject(); + + // Converting the SimpleXMLElement Object to array() + $response = json_encode($response); + $response = json_decode($response, TRUE); + + // Merging the remaining fields with the response array. + $remainingFields = $this->getRemainingIpnFields(); + $response = array_merge($remainingFields, $response); + + return $response; + } + + /** + * Add remaining fields to the datatype. + * + * Has child elements + * ['NotificationData'] [XML] - API call XML response data + * Convert to SimpleXML element object + * Type - Notification + * MessageId - ID of the Notification + * Topic ARN - Topic of the IPN. + * + * @return array + * The response in array format. + */ + private function simpleXmlObject() { + $ipnMessage = $this->returnMessage(); + $response = simplexml_load_string((string) $ipnMessage['NotificationData']); + $response->addChild('Type', $this->snsMessage['Type']); + $response->addChild('MessageId', $this->snsMessage['MessageId']); + $response->addChild('TopicArn', $this->snsMessage['TopicArn']); + return $response; + } + + /** + * Gets the remaining fields of the IPN. + */ + private function getRemainingIpnFields() { + $ipnMessage = $this->returnMessage(); + + $remainingFields = array( + 'NotificationReferenceId' => $ipnMessage['NotificationReferenceId'], + 'NotificationType' => $ipnMessage['NotificationType'], + 'SellerId' => $ipnMessage['SellerId'], + 'ReleaseEnvironment' => $ipnMessage['ReleaseEnvironment'], + ); + + return $remainingFields; + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayResponse.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayResponse.php new file mode 100644 index 00000000..01d4663e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/AmazonPayResponse.php @@ -0,0 +1,108 @@ +response = $response; + } + + /** + * Returns the XML portion of the response. + */ + public function toXml() { + return $this->response['ResponseBody']; + } + + /** + * Converts XML into JSON. + * + * @return string + * The response as JSON. + */ + public function toJson() { + $response = $this->simpleXmlObject(); + return drupal_json_encode($response); + } + + /** + * Converts XML into associative array. + * + * @return array + * The response as an array. + */ + public function toArray() { + $response = $this->simpleXmlObject(); + + // Converting the SimpleXMLElement Object to array() + $response = drupal_json_encode($response); + + return drupal_json_decode($response); + } + + /** + * Turns the response into an XML object. + */ + protected function simpleXmlObject() { + $response = $this->response; + + // Getting the HttpResponse Status code to the output as a string. + $status = strval($response['Status']); + + // Getting the Simple XML element object of the XML Response Body. + $response = simplexml_load_string((string) $response['ResponseBody']); + + // Adding the HttpResponse Status code to the output as a string. + $response->addChild('ResponseStatus', $status); + + return $response; + } + + /** + * Get the status of the Order Reference ID. + */ + public function getOrderReferenceDetailsStatus($response) { + $oroStatus = $this->getStatus('GetORO', '//GetORO:OrderReferenceStatus', $response); + + return $oroStatus; + } + + /** + * Get the status of the BillingAgreement. + */ + public function getBillingAgreementDetailsStatus($response) { + $baStatus = $this->getStatus('GetBA', '//GetBA:BillingAgreementStatus', $response); + + return $baStatus; + } + + /** + * Gets the request status. + */ + protected function getStatus($type, $path, $response) { + $data = new \SimpleXMLElement($response); + $namespaces = $data->getNamespaces(TRUE); + foreach ($namespaces as $key => $value) { + $namespace = $value; + } + $data->registerXPathNamespace($type, $namespace); + foreach ($data->xpath($path) as $value) { + $status = json_decode(json_encode((array) $value), TRUE); + } + + return $status; + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.admin.inc new file mode 100644 index 00000000..202e5532 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.admin.inc @@ -0,0 +1,1021 @@ + l(t('Currency settings page'), 'admin/commerce/config/currency'))); + $help_items[] = t('EUR for the German account.'); + $help_items[] = t('GBP for the UK account.'); + $help_items[] = t('USD for the United States account.'); + $content .= theme('item_list', array('items' => $help_items)); + $form['content']['#markup'] = $content; + return $form; + } + + $form['commerce_amazon_lpa_account_configuration'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('Account configuration'), + ); + + // Checkout by Amazon settings form. + $form['commerce_amazon_lpa_account_configuration']['commerce_amazon_lpa_merchant_id'] = array( + '#type' => 'textfield', + '#title' => t('Merchant ID'), + '#default_value' => variable_get('commerce_amazon_lpa_merchant_id', ''), + '#required' => TRUE, + ); + $form['commerce_amazon_lpa_account_configuration']['commerce_amazon_lpa_access_key'] = array( + '#type' => 'textfield', + '#title' => t('MWS Access key'), + '#default_value' => variable_get('commerce_amazon_lpa_access_key', ''), + '#required' => TRUE, + ); + $form['commerce_amazon_lpa_account_configuration']['commerce_amazon_lpa_secret_key'] = array( + '#type' => 'password', + '#title' => t('MWS Secret key'), + '#default_value' => variable_get('commerce_amazon_lpa_secret_key', ''), + '#required' => (variable_get('commerce_amazon_lpa_secret_key') === NULL), + '#description' => variable_get('commerce_amazon_lpa_secret_key') ? t('Secret key has been set. Leave blank unless you want to change the saved key.') : NULL, + ); + $form['commerce_amazon_lpa_account_configuration']['commerce_amazon_lpa_region'] = array( + '#type' => 'radios', + '#title' => t('Region'), + '#options' => array( + 'DE' => t('Germany'), + 'UK' => t('United Kingdom'), + 'US' => t('United States'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_region', 'US'), + '#required' => TRUE, + ); + $form['commerce_amazon_lpa_account_configuration']['commerce_amazon_lpa_client_id'] = array( + '#type' => 'textfield', + '#title' => t('LWA Client ID'), + '#default_value' => variable_get('commerce_amazon_lpa_client_id', ''), + '#required' => TRUE, + ); + + $form['commerce_amazon_lpa_general_settings'] = array( + '#type' => 'fieldset', + '#title' => t('General settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['commerce_amazon_lpa_general_settings']['commerce_amazon_lpa_operation_mode'] = array( + '#type' => 'radios', + '#title' => t('Operation mode'), + '#description' => t('Provide Amazon Pay and Login with Amazon or just Amazon Pay. Using Login with Amazon links Drupal user accounts with the Amazon account'), + '#options' => array( + AmazonLPA::OPERATION_MODE_LOGIN_AND_PAY => t('Amazon Pay and Login with Amazon'), + AmazonLPA::OPERATION_MODE_PAY_ONLY => t('Amazon Pay only'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_operation_mode', 'login_pay'), + ); + $form['commerce_amazon_lpa_general_settings']['commerce_amazon_lpa_environment'] = array( + '#type' => 'radios', + '#title' => t('Environment'), + '#options' => array(AmazonLPA::ENV_SANDBOX => t('Sandbox'), AmazonLPA::ENV_LIVE => t('Live')), + '#default_value' => variable_get('commerce_amazon_lpa_environment', AmazonLPA::ENV_SANDBOX), + ); + $form['commerce_amazon_lpa_general_settings']['commerce_amazon_lpa_hidden_mode'] = array( + '#type' => 'radios', + '#title' => t('Hidden button mode'), + '#description' => t('This allows you to keep the integration active, but only available to select roles.'), + '#options' => array('' => 'Disabled') + user_roles(TRUE), + '#default_value' => variable_get('commerce_amazon_lpa_hidden_mode', ''), + ); + $form['commerce_amazon_lpa_general_settings']['commerce_amazon_lpa_langcode'] = array( + '#type' => 'radios', + '#title' => t('Checkout language'), + '#options' => array( + 'en-US' => t('US English'), + 'en-GB' => t('UK English'), + 'de-DE' => t('German'), + 'fr-FR' => t('French'), + 'it-IT' => t('Italian'), + 'es-ES' => t('Spanish (Spain)'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_langcode', AmazonLPA::get_region_langcode(variable_get('commerce_amazon_lpa_region', ''))), + ); + + $form['commerce_amazon_lpa_checkout_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Checkout settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['commerce_amazon_lpa_checkout_settings']['commerce_amazon_lpa_checkout_strategy'] = array( + '#type' => 'radios', + '#title' => t('Checkout strategy'), + '#description' => t('If the user has logged in using Login with Amazon, which checkout options are available?'), + '#options' => array( + AmazonLPA::STRATEGY_AMAZON => t('Only Amazon checkout'), + AmazonLPA::STRATEGY_NORMAL => t('Normal checkout and Amazon checkout'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_checkout_strategy', AmazonLPA::STRATEGY_NORMAL), + ); + $form['commerce_amazon_lpa_checkout_settings']['commerce_amazon_lpa_popup'] = array( + '#type' => 'radios', + '#title' => t('Use a redirect or popup for login'), + '#description' => t("Integration can either use a pop up or redirect experience. You must add the following as Allowed Return URLs in your Login with Amazon application in Seller Central:
  • !redirect1
  • !redirect2
", array( + '!redirect1' => '' . url('checkout/amazon', array('absolute' => TRUE)) . '', + '!redirect2' => '' . url('user/login/amazon', array('absolute' => TRUE, 'query' => array('amzn' => 'LwA'))) . '', + )), + '#options' => array( + 'popup' => t('Popup'), + 'redirect' => t('Redirect'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_popup', 'popup'), + ); + + $form['commerce_amazon_lpa_payment_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Payment settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['commerce_amazon_lpa_payment_settings']['commerce_amazon_lpa_authorization_mode'] = array( + '#type' => 'select', + '#title' => t('Authorization mode'), + '#options' => array( + AmazonLPA::AUTH_SYNC => t('Automatic synchronous authorization in frontend'), + AmazonLPA::AUTH_NONSYNC => t('Automatic non-synchronous after order is placed'), + AmazonLPA::AUTH_MANUAL => t('Manual non-synchronous authorization through order management.'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_authorization_mode', 'automatic_nonsync'), + ); + $form['commerce_amazon_lpa_payment_settings']['commerce_amazon_lpa_capture_mode'] = array( + '#type' => 'select', + '#title' => t('Capture mode'), + '#options' => array( + AmazonLPA::CAPTURE_AUTH_CAPTURE => t('Immediate capture on successful authorization'), + AmazonLPA::CAPTURE_SHIPMENT_CAPTURE => t('Capture on shipment'), + AmazonLPA::CAPTURE_MANUAL_CAPTURE => t('Manual capture through order management'), + ), + '#default_value' => AmazonLPA::get_capture_mode(), + '#description' => t('Whitelisting with Amazon Pay is required for "Immediate capture on successful authorization"'), + ); + $form['commerce_amazon_lpa_payment_settings']['commerce_amazon_lpa_capture_auth_statement'] = array( + '#type' => 'textfield', + '#title' => t('Authorization statement'), + '#default_value' => variable_get('commerce_amazon_lpa_capture_auth_statement', ''), + '#description' => t('This will appear at the bottom of your Amazon order notifications.'), + '#states' => array( + 'visible' => array( + ':input[name="commerce_amazon_lpa_capture_mode"]' => array('value' => AmazonLPA::CAPTURE_AUTH_CAPTURE), + ), + ), + ); + + switch (variable_get('commerce_amazon_lpa_region')) { + case 'UK': + $ipn_link = 'https://sellercentral.amazon.co.uk/gp/pyop/seller/account/settings/user-settings-view.html'; + break; + + case 'DE': + $ipn_link = 'https://sellercentral.amazon.de/gp/pyop/seller/account/settings/user-settings-view.html'; + break; + + case 'US': + default: + $ipn_link = 'https://sellercentral.amazon.com/gp/pyop/seller/account/settings/user-settings-view.html'; + break; + } + $form['commerce_amazon_lpa_payment_settings']['commerce_amazon_lpa_payment_sync'] = array( + '#type' => 'radios', + '#title' => t('Method for synchronizing payment transactions'), + '#description' => t("You may !ipn-link or use Drupal's cron to update payment transactions. Your Merchant URL for the IPN is !ipn-destination.

Note, if you use the IPN Amazon must be able to resolve your domain name.

", array( + '!ipn-destination' => '' . url('commerce-amazon-lpa/ipn', array('absolute' => TRUE)) . '', + '!ipn-link' => l(t('enable Instant Payment Notifications (IPN)'), $ipn_link), + )), + '#options' => array( + 'ipn' => t('IPN'), + 'cron' => t('Cron'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_payment_sync', 'ipn'), + ); + $order_statuses = array(); + foreach (commerce_order_statuses() as $status => $data) { + $order_statuses[$status] = $data['title']; + } + $form['commerce_amazon_lpa_payment_settings']['commerce_amazon_lpa_auth_order_status'] = array( + '#type' => 'select', + '#title' => t('Order status on successful authorization'), + '#description' => t('Orders with a successful authorization will be in this status.'), + '#options' => $order_statuses, + '#default_value' => variable_get('commerce_amazon_lpa_auth_order_status', 'pending'), + ); + $form['commerce_amazon_lpa_payment_settings']['commerce_amazon_lpa_shipped_order_status'] = array( + '#type' => 'select', + '#title' => t('Order status on successful shipping'), + '#description' => t('If using the Capture on shipment capture mode, this status will trigger payment capture.'), + '#options' => $order_statuses, + '#default_value' => variable_get('commerce_amazon_lpa_shipped_order_status', 'completed'), + ); + + $form['commerce_amazon_lpa_appearance_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Appearance settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_pay_button_size'] = array( + '#type' => 'select', + '#title' => t('Pay button size'), + '#options' => array( + AmazonLPA::BUTTON_SIZE_SMALL => t('Small'), + AmazonLPA::BUTTON_SIZE_MEDIUM => t('Medium'), + AmazonLPA::BUTTON_SIZE_LARGE => t('Large'), + AmazonLPA::BUTTON_SIZE_X_LARGE => t('Extra large'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_pay_button_size', AmazonLPA::BUTTON_SIZE_MEDIUM), + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_pay_button_style'] = array( + '#type' => 'select', + '#title' => t('Pay button style'), + '#options' => array( + AmazonLPA::BUTTON_COLOR_GOLD => t('Gold'), + AmazonLPA::BUTTON_COLOR_LIGHT_GRAY => t('Light gray'), + AmazonLPA::BUTTON_COLOR_DARK_GRAY => t('Dark gray'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_pay_button_style', AmazonLPA::BUTTON_COLOR_GOLD), + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_login_button_size'] = array( + '#type' => 'select', + '#title' => t('Login button size'), + '#options' => array( + AmazonLPA::BUTTON_SIZE_SMALL => t('Small'), + AmazonLPA::BUTTON_SIZE_MEDIUM => t('Medium'), + AmazonLPA::BUTTON_SIZE_LARGE => t('Large'), + AmazonLPA::BUTTON_SIZE_X_LARGE => t('Extra large'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_login_button_size', AmazonLPA::BUTTON_SIZE_MEDIUM), + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_login_button_style'] = array( + '#type' => 'select', + '#title' => t('Login button style'), + '#options' => array( + AmazonLPA::BUTTON_COLOR_GOLD => t('Gold'), + AmazonLPA::BUTTON_COLOR_LIGHT_GRAY => t('Light gray'), + AmazonLPA::BUTTON_COLOR_DARK_GRAY => t('Dark gray'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_login_button_style', AmazonLPA::BUTTON_COLOR_GOLD), + ); + + $addressbook_size = explode('x', variable_get('commerce_amazon_lpa_addressbook_size')) + array('0', '250'); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_addressbook_size'] = array( + '#type' => 'item', + '#title' => t('Addressbook widget default size'), + '#element_validate' => array('_commerce_amazon_lpa_widget_size_validate'), + '#subelement_prefix' => 'addressbook_', + '#field_prefix' => '
', + '#description' => t('Set the width to 0 for full width'), + '#field_suffix' => '
', + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_addressbook_size']['addressbook_x'] = array( + '#type' => 'textfield', + '#title' => t('Width'), + '#title_display' => 'invisible', + '#default_value' => $addressbook_size[0] ? $addressbook_size[0] : 0, + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' x ', + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_addressbook_size']['addressbook_y'] = array( + '#type' => 'textfield', + '#title' => t('Height'), + '#title_display' => 'invisible', + '#default_value' => $addressbook_size[1], + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' ' . t('pixels'), + ); + + $wallet_size = explode('x', variable_get('commerce_amazon_lpa_wallet_size')) + array('0', '250'); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_wallet_size'] = array( + '#type' => 'item', + '#title' => t('Wallet default size'), + '#element_validate' => array('_commerce_amazon_lpa_widget_size_validate'), + '#subelement_prefix' => 'wallet_', + '#weight' => 4.1, + '#field_prefix' => '
', + '#description' => t('Set the width to 0 for full width'), + '#field_suffix' => '
', + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_wallet_size']['wallet_x'] = array( + '#type' => 'textfield', + '#title' => t('Maximum width'), + '#title_display' => 'invisible', + '#default_value' => $wallet_size[0] ? $wallet_size[0] : 0, + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' x ', + ); + $form['commerce_amazon_lpa_appearance_settings']['commerce_amazon_lpa_wallet_size']['wallet_y'] = array( + '#type' => 'textfield', + '#title' => t('Maximum height'), + '#title_display' => 'invisible', + '#default_value' => $wallet_size[1], + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' ' . t('pixels'), + ); + + $form['commerce_amazon_lpa_advanced_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['commerce_amazon_lpa_advanced_settings']['commerce_amazon_lpa_digital_product_types'] = array( + '#type' => 'select', + '#title' => t('Digital product types'), + '#description' => t('If you have digital products that are not shippable, select them. Any order that contains only digital products will not have shipping information collected'), + '#options' => array('_none' => t('None')) + commerce_product_type_get_name(), + '#multiple' => TRUE, + '#default_value' => variable_get('commerce_amazon_lpa_digital_product_types', array('_none')), + ); + $form['commerce_amazon_lpa_advanced_settings']['commerce_amazon_lpa_erp_mode'] = array( + '#type' => 'select', + '#title' => t('ERP Mode'), + '#description' => t('Disables all automated authorization and capture functionality and administrative functionality that may trigger authorizations or captures.'), + '#options' => array( + 0 => t('Disabled'), + 1 => t('Enabled'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_erp_mode', 0), + ); + + $simulation_options = array( + '_none' => t('Disabled'), + ); + $simulation_options['Authorizations - Declined'] = array( + 'Authorizations_InvalidPaymentMethod' => t('Invalid payment method'), + 'Authorizations_AmazonRejected' => t('Amazon rejected'), + 'Authorizations_TransactionTimedOut' => t('Transaction timed out'), + ); + $simulation_options['Authorizations - Closed'] = array( + 'Authorizations_ExpiredUnused' => t('Expired, unused'), + 'Authorizations_AmazonClosed' => t('Amazon closed'), + ); + $simulation_options['Captures'] = array( + 'Captures_Pending' => t('Pending'), + 'Captures_AmazonRejected' => t('Declined, Amazon rejected'), + 'Captures_AmazonClosed' => t('Closed, Amazon closed'), + ); + $simulation_options['Order Reference - Closed'] = array( + 'OrderReference_AmazonClosed' => t('Amazon closed'), + ); + $simulation_options['Refund'] = array( + 'Refund_AmazonRejected' => t('Declined, Amazon rejected'), + ); + + $form['commerce_amazon_lpa_advanced_settings']['commerce_amazon_lpa_simulation'] = array( + '#title' => t('Sandbox simulation'), + '#type' => 'select', + '#description' => t('Simulate different scenarios for testing. See !link for more information', array( + '!link' => l(t('this documentation'), 'https://payments.amazon.co.uk/developer/documentation/lpwa/201750790'), + )), + '#options' => $simulation_options, + '#access' => AmazonLPA::is_sandbox(), + '#default_value' => $simulation_code = variable_get('commerce_amazon_lpa_simulation', '_none'), + '#tree' => TRUE, + ); + + $form['commerce_amazon_lpa_logging_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Logging settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['commerce_amazon_lpa_logging_settings']['commerce_amazon_lpa_log_handling'] = array( + '#type' => 'radios', + '#title' => t('Log debugging information'), + '#description' => t('Errors and exceptions will still be logged.'), + '#options' => array( + 0 => t('Disabled'), + 1 => t('Enabled'), + ), + '#default_value' => variable_get('commerce_amazon_lpa_log_handling', 0), + ); + $form['commerce_amazon_lpa_logging_settings']['commerce_amazon_lpa_log_link'] = array( + '#markup' => l(t('View Amazon Pay and Login with Amazon logs'), 'admin/reports/dblog'), + ); + + $form = system_settings_form($form); + return $form; +} + +/** + * Element validate function for amazon widgets. + */ +function _commerce_amazon_lpa_widget_size_validate($element, &$form_state) { + $prefix = $element['#subelement_prefix']; + if (!empty($element[$prefix . 'x']['#value']) || !empty($element[$prefix . 'y']['#value'])) { + foreach (array('x', 'y') as $dimension) { + $value = $element[$prefix . $dimension]['#value']; + if (!is_numeric($value)) { + form_error($element[$prefix . $dimension], t('Height and width values must be numeric.')); + return; + } + if (intval($value) == 0 && $dimension == 'y') { + form_error($element[$prefix . $dimension], t('Height value must be specified in the !name field.', array('!name' => $element['#title']))); + return; + } + } + form_set_value($element, intval($element[$prefix . 'x']['#value']) . 'x' . intval($element[$prefix . 'y']['#value']), $form_state); + } + else { + form_set_value($element, '', $form_state); + } +} + +/** + * Form validator. + */ +function commerce_amazon_lpa_settings_form_validate($form, &$form_state) { + // Temporarily set the API credentials. + global $conf; + $conf['commerce_amazon_lpa_merchant_id'] = $form_state['values']['commerce_amazon_lpa_merchant_id']; + $conf['commerce_amazon_lpa_access_key'] = $form_state['values']['commerce_amazon_lpa_access_key']; + + // Secret key is a password field and not required, meaning it could be empty. + if (!empty($form_state['values']['commerce_amazon_lpa_secret_key'])) { + $conf['commerce_amazon_lpa_secret_key'] = $form_state['values']['commerce_amazon_lpa_secret_key']; + } + else { + // So we need to retain its value here. + $form_state['values']['commerce_amazon_lpa_secret_key'] = $conf['commerce_amazon_lpa_secret_key']; + } + + $conf['commerce_amazon_lpa_region'] = $form_state['values']['commerce_amazon_lpa_region']; + + try { + // Validate API credentials against order reference S00-0000000-0000000. + $api = AmazonLPA::instance(); + + $params = array( + 'amazon_order_reference_id' => 'S00-0000000-0000000', + ); + + $response = $api->getClient()->getOrderReferenceDetails($params)->toArray(); + switch ($response['Error']['Code']) { + case 'InvalidOrderReferenceId': + // This is what we want, do not react. + break; + + case 'InvalidAccessKeyId': + form_set_error('commerce_amazon_lpa_access_key', $response['Error']['Message']); + break; + + case 'SignatureDoesNotMatch': + form_set_error('commerce_amazon_lpa_secret_key', $response['Error']['Message']); + break; + + case 'InvalidParameterValue': + form_set_error('commerce_amazon_lpa_merchant_id', $response['Error']['Message']); + break; + + default: + form_set_error('commerce_amazon_lpa_merchant_id', $response['Error']['Message']); + } + } + catch (Exception $e) { + form_set_error('commerce_amazon_lpa_merchant_id', t('Invalid API credentials')); + } + + // Force the store to use a proper default currency. + // @see commerce_order_calculate_total() + $region_currency = AmazonLPA::get_region_currency_code(variable_get('commerce_amazon_lpa_region', 'US')); + $default_currency = commerce_default_currency(); + if ($region_currency != $default_currency) { + form_set_error('commerce_amazon_lpa_region', t('Your current default currency is @default, however your Amazon seller region requires @required.', array( + '@default' => $default_currency, + '@required' => $region_currency, + ))); + } + + // If using hidden button mode and Amazon only checkout, display warning. + if ($form_state['values']['commerce_amazon_lpa_checkout_strategy'] == AmazonLPA::STRATEGY_AMAZON + && $form_state['values']['commerce_amazon_lpa_hidden_mode'] != '') { + drupal_set_message(t('You have set the checkout strategy to Amazon only, but have Hidden Button Mode enabled. Customers might not be able to complete checkout.'), 'warning'); + } +} + +/** + * Form callback: allows the user to authorize a transaction on an order. + */ +function commerce_amazon_lpa_authorize_form($form, &$form_state, $order, $transaction = NULL) { + $balance = commerce_payment_order_balance($order); + + if (!$transaction) { + $transaction = commerce_payment_transaction_new('commerce_amazon_login_and_pay', $order->order_id); + $transaction->amount = $balance['amount']; + $transaction->currency_code = $balance['currency_code']; + } + + $form_state['order'] = $order; + $form_state['transaction'] = $transaction; + + // Load and store the payment method instance for this transaction. + $payment_method = commerce_payment_method_instance_load($transaction->instance_id); + $form_state['payment_method'] = $payment_method; + + if ($balance['amount'] > 0 && $balance['amount'] < $transaction->amount) { + $default_amount = $balance['amount']; + } + else { + $default_amount = $transaction->amount; + } + + // Convert the price amount to a user friendly decimal value. + $default_amount = number_format(commerce_currency_amount_to_decimal($default_amount, $transaction->currency_code), 2, '.', ''); + + $description = implode('
', array( + t('Authorization: @amount', array('@amount' => commerce_currency_format($transaction->amount, $transaction->currency_code))), + t('Order balance: @balance', array('@balance' => commerce_currency_format($balance['amount'], $balance['currency_code']))), + t('You may capture up to the remaining order balance.'), + )); + + $form['amount'] = array( + '#type' => 'textfield', + '#title' => t('Capture amount'), + '#description' => $description, + '#default_value' => $default_amount, + '#field_suffix' => check_plain($transaction->currency_code), + '#size' => 16, + ); + + $form = confirm_form($form, + t('What amount do you want to authorize?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Authorize'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Validate handler: ensure a valid amount is given. + */ +function commerce_amazon_lpa_authorize_form_validate($form, &$form_state) { + $transaction = $form_state['transaction']; + $amount = $form_state['values']['amount']; + + $balance = commerce_payment_order_balance($form_state['order']); + + if ($balance['amount'] > 0 && $balance['amount'] < $transaction->amount) { + $allowed_amount = $balance['amount']; + } + else { + $allowed_amount = $transaction->amount; + } + + // Ensure a positive numeric amount has been entered for capture. + if (!is_numeric($amount) || $amount <= 0) { + form_set_error('amount', t('You must specify a positive numeric amount to authorize.')); + } + + if ($allowed_amount < $amount) { + form_set_error('amount', t('That amount exceed the maximum allowed authorize amount.')); + } +} + +/** + * Submit handler: process a prior authorization capture. + */ +function commerce_amazon_lpa_authorize_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $order = $form_state['order']; + $api = AmazonLPA::instance(); + + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + + try { + $data = $api->authorize($order_wrapper, AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_AUTH_CAPTURE ? TRUE : FALSE, array( + 'amount' => commerce_currency_decimal_to_amount($form_state['values']['amount'], $transaction->currency_code), + 'currency_code' => $transaction->currency_code, + )); + commerce_amazon_lpa_add_debug_log(t('Terminal authorize: !debug', array('!debug' => '
' . print_r($data, TRUE) . '
'))); + + $api->processAuthorizeTransaction($transaction, $data); + + $authorization_details = $api->getAuthorizationDetails($data['AmazonAuthorizationId']); + if (isset($authorization_details['AuthorizationBillingAddress'])) { + $billing_address = $authorization_details['AuthorizationBillingAddress']; + try { + commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'billing', $billing_address); + commerce_order_save($order); + } + catch (Exception $e) { + watchdog('commerce_amazon_lpa', 'Error processing order billing information for Amazon: !error', array('!error' => '
' . print_r($data, TRUE) . '
'), WATCHDOG_ERROR); + } + } + + // Create a revision, since we have changed the remote ID. + $transaction->revision = TRUE; + $transaction->log = t('Authorization was created'); + + if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS) { + drupal_set_message(t('The authorization has been completed successfully.')); + } + elseif ($transaction->status == COMMERCE_PAYMENT_STATUS_PENDING) { + drupal_set_message(t('The authorisations has been submitted successfully and may take a moment to process.')); + } + else { + $status = $data['AuthorizationStatus']; + switch ($status['ReasonCode']) { + case 'AmazonRejected': + $message = t('Your payment could not be processed. Please try to place the order again using another payment method.'); + commerce_order_status_update($order, 'cart', FALSE, TRUE, t('Order canceled because it was rejected by Amazon')); + break; + + case 'TransactionTimedOut': + $message = t('Your payment could not be processed. Please try to place the order again using another payment method.'); + commerce_order_status_update($order, 'cart', FALSE, TRUE, t('Order canceled because the transaction timed out.')); + break; + + case 'ProcessingFailure': + $message = t('Your order could not be processed due to a system error. Please try to place the order again.'); + commerce_order_status_update($order, 'cart', FALSE, TRUE, t('Order canceled because the transaction failed to process.')); + break; + + case 'InvalidPaymentMethod': + default: + $message = t('Your payment could not be processed, please follow the instructions in the payment method box..'); + break; + } + drupal_set_message($message, 'error'); + } + } + catch (Exception $e) { + drupal_set_message($e->getMessage(), 'error'); + } + + // Redirect back to the current order payment page. + $form_state['redirect'] = 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment'; +} + +/** + * Form callback: allows the user to capture a prior authorization. + */ +function commerce_amazon_lpa_capture_form($form, &$form_state, $order, $transaction) { + $form_state['order'] = $order; + $form_state['transaction'] = $transaction; + + // Load and store the payment method instance for this transaction. + $payment_method = commerce_payment_method_instance_load($transaction->instance_id); + $form_state['payment_method'] = $payment_method; + + $balance = commerce_payment_order_balance($order); + + if ($balance['amount'] > 0 && $balance['amount'] < $transaction->amount) { + $default_amount = $balance['amount']; + } + else { + $default_amount = $transaction->amount; + } + + // Convert the price amount to a user friendly decimal value. + $default_amount = number_format(commerce_currency_amount_to_decimal($default_amount, $transaction->currency_code), 2, '.', ''); + + $description = implode('
', array( + t('Authorization: @amount', array('@amount' => commerce_currency_format($transaction->amount, $transaction->currency_code))), + t('Order balance: @balance', array('@balance' => commerce_currency_format($balance['amount'], $balance['currency_code']))), + t('You may capture up to the remaining order balance.'), + )); + + $form['amount'] = array( + '#type' => 'textfield', + '#title' => t('Capture amount'), + '#description' => $description, + '#default_value' => $default_amount, + '#field_suffix' => check_plain($transaction->currency_code), + '#size' => 16, + ); + + $form = confirm_form($form, + t('What amount do you want to capture?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Capture'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Validate handler: ensure a valid amount is given. + */ +function commerce_amazon_lpa_capture_form_validate($form, &$form_state) { + $transaction = $form_state['transaction']; + $amount = $form_state['values']['amount']; + + $balance = commerce_payment_order_balance($form_state['order']); + + if ($balance['amount'] > 0 && $balance['amount'] < $transaction->amount) { + $allowed_amount = $balance['amount']; + } + else { + $allowed_amount = $transaction->amount; + } + + // Ensure a positive numeric amount has been entered for capture. + if (!is_numeric($amount) || $amount <= 0) { + form_set_error('amount', + t('You must specify a positive numeric amount to capture.')); + } + + if ($allowed_amount < $amount) { + form_set_error('amount', + t('That amount exceed the maximum allowed capture amount.')); + } + + // If the authorization has expired, display an error message and redirect. + if (REQUEST_TIME - $transaction->created > 86400 * 30) { + drupal_set_message(t('This authorization has passed its 30 day limit and cannot be captured.'), 'error'); + drupal_goto('admin/commerce/orders/' . $form_state['order']->order_id . '/payment'); + } +} + +/** + * Submit handler: process a prior authorization capture. + */ +function commerce_amazon_lpa_capture_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $order = $form_state['order']; + $api = AmazonLPA::instance(); + + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + + try { + $data = $api->capture($order_wrapper, $transaction->remote_id, array( + 'amount' => commerce_currency_decimal_to_amount($form_state['values']['amount'], $transaction->currency_code), + 'currency_code' => $transaction->currency_code, + )); + + commerce_amazon_lpa_add_debug_log(t('Terminal capture: !debug', array('!debug' => '
' . print_r($data, TRUE) . '
'))); + + // Create a revision, since we have changed the remote ID. + $transaction->revision = TRUE; + $transaction->log = t('Authorization was captured'); + + // Create a transaction based on the original response we received. + $api->processCaptureTransaction($transaction, $data); + + if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS) { + drupal_set_message(t('The capture has been completed successfully.')); + } + elseif ($transaction->status == COMMERCE_PAYMENT_STATUS_PENDING) { + drupal_set_message(t('The capture has been submitted successfully and may take a moment to process.')); + } + else { + drupal_set_message(t('The capture was not successful.'), 'error'); + } + } + catch (Exception $e) { + drupal_set_message($e->getMessage(), 'error'); + + // Something went wrong, try to refresh the authorization. + $data = $api->getAuthorizationDetails($transaction->remote_id); + $api->processAuthorizeTransaction($transaction, $data); + } + + // Redirect back to the current order payment page. + $form_state['redirect'] = 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment'; +} + +/** + * Form callback: allows the user to issue a credit on a prior transaction. + */ +function commerce_amazon_lpa_refund_form($form, &$form_state, $order, $transaction) { + $form_state['order'] = $order; + $form_state['transaction'] = $transaction; + + // Load and store the payment method instance for this transaction. + $payment_method = commerce_payment_method_instance_load($transaction->instance_id); + $form_state['payment_method'] = $payment_method; + $balance = commerce_payment_order_balance($order); + + $default_amount = number_format(commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code), 2, '.', ''); + $max_refund = ($transaction->amount > 50000) ? $transaction->amount + 7500 : + $transaction->amount * 1.15; + + $description = implode('
', array( + t('Max allowed refund: @amount', array('@amount' => commerce_currency_format($max_refund, $transaction->currency_code))), + t('Order balance: @balance', array('@balance' => commerce_currency_format($balance['amount'], $balance['currency_code']))), + t('Original authorized amount: @original', array('@original' => commerce_currency_format($transaction->amount, $transaction->currency_code))), + )); + + $form['amount'] = array( + '#type' => 'textfield', + '#title' => t('Refund amount'), + '#description' => $description, + '#default_value' => $default_amount, + '#field_suffix' => check_plain($transaction->currency_code), + '#size' => 16, + ); + + $form['note'] = array( + '#type' => 'textfield', + '#title' => t('Note'), + '#description' => t('You can enter a note that will accompany the refund. Notes will be sent in an email to the customer.'), + '#default_value' => '', + '#size' => 60, + '#maxlength' => 255, + ); + + $form = confirm_form($form, + t('What amount do you want to refund?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Refund'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Validate handler: check the credit amount before attempting a refund request. + */ +function commerce_amazon_lpa_refund_form_validate($form, &$form_state) { + $transaction = $form_state['transaction']; + $order = $form_state['order']; + $amount = $form_state['values']['amount']; + $balance = commerce_payment_order_balance($order); + + // Ensure a positive numeric amount has been entered for refund. + if (!is_numeric($amount) || $amount <= 0) { + form_set_error('amount', t('You must specify a positive numeric amount to refund.')); + } + + // Since Amazon supports up to 10 refunds per capture, we need to ensure that + // the refund doesn't exceed their refund restrictions. For an individual + // capture, we can only refund up to 15% or $75 (which ever is smaller) of the + // original captured amount. + $transaction_amount = commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code); + $order_balance = commerce_currency_amount_to_decimal($balance['amount'], $transaction->currency_code); + $max_refund = ($transaction->amount > 50000) ? $transaction_amount + 7500 : + $transaction->amount * 1.15; + + // Ensure the amount is less than or equal to the remaining captured amount. + // We take the maximum possible refund, subtract the balance which would + // include any additional refunds that have already been processed. + if (($max_refund - $order_balance) < commerce_currency_decimal_to_amount($amount, $transaction->currency_code)) { + form_set_error('amount', t('The maximum refund allowed is @max_refund.', array('@max_refund' => commerce_currency_format($max_refund, $transaction->currency_code)))); + } + + // If the transaction is older than 60 days, display an error message and + // redirect. + if (time() - $transaction->created > 86400 * 60) { + drupal_set_message(t('This transaction has passed its 60 day limit for issuing refunds.'), 'error'); + drupal_goto('admin/commerce/orders/' . $form_state['order']->order_id . '/payment'); + } +} + +/** + * Submit handler: process a refund. + */ +function commerce_amazon_lpa_refund_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $amount = $form_state['values']['amount']; + $note = $form_state['values']['note']; + $order = $form_state['order']; + $payment_method = $form_state['payment_method']; + + // Set up the refund. + $api = AmazonLPA::instance(); + $data = $api->refund(entity_metadata_wrapper('commerce_order', $order), $transaction->remote_id, $amount, $note); + + $credit_transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id); + $credit_transaction->instance_id = $payment_method['instance_id']; + $transaction->payload[REQUEST_TIME . '-refund'] = $data; + + $api->processRefundTransaction($credit_transaction, $data); + + if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS) { + drupal_set_message(t('Refund for @amount completed successfully.', array( + '@amount' => commerce_currency_format($amount, $transaction->currency_code, NULL, FALSE), + ))); + } + elseif ($transaction->status == COMMERCE_PAYMENT_STATUS_PENDING) { + drupal_set_message(t('Refund for @amount is in progress.', array( + '@amount' => commerce_currency_format($amount, $transaction->currency_code, NULL, FALSE), + ))); + } + else { + // Display a failure message and response reason from Amazon. + drupal_set_message(t('Refund failed: @reason', array('@reason' => '
' . print_r($data, TRUE) . '
'), 'error')); + } + + $form_state['redirect'] = 'admin/commerce/orders/' . $order->order_id . '/payment'; +} + +/** + * Form callback: allows the user to issue a credit on a prior transaction. + */ +function commerce_amazon_lpa_close_form($form, &$form_state, $order, $transaction) { + $form_state['order'] = $order; + $form_state['transaction'] = $transaction; + + // Load and store the payment method instance for this transaction. + $payment_method = commerce_payment_method_instance_load($transaction->instance_id); + $form_state['payment_method'] = $payment_method; + + $default_amount = number_format(commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code), 2, '.', ''); + + $form['amount'] = array( + '#type' => 'textfield', + '#title' => t('Authorized amount'), + '#description' => t('The authorization for this amount will be released. You will not be able to process any further transactions with this authorization once it is closed.'), + '#default_value' => $default_amount, + '#field_suffix' => check_plain($transaction->currency_code), + '#disabled' => TRUE, + '#size' => 16, + ); + + $form['reason'] = array( + '#type' => 'textfield', + '#title' => t('Reason'), + '#description' => t('You can enter a reason for closing this transaction. Reasons will be sent in an email to the customer.'), + '#default_value' => '', + '#size' => 60, + '#maxlength' => 255, + ); + + $form = confirm_form($form, + t('Are you sure you want to close this authorization?'), + 'admin/commerce/orders/' . $order->order_id . '/payment', + '', + t('Close authorization'), + t('Cancel'), + 'confirm' + ); + + return $form; +} + +/** + * Validate handler: check the credit amount before attempting a refund request. + */ +function commerce_amazon_lpa_close_form_validate($form, &$form_state) { +} + +/** + * Submit handler: process a refund. + */ +function commerce_amazon_lpa_close_form_submit($form, &$form_state) { + $transaction = $form_state['transaction']; + $order = $form_state['order']; + $reason = $form_state['values']['reason']; + + $api = AmazonLPA::instance(); + $response = $api->closeAuthorization(entity_metadata_wrapper('commerce_order', $order), $transaction->remote_id, $reason); + + // Error handling. + if ($response) { + drupal_set_message(t('Authorization close request sent.')); + } + else { + // Display a failure message and response reason from Amazon. + drupal_set_message(t('Error: @reason', array('@reason' => '
' . print_r($response, TRUE) . '
'), 'error')); + } + + // Add this response to the payment transaction payload. + $transaction->payload[REQUEST_TIME . '-close'] = $response; + commerce_payment_transaction_save($transaction); + + $form_state['redirect'] = 'admin/commerce/orders/' . $order->order_id . '/payment'; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.checkout_pane.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.checkout_pane.inc new file mode 100644 index 00000000..65e6dd34 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.checkout_pane.inc @@ -0,0 +1,213 @@ +data['commerce_amazon_lpa'])) { + return $pane_form; + } + + $existing_reference_id = AmazonLPA::instance()->getOrderReferenceId(entity_metadata_wrapper('commerce_order', $order)); + $pane_form['reference_id'] = array( + '#type' => 'hidden', + '#default_value' => $existing_reference_id, + '#access' => empty($existing_reference_id), + ); + $pane_form['#attached']['js'][] = array( + 'data' => array( + 'AmazonLPA' => array( + 'orderReferenceId' => $existing_reference_id, + ), + ), + 'type' => 'setting', + ); + + return $pane_form; +} + +/** + * Checkout pane callback: builds a shipping quote selection form. + */ +function commerce_amazon_lpa_contract_id_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) { + if (empty($order->data['commerce_amazon_lpa'])) { + return TRUE; + } + + $pane_values = $form_state['values']['commerce_amazon_lpa_contract_id']; + + if (!empty($pane_values['reference_id'])) { + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + // Set the order. + $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD} = $pane_values['reference_id']; + + try { + $order->data['commerce_amazon_lpa']['order_reference'] = AmazonLPA::instance()->getOrderRef($order_wrapper); + } + catch (Exception $e) { + watchdog('commerce_amazon_lpa', 'Error processing order for Amazon: !error', array('!error' => '
' . print_r($e, TRUE) . '
'), WATCHDOG_ERROR); + form_set_error('', t('There was a problem with your checkout process. Please contact the store for further information.')); + return FALSE; + } + + $order_wrapper->save(); + + $form_state['order'] = $order_wrapper->value(); + return TRUE; + } + + // If the order is shippable, we need to have a valid contract ID from the + // addressbook widget. + if (commerce_amazon_lpa_order_is_shippable($order)) { + form_set_error('', t('There was a problem with your checkout process. Please contact the store for further information.')); + return FALSE; + } + + return TRUE; +} + +/** + * Checkout pane callback: builds a shipping quote selection form. + */ +function commerce_amazon_lpa_switch_checkout_pane_checkout_form($form, &$form_state, $checkout_pane, $order) { + $pane_form = array(); + + if (!empty($order->data['commerce_amazon_lpa'])) { + return $pane_form; + } + + $pane_form['switch_button'] = array( + '#markup' => theme('commerce_amazon_payment_button', array( + 'order_id' => $form_state['order']->order_id, + 'html_id' => 'amazon_lpa_cart_pay', + )), + ); + + return $pane_form; +} + +/** + * Checkout pane callback: builds a shipping quote selection form. + */ +function commerce_amazon_lpa_addressbook_pane_checkout_form($form, &$form_state, $checkout_pane, $order) { + $pane_form = array(); + + if (!empty($order->data['commerce_amazon_lpa']) && commerce_amazon_lpa_order_is_shippable($order)) { + // Build the checkout pane form. + $pane_form['amazon_addressbook_widget'] = array( + '#markup' => theme('commerce_amazon_addressbook_widget', array( + 'html_id' => 'checkout_amazon_addressbook', + )), + ); + } + + return $pane_form; +} + +/** + * Checkout pane callback: builds review pane. + */ +function commerce_amazon_lpa_addressbook_pane_review($form, &$form_state, $checkout_pane, $order) { + if (!empty($order->data['commerce_amazon_lpa']) && commerce_amazon_lpa_order_is_shippable($order)) { + return theme('commerce_amazon_addressbook_widget', array( + 'html_id' => 'customer_profile_shipping', + 'display_mode' => 'read', + )); + } + + return NULL; +} + +/** + * Checkout pane callback: validates a customer profile edit form. + */ +function commerce_amazon_lpa_addressbook_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) { + if (empty($order->data['commerce_amazon_lpa']) || !commerce_amazon_lpa_order_is_shippable($order)) { + return TRUE; + } + + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $data = AmazonLPA::instance()->getOrderRef($order_wrapper); + // Strip off GetOrderReferenceDetailsResult. + if (isset($data['Destination']['PhysicalDestination'])) { + // Store the Amazon shipping address in form state for submit handler. + $form_state['amazon_shipping_address'] = $data['Destination']['PhysicalDestination']; + return TRUE; + } + + // There was no physical destination set on the order. + form_set_error(NULL, t('You must provide a shipping address')); + return FALSE; +} + +/** + * Checkout pane callback: submit the Amazon address checkout pane. + * + * Mimic commerce_customer_profile_pane_checkout_form_submit() and build the + * proper profile so we can calculate shipping properly. + * + * @see commerce_customer_profile_pane_checkout_form_submit() + */ +function commerce_amazon_lpa_addressbook_pane_checkout_form_submit(&$form, &$form_state, $checkout_pane, $order) { + // Save the Amazon delivery address to shipping customer profile address. + if (!empty($order->data['commerce_amazon_lpa']) && commerce_amazon_lpa_order_is_shippable($order)) { + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $amazon_shipping_address = $form_state['amazon_shipping_address']; + + $pane_id = 'customer_profile_shipping'; + + // If the shipping profile pane is present, inject our values. + if (isset($form[$pane_id]['commerce_customer_address'])) { + // Don't require address form elements. + unset($form[$pane_id]['commerce_customer_address']['#required']); + $customer_profile = $form[$pane_id]['customer_profile']['#value']; + + $form_state['values'][$pane_id]['commerce_customer_address'][LANGUAGE_NONE][0] = array( + 'country' => $amazon_shipping_address['CountryCode'], + 'name_line' => isset($amazon_shipping_address['Name']) ? $amazon_shipping_address['Name'] : '', + 'postal_code' => isset($amazon_shipping_address['PostalCode']) ? $amazon_shipping_address['PostalCode'] : '', + 'locality' => isset($amazon_shipping_address['City']) ? $amazon_shipping_address['City'] : '', + 'administrative_area' => isset($amazon_shipping_address['StateOrRegion']) ? $amazon_shipping_address['StateOrRegion'] : '', + 'data' => serialize($amazon_shipping_address), + ); + commerce_unrequire_form_elements($form[$pane_id]); + field_attach_submit('commerce_customer_profile', $customer_profile, $form[$pane_id], $form_state); + $customer_profile->commerce_customer_address = $form_state['values'][$pane_id]['commerce_customer_address']; + // Save the customer profile. + commerce_customer_profile_save($customer_profile); + + // If Addressbook is enabled, make sure this is the default. + // This really only matters on instances like CK2 that display the + // information directly to the user and they may see the original + // partial profile before the order is confirmed. + if (module_exists('commerce_addressbook')) { + commerce_addressbook_set_default_profile($customer_profile); + } + } + // Write the profile ourself. + else { + $customer_profile = commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'shipping', $amazon_shipping_address); + } + + if ($field_name = variable_get('commerce_' . $pane_id . '_field', '')) { + $order_wrapper->{$field_name} = $customer_profile; + } + else { + // Make the association in the order's data array if no field was found. + $order->data['profiles'][$pane_id] = $customer_profile->profile_id; + } + + // Save the order with address data. + commerce_order_save($order); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.pages.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.pages.inc new file mode 100644 index 00000000..eb7a6b6d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.pages.inc @@ -0,0 +1,296 @@ + array( + 'library' => array(array('commerce_amazon_lpa', 'amazon_widgets')), + 'js' => array( + drupal_get_path('module', 'commerce_amazon_lpa') . '/js/commerce-amazon-lpa-redirect.js', + ), + ), + ); + } + + $api = AmazonLPA::instance(); + $order = commerce_cart_order_load($GLOBALS['user']->uid); + // If the user does not have access to checkout the order, return a 404. We + // could return a 403, but then the user would know they've identified a + // potentially valid checkout URL. + $user_info = $api->getUserInfo(); + if (empty($order) || !commerce_checkout_access($order) || empty($user_info)) { + drupal_goto('cart'); + } + + // Ensure user is logged in. If not, do so on their behalf. + if (AmazonLPA::get_operation_mode() == AmazonLPA::OPERATION_MODE_LOGIN_AND_PAY && !user_is_logged_in()) { + commerce_amazon_lpa_external_login(); + } + + // If an order is being saved without a contract ID, assume it has just left + // the cart and beginning life in checkout. + // Set the account info for the order. + $order->data['commerce_amazon_lpa']['user_info'] = $user_info; + if (empty($order->mail)) { + $order->mail = $user_info['email']; + } + + // Check if the order is re-entering Amazon checkout, and that is has + // a valid state. + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + if ($api->getOrderReferenceId($order_wrapper)) { + $order_reference = $api->getOrderRef($order_wrapper); + $amazon_order_state = $order_reference['OrderReferenceStatus']['State']; + if ($amazon_order_state != 'Draft') { + $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD} = ''; + } + } + + if (commerce_order_save($order)) { + drupal_goto('checkout'); + } + else { + drupal_set_message(t('There was an error initiating the Amazon checkout, please try again'), 'error'); + drupal_goto('cart'); + } +} + +/** + * Page callback for handling IPN notifications. + * + * @throws \Exception + * Amazon Exception. + */ +function commerce_amazon_lpa_process_ipn() { + $api = AmazonLPA::instance(); + + $headers = array(); + + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + + try { + $ipn_handler = new AmazonPayIpnHandler($headers, file_get_contents('php://input')); + } + catch (\Exception $e) { + return MENU_ACCESS_DENIED; + } + $ipn_message = $ipn_handler->toArray(); + + commerce_amazon_lpa_add_debug_log(t('Amazon IPN debug: !debug', array('!debug' => '
' . print_r($ipn_message, TRUE) . '
'))); + + // Decide what to do based on the notification type. Amazon defines several + // types of notifications. See link below. However, the notification types + // they define don't appear in the actual responses. e.g. + // AuthorizationNotification is defined in the docs, but PaymentAuthorize is + // what actually gets sent with the IPN notification. + // @see https://payments.amazon.com/documentation/apireference/201757720 + switch ($ipn_message['NotificationType']) { + case 'OrderReferenceNotification': + $data = $ipn_message['OrderReference']; + $order = commerce_amazon_lpa_order_from_amazon_reference_id($data['AmazonOrderReferenceId']); + + if (empty($order)) { + watchdog('commerce_amazon_lpa', 'Unable to find matching order for reference @id.', array('@id' => $data['AmazonOrderReferenceId']), WATCHDOG_ERROR); + break; + } + + $order->data['commerce_amazon_lpa']['order_reference'] = $data; + + switch ($data['OrderReferenceStatus']['State']) { + case 'Open': + if (AmazonLPA::AUTH_SYNC != AmazonLPA::get_authorization_mode()) { + $balance = commerce_payment_order_balance($order); + + if ($balance['amount'] <= 0) { + break; + } + + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $data = $api->authorize($order_wrapper, AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_AUTH_CAPTURE ? TRUE : FALSE, array( + 'amount' => $balance['amount'], + 'currency_code' => $balance['currency_code'], + )); + commerce_amazon_lpa_add_debug_log(t('IPN authorize: !debug', array('!debug' => '
' . print_r($data, TRUE) . '
'))); + + $transaction = commerce_payment_transaction_new('commerce_amazon_login_and_pay', $order->order_id); + $transaction->amount = $balance['amount']; + $transaction->currency_code = $balance['currency_code']; + $api->processAuthorizeTransaction($transaction, $data); + + $authorization_details = $api->getAuthorizationDetails($data['AmazonAuthorizationId']); + if (isset($authorization_details['AuthorizationBillingAddress'])) { + $billing_address = $authorization_details['AuthorizationBillingAddress']; + try { + commerce_amazon_lpa_amazon_address_to_customer_profile($order, 'billing', $billing_address); + commerce_order_save($order); + } + catch (Exception $e) { + watchdog('commerce_amazon_lpa', 'Error processing order billing information for Amazon: !error', array('!error' => '
' . print_r($data, TRUE) . '
'), WATCHDOG_ERROR); + } + } + } + break; + + case 'Canceled': + if ('canceled' != $order->status) { + commerce_order_status_update($order, 'cart', TRUE, TRUE, t('Order reference canceled by Amazon. Reason code: @code', array( + '@code' => $data['OrderReferenceStatus']['ReasonCode'], + ))); + } + break; + + case 'Suspended': + commerce_order_status_update($order, $order->status, TRUE, TRUE, t('Suspended by Amazon. Reason code: @code', array( + '@code' => $data['OrderReferenceStatus']['ReasonCode'], + ))); + break; + + case 'Closed': + if (AmazonLPA::AUTH_SYNC != AmazonLPA::get_authorization_mode()) { + $reason_code = $data['OrderReferenceStatus']['ReasonCode']; + if ($reason_code == 'Expired' || $reason_code == 'AmazonClosed') { + $order_status = 'cart'; + } + else { + $order_status = 'completed'; + } + + if ($order_status != $order->status) { + commerce_order_status_update($order, $order_status, TRUE, TRUE, t('Closed notification by Amazon. Reason code: @code', array( + '@code' => $data['OrderReferenceStatus']['ReasonCode'], + ))); + } + } + break; + } + + commerce_order_save($order); + break; + + case 'PaymentAuthorize': + case 'AuthorizationNotification': + $data = $ipn_message['AuthorizationDetails']; + + $transaction = commerce_amazon_lpa_remote_payment_transaction_load($data['AmazonAuthorizationId']); + // If we have no transaction, we must look up the matching authorization + // remote ID. This lets us know the authorization has been closed because + // it has met its maximum amount of captures. + if (!$transaction && !empty($data['IdList']['Id'])) { + $capture_id = $data['IdList']['Id']; + $capture_transaction = commerce_amazon_lpa_remote_payment_transaction_load($capture_id); + if ($capture_transaction) { + commerce_amazon_lpa_transaction_message_update_data($capture_transaction, 'Authorization', $data['AuthorizationStatus']); + $capture_transaction->payload[REQUEST_TIME . '-authorization'] = $data; + commerce_payment_transaction_save($capture_transaction); + } + } + elseif ($transaction) { + $data = $api->getAuthorizationDetails($transaction->remote_id); + $api->processAuthorizeTransaction($transaction, $data); + + // If working in manual or non-sync, check if we should change the + // order status to the Authorized status configured. + if (AmazonLPA::get_authorization_mode() != AmazonLPA::AUTH_SYNC) { + $order = commerce_order_load($transaction->order_id); + if (!empty($order->data['commerce_amazon_lpa_set_as_auth'])) { + $authorized_order_status = variable_get('commerce_amazon_lpa_auth_order_status', 'pending'); + unset($order->data['commerce_amazon_lpa_set_as_auth']); + commerce_order_status_update($order, $authorized_order_status, FALSE, TRUE, t('Payment with Amazon was authorized, moving to proper status.')); + } + } + } + else { + watchdog('commerce_amazon_lpa', 'Unable to find matching payment transaction authorization for @id', array('@id' => $data['AmazonAuthorizationId']), WATCHDOG_ERROR); + } + break; + + case 'PaymentCapture': + case 'CaptureNotification': + $data = $ipn_message['CaptureDetails']; + + // Try to load the transaction first, the ID will have been updated if it + // was captured / triggered in the UI. + $transaction = commerce_amazon_lpa_remote_payment_transaction_load($data['AmazonCaptureId']); + + // If we have no transaction, we must look up the matching authorization + // remote ID. + if (!$transaction) { + $id_components = explode('-', $data['AmazonCaptureId']); + $id_components[3] = str_replace('C', 'A', $id_components[3]); + $authorization_id = implode('-', $id_components); + $transaction = commerce_amazon_lpa_remote_payment_transaction_load($authorization_id); + } + + if ($transaction) { + $api->processCaptureTransaction($transaction, $data); + } + else { + watchdog('commerce_amazon_lpa', 'Unable to find matching payment transaction capture for @id', array('@id' => $data['AmazonCaptureId']), WATCHDOG_ERROR); + } + + break; + + case 'PaymentRefund': + case 'RefundNotification': + $data = $ipn_message['RefundDetails']; + $transaction = commerce_amazon_lpa_remote_payment_transaction_load($data['AmazonRefundId']); + if ($transaction) { + $api->processRefundTransaction($transaction, $data); + } + else { + watchdog('commerce_amazon_lpa', 'Unable to find matching payment transaction refund for @id', array('@id' => $data['AmazonRefundId']), WATCHDOG_ERROR); + } + break; + + default: + commerce_amazon_lpa_add_debug_log('Amazon IPN debug: IPN case did not match for @type', array( + '@type' => $ipn_message['NotificationType'], + )); + break; + } + +} + +/** + * Page callback for Amazon Login. + * + * Verifies the access token and logs the user in as an external user. + */ +function commerce_amazon_lpa_login_callback() { + if (variable_get('commerce_amazon_lpa_popup', 'popup') != 'popup' && !isset($_COOKIE['amazon_Login_accessToken'])) { + return array( + '#attached' => array( + 'library' => array(array('commerce_amazon_lpa', 'amazon_widgets')), + 'js' => array( + drupal_get_path('module', 'commerce_amazon_lpa') . '/js/commerce-amazon-lpa-redirect.js', + ), + ), + ); + } + else { + commerce_amazon_lpa_external_login(); + $cookie = $_COOKIE['amazon_Login_accessToken']; + setrawcookie('amazon_Login_accessToken', $cookie, 0, '/', '', TRUE); + drupal_goto('user'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.payment_method.inc b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.payment_method.inc new file mode 100644 index 00000000..8b20d054 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/includes/commerce_amazon_lpa.payment_method.inc @@ -0,0 +1,202 @@ + t('Complete the payment using Amazon Pay with the address and wallet selected in previous steps.'), + ); + + $wrapper = entity_metadata_wrapper('commerce_order', $order); + $form['amazon_order_reference_id'] = array( + '#type' => 'hidden', + '#default_value' => $wrapper->{AmazonLPA::REFERENCE_ID_FIELD}->value(), + ); + $form['wallet_widget'] = array( + '#theme' => 'commerce_amazon_wallet_widget', + '#display_mode' => path_is_admin(current_path()) ? 'read' : '', + '#order_reference_id' => $wrapper->{AmazonLPA::REFERENCE_ID_FIELD}->value(), + ); + return $form; +} + +/** + * Payment method callback: submit form validation. + * + * @see commerce_checkout_by_amazon_submit_form_validate() + */ +function commerce_amazon_login_and_pay_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) { + if (!isset($order->data['commerce_amazon_lpa'])) { + form_set_error('commerce_payment', t('Please select a payment method to continue.')); + drupal_set_message(t('Some information for proceeding with the payment might be missing, please review previous steps in order to complete at least address and wallet information.'), 'error'); + return FALSE; + } + + // There should be no more validation error before purchasing with Amazon. + if (form_get_errors()) { + return FALSE; + } +} + +/** + * Payment method callback: submit form submission. + * + * If an order is digital, the order reference will have been generated in the + * submit form, we save it here. + */ +function commerce_amazon_login_and_pay_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) { + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + + if (!commerce_amazon_lpa_order_is_shippable($order)) { + try { + $order_wrapper->{AmazonLPA::REFERENCE_ID_FIELD} = $pane_values['amazon_order_reference_id']; + $order->data['commerce_amazon_lpa']['order_reference'] = AmazonLPA::instance()->getOrderRef($order_wrapper); + $order_wrapper->save(); + } + catch (Exception $e) { + drupal_set_message(t('There was an error setting the Amazon order reference'), 'error'); + return FALSE; + } + } +} + +/** + * Payment method callback; generation callback for the payment redirect form. + * + * This actually processes the order and transaction with Amazon Payments and + * then moves forward with the checkout. + */ +function commerce_amazon_login_and_pay_redirect_form($form, &$form_state, $order, $payment_method) { + // We're in ERP mode and aren't doing any processing. + if (AmazonLPA::is_erp_mode()) { + commerce_payment_redirect_pane_next_page($order, t('Amazon Login and Pay operating in ERP mode, skipped payment.')); + drupal_goto(commerce_checkout_order_uri($order)); + } + $api = AmazonLPA::instance(); + /** @var EntityDrupalWrapper $order_wrapper */ + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_reference = $api->getOrderRef($order_wrapper); + + // On error we will redirect user's back to the checkout page with the + // commerce_payment pane. We load it here to. + $checkout_panes = commerce_checkout_panes(); + $commerce_payment_pane = $checkout_panes['commerce_payment']; + + // Process the transaction. + try { + $amazon_order_state = $order_reference['OrderReferenceStatus']['State']; + + if ($amazon_order_state == 'Draft') { + $response = $api->setOrderRef($order_wrapper); + if (!empty($response['Constraints'])) { + $constraint = $response['Constraints']['Constraint']; + throw new AmazonApiException( + 'ConstraintsExist', + $response, + t('Unable to set the order reference for @order_id: @reason', array( + '@order_id' => $order_wrapper->getIdentifier(), + '@reason' => t('@code - @message', array( + '@code' => $constraint['ConstraintID'], + '@message' => $constraint['Description'], + )), + ))); + } + } + + $api->confirmOrderRef($order_wrapper); + + // Create a transaction. + $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id); + $transaction->instance_id = $payment_method['instance_id']; + + // If the authorization mode is manual, create a placeholder transaction. + if (AmazonLPA::get_authorization_mode() == AmazonLPA::AUTH_MANUAL) { + $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING; + $transaction->data['commerce_amazon_lpa']['environment'] = variable_get('commerce_amazon_lpa_environment', AmazonLPA::ENV_SANDBOX); + $transaction->data['commerce_amazon_lpa']['transaction_type'] = 'authorization'; + $transaction->amount = $order_wrapper->commerce_order_total->amount->value(); + $transaction->currency_code = $order_wrapper->commerce_order_total->currency_code->value(); + commerce_payment_transaction_save($transaction); + } + // Otherwise run an authorization to be done async or sync. + else { + // Authorize so we can manually capture or automatically when shipped. + // If set to CAPTURE_AUTH_CAPTURE, then do capture with the authorization. + $data = $api->authorize($order_wrapper, AmazonLPA::get_capture_mode() == AmazonLPA::CAPTURE_AUTH_CAPTURE ? TRUE : FALSE); + $api->processAuthorizeTransaction($transaction, $data); + + // Mark the payment as a failure. + if ($transaction->status == COMMERCE_PAYMENT_STATUS_FAILURE) { + throw new AmazonApiException('AuthorizationStatus', $data, 'Payment failed'); + } + } + } + catch (AmazonApiException $e) { + $order_reference = $api->getOrderRef($order_wrapper); + $order->data['commerce_amazon_lpa']['order_reference'] = $order_reference; + + $error_message = $e->getMessage(); + $order_next_status = 'checkout_' . $commerce_payment_pane['page']; + if ($e->getErrorCode() == 'ConstraintsExist') { + foreach ($order_reference['Constraints'] as $constraint) { + switch ($constraint['ConstraintID']) { + case 'PaymentMethodNotAllowed': + $error_message = t('The selected payment method is not available for this transaction. Please select another one or add a new payment method to the wallet widget.'); + break; + + case 'PaymentPlanNotSet': + $error_message = t('No payment instrument has been selected for this order, please try to refresh the page or add a new payment instrument in the wallet widget.'); + break; + } + } + } + elseif ($e->getErrorCode() == 'AuthorizationStatus') { + $data = $e->getResponse(); + switch ($data['AuthorizationStatus']['ReasonCode']) { + case 'AmazonRejected': + // Amazon already closed the transaction. + $error_message = t('Your payment could not be processed. Please try to place the order again using another payment method.'); + $order_next_status = 'cart'; + break; + + case 'TransactionTimedOut': + $api->cancel($order_wrapper); + $error_message = t('Your payment could not be processed. Please try to place the order again using another payment method.'); + $order_next_status = 'cart'; + break; + + case 'ProcessingFailure': + $api->cancel($order_wrapper); + $error_message = t('Your order could not be processed due to a system error. Please try to place the order again.'); + $order_next_status = 'cart'; + break; + + case 'InvalidPaymentMethod': + default: + $error_message = t('Your payment could not be processed, please follow the instructions in the payment method box.'); + } + } + + if ($order_next_status == 'cart') { + commerce_amazon_lpa_user_logout($order_wrapper->owner->value()); + $destination = 'cart'; + } + else { + $destination = commerce_checkout_order_uri($order); + } + + drupal_set_message($error_message, 'error'); + commerce_order_status_update($order, $order_next_status, FALSE, TRUE, $e->getMessage()); + drupal_goto($destination); + } + + commerce_payment_redirect_pane_next_page($order, t('Order paid with Amazon Payments')); + drupal_goto(commerce_checkout_order_uri($order)); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/js/commerce-amazon-lpa-redirect.js b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/js/commerce-amazon-lpa-redirect.js new file mode 100644 index 00000000..1fdf6c9d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/js/commerce-amazon-lpa-redirect.js @@ -0,0 +1,21 @@ +(function (Drupal, $, document) { + "use strict"; + + function getURLParameter(name, source) { + return decodeURIComponent((new RegExp('[?|&|#]' + name + '=' + '([^&]+?)(&|#|;|$)').exec(source) || [,""])[1].replace(/\+/g, '%20')) || null; + } + + var accessToken = getURLParameter("access_token", location.hash); + + if (typeof accessToken === 'string' && accessToken.match(/^Atza/)) { + document.cookie = "amazon_Login_accessToken=" + accessToken + ";secure"; + } + + $(function () { + window.onAmazonLoginReady = function () { + amazon.Login.setClientId(Drupal.AmazonLPA.clientId); + amazon.Login.setUseCookie(true); + window.location.reload(); + }; + }); +})(Drupal, jQuery, document); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/js/commerce-amazon-lpa.js b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/js/commerce-amazon-lpa.js new file mode 100644 index 00000000..72faf6a5 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/js/commerce-amazon-lpa.js @@ -0,0 +1,201 @@ +/* global amazon */ +(function($, Drupal) { + 'use strict'; + + Drupal.AmazonLPA = { + merchantId: '', + authRequest: '', + clientId: '', + checkoutUrl: '', + langcode: '', + orderReferenceId: '', + isShippable: true, + loginButtonOptions: { + style: 'Gold', + size: 'medium', + type: 'LwA' + }, + loginOptions: { + scope: 'profile postal_code payments:widget payments:shipping_address', + popup: true + }, + payButtonOptions: { + style: 'Gold', + size: 'medium', + type: 'PwA' + }, + addressBookOptions: { + displayMode: 'edit' + }, + walletOptions: { + displayMode: 'edit' + }, + + errorHandler: function (f) { + console.log(f.getErrorCode()+f.getErrorMessage()) + }, + + initialize: function (settings) { + if (typeof settings.AmazonLPA.callbacks === 'object') { + for (var item in settings.AmazonLPA.callbacks) { + if (settings.AmazonLPA.callbacks.hasOwnProperty(item) && typeof eval(settings.AmazonLPA.callbacks[item].callback) === 'function') { + eval(settings.AmazonLPA.callbacks[item].callback)(settings.AmazonLPA.callbacks[item].param); + } + } + } + }, + + LoginButton: function (elId) { + if (document.getElementById(elId) !== null) { + var $el = $('#' + elId); + OffAmazonPayments.Button(elId, Drupal.AmazonLPA.merchantId, { + type: $el.data('pay-type'), + color: Drupal.AmazonLPA.loginButtonOptions.style, + size: $el.data('pay-size'), + language: Drupal.AmazonLPA.langcode, + useAmazonAddressBook: true, + authorization: function () { + var loginOptions = Drupal.AmazonLPA.loginOptions; + Drupal.AmazonLPA.authRequest = amazon.Login.authorize(loginOptions, $el.data('url')); + }, + onError: function (error) { + Drupal.AmazonLPA.errorHandler(error); + } + }); + } + }, + + PaymentButton: function (elId) { + if (document.getElementById(elId) !== null) { + var $el = $('#' + elId); + OffAmazonPayments.Button(elId, Drupal.AmazonLPA.merchantId, { + type: $el.data('pay-type'), + color: Drupal.AmazonLPA.payButtonOptions.style, + size: $el.data('pay-size'), + language: Drupal.AmazonLPA.langcode, + useAmazonAddressBook: true, + authorization: function () { + var loginOptions = Drupal.AmazonLPA.loginOptions; + Drupal.AmazonLPA.authRequest = amazon.Login.authorize(loginOptions, $el.data('checkout-url')); + }, + onError: function (error) { + Drupal.AmazonLPA.errorHandler(error); + } + }); + } + }, + + AddressBookWidget: function (elId) { + if (Drupal.AmazonLPA.addressBookOptions.displayMode === 'edit') { + $('input.checkout-continue').once('amazon-pay', function () { + $(this).attr('disabled', true); + }); + } + + new OffAmazonPayments.Widgets.AddressBook({ + sellerId: Drupal.AmazonLPA.merchantId, + amazonOrderReferenceId: Drupal.AmazonLPA.orderReferenceId || null, + displayMode: Drupal.AmazonLPA.addressBookOptions.displayMode, + design: { + designMode: 'responsive' + }, + /** + * Provide a way to return the current order's contract ID. + * + * @see c.Widgets.AddressBook.prototype.renderAddressBook + * + * @returns string|null + */ + getContractId: function () { + if (Drupal.AmazonLPA.orderReferenceId) { + return Drupal.AmazonLPA.orderReferenceId; + } + + return null; + }, + onOrderReferenceCreate: function (orderReference) { + if (!Drupal.AmazonLPA.orderReferenceId) { + Drupal.AmazonLPA.orderReferenceId = orderReference.getAmazonOrderReferenceId(); + var $referenceIdField = $('input[name="commerce_amazon_lpa_contract_id[reference_id]"]', document); + if ($referenceIdField.length > 0) { + $referenceIdField.val(orderReference.getAmazonOrderReferenceId()); + this.amazonOrderReferenceId = orderReference.getAmazonOrderReferenceId(); + } + } + }, + onAddressSelect: function (orderReference) { + // Support check forms with shipping pane inline and AJAX. + if (typeof $.fn.commerceCheckShippingRecalculation === 'function') { + $.fn.commerceCheckShippingRecalculation(); + } + if (Drupal.AmazonLPA.addressBookOptions.displayMode === 'edit') { + $('input.checkout-continue').attr('disabled', false); + } + }, + onError: function (error) { + Drupal.AmazonLPA.errorHandler(error); + console.log(error); + } + }).bind(elId); + }, + + WalletWidget: function (elId) { + $('input.checkout-continue').once('amazon-pay', function () { + $(this).attr('disabled', true); + }); + + var onOrderReferenceCreate = null; + if (!Drupal.AmazonLPA.isShippable) { + onOrderReferenceCreate = function(orderReference) { + // Use the following cod to get the generated Order Reference ID. + var $referenceIdField = $('input[name="commerce_payment[payment_details][amazon_order_reference_id]"]', document); + if ($referenceIdField.length > 0 && $referenceIdField.val() === "") { + $referenceIdField.val(orderReference.getAmazonOrderReferenceId()); + } + }; + } + + new OffAmazonPayments.Widgets.Wallet({ + sellerId: Drupal.AmazonLPA.merchantId, + amazonOrderReferenceId: Drupal.AmazonLPA.orderReferenceId || null, + onPaymentSelect: function () { + $('input.checkout-continue').attr('disabled', false); + }, + onOrderReferenceCreate: onOrderReferenceCreate, + design: { + designMode: 'responsive' + }, + onError: function (error) { + Drupal.AmazonLPA.errorHandler(error); + } + }).bind(elId); + } + }; + + $(function () { + var settings = Drupal.settings; + // Inject Widget.js + var ws = document.createElement('script'); + $.extend(true, Drupal.AmazonLPA, settings.AmazonLPA); + ws.type = 'text/javascript'; + ws.src = settings.AmazonLPA.widgetsJsUrl; + ws.id = 'AmazonLPAWidgets'; + ws.async = true; + document.getElementsByTagName('head')[0].appendChild(ws); + + window.onAmazonLoginReady = function () { + amazon.Login.setClientId(settings.AmazonLPA.clientId); + amazon.Login.setUseCookie(true); + Drupal.AmazonLPA.initialize(settings); + }; + }); + + Drupal.behaviors.commerceAmazonLPA = { + attach: function (context, settings) { + if (typeof amazon !== 'undefined' && context !== document) { + Drupal.AmazonLPA.initialize(settings); + } + } + }; + +})(jQuery, Drupal); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/.gitignore b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/.gitignore new file mode 100644 index 00000000..5a8c4564 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/.gitignore @@ -0,0 +1,4 @@ +vendor +bin +behat.yml +failures diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/behat.common.yml b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/behat.common.yml new file mode 100644 index 00000000..09e9ea81 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/behat.common.yml @@ -0,0 +1,24 @@ + +default: + suites: + default: + paths: + features: 'features' + contexts: + - FeatureContext + - FailureContext + - Drupal\DrupalExtension\Context\DrupalContext + - Drupal\DrupalExtension\Context\MinkContext + failure_path: '%paths.base%/failures' + extensions: + Behat\MinkExtension: + goutte: ~ + selenium2: + capabilities: + acceptSslCerts: true + files_path: ../../ + Drupal\DrupalExtension: + blackbox: ~ + region_map: + Tabs: ".tabs.primary" + Navbar: "#toolbar" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/behat.example.yml b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/behat.example.yml new file mode 100644 index 00000000..023fdde5 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/behat.example.yml @@ -0,0 +1,10 @@ +imports: + - behat.common.yml + +default: + extensions: + Behat\MinkExtension: + base_url: URL + Drupal\DrupalExtension: + drush: + alias: ALIAS diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/composer.json b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/composer.json new file mode 100644 index 00000000..d272640e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/composer.json @@ -0,0 +1,8 @@ +{ + "config": { + "bin-dir": "bin/" + }, + "require": { + "drupal/drupal-extension": "3.*" + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/composer.lock b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/composer.lock new file mode 100644 index 00000000..73ffbe9d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/composer.lock @@ -0,0 +1,1660 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "c0ff48cddc203dd4385bfe6bdcf4b60a", + "content-hash": "61e0c8500485eb7081a78211745c4a08", + "packages": [ + { + "name": "behat/behat", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "359d987b3064d78f2d3a6ba3a355277f3b09b47f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/359d987b3064d78f2d3a6ba3a355277f3b09b47f", + "reference": "359d987b3064d78f2d3a6ba3a355277f3b09b47f", + "shasum": "" + }, + "require": { + "behat/gherkin": "~4.4", + "behat/transliterator": "~1.0", + "ext-mbstring": "*", + "php": ">=5.3.3", + "symfony/class-loader": "~2.1|~3.0", + "symfony/config": "~2.3|~3.0", + "symfony/console": "~2.1|~3.0", + "symfony/dependency-injection": "~2.1|~3.0", + "symfony/event-dispatcher": "~2.1|~3.0", + "symfony/translation": "~2.3|~3.0", + "symfony/yaml": "~2.1|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "symfony/process": "~2.1|~3.0" + }, + "suggest": { + "behat/mink-extension": "for integration with Mink testing framework", + "behat/symfony2-extension": "for integration with Symfony2 web framework", + "behat/yii-extension": "for integration with Yii web framework" + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Behat": "src/", + "Behat\\Testwork": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "Agile", + "BDD", + "ScenarioBDD", + "Scrum", + "StoryBDD", + "User story", + "business", + "development", + "documentation", + "examples", + "symfony", + "testing" + ], + "time": "2016-03-28 07:04:45" + }, + { + "name": "behat/gherkin", + "version": "v4.4.1", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", + "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "symfony/yaml": "~2.1" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2015-12-30 14:47:00" + }, + { + "name": "behat/mink", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/Mink.git", + "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/e6930b9c74693dff7f4e58577e1b1743399f3ff9", + "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "~2.1|~3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Browser controller/emulator abstraction for PHP", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ], + "time": "2016-03-05 08:26:18" + }, + { + "name": "behat/mink-browserkit-driver", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", + "reference": "10e67fb4a295efcd62ea0bf16025a85ea19534fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/10e67fb4a295efcd62ea0bf16025a85ea19534fb", + "reference": "10e67fb4a295efcd62ea0bf16025a85ea19534fb", + "shasum": "" + }, + "require": { + "behat/mink": "^1.7.1@dev", + "php": ">=5.3.6", + "symfony/browser-kit": "~2.3|~3.0", + "symfony/dom-crawler": "~2.3|~3.0" + }, + "require-dev": { + "silex/silex": "~1.2", + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Symfony2 BrowserKit driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "Mink", + "Symfony2", + "browser", + "testing" + ], + "time": "2016-03-05 08:59:47" + }, + { + "name": "behat/mink-extension", + "version": "v2.2", + "source": { + "type": "git", + "url": "https://github.com/Behat/MinkExtension.git", + "reference": "5b4bda64ff456104564317e212c823e45cad9d59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59", + "reference": "5b4bda64ff456104564317e212c823e45cad9d59", + "shasum": "" + }, + "require": { + "behat/behat": "~3.0,>=3.0.5", + "behat/mink": "~1.5", + "php": ">=5.3.2", + "symfony/config": "~2.2|~3.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "~1.1", + "phpspec/phpspec": "~2.0" + }, + "type": "behat-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\MinkExtension": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + }, + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com" + } + ], + "description": "Mink extension for Behat", + "homepage": "http://extensions.behat.org/mink", + "keywords": [ + "browser", + "gui", + "test", + "web" + ], + "time": "2016-02-15 07:55:18" + }, + { + "name": "behat/mink-goutte-driver", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkGoutteDriver.git", + "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", + "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", + "shasum": "" + }, + "require": { + "behat/mink": "~1.6@dev", + "behat/mink-browserkit-driver": "~1.2@dev", + "fabpot/goutte": "~1.0.4|~2.0|~3.1", + "php": ">=5.3.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Goutte driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "goutte", + "headless", + "testing" + ], + "time": "2016-03-05 09:04:22" + }, + { + "name": "behat/mink-selenium2-driver", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkSelenium2Driver.git", + "reference": "473a9f3ebe0c134ee1e623ce8a9c852832020288" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/473a9f3ebe0c134ee1e623ce8a9c852832020288", + "reference": "473a9f3ebe0c134ee1e623ce8a9c852832020288", + "shasum": "" + }, + "require": { + "behat/mink": "~1.7@dev", + "instaclick/php-webdriver": "~1.1", + "php": ">=5.3.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Pete Otaqui", + "email": "pete@otaqui.com", + "homepage": "https://github.com/pete-otaqui" + } + ], + "description": "Selenium2 (WebDriver) driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "ajax", + "browser", + "javascript", + "selenium", + "testing", + "webdriver" + ], + "time": "2016-03-05 09:10:18" + }, + { + "name": "behat/transliterator", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "868e05be3a9f25ba6424c2dd4849567f50715003" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003", + "reference": "868e05be3a9f25ba6424c2dd4849567f50715003", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Transliterator": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "time": "2015-09-28 16:26:35" + }, + { + "name": "drupal/drupal-driver", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/jhedstrom/DrupalDriver.git", + "reference": "125d39918c97f7a08e3110d456a0a1db864dae46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jhedstrom/DrupalDriver/zipball/125d39918c97f7a08e3110d456a0a1db864dae46", + "reference": "125d39918c97f7a08e3110d456a0a1db864dae46", + "shasum": "" + }, + "require": { + "symfony/dependency-injection": "~2.6|~3.0", + "symfony/process": "~2.5|~3.0" + }, + "require-dev": { + "drupal/coder": "~8.2.0", + "drush-ops/behat-drush-endpoint": "*", + "mockery/mockery": "0.9.4", + "phpspec/phpspec": "~2.0", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Drupal\\Component": "src/", + "Drupal\\Driver": "src/", + "Drupal\\Tests\\Driver": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Jonathan Hedstrom", + "email": "jhedstrom@gmail.com" + } + ], + "description": "A collection of reusable Drupal drivers", + "homepage": "http://github.com/jhedstrom/DrupalDriver", + "keywords": [ + "drupal", + "test", + "web" + ], + "time": "2016-06-20 16:29:51" + }, + { + "name": "drupal/drupal-extension", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/jhedstrom/drupalextension.git", + "reference": "abe3a33abd94382ab62423dd972fa820c63962e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jhedstrom/drupalextension/zipball/abe3a33abd94382ab62423dd972fa820c63962e3", + "reference": "abe3a33abd94382ab62423dd972fa820c63962e3", + "shasum": "" + }, + "require": { + "behat/behat": "~3.1.0-rc2", + "behat/mink": "~1.5", + "behat/mink-extension": "~2.0", + "behat/mink-goutte-driver": "~1.0", + "behat/mink-selenium2-driver": "~1.1", + "drupal/drupal-driver": "~1.2", + "symfony/dependency-injection": "~2.7", + "symfony/event-dispatcher": "~2.7" + }, + "require-dev": { + "behat/mink-zombie-driver": "^1.2", + "phpspec/phpspec": "~2.0", + "phpunit/phpunit": "3.7.*" + }, + "type": "behat-extension", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Drupal\\Drupal": "src/", + "Drupal\\Exception": "src/", + "Drupal\\DrupalExtension": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Jonathan Hedstrom", + "email": "jhedstrom@gmail.com" + } + ], + "description": "Drupal extension for Behat", + "homepage": "http://drupal.org/project/drupalextension", + "keywords": [ + "drupal", + "test", + "web" + ], + "time": "2016-06-30 21:12:18" + }, + { + "name": "fabpot/goutte", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Goutte.git", + "reference": "3cbc6ed222422a28400e470050f14928a153207e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/3cbc6ed222422a28400e470050f14928a153207e", + "reference": "3cbc6ed222422a28400e470050f14928a153207e", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "symfony/browser-kit": "~2.1|~3.0", + "symfony/css-selector": "~2.1|~3.0", + "symfony/dom-crawler": "~2.1|~3.0" + }, + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Goutte\\": "Goutte" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/FriendsOfPHP/Goutte", + "keywords": [ + "scraper" + ], + "time": "2015-11-05 12:58:44" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "3f808fba627f2c5b69e2501217bf31af349c1427" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427", + "reference": "3f808fba627f2c5b69e2501217bf31af349c1427", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.3.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2016-07-15 17:22:37" + }, + { + "name": "guzzlehttp/promises", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579", + "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-05-18 16:56:05" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", + "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "time": "2016-06-24 23:00:38" + }, + { + "name": "instaclick/php-webdriver", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/instaclick/php-webdriver.git", + "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb", + "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "WebDriver": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Justin Bishop", + "email": "jubishop@gmail.com", + "role": "Developer" + }, + { + "name": "Anthon Pang", + "email": "apang@softwaredevelopment.ca", + "role": "Fork Maintainer" + } + ], + "description": "PHP WebDriver for Selenium 2", + "homepage": "http://instaclick.com/", + "keywords": [ + "browser", + "selenium", + "webdriver", + "webtest" + ], + "time": "2015-06-15 20:19:33" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "symfony/browser-kit", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "d2a07cc11c5fa94820240b1e67592ffb18e347b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d2a07cc11c5fa94820240b1e67592ffb18e347b9", + "reference": "d2a07cc11c5fa94820240b1e67592ffb18e347b9", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/class-loader", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "817f09b4c37b7688fa4342cb4642d8f2d81c1097" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/817f09b4c37b7688fa4342cb4642d8f2d81c1097", + "reference": "817f09b4c37b7688fa4342cb4642d8f2d81c1097", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2016-07-10 08:05:47" + }, + { + "name": "symfony/config", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "a7630397b91be09cdd2fe57fd13612e258700598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/a7630397b91be09cdd2fe57fd13612e258700598", + "reference": "a7630397b91be09cdd2fe57fd13612e258700598", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/console", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "f9e638e8149e9e41b570ff092f8007c477ef0ce5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/f9e638e8149e9e41b570ff092f8007c477ef0ce5", + "reference": "f9e638e8149e9e41b570ff092f8007c477ef0ce5", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/css-selector", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "2851e1932d77ce727776154d659b232d061e816a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2851e1932d77ce727776154d659b232d061e816a", + "reference": "2851e1932d77ce727776154d659b232d061e816a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2016-06-29 05:41:56" + }, + { + "name": "symfony/dependency-injection", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2b5a00d176f6a201dc430375c0ef37706ea3d12", + "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "require-dev": { + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2016-07-30 07:20:35" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "c7b9b8db3a6f2bac76dcd9a9db5446f2591897f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7b9b8db3a6f2bac76dcd9a9db5446f2591897f9", + "reference": "c7b9b8db3a6f2bac76dcd9a9db5446f2591897f9", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2016-07-28 16:56:28" + }, + { + "name": "symfony/filesystem", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "bb29adceb552d202b6416ede373529338136e84f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/bb29adceb552d202b6416ede373529338136e84f", + "reference": "bb29adceb552d202b6416ede373529338136e84f", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2016-07-20 05:44:26" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "dff51f72b0706335131b00a7f49606168c582594" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", + "reference": "dff51f72b0706335131b00a7f49606168c582594", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-05-18 14:26:46" + }, + { + "name": "symfony/process", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "04c2dfaae4ec56a5c677b0c69fac34637d815758" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/04c2dfaae4ec56a5c677b0c69fac34637d815758", + "reference": "04c2dfaae4ec56a5c677b0c69fac34637d815758", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2016-07-28 11:13:48" + }, + { + "name": "symfony/translation", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "7713ddf81518d0823b027fe74ec390b80f6b6536" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/7713ddf81518d0823b027fe74ec390b80f6b6536", + "reference": "7713ddf81518d0823b027fe74ec390b80f6b6536", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/yaml", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-07-17 14:02:08" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/admin/closed_on_shipped.feature b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/admin/closed_on_shipped.feature new file mode 100644 index 00000000..9f9ce677 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/admin/closed_on_shipped.feature @@ -0,0 +1,15 @@ +@api @javascript +Feature: Orders are closed on shipped + As a store owner + I can mark the order as shipped + With Amazon then closing the order + + Scenario: Marking order complete closes order + When I am logged in as a user with the "administrator" role + And an Amazon order has been created + And I go to "/admin/commerce/orders" + When I click on Quick Edit link + Then I select "completed" from "status" + And I press "edit-save" + And I wait for AJAX loading to finish + And I take an awesome screenshot diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/amazon_checkout.feature b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/amazon_checkout.feature new file mode 100644 index 00000000..faa43cbb --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/amazon_checkout.feature @@ -0,0 +1,70 @@ +@api @javascript +Feature: Checkout with Amazon + As a customer + I can add items to my cart + With my cart I can checkout with Amazon + + Scenario: Checkout with Amazon (Physical) + When I set the Amazon setting "commerce_amazon_lpa_popup" to "redirect" + When I set the Amazon setting "commerce_amazon_lpa_authorization_mode" to "automatic_sync" + And I set the Amazon setting "commerce_amazon_lpa_capture_mode" to "shipment_capture" + When I go to "/storage-devices/commerce-guys-usb-key" + And I press "Add to cart" + When I go to "/bags-cases/commerce-guys-laptop-bag" + And I press "Add to cart" + When I go to "/cart" + And I wait for Amazon to load + Then I click the Pay with Amazon button + And I switch to popup + Then I fill in the following: + | email | matt+1@commerceguys.com | + | password | password | + And I press the "Sign in using our secure server" button + When I switch back to original window + Then I am on the checkout form + And I wait for Amazon to load + And I wait for the Amazon order reference + Then the Amazon address book exists + Then I press "Continue to next step" + And I should see "Shipping service" + Then I press "Continue to next step" + And I wait for Amazon to load + # Find better way to see if this is loaded. + And I wait for 3 seconds + Then I should see "Review order" + And the Amazon wallet exists + And the Amazon address book review exists + Then I press "Continue to next step" + Then I should see "Checkout complete" + + Scenario: Checkout with Amazon (Digital) + When I set the Amazon setting "commerce_amazon_lpa_popup" to "redirect" + When I set the Amazon setting array "commerce_amazon_lpa_digital_product_types" to '["storage_devices"]' + When I set the Amazon setting "commerce_amazon_lpa_authorization_mode" to "automatic_sync" + And I set the Amazon setting "commerce_amazon_lpa_capture_mode" to "shipment_capture" + When I go to "/storage-devices/commerce-guys-usb-key" + And I press "Add to cart" + When I go to "/cart" + And I wait for Amazon to load + Then I click the Pay with Amazon button + And I switch to popup + Then I fill in the following: + | email | matt+1@commerceguys.com | + | password | password | + And I press the "Sign in using our secure server" button + When I switch back to original window + Then I am on the checkout form + And I wait for Amazon to load + And I wait for the Amazon order reference + Then the Amazon address book does not exist + Then I press "Continue to next step" + And I wait for Amazon to load + # Find better way to see if this is loaded. + And I wait for 3 seconds + Then I should see "Review order" + And the Amazon wallet exists + And the Amazon address book review does not exist + Then I press "Continue to next step" + Then I should see "Checkout complete" + # Fkx up + When I set the Amazon setting "commerce_amazon_lpa_digital_product_types" to '["_none"]' diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/bootstrap/FailureContext.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/bootstrap/FailureContext.php new file mode 100644 index 00000000..d92acbd6 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/bootstrap/FailureContext.php @@ -0,0 +1,88 @@ +minkContext = $scope->getEnvironment()->getContext('Drupal\DrupalExtension\Context\MinkContext'); + $this->failurePath = $scope->getEnvironment()->getSuite()->getSetting('failure_path'); + } + + /** + * @AfterStep + */ + public function handleFailure(AfterStepScope $scope) { + + if ($scope->getTestResult()->isPassed()) { + return; + } + + $fileName = $this->fileName($scope); + $this->dumpMarkup($fileName); + $this->screenShot($fileName); + } + + /** + * @Then /^I take an awesome screenshot$/ + */ + public function takeScreenShot() { + $fileName = $this->fileName(); + $this->dumpMarkup($fileName); + $this->screenShot($fileName); + } + + /** + * Compute a file name for the output. + */ + protected function fileName($scope = NULL) { + if ($scope) { + $baseName = pathinfo($scope->getFeature()->getFile()); + $baseName = substr($baseName['basename'], 0 , strlen($baseName['basename']) - strlen($baseName['extension']) - 1); + $baseName .= '-' . $scope->getStep()->getLine(); + } + else { + $baseName = 'screenshot'; + } + + $baseName .= '-' . date('YmdHis'); + $baseName = $this->failurePath . '/' . $baseName; + return $baseName; + } + + /** + * Save the markup from the failed step. + */ + protected function dumpMarkup($fileName) { + $fileName .= '.html'; + $html = $this->minkContext->getSession()->getPage()->getHtml(); + file_put_contents($fileName, $html); + sprintf("HTML available at: %s\n", $fileName); + } + + /** + * Save a screen shot from the failed step. + */ + protected function screenShot($fileName) { + $fileName .= '.png'; + $driver = $this->minkContext->getSession()->getDriver(); + + if ($driver instanceof Selenium2Driver) { + file_put_contents($fileName, $this->minkContext->getSession()->getDriver()->getScreenshot()); + sprintf("Screen shot available at: %s\n", $fileName); + return; + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/bootstrap/FeatureContext.php b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/bootstrap/FeatureContext.php new file mode 100644 index 00000000..3b471f95 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/bootstrap/FeatureContext.php @@ -0,0 +1,304 @@ +getSession()->getPage()->clickLink('Quick edit'); + $this->getSession()->wait(5000, 'jQuery(".entity-commerce-order").length > 0'); + } + + /** + * @Given /^(?:|I )wait(?:| for) (\d+) seconds?$/ + * + * Wait for the given number of seconds. ONLY USE FOR DEBUGGING! + */ + public function iWaitForSeconds($arg1) { + sleep($arg1); + } + + /** + * @Given /^(?:|I )wait for AJAX loading to finish$/ + * + * Wait for the jQuery AJAX loading to finish. ONLY USE FOR DEBUGGING! + */ + public function iWaitForAJAX() { + $this->getSession()->wait(5000, 'jQuery.active === 0'); + } + + /** + * @When I resize the browser to mobile + */ + public function iResizeTheBrowserToMobile() { + $this->getSession()->resizeWindow(200, 600, 'current'); + } + + /** + * @BeforeScenario + */ + public function beforeScenario() { + if (!$this->runningJavascript()) { + return; + } + + $current_driver = $this->getSession()->getDriver(); + if ($current_driver instanceof \Behat\Mink\Driver\Selenium2Driver) { + } + + $this->getSession()->executeScript('window.name = "main_window"'); + $this->getSession()->resizeWindow(1440, 900, 'current'); + + $originalWindowName = $this->getSession()->getWindowName(); //Get the original name + + if (empty($this->originalWindowName)) { + $this->originalWindowName = $originalWindowName; + } + print $this->originalWindowName; + } + + /** + * Returns whether the scenario is running in a browser that can run Javascript or not. + * + * @return boolean + */ + protected function runningJavascript() { + return get_class($this->getSession() + ->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver'; + } + + /** + * @Given I wait for Amazon to load + */ + public function amazonIsLoaded() { + $this->getSession()->wait(5000, 'typeof window.amazon !== "undefined"'); + } + + /** + * @Given I wait for the Amazon order reference + */ + public function amazonOrderReference() { + $this->getSession()->wait(5000, "document.getElementsByName('commerce_amazon_lpa_contract_id[reference_id]')[0].value !== ''"); + } + + /** + * @Then /^I switch to popup$/ + */ + public function iSwitchToPopup() { + $popupName = $this->getNewPopup($this->originalWindowName); + + //Switch to the popup Window + $this->getSession()->switchToWindow($popupName); + } + + /** + * @Then /^I switch back to original window$/ + */ + public function iSwitchBackToOriginalWindow() { + //Switch to the original window + $this->getSession()->switchToWindow($this->originalWindowName); + } + + /** + * This gets the window name of the new popup. + */ + private function getNewPopup($originalWindowName = NULL) { + //Get all of the window names first + $names = $this->getSession()->getWindowNames(); + + //Now it should be the last window name + $last = array_pop($names); + + if (!empty($originalWindowName)) { + while ($last == $originalWindowName && !empty($names)) { + $last = array_pop($names); + } + } + + return $last; + } + + /** + * @When I click the Login with Amazon button + */ + function clickLwAButton() { + $div = $this->getSession()->getPage()->find('xpath', '//div[@id="amazonloginbutton"]//img'); + if ($div === null) { + throw new ElementNotFoundException($this->getSession(), 'amazonloginbutton', 'xpath', '//div[@id="amazonloginbutton"]'); + } + $div->click(); + } + + /** + * @When I click the cart summary Pay with Amazon button + */ + function clickCartSummaryPwAButton() { + $div = $this->getSession()->getPage()->find('xpath', '//div[starts-with(@id, \'amazon-checkout-summary-link\')]//img'); + if ($div === null) { + throw new ElementNotFoundException($this->getSession(), 'amazonloginbutton', 'xpath', '//div[starts-with(@id, \'amazon-checkout-summary-link\')]//img'); + } + $div->click(); + } + + /** + * @When I click the Pay with Amazon button + */ + function clickPwAButton() { + $div = $this->getSession()->getPage()->find('xpath', '//div[starts-with(@id, "amazon_lpa_cart_pay")]//img'); + if ($div === null) { + throw new ElementNotFoundException($this->getSession(), 'amazonloginbutton', 'xpath', '//div[starts-with(@id, \'amazon-checkout-summary-link\')]//img'); + } + $div->click(); + } + + /** + * @Then I am on the checkout form + */ + function thenIAmOnTheCheckoutForm() { + $form = $this->getSession()->getPage()->find('xpath', '//form[@id="commerce-checkout-form-checkout"]'); + if ($form === null) { + throw new ElementNotFoundException($this->getSession(), 'amazonloginbutton', 'xpath', '//div[starts-with(@id, \'amazon-checkout-summary-link\')]//img'); + } + } + + /** + * @When the Amazon address book exists + */ + function amazonAddressBookExists() { + $addressbook_pane = $this->getSession()->getPage()->find('xpath', '//div[@id="checkout-amazon-addressbook"]'); + if ($addressbook_pane === null) { + throw new ElementNotFoundException($this->getSession()); + } + $addressbook_iframe = $this->getSession()->getPage()->find('xpath', '//div[@id="checkout-amazon-addressbook"]//iframe'); + if ($addressbook_iframe === null) { + throw new ElementNotFoundException($this->getSession()); + } + } + + /** + * @When the Amazon address book does not exist + */ + function amazonAddressBookNotExists() { + $addressbook_pane = $this->getSession()->getPage()->find('xpath', '//div[@id="checkout-amazon-addressbook"]'); + if ($addressbook_pane !== null) { + throw new ElementNotFoundException($this->getSession()); + } + } + + /** + * @When the Amazon address book review exists + */ + function amazonAddressBookReviewExists() { + $addressbook_pane = $this->getSession()->getPage()->find('xpath', '//div[@id="customer-profile-shipping"]'); + if ($addressbook_pane === null) { + throw new ElementNotFoundException($this->getSession()); + } + $addressbook_iframe = $this->getSession()->getPage()->find('xpath', '//div[@id="customer-profile-shipping"]//iframe'); + if ($addressbook_iframe === null) { + throw new ElementNotFoundException($this->getSession()); + } + } + + /** + * @When the Amazon address book review does not exist + */ + function amazonAddressBookReviewNotExists() { + $addressbook_pane = $this->getSession()->getPage()->find('xpath', '//div[@id="customer-profile-shipping"]'); + if ($addressbook_pane !== null) { + throw new ElementNotFoundException($this->getSession()); + } + } + + /** + * @When the Amazon wallet exists + */ + function amazonWalletExists() { + $addressbook_pane = $this->getSession()->getPage()->find('xpath', '//div[@id="walletwidgetdiv"]'); + if ($addressbook_pane === null) { + throw new ElementNotFoundException($this->getSession()); + } + $addressbook_iframe = $this->getSession()->getPage()->find('xpath', '//div[@id="walletwidgetdiv"]//iframe'); + if ($addressbook_iframe === null) { + throw new ElementNotFoundException($this->getSession()); + } + } + + /** + * @Then I set the Amazon setting :name to :value + */ + function setAmazonCheckoutSetting($name, $value) { + /** @var \Drupal\Driver\DrushDriver $drush */ + $drush = $this->getDriver('drush'); + + if (is_bool($value)) { + $value = (bool) $value; + } + + $drush->drush("variable-set $name $value"); + } + + /** + * @Then I set the Amazon setting array :name to :value + */ + function setAmazonCheckoutArraySetting($name, $value) { + /** @var \Drupal\Driver\DrushDriver $drush */ + $drush = $this->getDriver('drush'); + $output = $drush->drush("variable-set", array($name, sprintf("'%s'", $value)), array('format' => 'json')); + } + + /** + * @When an Amazon order has been created + */ + public function createAmazonOrder() { + $this->setAmazonCheckoutArraySetting('commerce_amazon_lpa_popup', 'redirect'); + $this->setAmazonCheckoutArraySetting('commerce_amazon_lpa_authorization_mode', 'automatic_sync'); + $this->setAmazonCheckoutArraySetting('commerce_amazon_lpa_capture_mode', 'shipment_capture'); + + $this->visitPath('/storage-devices/commerce-guys-usb-key'); + $this->getSession()->getPage()->pressButton('Add to cart'); + + $this->visitPath('/cart'); + $this->amazonIsLoaded(); + $this->clickPwAButton(); + $this->iSwitchToPopup(); + $this->getSession()->getPage()->fillField('email', 'matt+1@commerceguys.com'); + $this->getSession()->getPage()->fillField('password', 'password'); + $this->getSession()->getPage()->pressButton('Sign in using our secure server'); + $this->iSwitchBackToOriginalWindow(); + + $this->thenIAmOnTheCheckoutForm(); + $this->amazonIsLoaded(); + $this->amazonOrderReference(); + $this->amazonAddressBookExists(); + $this->getSession()->getPage()->pressButton('Continue to next step'); + $this->getSession()->getPage()->pressButton('Continue to next step'); + $this->amazonIsLoaded(); + $this->iWaitForSeconds(3); + $this->amazonWalletExists(); + $this->amazonAddressBookReviewExists(); + $this->getSession()->getPage()->pressButton('Continue to next step'); + $this->assertSession()->pageTextContains('Checkout complete'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/cart_summary_link.feature b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/cart_summary_link.feature new file mode 100644 index 00000000..109e2062 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/cart_summary_link.feature @@ -0,0 +1,19 @@ +@api @javascript +Feature: Cart summary link for Amazon + As a customer + I can click Pay with Amazon + From my cart widget + + Scenario: I can click the pay with Amazon button in cart widget + When I go to "/storage-devices/commerce-guys-usb-key" + And I press "Add to cart" + Then I click the cart summary Pay with Amazon button + And I switch to popup + Then I fill in the following: + | email | matt+1@commerceguys.com | + | password | password | + And I press the "Sign in using our secure server" button + When I switch back to original window + And I wait for AJAX loading to finish + Then I am on the checkout form + And I should see "Storage 1" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/login.feature b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/login.feature new file mode 100644 index 00000000..3d2b8071 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/login.feature @@ -0,0 +1,37 @@ +@api @javascript +Feature: Login with Amazon + As an anonymous user + I should be able to Login + + Scenario: Log in with Amazon + When I go to "/user/login" + And I wait for Amazon to load + When I click the Login with Amazon button + And I switch to popup + Then I fill in the following: + | email | matt+1@commerceguys.com | + | password | password | + And I press the "Sign in using our secure server" button + When I switch back to original window + Then I should see "Account information" + + Scenario: As a user, I can create an order, log in, and my cart persists. + When I go to "/bags-cases/commerce-guys-laptop-bag" + And I press "Add to cart" + When I go to "/user/login" + And I wait for Amazon to load + When I click the Login with Amazon button + And I switch to popup + Then I fill in the following: + | email | matt+1@commerceguys.com | + | password | password | + And I press the "Sign in using our secure server" button + When I switch back to original window + Then I should see "Account information" + When I go to "/cart" + Then I should see "LAP1-BLK-13" + And I should see "£50.40" + # Clean up. + Then I press "Remove" + And I should see "Laptop Bag 1 removed from your cart." + diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/sandbox/auth_amazon_rejected.feature b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/sandbox/auth_amazon_rejected.feature new file mode 100644 index 00000000..b57662f0 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_amazon_lpa/tests/behat/features/sandbox/auth_amazon_rejected.feature @@ -0,0 +1,41 @@ +@api @javascript +Feature: Sandbox: Testing rejected authorizations + As store owner + Amazon module should handle rejected authorizations + + Background: + When I set the Amazon setting "commerce_amazon_lpa_popup" to "redirect" + When I set the Amazon setting "commerce_amazon_lpa_authorization_mode" to "automatic_sync" + And I set the Amazon setting "commerce_amazon_lpa_capture_mode" to "shipment_capture" + And I set the Amazon setting "commerce_amazon_lpa_simulation" to "Authorizations_AmazonRejected" + + Scenario: Sync auth with Amazon Rejected + When I go to "/bags-cases/commerce-guys-laptop-bag" + And I press "Add to cart" + When I go to "/cart" + And I wait for Amazon to load + Then I click the Pay with Amazon button + And I switch to popup + Then I fill in the following: + | email | matt+1@commerceguys.com | + | password | password | + And I press the "Sign in using our secure server" button + When I switch back to original window + Then I am on the checkout form + And I wait for Amazon to load + And I wait for the Amazon order reference + Then the Amazon address book exists + Then I press "Continue to next step" + And I should see "Shipping service" + Then I press "Continue to next step" + And I wait for Amazon to load + # Find better way to see if this is loaded. + And I wait for 3 seconds + Then I should see "Review order" + And the Amazon wallet exists + And the Amazon address book review exists + Then I press "Continue to next step" + Then I should see "Shopping cart" + And I should see "Your order could not be completed, please select a different payment method." + And I should see "Your shopping cart is empty." + And I set the Amazon setting "commerce_amazon_lpa_simulation" to "_none" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.api.php b/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.api.php new file mode 100644 index 00000000..ed8d9c96 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.api.php @@ -0,0 +1,41 @@ +=')) { + if (isset($info['version']) && version_compare($info['version'], '7.x-2', '<')) { $requirements['commerce_authnet_cardonfile'] = array( 'title' => $t('Card on File'), 'value' => $t('Less than 2.x'), diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.module b/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.module index d54d1e26..24051b12 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_authnet/commerce_authnet.module @@ -108,7 +108,7 @@ function commerce_authnet_aim_void_access($order, $transaction) { // Return FALSE if it is more than 24 hours since the last update to the // transaction, as it will already have been settled. - if (time() - $transaction->changed > 2600 * 24) { + if (time() - $transaction->changed > 3600 * 24) { return FALSE; } @@ -157,6 +157,7 @@ function commerce_authnet_commerce_payment_method_info() { 'display_title' => t('Credit card'), 'description' => t('Integrates Authorize.Net Advanced Integration Method for card not present CC transactions.'), 'cardonfile' => array( + 'create callback' => 'commerce_authnet_cim_cardonfile_create', 'charge callback' => 'commerce_authnet_cim_cardonfile_charge', 'update callback' => 'commerce_authnet_cim_cardonfile_update', 'delete callback' => 'commerce_authnet_cim_cardonfile_delete', @@ -329,11 +330,19 @@ function commerce_authnet_aim_submit_form_validate($payment_method, $pane_form, * Payment method callback: checkout form submission. */ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) { - // If the customer specified payment using a card on file, attempt that now - // and simply return the result. - if (module_exists('commerce_cardonfile') && $payment_method['settings']['cardonfile'] && - !empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') { - return commerce_authnet_cim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge); + // If Card on File is enabled and active for this payment method, then we need + // to figure out what to do. + if (module_exists('commerce_cardonfile') && $payment_method['settings']['cardonfile'] && !empty($pane_values['cardonfile'])) { + if (!empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') { + // We're using a stored payment profile. Pass it along to CIM. + return commerce_authnet_cim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge); + } + else { + if (!empty($pane_values['credit_card']['cardonfile_store']) && $pane_values['credit_card']['cardonfile_store']) { + // We've got a request to store a new card. + return commerce_authnet_cim_submit_new_card_form_submit($payment_method, $pane_form, $pane_values, $order, $charge); + } + } } // Determine the credit card type if possible for use in later code. @@ -363,7 +372,7 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p $transaction->message = t('Invalid @amount transaction not attempted.', array('@amount' => commerce_currency_format($charge['amount'], $charge['currency_code']))); commerce_payment_transaction_save($transaction); - drupal_set_message('We encountered an error processing your transaction. Please contact us to resolve the issue.', 'error'); + drupal_set_message(t('We encountered an error processing your transaction. Please contact us to resolve the issue.'), 'error'); return FALSE; } } @@ -418,7 +427,7 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p ); // Prepare the billing address for use in the request. - if ($order_wrapper->commerce_customer_billing->value()){ + if (isset($order->commerce_customer_billing) && $order_wrapper->commerce_customer_billing->value()) { $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); if (empty($billing_address['first_name'])) { @@ -439,8 +448,28 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p 'x_country' => $billing_address['country'], ); } - else { - $billing_address = array(); + + // Prepare the shipping address for use in the request. + if (isset($order->commerce_customer_shipping) && $order_wrapper->commerce_customer_shipping->value()) { + $shipping_address = $order_wrapper->commerce_customer_shipping->commerce_customer_address->value(); + + if (empty($shipping_address['first_name'])) { + $name_parts = explode(' ', $shipping_address['name_line']); + $shipping_address['first_name'] = array_shift($name_parts); + $shipping_address['last_name'] = implode(' ', $name_parts); + } + + $nvp += array( + // Customer shipping Address + 'x_ship_to_first_name' => substr($shipping_address['first_name'], 0, 50), + 'x_ship_to_last_name' => substr($shipping_address['last_name'], 0, 50), + 'x_ship_to_company' => substr($shipping_address['organisation_name'], 0, 50), + 'x_ship_to_address' => substr($shipping_address['thoroughfare'], 0, 60), + 'x_ship_to_city' => substr($shipping_address['locality'], 0, 40), + 'x_ship_to_state' => substr($shipping_address['administrative_area'], 0, 40), + 'x_ship_to_zip' => substr($shipping_address['postal_code'], 0, 20), + 'x_ship_to_country' => $shipping_address['country'], + ); } // Submit the request to Authorize.Net. @@ -502,151 +531,182 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p drupal_set_message(check_plain($response[3]), 'error'); return FALSE; } +} - // If Card on File storage is enabled via CIM and the form says to store data... - if (module_exists('commerce_cardonfile') && !empty($payment_method['settings']['cardonfile']) && - !empty($pane_values['credit_card']['cardonfile_store']) && $pane_values['credit_card']['cardonfile_store']) { - // Build a payment details array for the credit card. - $payment_details = array( - 'cardNumber' => $pane_values['credit_card']['number'], - 'expirationDate' => $pane_values['credit_card']['exp_year'] . '-' . $pane_values['credit_card']['exp_month'], +/** + * Implements hook_form_FORM_ID_alter(). + */ +function commerce_authnet_form_commerce_payment_order_transaction_add_form_alter(&$form, &$form_state) { + // If the payment terminal is displayed for an authnet_aim transaction... + if (!empty($form['payment_terminal']) && $form_state['payment_method']['method_id'] == 'authnet_aim') { + // Add a select list to let the administrator choose a different transaction + // type than the payment method's default. + $form['payment_terminal']['payment_details']['txn_type'] = array( + '#type' => 'select', + '#title' => t('Transaction type'), + '#options' => array( + COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only'), + COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'), + ), + '#default_value' => $form_state['payment_method']['settings']['txn_type'], ); + } +} - if (isset($pane_values['credit_card']['code'])) { - $payment_details['cardCode'] = $pane_values['credit_card']['code']; - } +/** + * Handles advanced logic relating to creating CIM orders with new card data. + * + * @see commerce_authnet_aim_submit_form_submit() + */ +function commerce_authnet_cim_submit_new_card_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) { + // At this point, we have a few choices to make. We know the user is logged in + // and so we know if they have any existing card on file profiles. If they do + // not, then we can assume that they need a new profile. If they do have one, + // then we need take appropriate steps to add the CC info to the existing + // profile and process the payment based on their profile. + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - // First look to see if we already have cards on file for the user. - $stored_cards = commerce_cardonfile_load_multiple_by_uid($order->uid, $payment_method['instance_id']); - $add_to_profile = NULL; + $payment_details = array( + 'cardNumber' => $pane_values['credit_card']['number'], + 'expirationDate' => $pane_values['credit_card']['exp_year'] . '-' . $pane_values['credit_card']['exp_month'], + ); - // If we didn't find any, attempt to make a new Customer Profile now. - if (empty($stored_cards)) { - // Submit a CIM request to create the Customer Profile. - if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { - // If the Customer Profile creation was a success, store the new card on - // file data locally. - if ((string) $response->messages->resultCode == 'Ok') { - // Build a remote ID that includes the Customer Profile ID and the - // Payment Profile ID. - $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; + if (isset($pane_values['credit_card']['code'])) { + $payment_details['cardCode'] = $pane_values['credit_card']['code']; + } - $card_data = commerce_cardonfile_new(); - $card_data->uid = $order->uid; - $card_data->payment_method = $payment_method['method_id']; - $card_data->instance_id = $payment_method['instance_id']; - $card_data->remote_id = $remote_id; - $card_data->card_type = !empty($card_type) ? $card_type : 'card'; - $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; - $card_data->card_number = substr($pane_values['credit_card']['number'], -4); - $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; - $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; - $card_data->status = 1; - - // Save and log the creation of the new card on file. - commerce_cardonfile_save($card_data); - watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); - } - elseif ((string) $response->messages->message->code == 'E00039') { - // But if a Customer Profile already existed for this user, attempt - // instead to add this card as a new Payment Profile to it. - $result = array_filter(explode(' ', (string) $response->messages->message->text), 'is_numeric'); - $add_to_profile = reset($result); - } - } - } - else { - // Extract the user's Customer Profile ID from the first card's remote ID. - $card_data = reset($stored_cards); - list($cim_customer_profile_id, $cim_payment_profile_id) = explode('|', $card_data->remote_id); + // Prepare the billing address for use in the request. + if (isset($order->commerce_customer_billing) && $order_wrapper->commerce_customer_billing->value()) { + $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); - // Attempt to add the card as a new payment profile to this Customer Profile. - $add_to_profile = $cim_customer_profile_id; + if (empty($billing_address['first_name'])) { + $name_parts = explode(' ', $billing_address['name_line']); + $billing_address['first_name'] = array_shift($name_parts); + $billing_address['last_name'] = implode(' ', $name_parts); } + } + + $remote_id = FALSE; - // Attempt to add the card to an existing Customer Profile if specified. - if (!empty($add_to_profile)) { - $response = commerce_authnet_cim_create_customer_payment_profile_request($payment_method, $add_to_profile, $order, $payment_details); + // First look to see if we already have cards on file for the user. + $stored_cards = array(); + if (!user_is_anonymous()) { + $stored_cards = commerce_cardonfile_load_multiple_by_uid($order->uid, $payment_method['instance_id']); + } + $add_to_profile = NULL; - // If the Payment Profile creation was a success, store the new card on + if (empty($stored_cards)) { + // We do not, create the profile (which includes the payment details). + if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { + // If the Customer Profile creation was a success, store the new card on // file data locally. if ((string) $response->messages->resultCode == 'Ok') { - // Build a remote ID that includes the Customer Profile ID and the new + // Build a remote ID that includes the Customer Profile ID and the // Payment Profile ID. - $remote_id = $add_to_profile . '|' . (string) $response->customerPaymentProfileId; - - $card_data = commerce_cardonfile_new(); - $card_data->uid = $order->uid; - $card_data->payment_method = $payment_method['method_id']; - $card_data->instance_id = $payment_method['instance_id']; - $card_data->remote_id = $remote_id; - $card_data->card_type = !empty($card_type) ? $card_type : 'card'; - $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; - $card_data->card_number = substr($pane_values['credit_card']['number'], -4); - $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; - $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; - $card_data->status = 1; - - // Save and log the creation of the new card on file. - commerce_cardonfile_save($card_data); - watchdog('commerce_authnet', 'CIM Payment Profile added to Customer Profile @profile_id for user @uid.', array('@profile_id' => $add_to_profile, '@uid' => $order->uid)); + $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; + } + elseif ((string) $response->messages->message->code == 'E00039') { + // But if a Customer Profile already existed for this user, attempt + // instead to add this card as a new Payment Profile to it. + $result = array_filter(explode(' ', (string) $response->messages->message->text), 'is_numeric'); + $add_to_profile = reset($result); } - elseif (!empty($card_data) && (string) $response->messages->message->code == 'E00040') { - // But if we could not find a customer profile, assume the existing - // customer profile ID we had is no longer valid and deactivate the card - // data that resulted in the error. - $card_data->status = 0; - commerce_cardonfile_save($card_data); - - // Submit a CIM request to create the Customer Profile. - if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { - // If the Customer Profile creation was a success, store the new card on - // file data locally. - if ((string) $response->messages->resultCode == 'Ok') { - // Build a remote ID that includes the Customer Profile ID and the - // Payment Profile ID. - $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; - - $card_data = commerce_cardonfile_new(); - $card_data->uid = $order->uid; - $card_data->payment_method = $payment_method['method_id']; - $card_data->instance_id = $payment_method['instance_id']; - $card_data->remote_id = $remote_id; - $card_data->card_type = !empty($card_type) ? $card_type : 'card'; - $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; - $card_data->card_number = substr($pane_values['credit_card']['number'], -4); - $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; - $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; - $card_data->status = 1; - - // Save and log the creation of the new card on file. - commerce_cardonfile_save($card_data); - watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); + } + } + else { + // Extract the user's Customer Profile ID from the first card's remote ID. + $card_data = reset($stored_cards); + list($cim_customer_profile_id, ) = explode('|', $card_data->remote_id); + + // Attempt to add the card as a new payment profile to this Customer Profile. + $add_to_profile = $cim_customer_profile_id; + } + + // Attempt to add the card to an existing Customer Profile if specified. + if (!empty($add_to_profile)) { + $response = commerce_authnet_cim_create_customer_payment_profile_request($payment_method, $add_to_profile, $order, $payment_details); + // If the Payment Profile creation was a success, store the new card on + // file data locally. + if ((string) $response->messages->resultCode == 'Ok' || (string) $response->messages->message->code == 'E00039') { + // If we got a duplicate code, then the payment profile + // at Authorize.Net and needs to be represented locally. + if ((string) $response->messages->message->code == 'E00039') { + $cim_profile_response = commerce_authnet_cim_get_customer_profile_request($payment_method, $add_to_profile); + if ((string) $cim_profile_response->messages->resultCode == 'Ok') { + // Inspect the returned payment profiles to find the one that + // generated the duplicate error code. + $cim_payment_profiles = $cim_profile_response->profile->paymentProfiles; + if (!is_array($cim_payment_profiles)) { + $cim_payment_profiles = array($cim_payment_profiles); + } + + foreach ($cim_payment_profiles as $key => $payment_profile) { + // We match the submitted values against the existing payment + // profiles using the last 4 digits of the card number. This could + // potentially create a conflict if the same customer has two + // different cards that end in the same four digits, but that is + // highly unlikely. + if (substr($pane_values['credit_card']['number'], -4) == substr($payment_profile->payment->creditCard->cardNumber, -4)) { + $payment_profile_id = (string) $payment_profile->customerPaymentProfileId; + break; + } } } } + else { + $payment_profile_id = (string) $response->customerPaymentProfileId; + } + + // Build a remote ID that includes the customer profile ID and the new + // or existing payment profile ID. We don't do any check here to ensure + // we found a payment profile ID, as we shouldn't have got a duplicate + // error if it didn't actually exist. + $remote_id = $add_to_profile . '|' . $payment_profile_id; + } + elseif ($response->messages->message->code == 'E00040') { + // But if we could not find a customer profile, create a new one. + if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { + // If the Customer Profile creation was a success, store the new card on + // file data locally. + if ((string) $response->messages->resultCode == 'Ok') { + // Build a remote ID that includes the Customer Profile ID and the + // Payment Profile ID. + $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; + } + } } } -} -/** - * Implements hook_form_FORM_ID_alter(). - */ -function commerce_authnet_form_commerce_payment_order_transaction_add_form_alter(&$form, &$form_state) { - // If the payment terminal is displayed for an authnet_aim transaction... - if (!empty($form['payment_terminal']) && $form_state['payment_method']['method_id'] == 'authnet_aim') { - // Add a select list to let the administrator choose a different transaction - // type than the payment method's default. - $form['payment_terminal']['payment_details']['txn_type'] = array( - '#type' => 'select', - '#title' => t('Transaction type'), - '#options' => array( - COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only'), - COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'), - ), - '#default_value' => $form_state['payment_method']['settings']['txn_type'], - ); + // We couldn't store a profile. Abandon ship! + if (!$remote_id) { + return FALSE; } + + // Build our card for storing with card on file. + $card_data = commerce_cardonfile_new(); + $card_data->uid = $order->uid; + $card_data->payment_method = $payment_method['method_id']; + $card_data->instance_id = $payment_method['instance_id']; + $card_data->remote_id = $remote_id; + $card_data->card_type = !empty($card_type) ? $card_type : 'card'; + $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; + $card_data->card_number = substr($pane_values['credit_card']['number'], -4); + $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; + $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; + $card_data->status = 1; + + // Save and log the creation of the new card on file. + commerce_cardonfile_save($card_data); + + if ($add_to_profile) { + watchdog('commerce_authnet', 'CIM Payment Profile added to Customer Profile @profile_id for user @uid.', array('@profile_id' => $add_to_profile, '@uid' => $order->uid)); + } + else { + watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); + } + + // Process the payment + return commerce_authnet_cim_cardonfile_charge($payment_method, $card_data, $order, $charge); } /** @@ -708,6 +768,235 @@ function commerce_authnet_form_commerce_cardonfile_update_form_alter(&$form, &$f } } +/** + * Implements hook_form_FORM_ID_alter(). + * + * Adds a commerce customer billing address field to the cardonfile form. + */ +function commerce_authnet_form_commerce_cardonfile_card_form_alter(&$form, &$form_state) { + // Make sure this is the right payment method and the cardonfile create form. + if ($form_state['card']->payment_method == 'authnet_aim' && $form_state['op'] == 'create') { + + // Hide the existing name field. + $form['credit_card']['owner']['#access'] = FALSE; + + // Create a billing profile object and add the address form. + $profile = commerce_customer_profile_new('billing', $form_state['card']->uid); + $form_state['commerce_customer_profile'] = $profile; + $form['commerce_customer_profile'] = array(); + field_attach_form('commerce_customer_profile', $profile, $form['commerce_customer_profile'], $form_state); + + $form['commerce_customer_profile']['#weight'] = -1; + + // Add a validation callback so that we can call field_attach functions. + $form['#validate'][] = 'commerce_authnet_cim_cardonfile_create_validate'; + } +} + +/** + * Validation callback for card on file create. + */ +function commerce_authnet_cim_cardonfile_create_validate($form, &$form_state) { + $profile = $form_state['commerce_customer_profile']; + field_attach_form_validate('commerce_customer_profile', $profile, $form['commerce_customer_profile'], $form_state); +} + +/** + * Commerce Card on File create callback. + * + * @param array $form + * The card on file create form. + * @param array $form_state + * The card on file create form state. + * @param array $payment_method + * The payment method for the card on file request. + * @param object $card_data + * The commerce_cardonfile entity. + * + * @return object|bool + * A new commerce_cardonfile entity or FALSE if there was an error. + */ +function commerce_authnet_cim_cardonfile_create($form, &$form_state, $payment_method, $card_data) { + $account = user_load($card_data->uid); + + // Submit the profile to the field attach handlers. + $profile = $form_state['commerce_customer_profile']; + field_attach_submit('commerce_customer_profile', $profile, $form['commerce_customer_profile'], $form_state); + commerce_customer_profile_save($profile); + + // Format the address into the Auth.net schema. + $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); + $billto = commerce_authnet_cim_billto_array(NULL, $profile_wrapper->commerce_customer_address->value()); + + // Build the card data to submit to Auth.net. + $number = $form_state['values']['credit_card']['number']; + $card_data->card_exp_month = $form_state['values']['credit_card']['exp_month']; + $card_data->card_exp_year = $form_state['values']['credit_card']['exp_year']; + $card_expire = $card_data->card_exp_year . '-' . $card_data->card_exp_month; + $card_code = $form_state['values']['credit_card']['code']; + $card_type = $form_state['values']['credit_card']['type']; + + // Attempt to load and existing profile id from the customers card data. + $existing_cards = commerce_cardonfile_load_multiple_by_uid($account->uid, $payment_method['instance_id']); + + // Check for a remote ID. + $remote_id = NULL; + if (!empty($existing_cards)) { + $existing_card = reset($existing_cards); + $remote_id = $existing_card->remote_id; + } + + if ($remote_id) { + // Extract the profile id from the remote id. + list($cim_customer_profile_id, $cim_customer_payment_id) = explode('|', $remote_id); + + // Build a request to add a new payment method to an existing profile. + $api_request_data = array( + 'customerProfileId' => $cim_customer_profile_id, + 'paymentProfile' => array( + 'billTo' => $billto, + 'payment' => array( + 'creditCard' => array( + 'cardNumber' => $number, + 'expirationDate' => $card_expire, + 'cardCode' => $card_code, + ), + ), + ), + ); + + $xml_response = commerce_authnet_cim_request($payment_method, 'createCustomerPaymentProfileRequest', $api_request_data); + + } + else { + // There isn't a profile ID to extract + $cim_customer_profile_id = NULL; + + // Build a request to create a profile and add payment method to it. + $api_request_data = array( + 'profile' => array( + 'merchantCustomerId' => $account->uid, + 'description' => $billto['firstName'] . ' ' . $billto['lastName'], + 'email' => $account->mail, + 'paymentProfiles' => array( + 'billTo' => $billto, + 'payment' => array( + 'creditCard' => array( + 'cardNumber' => $number, + 'expirationDate' => $card_expire, + 'cardCode' => $card_code, + ), + ), + ), + ), + ); + + $xml_response = commerce_authnet_cim_request($payment_method, 'createCustomerProfileRequest', $api_request_data); + } + + if ((string) $xml_response->messages->resultCode == 'Ok') { + // Build a remote ID that includes the Customer Profile ID and the + // Payment Profile ID. + if ($cim_customer_profile_id) { + $remote_id = $cim_customer_profile_id . '|' . (string) $xml_response->customerPaymentProfileId; + } + else { + $remote_id = (string) $xml_response->customerProfileId . '|' . (string) $xml_response->customerPaymentProfileIdList->numericString; + } + + $card_data_wrapper = entity_metadata_wrapper('commerce_cardonfile', $card_data); + $card_data->uid = $account->uid; + $card_data->remote_id = $remote_id; + $card_data->card_type = $card_type; + $card_data->card_name = $billto['firstName'] . ' ' . $billto['lastName']; + $card_data->card_number = substr($number, -4); + $card_data->status = 1; + $card_data_wrapper->commerce_cardonfile_profile = $profile; + + return $card_data; + + } + elseif ((string) $xml_response->messages->message->code == 'E00039') { + // But if a Customer Profile already existed for this user, attempt + // instead to add this card as a new Payment Profile to it. + $result = array_filter(explode(' ', (string) $xml_response->messages->message->text), 'is_numeric'); + $add_to_profile = reset($result); + + // Build a request to add a new payment method to an existing profile. + $api_request_data = array( + 'customerProfileId' => $add_to_profile, + 'paymentProfile' => array( + 'billTo' => $billto, + 'payment' => array( + 'creditCard' => array( + 'cardNumber' => $number, + 'expirationDate' => $card_expire, + 'cardCode' => $card_code, + ), + ), + ), + ); + + $xml_response = commerce_authnet_cim_request($payment_method, 'createCustomerPaymentProfileRequest', $api_request_data); + + if ((string) $xml_response->messages->resultCode == 'Ok' || (string) $xml_response->messages->message->code == 'E00039') { + // If we got a duplicate code, then the payment profile already exists + // at Authorize.Net and needs to be represented locally. + if ((string) $xml_response->messages->message->code == 'E00039') { + $cim_profile_response = commerce_authnet_cim_get_customer_profile_request($payment_method, $add_to_profile); + if ((string) $cim_profile_response->messages->resultCode == 'Ok') { + // Inspect the returned payment profiles to find the one that + // generated the duplicate error code. + $cim_payment_profiles = $cim_profile_response->profile->paymentProfiles; + if (!is_array($cim_payment_profiles)) { + $cim_payment_profiles = array($cim_payment_profiles); + } + + foreach ($cim_payment_profiles as $key => $payment_profile) { + // We match the submitted values against the existing payment + // profiles using the last 4 digits of the card number. This could + // potentially create a conflict if the same customer has two + // different cards that end in the same four digits, but that is + // highly unlikely. + if (substr($number, -4) == substr($payment_profile->payment->creditCard->cardNumber, -4)) { + $payment_profile_id = (string) $payment_profile->customerPaymentProfileId; + break; + } + } + } + } + else { + $payment_profile_id = (string) $xml_response->customerPaymentProfileId; + } + + // Build a remote ID that includes the customer profile ID and the new + // or existing payment profile ID. We don't do any check here to ensure + // we found a payment profile ID, as we shouldn't have got a duplicate + // error if it didn't actually exist. + $remote_id = $add_to_profile . '|' . $payment_profile_id; + + $card_data_wrapper = entity_metadata_wrapper('commerce_cardonfile', $card_data); + $card_data->uid = $account->uid; + $card_data->remote_id = $remote_id; + $card_data->card_type = $card_type; + $card_data->card_name = $billto['firstName'] . ' ' . $billto['lastName']; + $card_data->card_number = substr($number, -4); + $card_data->status = 1; + $card_data_wrapper->commerce_cardonfile_profile = $profile; + + return $card_data; + } + else { + // Provide the user with information on the failure if it exists. + if (!empty($xml_response->messages->message->text)) { + drupal_set_message(t('Error: @error', array('@error' => (string) $xml_response->messages->message->text)), 'error'); + } + } + } + + return FALSE; +} + /** * Card on file callback: background charge payment * @@ -749,7 +1038,7 @@ function commerce_authnet_cim_cardonfile_charge($payment_method, $card_data, $or ), ), ), - 'extraOptions' => '', + 'extraOptions' => '', ); // If we get a response from the API server... @@ -811,7 +1100,7 @@ function commerce_authnet_cim_cardonfile_charge($payment_method, $card_data, $or return FALSE; } - return; + return (isset($transaction->status)) ? $transaction->status : TRUE; } elseif ((string) $xml_response->messages->message->code == 'E00040') { // If the response indicated a non-existent profile, deactive it now. @@ -834,7 +1123,8 @@ function commerce_authnet_cim_cardonfile_update($form, &$form_state, $payment_me // Extract the Customer Profile and Payment Profile IDs from the remote_id. list($cim_customer_profile_id, $cim_payment_profile_id) = explode('|', $card_data->remote_id); - if ($form_state['values']['credit_card']['number'] != $form['credit_card']['number']['#default_value']) { + if (!empty($form_state['values']['credit_card']['number']) && + $form_state['values']['credit_card']['number'] != $form['credit_card']['number']['#default_value']) { $number = $form_state['values']['credit_card']['number']; } else { @@ -904,14 +1194,20 @@ function commerce_authnet_cim_cardonfile_delete($form, &$form_state, $payment_me * * @param $order * The order object containing the billing information used for the billTo. + * @param $billing_address + * Optional. A commerce_customer_address field value array to use when the + * order object is empty; useful for generating an API request when you do not + * have an order object. * * @return * An array used to generate the billTo XML in CIM API requests. */ -function commerce_authnet_cim_billto_array($order) { - // Prepare the billing address for use in the request. - $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); +function commerce_authnet_cim_billto_array($order, $billing_address = NULL) { + // If an order was given, prepare the billing address for use in this request. + if (!empty($order)) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); + } // Ensure we have a first and last name in the address. if (empty($billing_address['first_name'])) { @@ -925,6 +1221,11 @@ function commerce_authnet_cim_billto_array($order) { $billing_address['administrative_area'] = $billing_address['locality']; } + // Ensure organisation name is keyed. + if (!isset($billing_address['organisation_name'])) { + $billing_address['organisation_name'] = ''; + } + // Return the billTo array. return array( 'firstName' => substr($billing_address['first_name'], 0, 50), @@ -938,6 +1239,39 @@ function commerce_authnet_cim_billto_array($order) { ); } +/** + * Generates a shipTo array for CIM API requests. + * + * @param $order + * The order object containing the shipping information used for the billTo. + * + * @return + * An array used to generate the shipTo XML in CIM API requests. + */ +function commerce_authnet_cim_shipto_array($order) { + // Prepare the shipping address for use in the request. + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $shipping_address = $order_wrapper->commerce_customer_shipping->commerce_customer_address->value(); + + // Ensure we have a first and last name in the address. + if (empty($shipping_address['first_name'])) { + $name_parts = explode(' ', $shipping_address['name_line']); + $shipping_address['first_name'] = array_shift($name_parts); + $shipping_address['last_name'] = implode(' ', $name_parts); + } + + // Return the shipTo array. + return array( + 'firstName' => substr($shipping_address['first_name'], 0, 50), + 'lastName' => substr($shipping_address['last_name'], 0, 50), + 'company' => substr($shipping_address['organisation_name'], 0, 50), + 'address' => substr($shipping_address['thoroughfare'], 0, 60), + 'city' => substr($shipping_address['locality'], 0, 40), + 'state' => substr($shipping_address['administrative_area'], 0, 40), + 'zip' => substr($shipping_address['postal_code'], 0, 20), + 'country' => $shipping_address['country'], + ); +} /** * Generates a creditCard array for CIM API requests. * @@ -997,6 +1331,15 @@ function commerce_authnet_cim_create_customer_profile_request($payment_method, $ ), ); + // Add the shipping address if available. + if (isset($order->commerce_customer_shipping)) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + + if ($order_wrapper->commerce_customer_shipping->value()) { + $api_request_data['profile']['shipToList'] = commerce_authnet_cim_shipto_array($order); + } + } + // If the order is anonymous, unset the merchantCustomerId from the request. if (empty($api_request_data['profile']['merchantCustomerId'])) { unset($api_request_data['profile']['merchantCustomerId']); @@ -1054,6 +1397,27 @@ function commerce_authnet_cim_create_customer_payment_profile_request($payment_m return commerce_authnet_cim_request($payment_method, 'createCustomerPaymentProfileRequest', $api_request_data); } +/** + * Submits a request to retrieve a Customer profile's payment profiles + * + * This is useful when Authorize.net returns an error code of E00039, duplicate. + * + * @param $payment_method + * @param $cim_customer_profile_id + * @param $number + * + * @return bool|string + * Returns FALSE if not payment profile found, or the matching profile ID. + */ +function commerce_authnet_cim_get_customer_profile_request($payment_method, $cim_customer_profile_id) { + // Query for the profile's payment profiles. + $api_request_data = array( + 'customerProfileId' => $cim_customer_profile_id, + ); + + return commerce_authnet_cim_request($payment_method, 'getCustomerProfileRequest', $api_request_data); +} + /** * Submits a getCustomerPaymentProfileRequest XML CIM API request to Authorize.Net. * @@ -1132,7 +1496,7 @@ function commerce_authnet_aim_request($payment_method, $nvp = array()) { ); // Allow modules to alter parameters of the API request. - drupal_alter('commerce_authnet_aim_request', $nvp); + drupal_alter('commerce_authnet_aim_request', $nvp, $payment_method); // Log the request if specified. if ($payment_method['settings']['log']['request'] == 'request') { @@ -1206,9 +1570,9 @@ function commerce_authnet_aim_server_url($txn_mode) { switch ($txn_mode) { case AUTHNET_TXN_MODE_LIVE: case AUTHNET_TXN_MODE_LIVE_TEST: - return 'https://secure.authorize.net/gateway/transact.dll'; + return variable_get('commerce_authnet_aim_server_url_live', 'https://secure2.authorize.net/gateway/transact.dll'); case AUTHNET_TXN_MODE_DEVELOPER: - return 'https://test.authorize.net/gateway/transact.dll'; + return variable_get('commerce_authnet_aim_server_url_dev', 'https://test.authorize.net/gateway/transact.dll'); } } @@ -1270,7 +1634,7 @@ function commerce_authnet_cim_request($payment_method, $request_type, $api_reque commerce_simplexml_add_children($api_request_element, $api_request_data); // Allow modules an opportunity to alter the request before it is sent. - drupal_alter('commerce_authnet_cim_request', $api_request_element); + drupal_alter('commerce_authnet_cim_request', $api_request_element, $payment_method, $request_type); // Generate an XML string. $xml = $api_request_element->asXML(); @@ -1306,7 +1670,6 @@ function commerce_authnet_cim_request($payment_method, $request_type, $api_reque // Build the array of header information for the request. $header = array(); $header[] = 'Content-type: text/xml; charset=utf-8'; - $header[] = 'Content-length: ' . strlen($api_request_element->asXML()); // Setup the cURL request. $ch = curl_init(); @@ -1361,9 +1724,9 @@ function commerce_authnet_cim_server_url($txn_mode) { switch ($txn_mode) { case AUTHNET_TXN_MODE_LIVE: case AUTHNET_TXN_MODE_LIVE_TEST: - return 'https://api.authorize.net/xml/v1/request.api'; + return variable_get('commerce_authnet_cim_server_url_live', 'https://api2.authorize.net/xml/v1/request.api'); case AUTHNET_TXN_MODE_DEVELOPER: - return 'https://apitest.authorize.net/xml/v1/request.api'; + return variable_get('commerce_authnet_cim_server_url_dev', 'https://apitest.authorize.net/xml/v1/request.api'); } } @@ -1413,7 +1776,7 @@ function commerce_authnet_cim_transaction_element_name($txn_type) { return 'profileTransCaptureOnly'; case COMMERCE_CREDIT_PRIOR_AUTH_CAPTURE: return 'profileTransPriorAuthCapture'; - case COMMERCE_CREDIT_CREDT: + case COMMERCE_CREDIT_CREDIT: return 'profileTransRefund'; case COMMERCE_CREDIT_VOID: return 'profileTransVoid'; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_authnet/includes/commerce_authnet.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_authnet/includes/commerce_authnet.admin.inc index 14fcca99..ae2955ce 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_authnet/includes/commerce_authnet.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_authnet/includes/commerce_authnet.admin.inc @@ -179,6 +179,11 @@ function commerce_authnet_aim_void_form_submit($form, &$form_state) { // Update the transaction message to show that it has been voided. $transaction->message .= '
' . t('Voided: @date', array('@date' => format_date(REQUEST_TIME, 'short'))); + + // Zero out transaction amount before saving, + // so original amount is not saved as a positive revenue transaction. + // Authorize.Net settles these as "0.00" (see the payload on a void response). + $transaction->amount = '000'; } else { drupal_set_message(t('Void failed: @reason', array('@reason' => check_plain($response[3]))), 'error'); @@ -258,6 +263,24 @@ function commerce_authnet_aim_credit_form_submit($form, &$form_state) { // Determine the last 4 credit card digits from the previous transaction. $transaction_payload = end($transaction->payload); + + // If this payment transaction does not have credit card digits, we will check + // for a previous transaction that has the information. + if (empty($transaction_payload[50])) { + + // We reverse the array because Voids do not contain credit card digits, + // and we will have to cycle through fewer transactions this way. + $transaction_payloads = array_reverse($transaction->payload); + foreach ($transaction_payloads as $transaction_id => $payload) { + if ($payload[0] == 1 && !empty($payload[50])) { + + // This payload has the last four digits. + $transaction_payload = $payload; + break; + } + } + } + $credit_card = !empty($transaction_payload[50]) ? substr($transaction_payload[50], 4, 8) : ''; // Make sure that the last 4 digits are available and valid. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_autosku/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_autosku/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_autosku/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_autosku/commerce_autosku.info b/profiles/commerce_kickstart/modules/contrib/commerce_autosku/commerce_autosku.info index 2cdd6200..10339a26 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_autosku/commerce_autosku.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_autosku/commerce_autosku.info @@ -10,10 +10,9 @@ core = 7.x files[] = commerce_autosku.test - -; Information added by drush on 2015-08-20 -version = "7.x-1.1+12-dev" +; Information added by Drupal.org packaging script on 2016-08-02 +version = "7.x-1.2" core = "7.x" project = "commerce_autosku" -datestamp = "1440110608" +datestamp = "1470142446" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/LICENSE.txt old mode 100755 new mode 100644 diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice.info b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice.info index 194fe263..f1057379 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice.info @@ -7,9 +7,9 @@ files[] = includes/views/handlers/commerce_backoffice_handler_filter_term_node_t files[] = includes/views/handlers/commerce_backoffice_handler_field_term_entity_tid.inc files[] = includes/views/handlers/commerce_backoffice_handler_field_node_operations.inc -; Information added by Drupal.org packaging script on 2013-12-24 -version = "7.x-1.4" +; Information added by Drupal.org packaging script on 2015-10-14 +version = "7.x-1.5" core = "7.x" project = "commerce_backoffice" -datestamp = "1387900413" +datestamp = "1444858742" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_content.info b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_content.info index 4f279c85..44d4a85a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_content.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_content.info @@ -11,9 +11,9 @@ dependencies[] = views_bulk_operations files[] = includes/views/handlers/commerce_backoffice_content_filter_node_type.inc files[] = includes/views/plugins/commerce_backoffice_content_plugin_display_system.inc -; Information added by Drupal.org packaging script on 2013-12-24 -version = "7.x-1.4" +; Information added by Drupal.org packaging script on 2015-10-14 +version = "7.x-1.5" core = "7.x" project = "commerce_backoffice" -datestamp = "1387900413" +datestamp = "1444858742" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.info b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.info index d5902d61..20c59073 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.info @@ -18,9 +18,9 @@ dependencies[] = views_megarow files[] = includes/views/handlers/commerce_backoffice_order_handler_field_order_operations.inc -; Information added by Drupal.org packaging script on 2013-12-24 -version = "7.x-1.4" +; Information added by Drupal.org packaging script on 2015-10-14 +version = "7.x-1.5" core = "7.x" project = "commerce_backoffice" -datestamp = "1387900413" +datestamp = "1444858742" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.module b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.module index 6633edcf..0608ba18 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_order.module @@ -129,7 +129,7 @@ function commerce_backoffice_order_field_extra_fields() { * Implements hook_entity_view(). */ function commerce_backoffice_order_entity_view($entity, $entity_type, $view_mode, $langcode) { - if ($entity_type == 'commerce_order' && $view_mode == 'backoffice') { + if ($entity_type == 'commerce_order' && in_array($view_mode, array('administrator', 'backoffice'))) { $status_form = drupal_get_form('commerce_backoffice_order_status_form', $entity); // Bubble-up the signal for refreshing the parent row. if (!empty($status_form['#megarow_refresh_parent'])) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.info b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.info index 7f439de7..c48b7d94 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.info @@ -8,12 +8,14 @@ dependencies[] = commerce_product dependencies[] = commerce_product_ui dependencies[] = views_bulk_operations dependencies[] = views_megarow +dependencies[] = commerce_product_reference files[] = includes/views/handlers/commerce_backoffice_product_handler_field_product_quick_edit_form.inc +files[] = commerce_backoffice_product.test -; Information added by Drupal.org packaging script on 2013-12-24 -version = "7.x-1.4" +; Information added by Drupal.org packaging script on 2015-10-14 +version = "7.x-1.5" core = "7.x" project = "commerce_backoffice" -datestamp = "1387900413" +datestamp = "1444858742" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.test b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.test new file mode 100644 index 00000000..6603e9a5 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/commerce_backoffice_product.test @@ -0,0 +1,109 @@ +randomName(); + $type = strtolower($name); + $edit = array( + 'product_type[name]' => $name, + 'product_type[type]' => $type, + ); + $this->drupalPost('admin/commerce/config/product-variation-types/add', $edit, t('Save product variation type')); + commerce_product_types_reset(); + $variation_type = commerce_product_type_load($type); + $this->assertEqual($variation_type['type'], $type, 'Type saved.'); + + // Use the inline_entity_form widget + $instance = field_info_instance('node', 'field_product', $variation_type['type']); + $instance['widget'] = array( + 'active' => 1, + 'module' => 'inline_entity_form', + 'settings' => array( + 'fields' => array(), + 'type_settings' => array( + 'allow_existing' => 0, + 'autogenerate_title' => 1, + 'delete_references' => 1, + 'match_operator' => 'CONTAINS', + 'use_variation_language' => 1, + ), + ), + 'type' => 'inline_entity_form', + ); + field_update_instance($instance); + + return $variation_type; + } + + /** + * Create a new product display. + * + * @param $variation_type + * The machine name of the product type. + */ + function createProductDisplay($variation_type) { + $title = $this->randomName(); + $sku = $this->randomName(); + $price = mt_rand(1, 100); + $edit = array( + 'title' => $title, + 'field_product[und][form][sku]' => $sku, + 'field_product[und][form][commerce_price][und][0][amount]' => $price, + ); + $this->drupalPost('node/add/' . $variation_type, $edit, t('Save')); + + $node = $this->drupalGetNodeByTitle($title); + $wrapper = entity_metadata_wrapper('node', $node); + $this->assertEqual($wrapper->field_product[0]->sku->value(), $sku, 'Sku saved.'); + $this->assertEqual($wrapper->field_product[0]->commerce_price->amount->value(), $price * 100); + + return $node; + } +} + +/** + * Tests the commerce backoffice product interface. + */ +class CommerceBackofficeProductTestCase extends CommerceBackofficeProductWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Commerce backoffice product interface', + 'description' => 'Test the commerce backoffice product interface.', + 'group' => 'Commerce backoffice product', + ); + } + + function setUp() { + parent::setUp('inline_entity_form', 'commerce_backoffice_product'); + + $this->admin_user = $this->drupalCreateUser(array('administer product types')); + $this->drupalLogin($this->admin_user); + $this->variationType = $this->createProductVariationType(); + node_type_cache_reset(); + } + + /** + * Create, display a product display var the user interface. + */ + function testProductDisplay() { + // Create a product display. + drupal_static_reset('checkPermissions'); + $admin_user = $this->drupalCreateUser(array('create ' . $this->variationType['type'] . ' content', + 'create commerce_product entities of bundle ' . $this->variationType['type'])); + $this->drupalLogin($admin_user); + $productDisplay = $this->createProductDisplay($this->variationType['type']); + + // visit the product display. + $this->drupalGet('node/' . $productDisplay->nid); + $this->assertResponse(200); + } +} + diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/theme/commerce-backoffice.css b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/theme/commerce-backoffice.css index be606239..c9e52526 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/theme/commerce-backoffice.css +++ b/profiles/commerce_kickstart/modules/contrib/commerce_backoffice/theme/commerce-backoffice.css @@ -80,7 +80,7 @@ } .commerce-backoffice-view .vbo-views-form select { float: left; - height: 25px; + min-height: 25px; margin: 2px 10px 2px 0; max-width: 250px; } @@ -88,7 +88,7 @@ position: relative; } .commerce-backoffice-view .vbo-views-form #edit-select select { - height: 25px; + min-height: 25px; } /* Mega Row Styles */ .commerce-backoffice-view div.views-megarow-content { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/LICENSE.txt old mode 100755 new mode 100644 diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/README.txt b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/README.txt new file mode 100644 index 00000000..3b1e219c --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/README.txt @@ -0,0 +1,19 @@ +Commerce Progress Checkout +========================== + +commerce_progress_checkout is a module that provides visual clues +about the progress of the checkout procedure for Drupal Commerce. + +Upgrade to 7.x-1.4: Links are now disabled by default +----------------------------------------------------- + +If you're upgrading to 7.x-1.4, you'll notice that the links on the +checkout pages are disabled. This is done to guarantee data integrity. + +The fact that the user can move around the checkout process phases +using the links causes problems coming from the fact that these are +GET requests and thus interact with the browser cache and possibly +invite issues like duplicated payments and incorrect address data into +the checkout. + +You can turn them back on at any time using in the block configuration. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.admin.inc index f88f86be..93cf38a5 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.admin.inc @@ -1,4 +1,5 @@ $page) { - $options[$page_id] = t($page['title']); - } - - $path['pages']['#options'] = $options; + $path['pages']['#options'] = commerce_checkout_progress_block_pages_options(); $path['pages']['#default_value'] = variable_get('commerce_checkout_progress_block_pages', array()); - // Add our custom submit handler so that we can process the 'pages' array into text. + // Add our custom submit handler so that we can process the 'pages' array into + // text. array_unshift($form['#submit'], 'commerce_checkout_progress_form_block_admin_configure_submit'); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.css b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.css index 96a4538d..e0a95408 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.css +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.css @@ -1,5 +1,5 @@ .commerce-checkout-progress li.active { - font-weight:bold; + font-weight: bold; } /** diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.info b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.info index 57212fbf..cdf06c34 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.info @@ -8,9 +8,9 @@ core = 7.x ; Files containing class or interface declarations ; files[] = -; Information added by packaging script on 2013-11-15 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2016-12-09 +version = "7.x-1.5" core = "7.x" project = "commerce_checkout_progress" -datestamp = "1384511307" +datestamp = "1481276590" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.install b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.install index 697d0c33..5a68f63b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.install @@ -1,4 +1,5 @@ $configuration_link)), 'warning'); + return st('Commerce Progress Checkout Links are currently enabled. It is recommended to disable them.'); + } + else { + return st('Commerce Progress Checkout links already disabled.'); + } +} + +/** + * Implements hook_requirements(). + */ +function commerce_checkout_progress_requirements($phase) { + $requirements = array(); + + if ($phase == 'runtime') { + // Link for checkout progress links. + $configuration_link = l(t('disable'), 'admin/commerce/config/checkout'); + + // Verify the current setup of the links in commerce_checkout_progress. + if (variable_get('commerce_checkout_progress_link', FALSE)) { + $value = t('Links enabled'); + $description = t('Commerce Checkout Progress links are active on the checkout process. You should !configure it to avoid data integrity problems during checkout.', array('!configure' => $configuration_link)); + $severity = REQUIREMENT_WARNING; + } + else { + $value = t('Links disabled'); + $description = ''; + $severity = REQUIREMENT_OK; + } + + $requirements['commerce_checkout_progress'] = array( + 'title' => st('Commerce Checkout Progress'), + 'value' => $value, + 'severity' => $severity, + 'description' => $description, + ); + } + + return $requirements; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.module b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.module index 6f2155ec..fdea2902 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_progress/commerce_checkout_progress.module @@ -1,29 +1,48 @@ array( 'info' => t('Checkout progress indication'), 'cache' => DRUPAL_NO_CACHE, - 'status' => 1, // If you enabled this module, you want the block + ), + ); + + if (module_exists('block')) { + $block['indication'] += array( + // If you enabled this module, you want the block. + 'status' => 1, 'region' => 'content', - 'weight' => -10, // on top - 'visibility' => BLOCK_VISIBILITY_LISTED, // Show on only the listed pages + // On top. + 'weight' => -10, + // Show on only the listed pages. + 'visibility' => BLOCK_VISIBILITY_LISTED, 'pages' => "checkout/*\ncart", - ) - ); + ); + } + + return $block; +} + +/** + * Implements hook_ctools_block_info(). + */ +function commerce_checkout_progress_ctools_block_info($module, $delta, &$info) { + $info['render last'] = TRUE; + $info['category'] = t('Miscellaneous'); } /** @@ -31,41 +50,33 @@ function commerce_checkout_progress_block_info() { */ function commerce_checkout_progress_block_view($delta = 'indication') { if ($delta === 'indication') { - // Get the current page that was created/stored earlier by commerce checkout. + // Get the current page that was created/stored earlier by commerce + // checkout. $page_id = commerce_checkout_progress_commerce_checkout_router(); + + if (is_null($page_id)) { + $page_id = 'cart'; + } $block_pages = variable_get('commerce_checkout_progress_block_pages', array()); - // on block configuration page, use select nothing, it mean, we need - // render the progress bar on all checkout pages. - $visible = empty($block_pages); - - // user selected some pages - if (!$visible) { - // if current page checkout page ID in configured-pages, the progress bar - // is visible - foreach ($block_pages as $block_page) { - if ($block_page == $page_id) { - $visible = TRUE; - break; - } - } + // Get the option list that is shown during block configuration, since 'no + // selection' means 'everything is selected'. + if (empty($block_pages)) { + $block_pages_options = commerce_checkout_progress_block_pages_options(); + $block_pages = array_keys($block_pages_options); } - if ($visible) { + // If current page checkout page ID in configured-pages, the progress bar + // is visible. + if (in_array($page_id, $block_pages)) { // Get non-empty pages. $pages = commerce_checkout_progress_get_items(); - if (empty($page_id)) { - if (!isset($pages['cart'])) { - return; - } - $page_id = 'cart'; - } $content = array( '#theme' => 'commerce_checkout_progress_list', '#items' => $pages, '#type' => variable_get('commerce_checkout_progress_list_type', 'ol'), '#current_page' => $page_id, - '#link' => variable_get('commerce_checkout_progress_link', TRUE), + '#link' => variable_get('commerce_checkout_progress_link', FALSE), ); return array( 'subject' => '', @@ -97,16 +108,13 @@ function commerce_checkout_progress_form_block_admin_configure_alter(&$form, $fo * checkout page doesn't have an arg(2). * * @see commerce_checkout_router() - * The function performs module_invoke_all('commerce_checkout_router', $order, $checkout_page); - * - * @return (text) - * Page ID of the current checkout page + * The function performs + * module_invoke_all('commerce_checkout_router', $order, $checkout_page); */ function commerce_checkout_progress_commerce_checkout_router($order = NULL, $checkout_page = NULL) { // There's still no official way to get the current checkout page ID. - // The function is called before block rendering function. - // avoid save value to user session, we save it to this function static - // variable. + // The function is called before block rendering function. Avoid save value to + // user session, we save it to this function static variable. static $page_id = NULL; if (isset($checkout_page['page_id'])) { @@ -125,8 +133,7 @@ function commerce_checkout_progress_theme($existing, $type, $theme, $path) { 'variables' => array( 'items' => NULL, 'type' => NULL, - 'attributes' => array(), - 'link' => TRUE, + 'link' => FALSE, 'current_page' => NULL, ), ), @@ -136,31 +143,34 @@ function commerce_checkout_progress_theme($existing, $type, $theme, $path) { /** * Theming function for checkout progress item list. * - * @param $variables + * @param array $variables * An associative array containing: - * - items: Array of items to be displayed in the list. The key is - * the Page ID. The value is the Page Title. + * - items: Array of items to be displayed in the list. The key is the Page + * ID. The value is the Page Title. * - type: The type of list to return (e.g. "ul", "ol"). - * - attributes: The attributes applied to the list element. * - link: (bool) List should contain links to previously visited pages. * - current_page: The page ID of the current page. + * + * @return string + * The HTML string. */ function theme_commerce_checkout_progress_list($variables) { $path = drupal_get_path('module', 'commerce_checkout_progress'); drupal_add_css($path . '/commerce_checkout_progress.css'); - extract($variables); + $items = $variables['items']; + $type = $variables['type']; + $link = $variables['link']; + $current_page = $variables['current_page']; // Option to display back pages as links. if ($link) { - // Load the *shopping cart* order. It gets deleted on last page. - if (module_exists('commerce_cart') && $order = commerce_cart_order_load($GLOBALS['user']->uid)) { + if ($order = menu_get_object('commerce_order')) { $order_id = $order->order_id; } - // If we don't have the Cart module and are on the checkout page, load the - // order from the arguments. - elseif (arg(0) == 'checkout' && $order_id = arg(1)) { - $order = commerce_order_load($order_id); + // Load the *shopping cart* order. It gets deleted on last page. + elseif (module_exists('commerce_cart') && $order = commerce_cart_order_load($GLOBALS['user']->uid)) { + $order_id = $order->order_id; } } @@ -186,14 +196,15 @@ function theme_commerce_checkout_progress_list($variables) { $data = t($page['title']); if ($visited) { - $class[] = 'visited'; // Issue #1345942. + // Issue #1345942. + $class[] = 'visited'; // On checkout complete page, the checkout order is deleted. if (isset($order_id) && $order_id) { - // If a user is on step 1, clicking a link next steps will be redirect them back. - // Only render the link on the pages those user has already been on. - // Make sure the loaded order is the same one found in the URL. - if (arg(1) == $order_id) { + // If a user is on step 1, clicking a link next steps will be redirect + // them back. Only render the link on the pages those user has already + // been on. Make sure the loaded order is the same one found in the URL. + if (arg(1) == $order_id && ($page_id == 'cart' || commerce_checkout_page_access($page, $order))) { $href = isset($page['href']) ? $page['href'] : "checkout/{$order_id}/{$page_id}"; $data = l(filter_xss($data), $href, array('html' => TRUE)); } @@ -206,7 +217,7 @@ function theme_commerce_checkout_progress_list($variables) { ); // Only set li title if the page has help text. if (isset($page['help'])) { - //#1322436 Filter help text to be sure it contains NO html. + // #1322436 Filter help text to be sure it contains NO html. $help = strip_tags($page['help']); // Make sure help has text event after filtering html. if (!empty($help)) { @@ -233,7 +244,7 @@ function theme_commerce_checkout_progress_list($variables) { /** * Implements hook_form_FORM_ID_alter(). * - * @see commerce_checkout_progress_get_items(). + * @see commerce_checkout_progress_get_items() */ function commerce_checkout_progress_form_commerce_checkout_builder_form_alter(&$form, $form_state, $form_id) { $form['commerce_checkout_progress'] = array( @@ -251,7 +262,8 @@ function commerce_checkout_progress_form_commerce_checkout_builder_form_alter(&$ 'commerce_checkout_progress_link' => array( '#type' => 'checkbox', '#title' => t('Render list as links'), - '#default_value' => variable_get('commerce_checkout_progress_link', TRUE), + '#description' => t("If enabled this could cause problems in the checkout process. It's strongly advised to leave this disabled."), + '#default_value' => variable_get('commerce_checkout_progress_link', FALSE), ), 'commerce_checkout_progress_cart' => array( '#type' => 'checkbox', @@ -266,34 +278,60 @@ function commerce_checkout_progress_form_commerce_checkout_builder_form_alter(&$ /** * Custom submit function to save module settings after form submit. * - * @see commerce_checkout_progress_form_commerce_checkout_builder_form_alter(). - * @see commerce_checkout_progress_get_items(). + * @see commerce_checkout_progress_form_commerce_checkout_builder_form_alter() + * @see commerce_checkout_progress_get_items() */ function commerce_checkout_progress_form_commerce_checkout_builder_form_submit($form, $form_state) { - // save module settings + // Save module settings. variable_set('commerce_checkout_progress_list_type', $form_state['values']['commerce_checkout_progress_list_type']); variable_set('commerce_checkout_progress_link', $form_state['values']['commerce_checkout_progress_link']); variable_set('commerce_checkout_progress_cart', $form_state['values']['commerce_checkout_progress_cart']); } +/** + * Get the list of items that COULD be shown in the indicator. + * + * @return array + * Item list. + */ +function commerce_checkout_progress_block_pages_options() { + $options = array(); + + // Check if the cart module is enabled, and add it as an option if it is. + if (module_exists('commerce_cart')) { + $options['cart'] = t('Cart'); + } + + // Grab possibilities from commerce. + $pages = commerce_checkout_pages(); + foreach ($pages as $page_id => $page) { + $options[$page_id] = t($page['title']); + } + return $options; +} + /** * Get checkout pages that have content and do not redirect. - * @see commerce_checkout_progress_form_commerce_checkout_builder_form_alter(). + * + * @see commerce_checkout_progress_form_commerce_checkout_builder_form_alter() */ function commerce_checkout_progress_get_items() { - // get checkout pages & panes + // Get checkout pages & panes. $pages = commerce_checkout_pages(); $panes = commerce_checkout_panes(); foreach ($panes as $id => $pane) { // Panes could exist in removed pages (through // hook_commerce_checkout_page_info_alter()). - if (isset($pages[$pane['page']])) { + if (!empty($pane['enabled']) && isset($pages[$pane['page']])) { // Pane has page ID, use this value to flag that the page is not empty. - $pages[$pane['page']]['has_item'] = TRUE; + $pages[$pane['page']]['has_item'] = isset($pages[$pane['page']]['has_item']) ? $pages[$pane['page']]['has_item'] : TRUE; } } + // Allow other modules to alter the enabled checkout pages. + drupal_alter('commerce_checkout_progress_get_items', $pages); + $items = array(); foreach (array_keys($pages) as $page_id) { if (!empty($pages[$page_id]['has_item'])) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.info b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.info index 68ca457e..dc5e01f1 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.info @@ -5,9 +5,9 @@ dependencies[] = commerce_cart core = 7.x configure = admin/commerce/config/checkout_redirect -; Information added by Drupal.org packaging script on 2014-07-23 -version = "7.x-2.0-rc1" +; Information added by Drupal.org packaging script on 2016-11-29 +version = "7.x-2.0" core = "7.x" project = "commerce_checkout_redirect" -datestamp = "1406127529" +datestamp = "1480426089" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.module b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.module index 0ebef14f..c8208945 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_checkout_redirect/commerce_checkout_redirect.module @@ -69,7 +69,7 @@ function commerce_checkout_redirect_settings($form, &$form_state) { '#default_value' => variable_get('commerce_checkout_redirect_username_as_order_email', FALSE), '#states' => array ( 'visible' => array( - ':input[name="commerce_checkout_redirect_anonymous"]' => array('checked' => TRUE), + ':input[name="commerce_checkout_redirect_anonymous"]' => array('checked' => true), ), ), ); @@ -80,7 +80,7 @@ function commerce_checkout_redirect_settings($form, &$form_state) { '#default_value' => variable_get('commerce_checkout_redirect_anonymous_as_login_option', FALSE), '#states' => array ( 'visible' => array( - ':input[name="commerce_checkout_redirect_anonymous"]' => array('checked' => TRUE), + ':input[name="commerce_checkout_redirect_anonymous"]' => array('checked' => true), ), ), ); @@ -109,11 +109,11 @@ function commerce_checkout_redirect_commerce_checkout_router($order, $checkout_p end($checkout_pages); $last_checkout_page = key($checkout_pages); // Check if the user's shopping cart order exists with something in the cart - if (commerce_cart_order_load() && commerce_checkout_redirect_items_in_cart()) { - if (($checkout_page['page_id'] == $first_checkout_page)) { + if (($checkout_page['page_id'] == $first_checkout_page)) { + if (commerce_cart_order_load() && commerce_checkout_redirect_items_in_cart()) { if (user_is_anonymous() && empty($_SESSION['commerce_checkout_redirect_bypass'])) { $_SESSION['commerce_checkout_redirect_anonymous'] = TRUE; - $commerce_checkout_redirect_message = variable_get('commerce_checkout_redirect_message', t('You need to be logged in to be able to checkout.')); + $commerce_checkout_redirect_message = variable_get('commerce_checkout_redirect_message', t('You need to be logged in to be able to checkout.')); if (!empty($commerce_checkout_redirect_message)) { drupal_set_message($commerce_checkout_redirect_message); } @@ -175,84 +175,93 @@ function commerce_checkout_redirect_user_login(&$edit, &$account) { */ function commerce_checkout_redirect_form_alter(&$form, &$form_state, $form_id) { $commerce_checkout_redirect_anonymous = variable_get('commerce_checkout_redirect_anonymous', FALSE); - // Check if user has an active cart order. - if ($order = commerce_cart_order_load()) { - // Check if there's anything in the cart and if user has not yet selected checkout method. - if (commerce_checkout_redirect_items_in_cart() && empty($_SESSION['commerce_checkout_redirect_bypass']) && !empty($_SESSION['commerce_checkout_redirect_anonymous'])) { - if (in_array($form_id, array('user_login', 'user_register_form', 'user_login_block', 'user_profile_form'))) { - // Append the checkout redirect function on user's forms. - $form['#submit'][] = 'commerce_checkout_redirect_redirect_anonymous_submit'; - unset($form['#action']); - // Anonymous checkout button. - if (variable_get('commerce_checkout_redirect_anonymous', FALSE)) { - $form_state['#order'] = $order; - $form['actions']['continue_button'] = array( - '#name' => 'continue_button', - '#type' => 'submit', - '#value' => t('Checkout without an account'), - '#limit_validation_errors' => array(), - '#submit' => array('commerce_checkout_redirect_anonymous_continue_checkout'), - '#states' => array ( - 'visible' => array( - ':input[name="have_pass"]' => array('value' => 0), - ), - ), - ); - // Anonymous checkout as alternative to login form. - if (variable_get('commerce_checkout_redirect_anonymous_as_login_option', FALSE)) { - $form['have_pass'] = array( - '#type' => 'radios', - '#title' => t('Do you have a password?'), - '#options' => array(0 => t('No (You can create an account later)'), 1 => t('Yes')), - '#weight' => 10, - '#default_value' => 0, - ); - $form['pass']['#states'] = array( - 'visible' => array( - ':input[name="have_pass"]' => array('value' => 1), - ), - ); - $form['actions']['submit']['#states'] = array( - 'visible' => array( - ':input[name="have_pass"]' => array('value' => 1), - ), - ); - $form['pass']['#weight'] = 10; - $form['actions']['continue_button']['#value'] = t('Continue'); - $form['actions']['continue_button']['#states'] = array ( - 'visible' => array( - ':input[name="have_pass"]' => array('value' => 0), + $user_forms = array( + 'user_login', + 'user_register_form', + 'user_login_block', + 'user_profile_form', + ); + $all_user_forms = $user_forms + array('user_pass_reset'); + if (in_array($form_id, $all_user_forms)) { + // Check if user has an active cart order. + if ($order = commerce_cart_order_load()) { + // Check if there's anything in the cart and if user has not yet selected checkout method. + if (commerce_checkout_redirect_items_in_cart() && empty($_SESSION['commerce_checkout_redirect_bypass']) && !empty($_SESSION['commerce_checkout_redirect_anonymous'])) { + if (in_array($form_id, $all_user_forms)) { + // Append the checkout redirect function on user's forms. + $form['#submit'][] = 'commerce_checkout_redirect_redirect_anonymous_submit'; + unset($form['#action']); + // Anonymous checkout button. + if (variable_get('commerce_checkout_redirect_anonymous', FALSE)) { + $form_state['#order'] = $order; + $form['actions']['continue_button'] = array( + '#name' => 'continue_button', + '#type' => 'submit', + '#value' => t('Checkout without an account'), + '#limit_validation_errors' => array(), + '#submit' => array('commerce_checkout_redirect_anonymous_continue_checkout'), + '#states' => array ( + 'visible' => array( + ':input[name="have_pass"]' => array('value' => '0'), + ), ), ); - } - // Use the username as order email. - // Email validation for the username form element - if (variable_get('commerce_checkout_redirect_username_as_order_email', FALSE)) { - $form['name']['#title'] = t('Email'); - $form['actions']['continue_button']['#limit_validation_errors'] = array(array('name')); - $form['actions']['continue_button']['#validate'][] = 'commerce_checkout_redirect_username_as_order_email_form_validate'; + // Anonymous checkout as alternative to login form. + if (variable_get('commerce_checkout_redirect_anonymous_as_login_option', FALSE)) { + $form['have_pass'] = array( + '#type' => 'radios', + '#title' => t('Do you have a password?'), + '#options' => array(0 => t('No (You can create an account later)'), 1 => t('Yes')), + '#weight' => 10, + '#default_value' => 0, + ); + $form['pass']['#states'] = array( + 'visible' => array( + ':input[name="have_pass"]' => array('value' => '1'), + ), + ); + $form['actions']['submit']['#states'] = array( + 'visible' => array( + ':input[name="have_pass"]' => array('value' => '1'), + ), + ); + $form['pass']['#weight'] = 10; + $form['actions']['continue_button']['#value'] = t('Continue'); + $form['actions']['continue_button']['#states'] = array ( + 'visible' => array( + ':input[name="have_pass"]' => array('value' => '0'), + ), + ); + } + // Use the username as order email. + // Email validation for the username form element + if (variable_get('commerce_checkout_redirect_username_as_order_email', FALSE)) { + $form['name']['#title'] = t('Email'); + $form['actions']['continue_button']['#limit_validation_errors'] = array(array('name')); + $form['actions']['continue_button']['#validate'][] = 'commerce_checkout_redirect_username_as_order_email_form_validate'; + } } } - } - // Reset password form. - // E-mail verification when a visitor creates an account. - elseif ($form_id == 'user_pass_reset' && !empty($form['actions'])) { - // Provide the checkout as an alternative to the new account - // reset password process. - // Message (help text) for the "Continue with checkout" button. - $checkout_redirect_reset_password_message = variable_get('commerce_checkout_redirect_reset_password_message', t('You can also continue with the checkout process.')); - if (!empty($checkout_redirect_reset_password_message)) { - $form['actions']['checkout_message']['#markup'] = '

' . $checkout_redirect_reset_password_message . '

'; + // Reset password form. + // E-mail verification when a visitor creates an account. + elseif ($form_id == 'user_pass_reset' && !empty($form['actions'])) { + // Provide the checkout as an alternative to the new account + // reset password process. + // Message (help text) for the "Continue with checkout" button. + $checkout_redirect_reset_password_message = variable_get('commerce_checkout_redirect_reset_password_message', t('You can also continue with the checkout process.')); + if (!empty($checkout_redirect_reset_password_message)) { + $form['actions']['checkout_message']['#markup'] = '

' . $checkout_redirect_reset_password_message . '

'; + } + // "Continue with checkout" submit button. + $form['actions']['checkout'] = array( + '#type' => 'submit', + '#value' => t('Continue with checkout'), + ); + // Append the checkout redirect function on user's forms. + $form['#submit'][] = 'commerce_checkout_redirect_redirect_anonymous_submit'; + // Unset the action, use submit form function(s) instead. + unset($form['#action']); } - // "Continue with checkout" submit button. - $form['actions']['checkout'] = array( - '#type' => 'submit', - '#value' => t('Continue with checkout'), - ); - // Append the checkout redirect function on user's forms. - $form['#submit'][] = 'commerce_checkout_redirect_redirect_anonymous_submit'; - // Unset the action, use submit form function(s) instead. - unset($form['#action']); } } } @@ -346,7 +355,7 @@ function commerce_checkout_redirect_anonymous_continue_checkout($form, &$form_st $order->mail = $form_state['values']['name']; commerce_order_save($order); } - $form_state['redirect'] = 'checkout/'; + $form_state['redirect'] = 'checkout'; } /** diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/.gitignore b/profiles/commerce_kickstart/modules/contrib/commerce_discount/.gitignore new file mode 100644 index 00000000..39fe1c20 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/.gitignore @@ -0,0 +1,3 @@ +# Sass cache and mapping are not needed in production. +.sass-cache +*.map diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_discount/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/README.md b/profiles/commerce_kickstart/modules/contrib/commerce_discount/README.md new file mode 100644 index 00000000..f3be4f45 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/README.md @@ -0,0 +1,31 @@ +## CONTENTS OF THIS FILE + + * Requirements + * Contributing + + +## REQUIREMENTS + +This module requires the following modules: + + * [Entity](https://drupal.org/project/entity) + * [Views](https://drupal.org/project/views) + * [Commerce](https://drupal.org/project/commerce) + * [Inline Conditions ](https://drupal.org/project/inline_conditions) + * [Entity Reference ](https://drupal.org/project/entityreference) + * [Inline Entity Form](https://drupal.org/project/inline_entity_form) + + +## CONTRIBUTING + +To contribute CSS fixes/improvements to this module please do so in the `sass` +folder. This is a SASS folder and you can compile the CSS using the following +command: + +One time: + +```sass --update--style expanded sass:css``` + +Watch changes and compile on the fly: + +```sass --watch --style expanded ass:css``` diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.api.php b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.api.php index 821f613e..4010f552 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.api.php @@ -71,31 +71,32 @@ function hook_commerce_discount_offer_type_info() { } /** - * Allow modules alter the rule object, with configuration specifc - * to commerce discount. + * Allow modules alter the rule object, with commerce discount configuration. * - * @param $rule + * @param RulesPlugin $rule * The rule configuration entity, passed by reference. - * @param $commerce_discount + * @param CommerceDiscount $commerce_discount * The commerce discount entity. */ -function hook_commerce_discount_rule_build($rule, $commerce_discount) { +function hook_commerce_discount_rule_build(RulesPlugin $rule, CommerceDiscount $commerce_discount) { if ($commerce_discount->name == 'foo') { $rule->action('drupal_message', array('message' => 'Discount FOO was applied.')); } } /** - * Alter the context that is going to be passed into a free product line item. + * Alter the context that is going to be passed into a bonus product line item. + * * Use this to modify the display path. * - * @param $context - * The context that is about to be passed into a new free product line item. - * @param $product - * The product that is being offered as free. - * @param $discount - * The discount containing this offer. + * @param array $context + * The context that is about to be passed into a new free bonus product line + * item. + * @param object $product + * The product that is being offered as free. + * @param CommerceDiscount $discount + * The discount containing this offer. */ -function hook_commerce_discount_free_product_context_alter(&$context, $product, $discount) { +function hook_commerce_discount_free_product_context_alter(&$context, $product, CommerceDiscount $discount) { $context['display_path'] = 'any/page'; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info index 8c8737f0..229c94d7 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info @@ -7,12 +7,17 @@ dependencies[] = number dependencies[] = entity dependencies[] = entityreference dependencies[] = inline_entity_form (>=1.5) +dependencies[] = list +dependencies[] = options +dependencies[] = text dependencies[] = views dependencies[] = commerce dependencies[] = commerce_price dependencies[] = commerce_line_item dependencies[] = commerce_product_reference dependencies[] = inline_conditions (>1.0-alpha4) +dependencies[] = date +dependencies[] = date_popup files[] = commerce_discount.info.inc files[] = includes/commerce_discount.admin.inc @@ -24,16 +29,25 @@ files[] = includes/commerce_discount.controller.inc ; Views files files[] = includes/views/commerce_discount.views.inc files[] = includes/views/handlers/commerce_discount_handler_field_operations_dropbutton.inc +files[] = includes/views/handlers/commerce_discount_handler_field_commerce_discount_analytics.inc ; Simple tests -files[] = commerce_discount.test - +files[] = tests/commerce_discount.test +files[] = tests/commerce_discount_base.test +files[] = tests/commerce_discount_conditions.test +files[] = tests/commerce_discount_date.test +files[] = tests/commerce_discount_date_ui.test +files[] = tests/commerce_discount_shipping.test +files[] = tests/commerce_discount_ui.test +files[] = tests/commerce_discount_usage.test +files[] = tests/commerce_discount_usage_ui.test + +test_dependencies[] = addressfield test_dependencies[] = commerce_shipping - -; Information added by drush on 2015-08-20 -version = "7.x-1.0-alpha5+0-dev" +; Information added by Drupal.org packaging script on 2017-12-19 +version = "7.x-1.0-beta5" core = "7.x" project = "commerce_discount" -datestamp = "1440110608" +datestamp = "1513724288" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info.inc index 74560a09..aeb1a3dd 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.info.inc @@ -6,16 +6,45 @@ */ /** - * Extend the defaults meta data properties for Commerce discount entity. + * Extend the defaults metadata properties for Commerce discount entity. */ class CommerceDiscountMetadataController extends EntityDefaultMetadataController { + /** + * {@inheritdoc} + */ public function entityPropertyInfo() { $info = parent::entityPropertyInfo(); $properties = &$info[$this->type]['properties']; + // Update certain elements of the property info array. + $properties['discount_id']['label'] = t('Discount ID'); + $properties['discount_id']['description'] = t('The serial numeric ID of the discount.'); + + $properties['name']['label'] = t('Machine name'); + $properties['name']['description'] = t('The text machine name of the discount.'); + + $properties['label']['label'] = t('Admin title'); + $properties['label']['description'] = t('The label used for the discount on administrative screens.'); + + $properties['type']['description'] = t('The type of the discount (e.g. Order vs. Product).'); + $properties['status']['type'] = 'boolean'; + $properties['status']['description'] = t('The status of the discount (i.e. Active vs. Disabled).'); + $properties['status']['setter callback'] = 'entity_property_verbatim_set'; + + $properties['export_status']['label'] = t('Export status'); + $properties['export_status']['description'] = t('The exportable status of the discount.'); + + $properties['module']['description'] = t('The name of the module that defined the discount if applicable.'); + + $properties['component_title']['label'] = t('Name'); + $properties['component_title']['description'] = t('The name of the discount as shown to customers (e.g. in the price component list).'); + + $properties['sort_order']['label'] = t('Sort order'); + $properties['sort_order']['description'] = t('The default sort order of the rule created for the discount.'); return $info; } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.inline_conditions.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.inline_conditions.inc index f21b171e..13ca301b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.inline_conditions.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.inline_conditions.inc @@ -1,5 +1,10 @@ 'commerce_order_has_specific_quantity_products_build', ), ); + + $conditions['commerce_order_compare_address'] = array( + 'label' => t('Address'), + 'entity type' => 'commerce_order', + 'callbacks' => array( + 'configure' => 'commerce_order_compare_address_configure', + 'build' => 'commerce_order_rules_compare_address', + ), + ); } if (module_exists('commerce_shipping')) { @@ -104,19 +118,23 @@ function commerce_discount_inline_conditions_build_alter(&$value) { switch ($value['condition_name']) { case 'commerce_order_contains_products': case 'commerce_order_has_specific_quantity_products': - $entity_ids = array(); - foreach ($value['condition_settings']['products'] as $delta) { - $entity_ids[] = reset($delta); - } - $products = commerce_product_load_multiple($entity_ids); + if (!empty($value['condition_settings']['products'])) { + $entity_ids = array(); - $value['condition_settings']['products'] = ''; - foreach ($products as $product) { - $value['condition_settings']['products'] .= $product->sku; - if ($product !== end($products)) { - $value['condition_settings']['products'] .= ', '; + foreach ($value['condition_settings']['products'] as $delta) { + $entity_ids[] = reset($delta); + } + $products = commerce_product_load_multiple($entity_ids); + + $value['condition_settings']['products'] = ''; + foreach ($products as $product) { + $value['condition_settings']['products'] .= $product->sku; + if ($product !== end($products)) { + $value['condition_settings']['products'] .= ', '; + } } } + break; case 'commerce_product_has_specified_terms': @@ -162,24 +180,32 @@ function commerce_discount_inline_conditions_build_alter(&$value) { function commerce_order_compare_order_amount_configure($settings) { $form = array(); - // Ensure we've default settings set. + module_load_include('inc', 'commerce_discount', 'commerce_discount.rules'); + + // Ensure we have default values for the condition settings. $settings += array( 'operator' => '>=', 'total' => array('amount' => 0), + 'line_item_types' => commerce_order_compare_order_amount_options_default(), ); // Get the default website currency. $default_currency = commerce_currency_load(NULL); + $form['line_item_types'] = array( + '#type' => 'checkboxes', + '#title' => t('Included line item types'), + '#title_display' => 'invisible', + '#options' => commerce_order_compare_order_amount_options_list(), + '#default_value' => $settings['line_item_types'], + '#multiple' => TRUE, + ); + $form['operator'] = array( '#type' => 'select', '#title' => t('Operator'), '#title_display' => 'invisible', - '#options' => array( - '<' => t('lower than'), - '==' => t('equals'), - '>' => t('greater than'), - ), + '#options' => _commerce_discount_operator_options(), '#default_value' => $settings['operator'], ); @@ -187,10 +213,7 @@ function commerce_order_compare_order_amount_configure($settings) { '#type' => 'container', '#tree' => TRUE, '#element_validate' => array('commerce_price_field_widget_validate'), - '#suffix' => '
' . t( - 'The discount is active only if the order total matches the above condition. -
(the other order discounts are not taken into the comparison process)' - ) . '
', + '#suffix' => '
' . t('The discount is active only if the total of the selected line items on the order passes the comparison condition.
(Other order / product discount line items are not taken into account in the comparison process.)') . '
', ); $form['total']['amount'] = array( @@ -264,13 +287,13 @@ function commerce_product_has_owner_configure($settings) { $form['data:select'] = array( '#type' => 'textfield', '#access' => FALSE, - '#default_value' => 'site:current-user:uid' + '#default_value' => 'site:current-user:uid', ); $form['op'] = array( '#type' => 'textfield', '#access' => FALSE, - '#default_value' => '==' + '#default_value' => '==', ); $form['value'] = array( @@ -330,6 +353,19 @@ function commerce_order_contains_products_configure($settings) { $form = array(); + $form['operator'] = array( + '#type' => 'select', + '#title' => t('Operator'), + '#title_display' => 'invisible', + '#options' => array( + 'any' => t('any of'), + 'all' => t('all of'), + 'exactly' => t('exactly'), + 'only' => t('only any of'), + ), + '#default_value' => !empty($settings['operator']) ? $settings['operator'] : 'only', + ); + $form['products'] = array( '#type' => 'textfield', '#title' => t('SKUs'), @@ -339,7 +375,7 @@ function commerce_order_contains_products_configure($settings) { '#maxlength' => 4096, '#autocomplete_path' => 'commerce_product/autocomplete/commerce_product/0/0', '#element_validate' => array('commerce_product_reference_autocomplete_validate'), - '#suffix' => '
' . t('Select products when ordered make discount active.') . '
', + '#suffix' => '
' . t('Select products when ordered make discount active. Conditions:
Any of the products are in the order (can include other products).
All of the products listed are in the order (can include other products).
Exactly the products listed are in the order (cannot contain other products).
Only any of the products are in the order (cannot include other products).') . '
', '#attributes' => array('placeholder' => array(t('enter product name'))), ); @@ -373,8 +409,6 @@ function commerce_order_has_specific_quantity_products_configure($settings) { } } - $form = array(); - $form['products'] = array( '#type' => 'textfield', '#title' => t('Products'), @@ -387,15 +421,12 @@ function commerce_order_has_specific_quantity_products_configure($settings) { '#attributes' => array('placeholder' => array(t('enter product name'))), ); + module_load_include('inc', 'commerce_discount', 'commerce_discount.rules'); $form['operator'] = array( '#type' => 'select', - '#title' => t('Operator'), - '#title_display' => 'invisible', - '#options' => array( - '<' => t('quantity lower than'), - '==' => t('quantity equals'), - '>' => t('quantity greater than'), - ), + '#title' => t('Quantity'), + '#title_display' => 'before', + '#options' => _commerce_discount_operator_options(), '#default_value' => !empty($settings['operator']) ? $settings['operator'] : '==', ); @@ -403,7 +434,7 @@ function commerce_order_has_specific_quantity_products_configure($settings) { '#type' => 'textfield', '#title' => t('Quantity'), '#title_display' => 'invisible', - '#default_value' => !empty($settings['quantity']) ? $settings['quantity'] : '', + '#default_value' => !empty($settings['quantity']) ? $settings['quantity'] : '1', '#size' => 5, '#required' => TRUE, '#element_validate' => array('element_validate_integer'), @@ -413,6 +444,63 @@ function commerce_order_has_specific_quantity_products_configure($settings) { return $form; } +/** + * Configuration callback for commerce_order_compare_address_configure. + * + * @param array $settings + * An array of rules condition settings. + * + * @return array; + * A form element array. + */ +function commerce_order_compare_address_configure($settings) { + module_load_include('inc', 'commerce_order', 'commerce_order.rules'); + + // Need a list of all address fields on customer profile bundles. + $form['address_field'] = array( + '#type' => 'select', + '#multiple' => FALSE, + '#options' => commerce_order_address_field_options_list(), + '#title' => t('Address field'), + '#title_display' => 'invisible', + '#default_value' => !empty($settings['address_field']) ? $settings['address_field'] : FALSE, + '#require' => TRUE, + ); + + // Need a list of address component options to compare. + $form['address_component'] = array( + '#type' => 'select', + '#multiple' => FALSE, + '#options' => commerce_order_address_component_options_list(), + '#title' => t('Address component'), + '#title_display' => 'invisible', + '#default_value' => !empty($settings['address_component']) ? $settings['address_component'] : FALSE, + '#require' => TRUE, + ); + + $form['operator'] = array( + '#type' => 'select', + '#multiple' => FALSE, + '#options' => commerce_order_address_comparison_operator_options_list(), + '#title' => t('Operator'), + '#title_display' => 'invisible', + '#default_value' => !empty($settings['operator']) ? $settings['operator'] : FALSE, + '#require' => TRUE, + ); + + $form['value'] = array( + '#type' => 'textarea', + '#title' => t('Value'), + '#title_display' => 'invisible', + '#rows' => 3, + '#default_value' => !empty($settings['value']) ? $settings['value'] : FALSE, + '#require' => TRUE, + '#suffix' => '
' . t('The discount is active if the order address component selected matches the entered value. For the entered value bear in mind that addresses using select lists for various components may use a value different from the option you select. For example, countries are selected by name, but the value is the two letter abbreviation. For comparisons with multiple possible values, place separate values on new lines.') . '
', + ); + + return $form; +} + /** * Configuration callback for commerce_shipping_compare_shipping_service. * diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.install b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.install index 311dad3b..84cf0e70 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.install @@ -6,7 +6,14 @@ */ /** - * Implements hook_schema() + * Implements hook_install(). + */ +function commerce_discount_install() { + commerce_discount_install_helper(); +} + +/** + * Implements hook_schema(). */ function commerce_discount_schema() { $schema['commerce_discount'] = array( @@ -66,6 +73,13 @@ function commerce_discount_schema() { 'length' => 255, 'not null' => FALSE, ), + 'sort_order' => array( + 'description' => 'The discount sort order.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 10, + 'size' => 'tiny', + ), ), 'primary key' => array('discount_id'), 'unique keys' => array( @@ -92,6 +106,51 @@ function commerce_discount_schema() { 'primary key' => array('discount_offer_id'), ); + $schema['commerce_discount_usage'] = array( + 'fields' => array( + 'discount' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => 'Discount name.', + ), + 'mail' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The email of the customer that used this discount.', + ), + 'order_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'The order id that this discount was used with.', + ), + ), + 'unique keys' => array( + 'discount_mail_order_id' => array('discount', 'mail', 'order_id'), + ), + 'foreign keys' => array( + 'discount' => array( + 'table' => 'commerce_discount', + 'columns' => array('discount' => 'name'), + ), + 'order_id' => array( + 'table' => 'commerce_order', + 'columns' => array('order_id' => 'order_id'), + ), + 'mail' => array( + 'table' => 'users', + 'columns' => array('mail' => 'mail'), + ), + ), + 'indexes' => array( + 'mail' => array('mail'), + 'discount' => array('discount'), + 'order_id' => array('order_id'), + ), + ); + return $schema; } @@ -119,31 +178,27 @@ function commerce_discount_requirements($phase) { } /** - * Helper function to define and create the required fields & instances in case - * they don't exist already. + * Helper function to define and create the required fields & instances. * - * Create the entityreference field on commerce discount, referencing + * Ensure creation of an entityreference field on commerce discount, referencing * commerce discount offer. * The instance will be added for every newly created bundle. */ function commerce_discount_install_helper() { $fields = field_read_fields(array(), array('include_inactive' => TRUE)); $field_types = field_info_field_types(); - // Clear field info cache, so entity reference, inline_conditions and // commerce_product_reference field types can be used. - if ( - !isset($field_types['entityreference']) - || - !isset($field_types['inline_conditions']) - || - !isset($field_types['commerce_product_reference']) - ) { + if (!isset($field_types['entityreference'], $field_types['inline_conditions'], $field_types['commerce_product_reference'], $field_types['datestamp'])) { field_info_cache_clear(); + // Overwrites variable, because during installation module after clears the + // field info cache the field type 'entityreference' not containing + // in variables. Under this the field, use this field type is not created. + $field_types = field_info_field_types(); } + // Create the discount offer entity reference field for use on all discounts. if (empty($fields['commerce_discount_offer']) && isset($field_types['entityreference'])) { - // Create entity reference field. $field = array( 'entity_types' => array('commerce_discount'), 'settings' => array( @@ -161,6 +216,43 @@ function commerce_discount_install_helper() { ); field_create_field($field); } + + // Create the discount compatibility strategy field for use on all discounts. + if (empty($fields['commerce_compatibility_strategy'])) { + $field = array( + 'type' => 'list_text', + 'field_name' => 'commerce_compatibility_strategy', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_discount_compatibility_strategies', + ), + ); + field_create_field($field); + } + + // Create the selected discounts field for use on all discounts. + if (empty($fields['commerce_compatibility_selection'])) { + $field = array( + 'type' => 'entityreference', + 'field_name' => 'commerce_compatibility_selection', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'locked' => TRUE, + 'settings' => array( + 'target_type' => 'commerce_discount', + 'handler' => 'base', + 'handler_settings' => array( + 'sort' => array( + 'type' => 'property', + 'property' => 'label', + 'direction' => 'ASC', + ), + ), + ), + ); + field_create_field($field); + } + if (empty($fields['commerce_fixed_amount'])) { // Add price field to the commerce discount offer "fixed_amount" bundle. $field = array( @@ -208,38 +300,130 @@ function commerce_discount_install_helper() { ); field_create_field($field); } - // Creating field for free shipping offer type. - if (module_exists('commerce_shipping') && empty($fields['commerce_free_shipping'])) { + if (empty($fields['commerce_discount_date'])) { $field = array( - 'entity_types' => array('commerce_discount_offer'), - 'field_name' => 'commerce_free_shipping', - 'type' => 'text', + 'entity_types' => array('commerce_discount'), + 'settings' => array( + 'granularity' => array( + 'month' => 'month', + 'day' => 'day', + 'year' => 'year', + ), + 'tz_handling' => 'none', + 'timezone_db' => '', + 'todate' => 'optional', + 'handler' => 'base', + 'target_type' => 'commerce_discount', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'commerce_discount_date', + 'type' => 'datestamp', 'locked' => TRUE, ); field_create_field($field); } - // Creating field for percent off of shipping - if (module_exists('commerce_shipping') && empty($fields['commerce_percent_off_shipping'])) { + if (empty($fields['discount_usage_per_person'])) { + // Create entity reference field. $field = array( - 'entity_types' => array('commerce_discount_offer'), - 'field_name' => 'commerce_percent_off_shipping', - 'type' => 'number_decimal', + 'entity_types' => array('commerce_discount'), + 'field_name' => 'discount_usage_per_person', + 'type' => 'number_integer', 'locked' => TRUE, ); field_create_field($field); } - // Creating field for percent off of shipping - if (module_exists('commerce_shipping') && empty($fields['commerce_percent_off_ship_serv'])) { + // Discount usage. + if (empty($fields['discount_usage_limit'])) { + // Create entity reference field. $field = array( - 'entity_types' => array('commerce_discount_offer'), - 'field_name' => 'commerce_percent_off_ship_serv', - 'type' => 'text', + 'entity_types' => array('commerce_discount'), + 'field_name' => 'discount_usage_limit', + 'type' => 'number_integer', 'locked' => TRUE, ); field_create_field($field); } - // Creating field for free products offer type. + // Create fields for the shipping related offer types. + if (module_exists('commerce_shipping')) { + // Creating the shipping service text field and checkbox for the "Free + // shipping" offer. + if (empty($fields['commerce_free_shipping'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_free_shipping', + 'type' => 'text', + 'locked' => TRUE, + ); + field_create_field($field); + } + if (empty($fields['commerce_free_shipping_strategy'])) { + $field = array( + 'type' => 'list_text', + 'field_name' => 'commerce_free_shipping_strategy', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_discount_free_shipping_strategies', + ), + ); + field_create_field($field); + } + + // Create the percentage off and shipping service text fields for the "% off + // of shipping" offer. + if (empty($fields['commerce_percent_off_shipping'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_percent_off_shipping', + 'type' => 'number_decimal', + 'locked' => TRUE, + ); + field_create_field($field); + } + if (empty($fields['commerce_percent_off_ship_serv'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_percent_off_ship_serv', + 'type' => 'text', + 'locked' => TRUE, + ); + field_create_field($field); + } + + // Create the target and source shipping service text fields for the + // "Shipping service upgrade" offer. + if (empty($fields['commerce_shipping_upgrade_target'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_shipping_upgrade_target', + 'type' => 'list_text', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_shipping_service_options_list', + ), + ); + field_create_field($field); + } + if (empty($fields['commerce_shipping_upgrade_source'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_shipping_upgrade_source', + 'type' => 'list_text', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_shipping_service_options_list', + ), + ); + field_create_field($field); + } + } + + // Creating field for free bonus products offer type. if (empty($fields['commerce_free_products']) && isset($field_types['commerce_product_reference'])) { $field = array( 'entity_types' => array('commerce_discount_offer'), @@ -275,7 +459,62 @@ function commerce_discount_install_helper() { ); field_create_instance($instance); } + if (empty($instances['commerce_discount'][$type]['commerce_compatibility_strategy'])) { + $instance = array( + 'field_name' => 'commerce_compatibility_strategy', + 'label' => t('Compatibility with other discounts'), + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'required' => TRUE, + 'widget' => array( + 'weight' => -10, + 'type' => 'options_buttons', + 'module' => 'options', + ), + 'default_value' => array( + 0 => array('value' => 'any'), + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount'][$type]['commerce_compatibility_selection'])) { + $instance = array( + 'field_name' => 'commerce_compatibility_selection', + 'label' => t('Selected discounts'), + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'required' => FALSE, + 'widget' => array( + 'weight' => -9, + 'type' => 'entityreference_autocomplete', + 'module' => 'entityreference', + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount'][$type]['commerce_discount_date'])) { + $instance = array( + 'field_name' => 'commerce_discount_date', + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'label' => t('Discount dates'), + 'widget' => array( + 'module' => 'date', + 'type' => 'date_popup', + 'weight' => -11, + 'settings' => array( + 'no_fieldset' => TRUE, + ), + ), + 'settings' => array( + 'default_value' => 'blank', + 'default_value2' => 'blank', + ), + ); + field_create_instance($instance); + } } + if (empty($instances['commerce_discount_offer']['fixed_amount']['commerce_fixed_amount'])) { $instance = array( 'field_name' => 'commerce_fixed_amount', @@ -299,14 +538,18 @@ function commerce_discount_install_helper() { ); field_create_instance($instance); } - if (empty($instances['commerce_order']['commerce_order']['commerce_discounts'])) { - $instance = array( - 'field_name' => 'commerce_discounts', - 'entity_type' => 'commerce_order', - 'bundle' => 'commerce_order', - 'label' => t('Discount reference'), - ); - field_create_instance($instance); + // Add discount field to all commerce_order bundles. + $order_entity_info = entity_get_info('commerce_order'); + foreach (array_keys($order_entity_info['bundles']) as $bundle) { + if (empty($instances['commerce_order'][$bundle]['commerce_discounts'])) { + $instance = array( + 'field_name' => 'commerce_discounts', + 'entity_type' => 'commerce_order', + 'bundle' => $bundle, + 'label' => t('Discount reference'), + ); + field_create_instance($instance); + } } foreach (commerce_discount_types() as $type => $value) { if (empty($instances['commerce_discount'][$type]['inline_conditions']) && isset($field_types['inline_conditions'])) { @@ -327,45 +570,134 @@ function commerce_discount_install_helper() { field_create_instance($instance); } } - if (module_exists('commerce_shipping') && empty($instances['commerce_discount_offer']['free_shipping']['commerce_free_shipping'])) { - $instance = array( - 'field_name' => 'commerce_free_shipping', - 'entity_type' => 'commerce_discount_offer', - 'bundle' => 'free_shipping', - 'label' => t('Shipping service'), - 'required' => TRUE, - 'widget' => array( - 'type' => 'options_select', - ), - ); - field_create_instance($instance); + + foreach (commerce_discount_types() as $type => $info) { + if (empty($instances['commerce_discount'][$type]['discount_usage_per_person'])) { + $instance = array( + 'field_name' => 'discount_usage_per_person', + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'label' => t('Maximum usage per customer'), + 'description' => t('Enter the maximum number of times a specific person (as identified by email) may use this discount. Leave blank for unlimited.'), + 'required' => FALSE, + 'widget' => array( + 'weight' => 100, + ), + 'settings' => array( + 'min' => 0, + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount'][$type]['discount_usage_limit'])) { + $instance = array( + 'field_name' => 'discount_usage_limit', + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'label' => t('Maximum overall usage'), + 'description' => t('Enter the maximum number of times this discount may be used on the site, by anyone. Leave blank for unlimited.'), + 'required' => FALSE, + 'widget' => array( + 'weight' => 100, + ), + 'settings' => array( + 'min' => 0, + ), + ); + field_create_instance($instance); + } } - if (module_exists('commerce_shipping') && empty($instances['commerce_discount_offer']['percent_off_shipping']['commerce_percent_off_shipping'])) { - // Add decimal selection - $instance = array( - 'field_name' => 'commerce_percent_off_shipping', - 'entity_type' => 'commerce_discount_offer', - 'bundle' => 'percent_off_shipping', - 'label' => t('Percentage off of shipping'), - 'settings' => array( - 'suffix' => '%', - ), - 'required' => TRUE, - ); - field_create_instance($instance); + // Create field instances for the shipping related offer types. + if (module_exists('commerce_shipping')) { + // Add the shipping service text field to the "Free shipping" offer. + if (empty($instances['commerce_discount_offer']['free_shipping']['commerce_free_shipping'])) { + $instance = array( + 'field_name' => 'commerce_free_shipping', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'free_shipping', + 'label' => t('Shipping service'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_select', + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount_offer']['free_shipping']['commerce_free_shipping_strategy'])) { + $instance = array( + 'field_name' => 'commerce_free_shipping_strategy', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'free_shipping', + 'label' => t('Free shipping discount strategy'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_buttons', + 'module' => 'options', + ), + 'default_value' => array( + 0 => array('value' => 'only_selected'), + ), + ); + field_create_instance($instance); + } - // Add service selection - $instance = array( - 'field_name' => 'commerce_percent_off_ship_serv', - 'entity_type' => 'commerce_discount_offer', - 'bundle' => 'percent_off_shipping', - 'label' => t('Shipping service to take % off shipping'), - 'widget' => array( - 'type' => 'options_select', - ), - ); - field_create_instance($instance); + // Add the percentage off and shipping service text fields to the "% off of + // shipping" offer. + if (empty($instances['commerce_discount_offer']['percent_off_shipping']['commerce_percent_off_shipping'])) { + $instance = array( + 'field_name' => 'commerce_percent_off_shipping', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'percent_off_shipping', + 'label' => t('Percentage off of shipping'), + 'settings' => array( + 'suffix' => '%', + ), + 'required' => TRUE, + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount_offer']['percent_off_shipping']['commerce_percent_off_ship_serv'])) { + $instance = array( + 'field_name' => 'commerce_percent_off_ship_serv', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'percent_off_shipping', + 'label' => t('Shipping service to take % off shipping'), + 'widget' => array( + 'type' => 'options_select', + ), + ); + field_create_instance($instance); + } + + // Add the target and source shipping service text fields to the "Shipping + // service upgrade" offer. + if (empty($instances['commerce_discount_offer']['shipping_upgrade']['commerce_shipping_upgrade_target'])) { + $instance = array( + 'field_name' => 'commerce_shipping_upgrade_target', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'shipping_upgrade', + 'label' => t('Let customers select this service'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_select', + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount_offer']['shipping_upgrade']['commerce_shipping_upgrade_source'])) { + $instance = array( + 'field_name' => 'commerce_shipping_upgrade_source', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'shipping_upgrade', + 'label' => t('For the same price as this service'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_select', + ), + ); + field_create_instance($instance); + } } if (empty($instances['commerce_discount_offer']['free_products']['commerce_free_products'])) { @@ -373,7 +705,7 @@ function commerce_discount_install_helper() { 'field_name' => 'commerce_free_products', 'entity_type' => 'commerce_discount_offer', 'bundle' => 'free_products', - 'label' => t('Select free products'), + 'label' => t('Select bonus products'), 'description' => t('Enter a comma-separated list of SKUs. Each product is added to customer order with a quantity of 1.'), 'required' => TRUE, 'widget' => array( @@ -394,6 +726,7 @@ function commerce_discount_install_helper() { * @see commerce_discount_offer_type_info() */ function commerce_discount_uninstall() { + module_load_include('module', 'commerce'); // Drop all bundles attached on commerce_discount and commerce_discount_offer // entity types. field_attach_delete_bundle('commerce_discount', 'order_discount'); @@ -405,7 +738,11 @@ function commerce_discount_uninstall() { field_attach_delete_bundle('commerce_discount_offer', 'commerce_percent_off_ship_serv'); field_attach_delete_bundle('commerce_discount_offer', 'free_products'); - // Then delete the related fields + // Delete custom line items types. + field_attach_delete_bundle('commerce_line_item', 'commerce_discount'); + field_attach_delete_bundle('commerce_line_item', 'product_discount'); + + // Then delete the related fields. commerce_delete_field('commerce_discount_offer'); commerce_delete_field('commerce_fixed_amount'); commerce_delete_field('commerce_percentage'); @@ -415,6 +752,9 @@ function commerce_discount_uninstall() { commerce_delete_field('commerce_percent_off_shipping'); commerce_delete_field('commerce_percent_off_ship_serv'); commerce_delete_field('commerce_free_products'); + commerce_delete_field('commerce_discount_date'); + commerce_delete_field('discount_usage_per_person'); + commerce_delete_field('discount_usage_limit'); // Delete all rules added by commerce_discount module. $query = new EntityFieldQuery(); @@ -432,13 +772,15 @@ function commerce_discount_uninstall() { } /** - * #1875524 Update the widget type of field commerce_discount_offer + * Update the widget type of field commerce_discount_offer. + * + * @see https://drupal.org/node/1875524 */ -function commerce_discount_update_7101(){ +function commerce_discount_update_7101() { $instances = field_info_instances('commerce_discount'); - foreach($instances as $bundle => $bundle_instances ){ - foreach($bundle_instances as $field_name => $instance){ - if( $field_name == 'commerce_discount_offer'){ + foreach ($instances as $bundle => $bundle_instances) { + foreach ($bundle_instances as $field_name => $instance) { + if ($field_name == 'commerce_discount_offer') { $instance['widget']['type'] = 'inline_entity_form_single'; field_update_instance($instance); } @@ -447,13 +789,21 @@ function commerce_discount_update_7101(){ } /** - * #2034685 Discount component price names. + * Discount component price names. + * + * @link https://drupal.org/node/2034685 * * Add a new column in commerce_discount table in order to display customized * component price title for a discount. */ function commerce_discount_update_7102() { - db_add_field('commerce_discount', 'component_title', array('description' => 'The component price title', 'type' => 'varchar', 'length' => 255, 'not null' => FALSE)); + $field_schema = array( + 'description' => 'The component price title', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ); + db_add_field('commerce_discount', 'component_title', $field_schema); } /** @@ -464,12 +814,313 @@ function commerce_discount_update_7103() { } /** - * #1854108 Changes to commerce_discount schema. + * Changes to commerce_discount schema. + * + * @see http://drupal.org/node/1854108 */ function commerce_discount_update_7104() { // Rename "enabled" to "status" and "status" to "export_status". - db_change_field('commerce_discount', 'status', 'export_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0x01, 'size' => 'tiny', 'description' => 'The exportable status of the entity.')); - db_change_field('commerce_discount', 'enabled', 'status', array('type' => 'int', 'not null' => TRUE, 'default' => 1, 'size' => 'tiny', 'description' => 'Whether the discount is active.')); + db_change_field('commerce_discount', 'status', 'export_status', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0x01, + 'size' => 'tiny', + 'description' => 'The exportable status of the entity.', + )); + db_change_field('commerce_discount', 'enabled', 'status', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + 'size' => 'tiny', + 'description' => 'Whether the discount is active.', + )); // Rebuild the schema. drupal_get_complete_schema(TRUE); } + +/** + * Add a sort order column to the Commerce Discount table. + */ +function commerce_discount_update_7105() { + $field_schema = array( + 'description' => 'The discount sort order.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 10, + 'size' => 'tiny', + ); + db_add_field('commerce_discount', 'sort_order', $field_schema); + + return t('Sort order added to the Commerce Discount table.'); +} + +/** + * Add discount compatibility fields to all discount types. + */ +function commerce_discount_update_7106() { + $fields = field_read_fields(array(), array('include_inactive' => TRUE)); + + // Create the discount compatibility strategy field for use on all discounts. + if (empty($fields['commerce_compatibility_strategy'])) { + $field = array( + 'type' => 'list_text', + 'field_name' => 'commerce_compatibility_strategy', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_discount_compatibility_strategies', + ), + ); + field_create_field($field); + } + + // Create the selected discounts field for use on all discounts. + if (empty($fields['commerce_compatibility_selection'])) { + $field = array( + 'type' => 'entityreference', + 'field_name' => 'commerce_compatibility_selection', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'locked' => TRUE, + 'settings' => array( + 'target_type' => 'commerce_discount', + 'handler' => 'base', + 'handler_settings' => array( + 'sort' => array( + 'type' => 'property', + 'property' => 'label', + 'direction' => 'ASC', + ), + ), + ), + ); + field_create_field($field); + } + + field_sync_field_status(); + field_info_cache_clear(); + $instances = field_info_instances(); + + foreach (commerce_discount_types() as $type => $value) { + if (empty($instances['commerce_discount'][$type]['commerce_compatibility_strategy'])) { + $instance = array( + 'field_name' => 'commerce_compatibility_strategy', + 'label' => t('Compatibility with other discounts'), + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'required' => TRUE, + 'widget' => array( + 'weight' => -10, + 'type' => 'options_buttons', + 'module' => 'options', + ), + 'default_value' => array( + 0 => array('value' => 'any'), + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount'][$type]['commerce_compatibility_selection'])) { + $instance = array( + 'field_name' => 'commerce_compatibility_selection', + 'label' => t('Selected discounts'), + 'entity_type' => 'commerce_discount', + 'bundle' => $type, + 'required' => FALSE, + 'widget' => array( + 'weight' => -9, + 'type' => 'entityreference_autocomplete', + 'module' => 'entityreference', + ), + ); + field_create_instance($instance); + } + } + + return t('Discount compatibility fields added to all discount types.'); +} + +/** + * Add fields to support a new "Shipping service upgrade" offer type. + */ +function commerce_discount_update_7107() { + if (module_exists('commerce_shipping')) { + $fields = field_read_fields(array(), array('include_inactive' => TRUE)); + + if (empty($fields['commerce_shipping_upgrade_target'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_shipping_upgrade_target', + 'type' => 'list_text', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_shipping_service_options_list', + ), + ); + field_create_field($field); + } + if (empty($fields['commerce_shipping_upgrade_source'])) { + $field = array( + 'entity_types' => array('commerce_discount_offer'), + 'field_name' => 'commerce_shipping_upgrade_source', + 'type' => 'list_text', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_shipping_service_options_list', + ), + ); + field_create_field($field); + } + + field_sync_field_status(); + field_info_cache_clear(); + $instances = field_info_instances(); + + if (empty($instances['commerce_discount_offer']['shipping_upgrade']['commerce_shipping_upgrade_target'])) { + $instance = array( + 'field_name' => 'commerce_shipping_upgrade_target', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'shipping_upgrade', + 'label' => t('Let customers select this service'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_select', + ), + ); + field_create_instance($instance); + } + if (empty($instances['commerce_discount_offer']['shipping_upgrade']['commerce_shipping_upgrade_source'])) { + $instance = array( + 'field_name' => 'commerce_shipping_upgrade_source', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'shipping_upgrade', + 'label' => t('For the same price as this service'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_select', + ), + ); + field_create_instance($instance); + } + } + + return t('Fields added to support the new "Shipping service upgrade" offer type.'); +} + +/** + * Add a field to support the concept of "Free or reduced shipping" offers. + */ +function commerce_discount_update_7108() { + if (module_exists('commerce_shipping')) { + $fields = field_read_fields(array(), array('include_inactive' => TRUE)); + + if (empty($fields['commerce_free_shipping_strategy'])) { + $field = array( + 'type' => 'list_text', + 'field_name' => 'commerce_free_shipping_strategy', + 'locked' => TRUE, + 'settings' => array( + 'allowed_values' => array(), + 'allowed_values_function' => 'commerce_discount_free_shipping_strategies', + ), + ); + field_create_field($field); + } + + field_sync_field_status(); + field_info_cache_clear(); + $instances = field_info_instances(); + + if (empty($instances['commerce_discount_offer']['free_shipping']['commerce_free_shipping_strategy'])) { + $instance = array( + 'field_name' => 'commerce_free_shipping_strategy', + 'entity_type' => 'commerce_discount_offer', + 'bundle' => 'free_shipping', + 'label' => t('Free shipping discount strategy'), + 'required' => TRUE, + 'widget' => array( + 'type' => 'options_buttons', + 'module' => 'options', + ), + 'default_value' => array( + 0 => array('value' => 'only_selected'), + ), + ); + field_create_instance($instance); + } + } + + return t('Field added to support the concept of "Free or reduced shipping" offers.'); +} + +/** + * Check for wrong discount values, before form validation was introduced. + * + * @see https://www.drupal.org/node/2468159#comment-10100064 + */ +function commerce_discount_update_7109() { + // Every value below and including '1' should be recalculated. + $query = db_select('field_data_commerce_percentage', 'percent'); + $query->condition('percent.commerce_percentage_value', 1, '<=') + ->fields('percent', array('commerce_percentage_value', 'entity_id')); + $result = $query->execute(); + + if ($result) { + foreach ($result as $record) { + // We got results, perform the updates. + db_update('field_data_commerce_percentage') + ->fields(array('commerce_percentage_value' => abs($record->commerce_percentage_value * 100))) + ->condition('entity_id', $record->entity_id, '=') + ->execute(); + } + } + + $text = t('Update 7109 finished. Please check all your percentage based discount settings for correct values after this update!'); + $text .= t('Number of percentage values in table "field_data_commerce_percentage" who were updated as a result: @count', array( + '@count' => $result->rowCount(), + )); + + return $text; +} + +/** + * Enable and update usage and date sub-modules into discount core. + */ +function commerce_discount_update_7110() { + // Fail early if the Date module is not present. + if (!module_exists('date')) { + throw new DrupalUpdateException('The Date module is required.'); + } + + commerce_discount_install_helper(); + $fields = field_read_fields(array(), array('include_inactive' => TRUE)); + + if (!db_table_exists('commerce_discount_usage')) { + db_create_table('commerce_discount_usage', drupal_get_schema_unprocessed('commerce_discount', 'commerce_discount_usage')); + } + + // Migrate from commerce_discount_max_uses to discount_usage_limit. + foreach (entity_load('commerce_discount') as $discount) { + $wrapper = entity_metadata_wrapper('commerce_discount', $discount); + if ((!isset($wrapper->discount_usage_limit) || $wrapper->discount_usage_limit->value() == FALSE) + && isset($wrapper->commerce_discount_max_uses) && $wrapper->commerce_discount_max_uses->value()) { + $wrapper->discount_usage_limit = $wrapper->commerce_discount_max_uses->value(); + entity_save('commerce_discount', $discount); + } + } + if (!empty($fields['commerce_discount_max_uses'])) { + field_delete_field('commerce_discount_max_uses'); + } + + $disable_modules = array('commerce_discount_usage', 'commerce_discount_date'); + db_update('system') + ->fields(array('status' => 0)) + ->condition('name', $disable_modules) + ->condition('type', 'module') + ->execute(); + + field_info_cache_clear(); + + return t('Discount date and usage sub-modules are now in core.'); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.module b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.module index c5e82bfc..335cf5d5 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.module @@ -2,90 +2,112 @@ /** * @file - * Defines the discount and discount offer entities (always managed together), + * Defines the discount and discount offer entities, bundles and functionality. + * + * Discount and offer entities are always managed together, * their bundles, and all surrounding functionality (API, UI). */ /** * Implements hook_commerce_cart_order_refresh(). - * - * Remove any existing commerce discount line item. */ -function commerce_discount_commerce_cart_order_refresh($wrapper) { - $line_items_to_delete = array(); - $cloned_line_items = array(); +function commerce_discount_commerce_cart_order_refresh($order_wrapper) { // Remove all discount references from the order. // (If there are none, setting array() would modify the order, so test first.) - if ($wrapper->__isset('commerce_discounts') && $wrapper->commerce_discounts->value()) { - $wrapper->commerce_discounts->set(array()); + if (isset($order_wrapper->commerce_discounts) && $order_wrapper->commerce_discounts->value()) { + $order_wrapper->commerce_discounts = array(); } + if (!isset($order_wrapper->commerce_line_items) || $order_wrapper->commerce_line_items->count() <= 0) { + return; + } + $line_items_to_delete = array(); - // Remove discount components from the order total price. - commerce_discount_remove_discount_components($wrapper->commerce_order_total); + $currency_code = commerce_default_currency(); + if (!is_null($order_wrapper->commerce_order_total->value())) { + $currency_code = $order_wrapper->commerce_order_total->currency_code->value(); + } + // We create an empty price structure to be used by discount line items, + // we want to remove all price components present on discount line items such + // as VAT, the discount price component itself etc. + $empty_price = commerce_discount_empty_price($currency_code); - foreach ($wrapper->commerce_line_items as $delta => $wrapper_line_item) { - if ($wrapper_line_item->getBundle() == 'product_discount') { - // Delete the line item, and remove it from the order. - $line_items_to_delete[] = $wrapper_line_item->line_item_id->value(); - $wrapper->commerce_line_items->offsetUnset($delta); + foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { + if (!$line_item_wrapper->value()) { + continue; } - elseif (is_object($wrapper_line_item) && $line_item = $wrapper_line_item->value()) { - $cloned_line_items[$wrapper_line_item->getIdentifier()] = clone $line_item; - - // Remove discount components from the line item price fields; the line - // item may be a discount (so the price will become 0) or another line - // item like a product (so the price will become the 'original' price). - commerce_discount_remove_discount_components($wrapper_line_item->commerce_unit_price); - commerce_discount_remove_discount_components($wrapper_line_item->commerce_total); + // Remove all the price components on discount line items, if the unit price + // is still == 0 after evaluating order discount rules, the discount line + // item will be deleted. + if ($line_item_wrapper->getBundle() == 'commerce_discount') { + $line_item_wrapper->commerce_unit_price = $empty_price; + $line_item_wrapper->commerce_total = $empty_price; + continue; + } + // Remove product_discount line items each time we refresh the order, + // we don't know in advance if "free bonus product" rules are still + // applicable, if they're, a new line item is going to be recreated when + // the "commerce_discount_order" rules event is fired. + elseif ($line_item_wrapper->getBundle() == 'product_discount') { + $line_items_to_delete[] = $line_item_wrapper->getIdentifier(); + $order_wrapper->commerce_line_items->offsetUnset($delta); + continue; + } + elseif ($line_item_wrapper->getBundle() == 'shipping') { + $changed = commerce_discount_remove_discount_components($line_item_wrapper->commerce_unit_price); + if ($changed) { + // Since we're saving the line item, there's no need to manually + // update the line item's total since that'll be done in the controller. + $line_item_wrapper->save(); + } } } - // Save the line items which have changes after removing/re-adding discounts; - // except if one of our discount type line items still has price 0, delete it. - foreach ($wrapper->commerce_line_items as $delta => $wrapper_line_item) { - if ($wrapper_line_item->getBundle() != 'product_discount') { - $id = $wrapper_line_item->getIdentifier(); + // We need to make sure the order total is correct before evaluating discount + // rules, order total may not be correct when our refresh implementation is + // invoked. + commerce_discount_calculate_order_total($order_wrapper); + // Re-add all applicable discount price components and/or line items. + rules_invoke_event('commerce_discount_order', $order_wrapper); - // Only check non-new line items; new ones have already been saved. - if (isset($cloned_line_items[$id])) { - if ($wrapper_line_item->getBundle() == 'commerce_discount' && $wrapper_line_item->commerce_unit_price->amount->value() == 0) { - $line_items_to_delete[] = $id; - $wrapper->commerce_line_items->offsetUnset($delta); - } - else { - // Check for changes, in commerce_unit_price (in the same way as - // commerce_cart_order_refresh() does) and quantity. - // (Don't check e.g. commerce_total; it may not be updated yet.) - $old_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $cloned_line_items[$id]); - $old_data = $old_line_item_wrapper->commerce_unit_price->data->value() + array('components' => array()); - $data = (array) $wrapper_line_item->commerce_unit_price->data->value() + array('components' => array()); - - if ($wrapper_line_item->commerce_unit_price->amount->value() != $old_line_item_wrapper->commerce_unit_price->amount->value() || - $wrapper_line_item->commerce_unit_price->currency_code->value() != $old_line_item_wrapper->commerce_unit_price->currency_code->value() || - $data['components'] != $old_data['components']) { - // Save changed line item (which will correctly set commerce_total). - $wrapper_line_item->save(); - } - } - } + // After the discount rules were evaluated, we need to check if we have + // some discount line items with empty prices (we need to delete them). + foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { + if (!$line_item_wrapper->value() || $line_item_wrapper->getBundle() != 'commerce_discount') { + continue; + } + $unit_price = commerce_price_wrapper_value($line_item_wrapper, 'commerce_unit_price', TRUE); + if (empty($unit_price['amount'])) { + $line_items_to_delete[] = $line_item_wrapper->getIdentifier(); + $order_wrapper->commerce_line_items->offsetUnset($delta); } } - // Delete line items. - commerce_line_item_delete_multiple($line_items_to_delete); - - // Re-add all applicable discount price components and/or line items. - rules_invoke_event('commerce_discount_order', $wrapper->value()); + // Delete discount line item if necessary. + if ($line_items_to_delete) { + commerce_line_item_delete_multiple($line_items_to_delete, TRUE); + } } + /** * Remove discount components from a given price and recalculate the total. * * @param object $price_wrapper * Wrapped commerce price. + * @param array $discount_types_to_remove + * An array of discount type strings to remove. + * + * @return bool + * TRUE if at least one price component has been removed, FALSE otherwise. */ -function commerce_discount_remove_discount_components($price_wrapper) { - $data = (array) $price_wrapper->data->value() + array('components' => array()); +function commerce_discount_remove_discount_components($price_wrapper, $discount_types_to_remove = array('order_discount')) { + $price = $price_wrapper->value(); + // If there are no price or components, there is nothing to remove. + if (!$price || empty($price['data']['components'])) { + return FALSE; + } + + $data = (array) $price['data'] + array('components' => array()); $component_removed = FALSE; // Remove price components belonging to order discounts. foreach ($data['components'] as $key => $component) { @@ -94,14 +116,16 @@ function commerce_discount_remove_discount_components($price_wrapper) { // Remove all discount components. if (!empty($component['price']['data']['discount_name'])) { $discount_name = $component['price']['data']['discount_name']; - $wrapper = entity_metadata_wrapper('commerce_discount', $discount_name); - if (!$wrapper->value() || $wrapper->getBundle() == 'order_discount') { + $discount = entity_load_single('commerce_discount', $discount_name); + if (!$discount || in_array($discount->type, $discount_types_to_remove)) { $remove = TRUE; } - } else if ($component['name'] != 'base_price') { - // As long as the component is neither base price nor discount, allow other modules to say whether it gets reset. - // This is so that discounts can apply against a price that has been stripped of components from modules that - // need to apply against a post-discount price. + } + elseif ($component['name'] != 'base_price') { + // As long as the component is neither base price nor discount, allow + // other modules to say whether it gets reset. This is so that discounts + // can apply against a price that has been stripped of components from + // modules that need to apply against a post-discount price. drupal_alter('commerce_discount_remove_price_component', $component, $remove); } @@ -112,15 +136,18 @@ function commerce_discount_remove_discount_components($price_wrapper) { } // Don't alter the price components if no components were removed. if (!$component_removed) { - return; + return FALSE; } + // Properly re-key the components array. + $data['components'] = array_values($data['components']); // Re-save the price without the discounts (if existed). $price_wrapper->data->set($data); // Re-set the total price. $total = commerce_price_component_total($price_wrapper->value()); - $price_wrapper->amount->set($total['amount']); + $price_wrapper->amount = $total['amount']; + return TRUE; } /** @@ -137,20 +164,17 @@ function commerce_discount_commerce_cart_order_empty($order) { } } - // Delete line items. - commerce_line_item_delete_multiple($line_items_to_delete); + if ($line_items_to_delete) { + // Delete line items. + commerce_line_item_delete_multiple($line_items_to_delete); + } } /** - * Implements hook_commerce_shipping_method_collect_rates(). - * - * Invoke the commerce_discount_free_shipping event and the - * commerce_discount_percent_off_shipping event when the shipping rates - * are being collected. + * Discount the shipping services for a given order. */ -function commerce_discount_commerce_shipping_method_collect_rates($method, $order) { - rules_invoke_event('commerce_discount_free_shipping', $order); - rules_invoke_event('commerce_discount_percent_off_shipping', $order); +function commerce_discount_shipping_services($order) { + commerce_cart_order_refresh($order); } /** @@ -182,7 +206,7 @@ function commerce_discount_entity_info() { 'views controller class' => 'CommerceDiscountViewsController', // Enable the entity API's admin UI. 'admin ui' => array( - 'path' => 'admin/commerce/store/discounts', + 'path' => 'admin/commerce/discounts', 'file' => 'includes/commerce_discount.admin.inc', 'controller class' => 'CommerceDiscountUIController', ), @@ -243,12 +267,10 @@ function commerce_discount_permission() { * Implements hook_views_api(). */ function commerce_discount_views_api($module, $api) { - if ($module == 'views') { - return array( - 'version' => 3, - 'path' => drupal_get_path('module', 'commerce_discount') . '/includes/views', - ); - } + return array( + 'version' => 3, + 'path' => drupal_get_path('module', 'commerce_discount') . '/includes/views', + ); } /** @@ -280,7 +302,6 @@ function commerce_discount_form_features_export_form_alter(&$form, $from_state) * Implements hook_commerce_price_formatted_components_alter(). */ function commerce_discount_commerce_price_formatted_components_alter(&$components, $price, $entity) { - // if (isset($price['data']['components'])) { // Loop into price components and alter the component title if the discount // component label is found. @@ -296,12 +317,12 @@ function commerce_discount_commerce_price_formatted_components_alter(&$component /** * Implements hook_form_FORM_ID_alter(). * - * Disable alteration of discounts with overriden rules. + * Disable alteration of discounts with overridden rules. */ function commerce_discount_form_commerce_discount_form_alter(&$form, $form_state) { // Add clearfix to the discount type container. $form['commerce_discount_type']['#attributes']['class'][] = 'clearfix'; - $form['commerce_discount_fields']['commerce_discount_offer'][LANGUAGE_NONE]['form']['type']['#attributes']['class'][] = 'clearfix'; + $form['commerce_discount_fields']['commerce_discount_offer']['#attributes']['class'][] = 'clearfix'; if (empty($form_state['commerce_discount']->discount_id)) { // Entity is new. @@ -315,7 +336,7 @@ function commerce_discount_form_commerce_discount_form_alter(&$form, $form_state $rule = rules_config_load('commerce_discount_rule_' . $form_state['commerce_discount']->name); if ($rule && $rule->hasStatus(ENTITY_OVERRIDDEN)) { - drupal_set_message(t('The rule associated with this discount is overriden, making it impossible to edit the discount.'), 'warning'); + drupal_set_message(t('The rule associated with this discount is overridden, making it impossible to edit the discount.'), 'warning'); $form['actions']['submit']['#disabled'] = TRUE; } } @@ -329,15 +350,24 @@ function commerce_discount_field_widget_form_alter(&$element, &$form_state, $con $element['#options'] += commerce_shipping_service_options_list(); if ($form_state['op'] == 'edit') { $wrapper_offer = entity_metadata_wrapper('commerce_discount_offer', $element['#entity']); - if($element['#field_name'] == 'commerce_free_shipping'){ + if ($element['#field_name'] == 'commerce_free_shipping') { $element['#default_value'] = $wrapper_offer->commerce_free_shipping->value(); } - else{ + else { $element['#default_value'] = $wrapper_offer->commerce_percent_off_ship_serv->value(); } - } } + + // If the current element is the free shipping discount strategy select list, + // alter it to only be visible when a free shipping service has been selected. + if (!empty($element['#field_name']) && $element['#field_name'] == 'commerce_free_shipping_strategy') { + $element['#states'] = array( + 'invisible' => array( + ':input[name="commerce_discount_fields[commerce_discount_offer][' . LANGUAGE_NONE . '][form][commerce_free_shipping][' . LANGUAGE_NONE . ']"]' => array('value' => '_none'), + ), + ); + } } /** @@ -346,7 +376,13 @@ function commerce_discount_field_widget_form_alter(&$element, &$form_state, $con * Rebuild Rules configuration. */ function commerce_discount_commerce_discount_insert($entity) { - entity_defaults_rebuild(array('rules_config')); + if (module_exists('i18n_string')) { + i18n_string_object_update('commerce_discount', $entity); + } + // We need to reload the discount afresh because some fields contain + // serialized and empty values. + $discount = entity_load_single('commerce_discount', $entity->discount_id); + _commerce_discount_rebuild_rules_config(array(clone $discount)); } /** @@ -355,7 +391,146 @@ function commerce_discount_commerce_discount_insert($entity) { * Rebuild Rules configuration. */ function commerce_discount_commerce_discount_update($entity) { - entity_defaults_rebuild(array('rules_config')); + if (module_exists('i18n_string')) { + // Account for name changes. + if ($entity->original->name != $entity->name) { + i18n_string_update_context( + "commerce_discount:commerce_discount:" . $entity->original->name . ":component_title", + "commerce_discount:commerce_discount:" . $entity->name . ":component_title" + ); + } + i18n_string_object_update('commerce_discount', $entity); + } + // We need to reload the discount afresh because some fields contain + // serialized and empty values. + $discount = entity_load_single('commerce_discount', $entity->discount_id); + _commerce_discount_rebuild_rules_config(array(clone $discount)); +} + +/** + * Actually rebuild the defaults of a given entity. + * + * @param array $discounts + * An array of discount entities. + * + * @see entity_defaults_rebuild() + */ +function _commerce_discount_rebuild_rules_config(array $discounts) { + // Return early if we can't acquire a lock on the rules config. + if (!lock_acquire('entity_rebuild_rules_config')) { + return; + } + + $existing_rules_config = FALSE; + $rules_config_names = array(); + foreach ($discounts as $discount) { + $rules_config_names[] = commerce_discount_build_rule_machine_name($discount->name); + } + + $info = entity_get_info('rules_config'); + $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_rules_config'; + $keys = $info['entity keys'] + array( + 'module' => 'module', + 'status' => 'status', + 'name' => $info['entity keys']['id'], + ); + + // Check for the existence of the module and status columns. + if (!in_array($keys['status'], $info['schema_fields_sql']['base table']) || !in_array($keys['module'], $info['schema_fields_sql']['base table'])) { + trigger_error("Missing database columns for the exportable entity 'rules_config' as defined by entity_exportable_schema_fields(). Update the according module and run update.php!", E_USER_WARNING); + return; + } + + // Rebuild the discount rules for the given discounts. + $entities = array(); + foreach (commerce_discount_build_discount_rules($discounts) as $name => $entity) { + if (!in_array($name, $rules_config_names)) { + continue; + } + $entity->{$keys['name']} = $name; + $entity->{$keys['module']} = 'commerce_discount'; + $entities[$name] = $entity; + $existing_rules_config = TRUE; + } + drupal_alter($hook, $entities); + + // Check for defaults that disappeared or overridden? + if (!$existing_rules_config) { + $statuses = array( + $keys['status'] => array( + ENTITY_OVERRIDDEN, + ENTITY_IN_CODE, + ENTITY_FIXED, + ), + ); + $existing_defaults = entity_load_multiple_by_name('rules_config', FALSE, $statuses); + + foreach ($existing_defaults as $name => $entity) { + if (empty($entities[$name]) && in_array($name, $rules_config_names)) { + $entity->is_rebuild = TRUE; + if (entity_has_status('rules_config', $entity, ENTITY_OVERRIDDEN)) { + $entity->{$keys['status']} = ENTITY_CUSTOM; + entity_save('rules_config', $entity); + } + else { + entity_delete('rules_config', $name); + } + unset($entity->is_rebuild); + } + } + } + + // Load all existing entities. + $existing_entities = entity_load_multiple_by_name('rules_config', array_keys($entities)); + + foreach ($existing_entities as $name => $entity) { + if (entity_has_status('rules_config', $entity, ENTITY_CUSTOM)) { + // If the entity already exists but is not yet marked as overridden, we + // have to update the status. + if (!entity_has_status('rules_config', $entity, ENTITY_OVERRIDDEN)) { + $entity->{$keys['status']} |= ENTITY_OVERRIDDEN; + $entity->{$keys['module']} = $entities[$name]->{$keys['module']}; + $entity->is_rebuild = TRUE; + entity_save('rules_config', $entity); + unset($entity->is_rebuild); + } + + // The entity is overridden, so we do not need to save the default. + unset($entities[$name]); + } + } + + // Save defaults. + $originals = array(); + foreach ($entities as $name => $entity) { + if (!empty($existing_entities[$name])) { + // Make sure we are updating the existing default. + $entity->{$keys['id']} = $existing_entities[$name]->{$keys['id']}; + unset($entity->is_new); + } + // Pre-populate $entity->original as we already have it. So we avoid + // loading it again. + $entity->original = !empty($existing_entities[$name]) ? $existing_entities[$name] : FALSE; + // Keep original entities for hook_{entity_type}_defaults_rebuild() + // implementations. + $originals[$name] = $entity->original; + + if (!isset($entity->{$keys['status']})) { + $entity->{$keys['status']} = ENTITY_IN_CODE; + } + else { + $entity->{$keys['status']} |= ENTITY_IN_CODE; + } + $entity->is_rebuild = TRUE; + entity_save('rules_config', $entity); + unset($entity->is_rebuild); + } + + // Invoke an entity type-specific hook so modules may apply changes, e.g. + // efficiently rebuild caches. + module_invoke_all('rules_config_defaults_rebuild', $entities, $originals); + + lock_release('entity_rebuild_rules_config'); } /** @@ -364,6 +539,9 @@ function commerce_discount_commerce_discount_update($entity) { * Delete referenced commerce_discount_offer upon commerce_discount deletion. */ function commerce_discount_commerce_discount_delete($entity) { + if (module_exists('i18n_string')) { + i18n_string_object_remove('commerce_discount', $entity); + } $wrapper = entity_metadata_wrapper('commerce_discount', $entity); // Delete the referenced commerce_discount_offer. if ($wrapper->commerce_discount_offer->value()) { @@ -418,20 +596,23 @@ function commerce_discount_commerce_discount_offer_type_info() { if (module_exists('commerce_shipping')) { $types['free_shipping'] = array( 'label' => t('Free shipping'), - 'action' => 'commerce_discount_free_shipping_service', + 'action' => 'commerce_discount_shipping_service', 'entity types' => array('commerce_order'), - 'event' => 'commerce_discount_free_shipping', ); $types['percent_off_shipping'] = array( 'label' => t('% off of shipping'), - 'action' => 'commerce_discount_percent_off_shipping_service', + 'action' => 'commerce_discount_shipping_service', + 'entity types' => array('commerce_order'), + ); + $types['shipping_upgrade'] = array( + 'label' => t('Shipping service upgrade'), + 'action' => 'commerce_discount_shipping_service', 'entity types' => array('commerce_order'), - 'event' => 'commerce_discount_percent_off_shipping', ); } $types['free_products'] = array( - 'label' => t('Free products'), + 'label' => t('Add free bonus products'), 'action' => 'commerce_discount_free_products', 'entity types' => array('commerce_order'), ); @@ -459,11 +640,54 @@ function commerce_discount_commerce_discount_rule_build($rule, $discount) { if ($action->getElementName() == 'commerce_discount_free_shipping_service') { $action->settings['shipping_service'] = $shipping_service; } - else { - $action->delete(); - } } } + + // Product level discounts must pass the line item's order. + $map = array( + 'order_discount' => 'commerce-order', + 'product_discount' => 'commerce-line-item:order', + ); + + if (isset($map[$discount->type])) { + // Add condition for per-person usage. + if ($wrapper->discount_usage_per_person->value()) { + $rule->condition( + 'commerce_discount_usage_max_usage_per_person', + array( + 'commerce_discount' => $discount->name, + 'order:select' => $map[$discount->type], + 'usage' => $wrapper->discount_usage_per_person->value(), + ) + ); + } + // For normal usage. + if ($wrapper->discount_usage_limit->value()) { + $rule->condition( + 'commerce_discount_usage_max_usage', + array( + 'commerce_discount' => $discount->name, + 'order:select' => $map[$discount->type], + 'usage' => $wrapper->discount_usage_limit->value(), + ) + ); + } + } + + if (!is_null($wrapper->commerce_discount_date->value())) { + // If the end date for the discount has already passed, disable the rule. + // Note that we add a day to the discount end date. The date field value is + // set for 12:00:00 AM on the end date, but we want the discount to remain + // valid through that day. + if (REQUEST_TIME >= $wrapper->commerce_discount_date->value2->value() + 86400) { + $rule->active = FALSE; + } + + // Add condition to check usage didn't reach max uses. + $rule->condition('commerce_discount_date_condition', array( + 'commerce_discount' => $discount->name, + )); + } } /** @@ -518,8 +742,8 @@ function commerce_discount_type($discount_type) { * * @return mixed * Either an array of all discount type names keyed by the machine name or a - * string containing the human readable name for the specified type. If a - * type is specified that does not exist, this function returns FALSE. + * string containing the human readable name for the specified type. If a type + * is specified that does not exist, this function returns FALSE. */ function commerce_discount_type_get_name($type = NULL) { $discount_types = commerce_discount_types(); @@ -621,7 +845,7 @@ function commerce_discount_offer_type_get_name($type = NULL) { } /** - * Return an array keyed by commerce order type and label as value. + * Return an array keyed by commerce discount name and label as value. */ function commerce_discount_entity_list() { $options = array(); @@ -643,6 +867,9 @@ function commerce_discount_commerce_line_item_type_info() { 'description' => t('Line item for fixed amounts.'), 'add_form_submit_value' => t('Add discount'), 'base' => 'commerce_discount_line_item', + 'callbacks' => array( + 'title' => 'commerce_discount_line_item_title', + ), ), 'product_discount' => array( 'type' => 'product_discount', @@ -651,6 +878,9 @@ function commerce_discount_commerce_line_item_type_info() { 'product' => TRUE, 'add_form_submit_value' => t('Add product'), 'base' => 'commerce_product_line_item', + 'callbacks' => array( + 'title' => 'commerce_discount_product_line_item_title', + ), ), ); } @@ -658,9 +888,646 @@ function commerce_discount_commerce_line_item_type_info() { /** * Determine the discount's line item title. * - * @return + * @return string * The line item title. */ -function commerce_discount_line_item_title() { - return t('Fixed amount discount'); +function commerce_discount_line_item_title($line_item) { + $wrapper = entity_metadata_wrapper('commerce_discount', $line_item->data['discount_name']); + $title = $wrapper->component_title->value(); + return empty($title) ? t('Fixed amount discount') : check_plain($title); +} + +/** + * Determine the product_discount's line item title. + * + * This function wrap's the line item's title in a span element with a class to + * provide stylistic changes to a product which has a discount. + * + * @return string + * The line item title. + */ +function commerce_discount_product_line_item_title($line_item) { + return '' . commerce_product_line_item_title($line_item) . ''; +} + +/** + * Returns an array of discount compatibility strategies. + * + * Returned array used in a radio buttons select list. + */ +function commerce_discount_compatibility_strategies() { + return array( + 'any' => t('Any discount'), + 'except' => t('Any discount except specific discounts'), + 'only' => t('Only with specific discounts'), + 'none' => t('Not with any other discount'), + ); +} + +/** + * Returns the compatibility strategy for the given discount. + * + * @param CommerceDiscount $discount + * A Commerce Discount object with a commerce_compatibility_strategy field. + * + * @return string + * The discount's compatibility strategy or 'any' if none is set. + */ +function commerce_discount_get_compatibility_strategy($discount) { + if (isset($discount->commerce_compatibility_strategy[LANGUAGE_NONE])) { + $strategy = $discount->commerce_compatibility_strategy[LANGUAGE_NONE][0]['value']; + } + + // If the compatibility strategy is not set, default to 'any'. + if (empty($strategy)) { + $strategy = 'any'; + } + + // Note that if the field is set to an unknown value, we still return it. The + // default condition for evaluating discount compatibility will pass through + // unknown discount strategies, so modules that set them are responsible also + // to evaluate them on behalf of all discounts. + return $strategy; +} + +/** + * Returns the compatibility selection for the given discount. + * + * @param CommerceDiscount $discount + * A Commerce Discount object with a commerce_compatibility_selection field. + * + * @return int[] + * An array of discount IDs selected for the discount's compatibility strategy + * or an empty array if none are selected (or a non-array value is found). + */ +function commerce_discount_get_compatibility_selection($discount) { + // Don't use the entity_metadata_wrapper here on purpose for performance + // reasons. + $items = field_get_items('commerce_discount', $discount, 'commerce_compatibility_selection', LANGUAGE_NONE); + $selection = array(); + + if (!$items) { + return $selection; + } + + foreach ($items as $item) { + $selection[] = $item['target_id']; + } + + return $selection; +} + +/** + * Returns a list of discounts applied. + * + * Returned list used to make a particular price as determined by its + * price components. + * + * @param array $price + * A price field value array including a data array with price components. + * @param string $type + * Optional. A discount type to filter the return array by so that only + * discounts of a matching type are returned. + * + * @return array + * Array of applied discounts. + */ +function commerce_discount_get_discounts_applied_to_price($price, $type = '') { + $applied_discounts = array(); + + // Return early if the price has no components. + if (empty($price['data']['components'])) { + return array(); + } + + // Loop over the price components looking for known discount components. + foreach ($price['data']['components'] as $component) { + if (strpos($component['name'], 'discount|') === 0) { + // Load the discount entity represented by this component. + $applied_discount_name = substr($component['name'], 9); + $applied_discount = entity_load_single('commerce_discount', $applied_discount_name); + + // Add it to the list of applied discounts keyed by ID to prevent + // duplicates. + if (!empty($applied_discount)) { + // Only add the discount to the return value if type filtering was not + // requested or the type matches. + if (empty($type) || $applied_discount->type == $type) { + $applied_discounts[$applied_discount->discount_id] = $applied_discount_name; + } + } + } + } + + return $applied_discounts; +} + +/** + * Returns a list of discount strategies for free shipping offers. + */ +function commerce_discount_free_shipping_strategies() { + return array( + 'only_selected' => t('Only discount the selected shipping service.'), + 'discount_all' => t('Discount all other shipping services by the same amount as the selected shipping service.'), + ); +} + +/** + * Returns the free shipping strategy for the given discount. + * + * @param CommerceDiscount $discount + * A Commerce Discount object with a commerce_free_shipping_strategy field. + * + * @return string + * The discount's free shipping strategy or 'only_selected' if none is set. + */ +function commerce_discount_get_free_shipping_strategy($discount) { + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $strategy = $discount_wrapper->commerce_discount_offer->commerce_free_shipping_strategy->value(); + + // If the free shipping strategy is not set, default to 'only_selected'. + if (empty($strategy)) { + $strategy = 'only_selected'; + } + + // Note that if the field is set to an unknown value, we still return it. The + // default action for applying a free shipping discount will pass through + // unknown discount strategies, so modules that set them are responsible also + // to evaluate them on behalf of all discounts. + return $strategy; +} + +/** + * Get usage of a discount for a user, excluding a certain order id. + * + * @param string $discount_name + * The discount name. + * @param string $mail + * The user mail. + * @param int|bool $exclude_order_id + * If provided, the order_id to ignore when calculating the discount usage. + * + * @return int + * Return the number of usage by mail. + */ +function commerce_discount_usage_get_usage_by_mail($discount_name, $mail, $exclude_order_id = FALSE) { + $usage = &drupal_static(__FUNCTION__, array()); + + if (!isset($usage[$mail])) { + $usage[$mail] = array(); + $query = db_select('commerce_discount_usage', 'g') + ->fields('g') + ->condition('g.mail', $mail); + $results = $query->execute()->fetchAll(PDO::FETCH_ASSOC); + + foreach ($results as $result) { + $usage[$mail] += array( + $result['discount'] => array(), + ); + $usage[$mail][$result['discount']][] = $result['order_id']; + } + } + // The usage array for the given discount must be initialized if not set, + // otherwise the array_diff will get a wrong argument. + $usage[$mail] += array( + $discount_name => array(), + ); + + // Exclude the order_id passed if necessary. + if ($exclude_order_id) { + $usage[$mail][$discount_name] = array_diff($usage[$mail][$discount_name], array($exclude_order_id)); + } + + return count($usage[$mail][$discount_name]); +} + +/** + * Implements hook_commerce_order_update(). + */ +function commerce_discount_commerce_order_update($order) { + commerce_discount_usage_record_order_usage($order); +} + +/** + * Implements hook_commerce_order_insert(). + */ +function commerce_discount_commerce_order_insert($order) { + commerce_discount_usage_record_order_usage($order); +} + +/** + * Implements hook_commerce_order_delete(). + */ +function commerce_discount_commerce_order_delete($order) { + commerce_discount_usage_reset_order_usage($order); +} + +/** + * Loads all discounts connected to an order. + * + * Loads discounts including line item level discounts traced through + * line item unit price components. + * + * @param object $order + * A fully qualified order object. + * + * @return array + * List of order discounts. + */ +function commerce_discount_usage_order_discounts($order) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_discounts = array(); + + foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { + try { + $unit_price = commerce_price_wrapper_value($line_item_wrapper, 'commerce_unit_price', TRUE); + } + catch (Exception $ex) { + // Log and continue if we're unable to load the unit price. + // @TODO Resolve how to prevent commerce_cart_order_refresh() from firing + // inside an order save via entity_load_unchanged(). + // @see https://www.drupal.org/node/2661530 + $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS); + $watchdog_variables = array('@backtrace' => json_encode($backtrace, JSON_PRETTY_PRINT)); + watchdog('commerce_discount', 'Discount usage line item is missing. Backtrace:
@backtrace
', $watchdog_variables, WATCHDOG_DEBUG); + continue; + } + + foreach ($unit_price['data']['components'] as $key => $component) { + if (strpos($component['name'], 'discount|') === 0 && !empty($component['price']['data']['discount_name'])) { + $order_discount_name = $component['price']['data']['discount_name']; + $order_discount_wrapper = entity_metadata_wrapper('commerce_discount', $order_discount_name); + // Make a list of discounts present via the order's line item price + // components. + if ($order_discount_wrapper->value()) { + $order_discounts[] = $order_discount_wrapper->name->value(); + } + } + } + } + + // Add the set of discounts directly referenced on the order. + if (isset($order->commerce_discounts) && $order_wrapper->commerce_discounts->value()) { + foreach ($order_wrapper->commerce_discounts->value() as $discount) { + if (isset($discount->name)) { + $order_discounts[] = $discount->name; + } + } + } + + $order_discounts = array_unique($order_discounts); + + return $order_discounts; +} + +/** + * Record order usage. + * + * @param object $order + * A fully qualified order object. + */ +function commerce_discount_usage_record_order_usage($order) { + // Reset usage for this order first. + commerce_discount_usage_reset_order_usage($order); + + // Only record discount usage if the order has an email. + $discount_names = commerce_discount_usage_order_discounts($order); + + foreach ($discount_names as $discount_name) { + $record = array( + 'discount' => $discount_name, + 'mail' => $order->mail ? $order->mail : '', + 'order_id' => $order->order_id, + ); + drupal_write_record('commerce_discount_usage', $record); + } +} + +/** + * Reset usage statistics for an entire order. + * + * @param object $order + * A fully qualified order object. + * + * @return DeleteQuery + * Return a new DeleteQuery object. + */ +function commerce_discount_usage_reset_order_usage($order) { + return db_delete('commerce_discount_usage') + ->condition('order_id', $order->order_id) + ->execute(); +} + +/** + * Get usage of a discount, excluding a certain order id. + * + * @param string $discount_name + * The discount name. + * @param bool $exclude_order_id + * If TRUE, the order id will be excluded from the SQL request. + * + * @return int + * Return the number of usage. + */ +function commerce_discount_usage_get_usage($discount_name, $exclude_order_id = FALSE) { + $query = db_select('commerce_discount_usage', 'g') + ->fields('g') + ->condition('g.discount', $discount_name); + + if ($exclude_order_id) { + $query->condition('g.order_id', $exclude_order_id, '<>'); + } + + return $query->execute()->rowCount(); +} + +/** + * Deletes the first discount line item on an order matching by discount name. + * + * @param EntityDrupalWrapper $order_wrapper + * The wrapped order entity. + * @param string $discount_name + * The name of the discount whose line item should be deleted. + * + * @return bool + * TRUE if line item deleted, or FALSE if not found. + */ +function commerce_discount_delete_discount_line_item_by_name($order_wrapper, $discount_name) { + foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { + if ($line_item_wrapper->getBundle() == 'commerce_discount') { + $line_item = $line_item_wrapper->value(); + + if (isset($line_item->data['discount_name']) && $line_item->data['discount_name'] == $discount_name) { + commerce_line_item_delete($line_item_wrapper->line_item_id->value()); + return TRUE; + } + } + } + + return FALSE; +} + +/** + * Implements hook_entity_info_alter(). + */ +function commerce_discount_entity_info_alter(&$info) { + if (module_exists('i18n_string')) { + // Enable i18n support via the entity API. + $info['commerce_discount']['i18n controller class'] = 'EntityDefaultI18nStringController'; + } +} + +/** + * Implements hook_entity_property_info_alter(). + */ +function commerce_discount_entity_property_info_alter(&$info) { + if (module_exists('i18n_string')) { + // Mark some properties as translatable, but also denote that translation + // works with i18n_string. + foreach (array('component_title') as $name) { + $info['commerce_discount']['properties'][$name]['translatable'] = TRUE; + $info['commerce_discount']['properties'][$name]['i18n string'] = TRUE; + } + } +} + +/** + * Implements hook_query_TAG_alter(). + */ +function commerce_discount_query_entityreference_alter($query) { + $field = $query->getMetaData('field'); + + // Alter the default autocomplete for the compatibility selection field... + if ($field['field_name'] == 'commerce_compatibility_selection' && + strpos(current_path(), 'entityreference/autocomplete') === 0) { + // Extract the bundle of the discount whose compatibility settings are + // being adjusted and limit the autocomplete query to discounts of the + // same bundle (i.e. discount type, Product vs. Order). + $bundle_name = arg(5); + + if (in_array($bundle_name, array('product_discount', 'order_discount'))) { + $query->condition('type', $bundle_name); + } + + // Also do not include the current discount itself as an option. + $entity_id = arg(6); + + if ($entity_id != 'NULL') { + $query->condition('discount_id', $entity_id, '!='); + } + } +} + +/** + * Build the rules configuration for the given discounts. + * + * @param array $discounts + * An array of discount entities. + * + * @return array + * An array of rules configuration objects. + */ +function commerce_discount_build_discount_rules(array $discounts) { + $rules = array(); + $types = commerce_discount_types(); + $offer_types = commerce_discount_offer_types(); + + foreach ($discounts as $discount) { + $wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $wrapper_properties = $wrapper->getPropertyInfo(); + // Only for Commerce Discount wrappers with Commerce Discount Offer defined. + if (isset($wrapper->commerce_discount_offer)) { + $offer_bundle = $wrapper->commerce_discount_offer->getBundle(); + if (!isset($offer_types[$offer_bundle])) { + continue; + } + + $type = $types[$discount->type]; + $offer_type = $offer_types[$offer_bundle]; + + $rule = rules_reaction_rule(); + + $rule->label = $discount->label; + $rule->active = $discount->status; + $rule->weight = !empty($discount->sort_order) ? $discount->sort_order - 11 : -1; + $rule->tags = array('Commerce Discount', check_plain($type['label'])); + + $rule + ->event(!empty($offer_type['event']) ? $offer_type['event'] : $type['event']) + ->action( + $offer_type['action'], array( + 'entity:select' => $type['entity type'], + 'commerce_discount' => $discount->name, + ) + ); + + // Add the compatibility condition to all discounts. Even if the current + // discount is compatible with any other discount, we need to prevent it + // from being added if a previously applied discount indicates it is not + // compatible with the current one. + if ($type['entity type'] == 'commerce_order') { + $rule->condition('commerce_discount_compatibility_check', array( + 'commerce_order:select' => 'commerce-order', + 'commerce_discount' => $discount->name, + )); + } + else { + $rule->condition('commerce_discount_line_item_compatibility_check', array( + 'commerce_line_item:select' => 'commerce-line-item', + 'commerce_discount' => $discount->name, + )); + } + + // Let other modules alter the rule object, with configuration specific + // to commerce discount. We don't invoke an alter function, as it can + // be already achieved by implementing + // hook_default_rules_configuration_alter(). + module_invoke_all('commerce_discount_rule_build', $rule, $discount); + + // Let inline_conditions fields add their own conditions. + foreach ($wrapper_properties as $field_name => $property) { + if (stripos($property['type'], 'inline_conditions') !== FALSE) { + inline_conditions_build($rule, $wrapper->{$field_name}->value()); + } + } + + // Add the commerce discount to the rule configuration, so other may act + // according to it, in hook_default_rules_configuration_alter(). + $rule->commerce_discount = $discount; + + $rule_machine_name = commerce_discount_build_rule_machine_name($discount->name); + $rules[$rule_machine_name] = $rule; + } + } + + return $rules; +} + +/** + * Builds a machine name suitable for use as the rule machine name. + * + * @param string $discount_name + * The machine-name of the discount to build the rule name for. + * + * @return string + * A machine name to be used as the rule configuration machine name. + */ +function commerce_discount_build_rule_machine_name($discount_name) { + $rule_machine_name = 'commerce_discount_rule_' . $discount_name; + // Ensure the name isn't too long. + if (strlen($rule_machine_name) > 64) { + // Shorten the name but ensure uniqueness by using a hash. + $hash = crc32($discount_name); + $rule_machine_name = substr($rule_machine_name, 0, 64 - strlen($hash) - 1) . '_' . $hash; + } + return $rule_machine_name; +} + +/** + * Returns the default array structure for a price field for use when + * reinitializing discount line items. + * + * @param string $currency_code + * The currency code. + * + * @return array + * An initialized price array. + */ +function commerce_discount_empty_price($currency_code) { + $price = array( + 'amount' => 0, + 'currency_code' => $currency_code, + ); + $price['data'] = commerce_price_component_add($price, 'base_price', $price, TRUE, FALSE); + return $price; +} + +/** + * Dumb copy of commerce_order_calculate_total() which accepts a wrapped order. + * This is necessary when called from within a function that already has a + * wrapped order entity to make sure the static cache of the + * commerce_order_total field is correctly updated. + */ +function commerce_discount_calculate_order_total($order_wrapper) { + // First determine the currency to use for the order total. + $default_currency_code = $currency_code = commerce_default_currency(); + $currencies = array(); + + // Populate an array of how many line items on the order use each currency. + foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { + // If the current line item actually no longer exists... + if (!$line_item_wrapper->value()) { + // Remove the reference from the order and continue to the next value. + $order_wrapper->commerce_line_items->offsetUnset($delta); + continue; + } + + $line_item_currency_code = $line_item_wrapper->commerce_total->currency_code->value(); + + if (!isset($currencies[$line_item_currency_code])) { + $currencies[$line_item_currency_code] = 1; + } + else { + $currencies[$line_item_currency_code]++; + } + } + + reset($currencies); + + // If only one currency is present on the order, use that to calculate the + // order total. + if (count($currencies) == 1) { + $currency_code = key($currencies); + } + elseif (isset($currencies[$default_currency_code])) { + // Otherwise use the site default currency if it's in the order. + $currency_code = $default_currency_code; + } + elseif (count($currencies) > 1) { + // Otherwise use the first currency on the order. We do this instead of + // trying to determine the most dominant currency for now because using the + // first currency leaves the option open for a UI based module to let + // customers reorder the items in the cart by currency to get the order + // total in a different currency. The currencies array still contains useful + // data, though, should we decide to expand on the count by currency approach. + $currency_code = key($currencies); + } + + // Initialize the order total with the selected currency. + $order_wrapper->commerce_order_total->amount = 0; + $order_wrapper->commerce_order_total->currency_code = $currency_code; + + // Reset the data array of the order total field to only include a + // base price component, set the currency code from any line item. + $base_price = array( + 'amount' => 0, + 'currency_code' => $currency_code, + 'data' => array(), + ); + + $order_wrapper->commerce_order_total->data = commerce_price_component_add($base_price, 'base_price', $base_price, TRUE); + $order_total = $order_wrapper->commerce_order_total->value(); + + // Then loop over each line item and add its total to the order total. + $amount = 0; + + foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { + // Convert the line item's total to the order's currency for totalling. + $line_item_total = $line_item_wrapper->commerce_total->value(); + $component_total = commerce_price_component_total($line_item_total); + + // Add the totals. + $amount += commerce_currency_convert( + $component_total['amount'], + $component_total['currency_code'], + $currency_code + ); + + // Combine the line item total's component prices into the order total. + $order_total['data'] = commerce_price_components_combine($order_total, $line_item_total); + } + + // Update the order total price field with the final total amount and data. + $order_wrapper->commerce_order_total->amount = round($amount); + $order_wrapper->commerce_order_total->data = $order_total['data']; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules.inc index 1b4a1fe7..3c02807a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules.inc @@ -12,6 +12,101 @@ function commerce_discount_rules_condition_info() { $inline_conditions = inline_conditions_get_info(); $conditions = array(); + // Max usage per person. + $conditions['commerce_discount_usage_max_usage_per_person'] = array( + 'label' => t('Max usage per person'), + 'parameter' => array( + 'order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + ), + 'commerce_discount' => array( + 'label' => t('Commerce Discount'), + 'type' => 'token', + 'options list' => 'commerce_discount_entity_list', + ), + 'usage' => array( + 'type' => 'integer', + 'label' => t('Maximum usage per customer'), + 'description' => t('Enter the maximum number of times a specific person (as identified by email) may use this discount. Leave blank for unlimited.'), + ), + ), + 'group' => t('Commerce Discount'), + ); + + // Max usage. + $conditions['commerce_discount_usage_max_usage'] = array( + 'label' => t('Max usage'), + 'parameter' => array( + 'order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + ), + 'commerce_discount' => array( + 'label' => t('Commerce Discount'), + 'type' => 'token', + 'options list' => 'commerce_discount_entity_list', + ), + 'usage' => array( + 'type' => 'integer', + 'label' => t('Maximum overall usage'), + 'description' => t('Enter the maximum number of times this discount may be used on the site, by anyone. Leave blank for unlimited.'), + ), + ), + 'group' => t('Commerce Discount'), + ); + + $conditions['commerce_discount_date_condition'] = array( + 'label' => t('Check discount dates'), + 'group' => t('Commerce Discount'), + 'parameter' => array( + 'commerce_discount' => array( + 'label' => t('Commerce Discount'), + 'type' => 'token', + 'options list' => 'commerce_discount_entity_list', + ), + ), + 'base' => 'commerce_discount_date_condition', + ); + + $conditions['commerce_discount_compatibility_check'] = array( + 'label' => t('Check discount compatibility at the order level'), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + 'description' => t('The order the discount would be applied to.'), + 'wrapped' => TRUE, + ), + 'commerce_discount' => array( + 'type' => 'token', + 'label' => t('Discount'), + 'description' => t('A discount with a compatibility strategy that requires evaluation.'), + 'options list' => 'commerce_discount_entity_list', + ), + ), + 'group' => t('Commerce Discount'), + ); + + $conditions['commerce_discount_line_item_compatibility_check'] = array( + 'label' => t('Check discount compatibility at the line item level'), + 'parameter' => array( + 'commerce_line_item' => array( + 'type' => 'commerce_line_item', + 'label' => t('Line item'), + 'description' => t('The line item the discount would be applied to.'), + 'wrapped' => TRUE, + ), + 'commerce_discount' => array( + 'type' => 'token', + 'label' => t('Discount'), + 'description' => t('A discount with a compatibility strategy that requires evaluation.'), + 'options list' => 'commerce_discount_entity_list', + ), + ), + 'group' => t('Commerce Discount'), + ); + if (module_exists('commerce_order')) { $conditions['commerce_order_compare_order_amount'] = array( 'label' => t('Order amount comparison'), @@ -27,7 +122,7 @@ function commerce_discount_rules_condition_info() { 'label' => t('Operator'), 'description' => t('The operator used with the order amount value below.'), 'default value' => '>=', - 'options list' => '_inline_conditions_operator_options', + 'options list' => '_commerce_discount_operator_options', ), 'total' => array( 'type' => 'commerce_price', @@ -35,9 +130,17 @@ function commerce_discount_rules_condition_info() { 'default value' => '', 'description' => t('The value to compare against the passed order amount.'), ), + 'line_item_types' => array( + 'type' => 'list', + 'label' => t('Line item types'), + 'description' => t('Select the line item types that should be included in the order total amount. Discount created line items are always excluded.'), + 'default value' => commerce_order_compare_order_amount_options_default(), + 'options list' => 'commerce_order_compare_order_amount_options_list', + 'optional' => TRUE, + ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Order'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_order_compare_order_amount']['callbacks']['build'], ), @@ -59,7 +162,7 @@ function commerce_discount_rules_condition_info() { ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Order'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_order_has_owner']['callbacks']['build'], ), @@ -79,9 +182,17 @@ function commerce_discount_rules_condition_info() { 'label' => t('Product SKU(s)'), 'description' => t('Products SKU to look for on the order. Enter a comma-separated list of product SKU(s).'), ), + 'operator' => array( + 'type' => 'text', + 'label' => t('Operator'), + 'description' => t('The comparison type used with the SKUs above.'), + 'default value' => 'any', + 'options list' => '_commerce_discount_rules_product_match_options', + 'optional' => TRUE, + ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Order'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_order_contains_products']['callbacks']['build'], ), @@ -106,7 +217,7 @@ function commerce_discount_rules_condition_info() { 'label' => t('Operator'), 'description' => t('The operator used with the product quantity value below.'), 'default value' => '>=', - 'options list' => '_inline_conditions_order_operator_options', + 'options list' => '_commerce_discount_operator_options', ), 'quantity' => array( 'type' => 'integer', @@ -115,7 +226,7 @@ function commerce_discount_rules_condition_info() { ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Order'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_order_has_specific_quantity_products']['callbacks']['build'], ), @@ -139,7 +250,7 @@ function commerce_discount_rules_condition_info() { ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Line Item'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_product_contains_products']['callbacks']['build'], ), @@ -161,7 +272,7 @@ function commerce_discount_rules_condition_info() { ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Line Item'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_product_has_type']['callbacks']['build'], ), @@ -185,13 +296,28 @@ function commerce_discount_rules_condition_info() { ), ), 'module' => 'commerce_discount', - 'group' => t('Commerce Line Item'), + 'group' => t('Commerce Discount'), 'callbacks' => array( 'execute' => $inline_conditions['commerce_product_has_specified_terms']['callbacks']['build'], ), ); } + // Add a very simple boolean condition to check whether or not a line item was + // added to an order via a discount. This should likely be accompanied by an + // action to load that discount and additional conditions to check the name, + // type, and offer type of the discount. + $conditions['commerce_discount_line_item_added_by_discount'] = array( + 'label' => t('Line item added by discount'), + 'parameter' => array( + 'commerce_line_item' => array( + 'type' => 'commerce_line_item', + 'label' => t('Line item'), + ), + ), + 'group' => t('Commerce Discount'), + ); + return $conditions; } @@ -202,9 +328,9 @@ function commerce_discount_rules_action_info() { $types = commerce_discount_offer_types(); $items = array(); - foreach (array('fixed_amount', 'percentage', 'free_products') as $type) { - $items[$types[$type]['action']] = array( - 'label' => $types[$type]['label'], + foreach ($types as $info) { + $items[$info['action']] = array( + 'label' => $info['label'], 'parameter' => array( 'entity' => array( 'label' => t('Entity'), @@ -218,55 +344,49 @@ function commerce_discount_rules_action_info() { ), ), 'group' => t('Commerce Discount'), - 'base' => $types[$type]['action'], + 'base' => $info['action'], ); } - $items['commerce_discount_free_shipping_service'] = array( - 'label' => t('Free shipping service'), + $items['commerce_discount_remove_discount_components_on_products'] = array( + 'label' => t('Remove discount price components'), 'group' => t('Commerce Discount'), 'parameter' => array( - 'entity' => array( - 'label' => t('Entity'), - 'type' => 'entity', - 'wrapped' => FALSE, - ), - 'commerce_discount' => array( - 'label' => t('Commerce Discount'), - 'type' => 'token', - 'options list' => 'commerce_discount_entity_list', - ), - 'shipping_service' => array( - 'label' => t('Shipping service'), - 'type' => 'token', - 'options list' => 'commerce_shipping_service_options_list', + 'commerce_line_item' => array( + 'type' => 'commerce_line_item', + 'label' => t('Line item'), + 'wrapped' => TRUE, ), ), - 'base' => 'commerce_discount_free_shipping_service', ); - $items['commerce_discount_percent_off_shipping_service'] = array( - 'label' => t('% off of shipping'), + $items['commerce_discount_shipping_service'] = array( + 'label' => t('Apply shipping discount'), 'group' => t('Commerce Discount'), 'parameter' => array( 'entity' => array( 'label' => t('Entity'), 'type' => 'entity', - 'wrapped' => FALSE, + 'wrapped' => TRUE, ), 'commerce_discount' => array( 'label' => t('Commerce Discount'), 'type' => 'token', 'options list' => 'commerce_discount_entity_list', ), - 'shipping_service' => array( - 'label' => t('Shipping service'), - 'type' => 'token', - 'optional' => TRUE, - 'options list' => 'commerce_shipping_service_options_list', + ), + 'base' => 'commerce_discount_shipping_service', + ); + + $items['commerce_discount_shipping_services'] = array( + 'label' => t('Discount shipping services'), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), ), ), - 'base' => 'commerce_discount_percent_off_shipping_service', + 'group' => t('Commerce Discount'), ); return $items; @@ -280,26 +400,168 @@ function commerce_discount_rules_event_info() { $items['commerce_discount_order'] = array( 'label' => t('Apply a discount to a given order'), - 'group' => t('Commerce - discount'), + 'group' => t('Commerce Discount'), 'variables' => entity_rules_events_variables('commerce_order', t('Order', array(), array('context' => 'a drupal commerce order'))), 'access callback' => 'commerce_order_rules_access', ); - $items['commerce_discount_free_shipping'] = array( - 'label' => t('Makes a shipping service free'), - 'group' => t('Commerce - discount'), - 'variables' => entity_rules_events_variables('commerce_order', t('Order', array(), array('context' => 'a drupal commerce order'))), - 'access callback' => 'commerce_order_rules_access', - ); + return $items; +} - $items['commerce_discount_percent_off_shipping'] = array( - 'label' => t('% off of a shipping service'), - 'group' => t('Commerce - discount'), - 'variables' => entity_rules_events_variables('commerce_order', t('Order', array(), array('context' => 'a drupal commerce order'))), - 'access callback' => 'commerce_order_rules_access', - ); +/** + * Rules callback: executes the "Check discount compatibility at the order + * level" condition. + * + * @param EntityDrupalWrapper $order_wrapper + * The order the discount would be applied to. + * @param string $discount_name + * The discount name whose compatibility needs to be checked. + * + * @return bool + * Returns TRUE if nothing has disqualified compatibility. + */ +function commerce_discount_compatibility_check(EntityDrupalWrapper $order_wrapper, $discount_name) { + // Ensure the discount we're loading still exists. + if (!$discount = entity_load_single('commerce_discount', $discount_name)) { + return FALSE; + } - return $items; + // Use the order total price field for order level compatibility checks. + $order_total = $order_wrapper->commerce_order_total->value(); + $return = _commerce_discount_check_compatibility($discount, $order_total); + return $return; +} + +/** + * Rules callback: executes the "Check discount compatibility at the line item + * level" condition. + * + * @param EntityDrupalWrapper $line_item_wrapper + * The line item the discount would be applied to. + * @param string $discount_name + * The discount name whose compatibility needs to be checked. + * + * @return bool + * Returns TRUE if nothing has disqualified compatibility. + */ +function commerce_discount_line_item_compatibility_check(EntityDrupalWrapper $line_item_wrapper, $discount_name) { + // Ensure the discount we're loading still exists. + if (!$discount = entity_load_single('commerce_discount', $discount_name)) { + return FALSE; + } + + // Use the line item unit price field for line item level compatibility checks. + $unit_price = $line_item_wrapper->commerce_unit_price->value(); + + return _commerce_discount_check_compatibility($discount, $unit_price); +} + +/** + * Performs a discount compatibility check for the given price field. + * + * @param object $discount + * The discount entity whose compatibility is being checked. + * @param array $price + * The price field value whose components array indicates which discounts have + * already been applied to it. + * + * @return bool + * Boolean indicating whether or not the compatibility check passed. + */ +function _commerce_discount_check_compatibility($discount, $price) { + // Get the strategy from the compatibility strategy field. + $strategy = commerce_discount_get_compatibility_strategy($discount); + + // Determine which discounts of a similar discount type have been applied to + // the price thus far. Compatibility checks currently only take into account + // discounts of the same type due to process limitations (i.e. Product + // discounts *always* apply before Order discounts) and usability constraints + // (i.e. we have data to make Order discount applicability dependent on + // Product discounts, but we do not have a clear way to indicate that uni- + // directionality in the UI yet). + $applied_discounts = commerce_discount_get_discounts_applied_to_price($price, $discount->type); + + // Ensure none of them indicate they are incompatible with the current one. + foreach ($applied_discounts as $applied_discount_id => $applied_discount_name) { + // Determine the strategy of the existing discount. + $existing_discount = entity_load_single('commerce_discount', $applied_discount_name); + $existing_strategy = commerce_discount_get_compatibility_strategy($existing_discount); + + // If the discount is compatible with any other discount, continue on. + if ($existing_strategy == 'any') { + continue; + } + + // If the discount is not compatible with any other discount, return FALSE. + if ($existing_strategy == 'none') { + return FALSE; + } + + // Get the selected discount value from the existing discount. + $existing_selection = commerce_discount_get_compatibility_selection($existing_discount); + + // If the discount is incompatible with select discounts and the current + // discount was selected, return FALSE. + if ($existing_strategy == 'except' && in_array($discount->discount_id, $existing_selection)) { + return FALSE; + } + + // If the discount is only compatible with select discounts and the current + // discount was not selected, return FALSE. + if ($existing_strategy == 'only' && !in_array($discount->discount_id, $existing_selection)) { + return FALSE; + } + } + + // If the discount is not compatible with any other discount and we found + // applied discounts, return FALSE. + if ($strategy == 'none' && count($applied_discounts) > 0) { + return FALSE; + } + + // Get the selected discount value from the discount being evaluated. + $selection = commerce_discount_get_compatibility_selection($discount); + + // If the discount is not compatible with selected discounts, ensure they + // are not on the order. + if ($strategy == 'except') { + foreach ($applied_discounts as $applied_discount_id => $applied_discount_name) { + if (in_array($applied_discount_id, $selection)) { + return FALSE; + } + } + } + + // If the discount is only compatible with selected discounts, ensure no other + // discounts are on the order. + if ($strategy == 'only') { + foreach ($applied_discounts as $applied_discount_id => $applied_discount_name) { + if (!in_array($applied_discount_id, $selection)) { + return FALSE; + } + } + } + + // Return TRUE if nothing has disqualified compatibility yet. For example, if + // the discount is compatible with any other discount and no other discounts + // on the order specifies an incompatibility with this discount or if this + // discount were incompatible with other discounts but none have been applied + // to the order yet. + return TRUE; +} + +/** + * Options list for commerce_order_compare_order_amount line item types. + */ +function commerce_order_compare_order_amount_options_list() { + return array_diff_key(commerce_line_item_type_options_list(), drupal_map_assoc(array('commerce_discount', 'product_discount'))); +} + +/** + * Default value array for commerce_order_compare_order_amount line item types. + */ +function commerce_order_compare_order_amount_options_default() { + return drupal_map_assoc(array_keys(commerce_order_compare_order_amount_options_list())); } /** @@ -315,17 +577,23 @@ function commerce_discount_rules_event_info() { * @return bool * return true if condition is valid. false otherwise. */ -function commerce_order_compare_order_amount_build(EntityDrupalWrapper $wrapper, $operator, $total) { +function commerce_order_compare_order_amount_build(EntityDrupalWrapper $wrapper, $operator, $total, $line_item_types) { $total_order = 0; - // Ensure the discount currency code is the same than the order. + // Ensure the discount currency code is the same as the order. if ($wrapper->commerce_order_total->currency_code->value() != $total['currency_code']) { return FALSE; } + // If $line_item_types is not an array, then we need to total all line item + // types to preserve backwards compatibility. + if (!is_array($line_item_types)) { + $line_item_types = commerce_order_compare_order_amount_options_default(); + } + // Get given total order amount. foreach ($wrapper->commerce_line_items as $line_item_wrapper) { - if ($line_item_wrapper->getBundle() != 'commerce_discount') { + if (in_array($line_item_wrapper->getBundle(), $line_item_types, TRUE)) { // Convert the line item's total to the order's currency for totalling. $component_total = commerce_price_component_total($line_item_wrapper->commerce_total->value()); @@ -342,12 +610,18 @@ function commerce_order_compare_order_amount_build(EntityDrupalWrapper $wrapper, case '<': return $total_order < $total['amount']; + case '<=': + return $total_order <= $total['amount']; + case '==': return $total_order == $total['amount']; case '>': return $total_order > $total['amount']; + case '>=': + return $total_order >= $total['amount']; + default: return FALSE; } @@ -377,27 +651,58 @@ function commerce_order_has_owner_build(EntityDrupalWrapper $wrapper, $account) * Build callback for commerce_order_contains_products. * * @param EntityDrupalWrapper $wrapper - * The wrapped entity given by the rule + * The wrapped entity given by the rule. * @param string $products * A list of products SKU. + * @param string $operator + * An optional operator for comparison. The default is 'any'. * * @return bool * Returns True if condition is valid. False otherwise. */ -function commerce_order_contains_products_build(EntityDrupalWrapper $wrapper, $products) { - +function commerce_order_contains_products_build(EntityDrupalWrapper $wrapper, $products, $operator = 'any') { + // Split by comma. Allow some grace for spaces. $products_sku = explode(', ', (string) $products); + $order_skus = array(); foreach ($wrapper->commerce_line_items as $wrapper_line_item) { // Ensures the passed line item is a product. if (in_array('commerce_product', array_keys($wrapper_line_item->getPropertyInfo()))) { - if (($key = array_search($wrapper_line_item->commerce_product->sku->value(), $products_sku)) !== FALSE) { - unset($products_sku[$key]); - } + $order_skus[] = $wrapper_line_item->commerce_product->sku->value(); } } - return empty($products_sku); + // Products that are in the list but not on the order. + $list_but_not_order = array_diff($products_sku, $order_skus); + + if ($operator == 'all') { + // If the array is empty, then everything in the list is on the order. + return (count($list_but_not_order) == 0); + } + elseif ($operator == 'any') { + // If the number of items in the list isn't the same as the products_sku, + // then we know that at least one of the items is on the order. + return (count($list_but_not_order) != count($products_sku)); + } + else { + // Generate a list of products that are on the order but not on the list. + $order_but_not_list = array_diff($order_skus, $products_sku); + + if ($operator == 'only') { + // If all of the order SKUs are on the list and the list of product SKUs + // is not the same as the original list, then the order contains at least + // one of the products in the list and doesn't include any products not + // on the list. + return empty($order_but_not_list) && (count($list_but_not_order) != count($products_sku)); + } + elseif ($operator == 'exactly') { + // If both arrays are empty, then the order contains every product on + // the list but no additional products. + return empty($list_but_not_order) && empty($order_but_not_list); + } + } + + return FALSE; } /** @@ -457,6 +762,15 @@ function commerce_order_has_specific_quantity_products_build(EntityDrupalWrapper } break; + case '<=': + if ($wrapper_line_item->quantity->value() <= $quantity) { + unset($products_sku[$key]); + } + else { + return FALSE; + } + break; + case '==': if ($wrapper_line_item->quantity->value() == $quantity) { unset($products_sku[$key]); @@ -475,6 +789,15 @@ function commerce_order_has_specific_quantity_products_build(EntityDrupalWrapper } break; + case '>=': + if ($wrapper_line_item->quantity->value() >= $quantity) { + unset($products_sku[$key]); + } + else { + return FALSE; + } + break; + } } } @@ -523,14 +846,14 @@ function commerce_product_has_specified_terms_build(EntityDrupalWrapper $wrapper if (preg_match('/taxonomy_term/', $property['type'])) { // If the wrapped field is an instance of EntityListWrapper class, that // means that field contains multiple values. - if ($wrapper->commerce_product->$field_name instanceof EntityListWrapper) { - foreach ($wrapper->commerce_product->$field_name as $wrapper_term) { + if ($wrapper->commerce_product->{$field_name} instanceof EntityListWrapper) { + foreach ($wrapper->commerce_product->{$field_name} as $wrapper_term) { if (($key = array_search($wrapper_term->getIdentifier(), $terms_name)) !== FALSE) { unset($terms_name[$key]); } } } - elseif ($term = $wrapper->commerce_product->$field_name->value()) { + elseif ($term = $wrapper->commerce_product->{$field_name}->value()) { if (($key = array_search($term->tid, $terms_name)) !== FALSE) { unset($terms_name[$key]); } @@ -542,6 +865,13 @@ function commerce_product_has_specified_terms_build(EntityDrupalWrapper $wrapper return empty($terms_name); } +/** + * Condition callback: looks to see if the line item was added by a discount. + */ +function commerce_discount_line_item_added_by_discount($line_item) { + return !empty($line_item->data['discount_name']); +} + /** * Rules action: Apply fixed amount discount. */ @@ -550,11 +880,14 @@ function commerce_discount_fixed_amount(EntityDrupalWrapper $wrapper, $discount_ $discount_price = $discount_wrapper->commerce_discount_offer->commerce_fixed_amount->value(); $discount_price['amount'] = -$discount_price['amount']; + // Get the line item types to apply the discount to. + $line_item_types = variable_get('commerce_discount_line_item_types', array_diff(commerce_product_line_item_types(), array('product_discount'))); + switch ($wrapper->type()) { case 'commerce_order': // Exit if the wrapper doesn't have a commerce_discounts property. - if (!$wrapper->__isset('commerce_discounts')) { + if (!isset($wrapper->commerce_discounts)) { return; } @@ -562,8 +895,8 @@ function commerce_discount_fixed_amount(EntityDrupalWrapper $wrapper, $discount_ // @todo: It doesn't work with the wrapper. $order = $wrapper->value(); - // If the discount will bring the order to less than zero, set the discount - // amount so that it stops at zero. + // If the discount will bring the order to less than zero, set the + // discount amount so that it stops at zero. $order_amount = $wrapper->commerce_order_total->amount->value(); if (-$discount_price['amount'] > $order_amount) { $discount_price['amount'] = -$order_amount; @@ -578,14 +911,19 @@ function commerce_discount_fixed_amount(EntityDrupalWrapper $wrapper, $discount_ } // Update the total order price, for the next rules condition (if any). - commerce_order_calculate_total($order); + commerce_discount_calculate_order_total($wrapper); break; case 'commerce_line_item': - // Check whether this discount was already added as a price component. - $price_data = $wrapper->commerce_unit_price->data->value(); + // Check if the line item is configured in the settings to apply the + // discount. + if (!in_array($wrapper->getBundle(), $line_item_types)) { + return; + } - foreach ($price_data['components'] as $component) { + // Check whether this discount was already added as a price component. + $unit_price = commerce_price_wrapper_value($wrapper, 'commerce_unit_price', TRUE); + foreach ($unit_price['data']['components'] as $component) { if (!empty($component['price']['data']['discount_name']) && $component['price']['data']['discount_name'] == $discount_wrapper->getIdentifier()) { return; } @@ -607,20 +945,18 @@ function commerce_discount_fixed_amount(EntityDrupalWrapper $wrapper, $discount_ */ function commerce_discount_percentage(EntityDrupalWrapper $wrapper, $discount_name) { $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount_name); - $rate = $discount_wrapper->commerce_discount_offer->commerce_percentage->value(); + $rate = $discount_wrapper->commerce_discount_offer->commerce_percentage->value() / 100; // Get the line item types to apply the discount to. - $line_item_types = variable_get('commerce_discount_line_item_types', array('product' => 'product')); - - if ($rate > 1) { - $rate = $rate / 100; - } + $line_item_types = variable_get('commerce_discount_line_item_types', array_diff(commerce_product_line_item_types(), array('product_discount'))); + // Filter out 0 values in the variable. + $line_item_types = array_filter($line_item_types); switch ($wrapper->type()) { case 'commerce_order': // Exit if there are no line items or the wrapper doesn't contain // the commerce_discounts property. - if (!$wrapper->commerce_line_items->value() || !$wrapper->__isset('commerce_discounts')) { + if (!isset($wrapper->commerce_discounts) || !$wrapper->commerce_line_items->value()) { return; } @@ -633,7 +969,10 @@ function commerce_discount_percentage(EntityDrupalWrapper $wrapper, $discount_na $calculated_discount = 0; // Loop the line items of the order and calculate the total discount. foreach ($wrapper->commerce_line_items as $line_item_wrapper) { - if (!empty($line_item_types[$line_item_wrapper->type->value()])) { + // Check if the line item is configured in the discount settings to + // apply the discount. + $line_item_type = $line_item_wrapper->getBundle(); + if (in_array($line_item_type, $line_item_types, TRUE)) { $line_item_total = commerce_price_wrapper_value($line_item_wrapper, 'commerce_total', TRUE); $calculated_discount += $line_item_total['amount'] * $rate; } @@ -645,36 +984,35 @@ function commerce_discount_percentage(EntityDrupalWrapper $wrapper, $discount_na 'currency_code' => $wrapper->commerce_order_total->currency_code->value(), ); - // Modify the existing discount line item or add a new one if that fails. + // Modify the existing discount line item or add a new line item + // if that fails. if (!commerce_discount_set_existing_line_item_price($wrapper, $discount_name, $discount_amount)) { commerce_discount_add_line_item($wrapper, $discount_name, $discount_amount); } // Update the total order price, for the next rules condition (if any). - commerce_order_calculate_total($order); + commerce_discount_calculate_order_total($wrapper); } break; case 'commerce_line_item': - // Check if the line item is configured in the settings to apply the - // discount. - if (empty($line_item_types[$wrapper->type->value()])) { + // Check if the line item is configured in the discount settings to apply + // the discount. + $line_item_type = $wrapper->getBundle(); + if (!in_array($line_item_type, $line_item_types, TRUE)) { return; } // Check whether this discount was already added as a price component. - $price_data = $wrapper->commerce_unit_price->data->value(); - foreach ($price_data['components'] as $component) { + $unit_price = commerce_price_wrapper_value($wrapper, 'commerce_unit_price', TRUE); + foreach ($unit_price['data']['components'] as $component) { if (!empty($component['price']['data']['discount_name']) && $component['price']['data']['discount_name'] == $discount_name) { return; } } - $unit_price = commerce_price_wrapper_value($wrapper, 'commerce_unit_price', TRUE); - $calculated_discount = $unit_price['amount'] * $rate * -1; - $discount_amount = array( - 'amount' => $calculated_discount, + 'amount' => $unit_price['amount'] * $rate * -1, 'currency_code' => $unit_price['currency_code'], ); commerce_discount_add_price_component($wrapper, $discount_name, $discount_amount); @@ -683,126 +1021,228 @@ function commerce_discount_percentage(EntityDrupalWrapper $wrapper, $discount_na } /** - * Rules action: Apply free shipping discount. + * Rules action: Apply shipping discount. * - * @param object $order - * Wrapped commerce_order entity type. + * @param EntityDrupalWrapper $order_wrapper + * The wrapped order entity. * @param string $discount_name * The name of the discount. - * @param string $service_name - * The shipping service machine-name. */ -function commerce_discount_free_shipping_service($order, $discount_name, $service_name) { - if (isset($order->shipping_rates[$service_name])) { - $wrapper_line_item = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$service_name]); - - // Exit if the rule has already been processed. - if ($wrapper_line_item->commerce_unit_price->amount->value() == 0) { +function commerce_discount_shipping_service($order_wrapper, $discount_name) { + $order = $order_wrapper->value(); + // Shipping discount rules actions run on two occasions: + // 1. When selecting the shipping service on checkout. This time + // $order->shipping_rates is already set. The cart refresh is triggered + // manually in commerce_discount_commerce_shipping_method_collect_rates() + // and we don't save the shipping line items, since we create them only to + // display the price. + // 2. On any other cart refresh the $order->shipping_rates is initially + // empty so we set it to the shipping line item present on the order (if + // any). In this case we want to save the shipping line item. + // What is common in the two cases is that we calculate the discount only + // for the shipping services in $order->shipping_rates. + // If the order hasn't had any shipping rates calculated for it, check if + // there's already a shipping line item referenced by the order. + if (empty($order->shipping_rates)) { + foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { + if ($line_item_wrapper->value() && $line_item_wrapper->getBundle() === 'shipping') { + $shipping_service = $line_item_wrapper->commerce_shipping_service->value(); + $order->shipping_rates[$shipping_service] = $line_item_wrapper->value(); + } + } + if (empty($order->shipping_rates)) { return; } + } - $wrapper_line_item->commerce_unit_price->amount = 0; + // Load the discount to find the free shipping service and strategy. + $discount = entity_load_single('commerce_discount', $discount_name); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $discount_offer_wrapper = $discount_wrapper->commerce_discount_offer; + $shipping_discount_offer_type = $discount_offer_wrapper->getBundle(); - $shipping_service = commerce_shipping_service_load($service_name); + switch ($shipping_discount_offer_type) { + case 'percent_off_shipping': + $shipping_service_to_discount = $discount_offer_wrapper->commerce_percent_off_ship_serv->value(); - // Updating the data component. - $wrapper_line_item->commerce_unit_price->data = array(); - $wrapper_line_item->commerce_unit_price->data = commerce_price_component_add( - $wrapper_line_item->commerce_unit_price->value(), - $shipping_service['price_component'], - $wrapper_line_item->commerce_unit_price->value(), - TRUE, - FALSE - ); - } -} + // Determine the discount multiplier based on submitted value. + $discount_value = $discount_offer_wrapper->commerce_percent_off_shipping->value(); + if ($discount_value > 1 && $discount_value <= 100) { + $discount_multiplier = $discount_value / 100; + } + else { + return; + } -/** - * Rules action: Apply percent off of shipping discount. - * - * @param object $order - * Wrapped commerce_order entity type. - * @param string $discount_name - * The name of the discount. - */ -function commerce_discount_percent_off_shipping_service($order, $discount_name) { + foreach ($order->shipping_rates as $shipping_service => $shipping_object) { + // Check if the order contains the shipping rate we want to apply a + // discount against. Not all shipping rates apply to all orders. + if (isset($shipping_service_to_discount) && $shipping_service_to_discount !== $shipping_service) { + continue; + } + // Instantiate the line item wrapper. + $shipping_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$shipping_service]); + + // Calculate the correct value to discount the line item. + $discount_shipping_value = -$shipping_line_item_wrapper->commerce_unit_price->amount->value() * $discount_multiplier; + _commerce_discount_add_shipping_discount_price_component($shipping_line_item_wrapper, $order_wrapper, $discount_name, $discount_shipping_value); + } + break; + case 'free_shipping': + try { + $free_service = $discount_wrapper->commerce_discount_offer->commerce_free_shipping->raw(); + $free_shipping_strategy = commerce_discount_get_free_shipping_strategy($discount); + } + catch (Exception $e) { + watchdog('commerce_discount', 'Free shipping configuration issue detected on discount %name.', array('%name' => $discount_wrapper->label())); + return; + } - // Load the discount wrapper, which has all the discount information in it. - $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount_name); + // If a shipping service is specified, ensure it exists. + if (!empty($free_service)) { + if (!empty($order->shipping_rates[$free_service])) { + // Exit if the shipping service is already free. + $free_service_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$free_service]); - // Load the shipping service, if one was selected. - $shipping_service_to_discount = $discount_wrapper->commerce_discount_offer->commerce_percent_off_ship_serv->value(); + if ($free_service_wrapper->commerce_unit_price->amount->value() <= 0) { + return; + } - // Get the discount label for later usage. - $discount_label = $discount_wrapper->value()->label; + // Update the free shipping service line item's rate to be free using the + // service's price. + $discount_amount = $free_service_wrapper->commerce_unit_price->amount->value(); + $order->shipping_rates[$free_service]->data['shipping_service']['description'] .= ' ' . check_plain($discount->component_title) . ''; + _commerce_discount_add_shipping_discount_price_component($free_service_wrapper, $order_wrapper, $discount_name, -$discount_amount); + } - // Determine the discount multiplifer based on submitted value. - $discount_value = $discount_wrapper->commerce_discount_offer->commerce_percent_off_shipping->value(); - if ($discount_value > 1 && $discount_value <= 100) { - $discount_multiplier = (100 - $discount_value) / 100; - } + // The free shipping discount strategy requires us to discount all other + // shipping services an equivalent amount. + if ($free_shipping_strategy == 'discount_all') { + foreach ($order->shipping_rates as $service_name => $line_item) { + // Skip the free shipping service. + if ($service_name == $free_service) { + continue; + } + + $free_shipping_service = commerce_shipping_service_load($free_service); + $callback = commerce_shipping_service_callback($free_shipping_service, 'rate'); + $price = $callback($free_shipping_service, $order); + $discount_amount = $price['amount']; - // Bring in the $conf global for stashing what shipping services were altered. - $conf['discounted_shipping_services'] = array(); + // Apply the discount, ensuring the rate does not discount below 0, + // but do not use the message again to reduce spam on the form. + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $service_discount_amount = min($discount_amount, $line_item_wrapper->commerce_unit_price->amount->value()); + _commerce_discount_add_shipping_discount_price_component($line_item_wrapper, $order_wrapper, $discount_name, -$service_discount_amount); + } + } + } + break; - // Determine whether to affect a singular service, or all services. - if (isset($shipping_service_to_discount)) { - // Add singular shipping service to $conf global array. - $order->shipping_rates[$shipping_service_to_discount]->data['shipping_service']['description'] .= ' ' . $discount_label . ''; + case 'shipping_upgrade': + try { + $target_service_id = $discount_offer_wrapper->commerce_shipping_upgrade_target->raw(); + $source_service_id = $discount_offer_wrapper->commerce_shipping_upgrade_source->raw(); + } + catch (Exception $e) { + watchdog('commerce_discount', 'Shipping service upgrade configuration issue detected on discount %name.', array('%name' => $discount_wrapper->label())); + return; + } - // Instantiate the line item wrapper. - $shipping_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$shipping_service_to_discount]); + // Exit now if we either could not determine a target or source service or + // they were not calculated for this order. + if (empty($target_service_id) || + empty($source_service_id) || empty($order->shipping_rates[$target_service_id])) { + return; + } - // Calculate the correct value to discount the line item. - $discounted_shipping_value = $shipping_line_item_wrapper->commerce_unit_price->amount->value() * $discount_multiplier; + $target_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$target_service_id]); + + // To calculate the source shipping rate with discounts, we need to + // remove the target service from the order and apply the source instead. + $cloned_order = clone $order; + $cloned_order_wrapper = entity_metadata_wrapper('commerce_order', $cloned_order); + if (empty($cloned_order->shipping_rates[$source_service_id])) { + foreach ($cloned_order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { + // If this line item is a shipping line item... + if ($line_item_wrapper->value() && $line_item_wrapper->getBundle() === 'shipping') { + $original_shipping_line_item = $line_item_wrapper->value(); + $cloned_order_wrapper->commerce_line_items->offsetUnset($delta); + unset($cloned_order->shipping_rates[$target_service_id]); + } + } + module_load_include('inc', 'commerce_shipping', 'commerce_shipping.rules'); + commerce_shipping_rate_apply($cloned_order, $source_service_id); + // Get the new line item. + $line_items = field_get_items('commerce_order', $cloned_order, 'commerce_line_items'); + $line_item_id = array_pop($line_items); + $source_shipping_line_item = commerce_line_item_load($line_item_id['line_item_id']); + $cloned_order->shipping_rates[$source_service_id] = $source_shipping_line_item; + // Apply discounts on source line item. This won't create an endless + // loop since we have unset the source service on the order above. + commerce_discount_commerce_cart_order_refresh($cloned_order_wrapper); + } + // Get the discounted rate for the source service. + $source_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $cloned_order->shipping_rates[$source_service_id]); - // The following applies the shipping discount to the line item. - $shipping_line_item_wrapper->commerce_unit_price->amount = $discounted_shipping_value; - $shipping_service = commerce_shipping_service_load($discount_name); + // If the prices are already the same, exit now. + if ($target_line_item_wrapper->commerce_unit_price->amount->value() <= $source_line_item_wrapper->commerce_unit_price->amount->value()) { + return; + } - // Updating the data component. Effectively updates line item price! - $shipping_line_item_wrapper->commerce_unit_price->data = array(); - $shipping_line_item_wrapper->commerce_unit_price->data = commerce_price_component_add( - $shipping_line_item_wrapper->commerce_unit_price->value(), - $shipping_service['price_component'], - $shipping_line_item_wrapper->commerce_unit_price->value(), - TRUE, - FALSE - ); + // Otherwise, calculate the difference between the source and the target. + $difference = $source_line_item_wrapper->commerce_unit_price->amount->value() - $target_line_item_wrapper->commerce_unit_price->amount->value(); + _commerce_discount_add_shipping_discount_price_component($target_line_item_wrapper, $order_wrapper, $discount_name, $difference); + break; } - else{ - foreach ($order->shipping_rates as $shipping_service => $shipping_object) { - // Add singular shipping service to $conf global array. - $order->shipping_rates[$shipping_service]->data['shipping_service']['description'] .= ' ' . $discount_label . ''; - - // Instantiate the line item wrapper. - $shipping_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$shipping_service]); - - // Calculate the correct value to discount the line item. - $discounted_shipping_value = $shipping_line_item_wrapper->commerce_unit_price->amount->value() * $discount_multiplier; - - // The following applies the shipping discount to the line item. - $shipping_line_item_wrapper->commerce_unit_price->amount = $discounted_shipping_value; - - $shipping_service = commerce_shipping_service_load($discount_name); - - // Updating the data component. Effectively updates line item price! - $shipping_line_item_wrapper->commerce_unit_price->data = array(); - $shipping_line_item_wrapper->commerce_unit_price->data = commerce_price_component_add( - $shipping_line_item_wrapper->commerce_unit_price->value(), - $shipping_service['price_component'], - $shipping_line_item_wrapper->commerce_unit_price->value(), - TRUE, - FALSE - ); - } +} + +/** + * Helper function for the shipping discount rules action. + * + * @param EntityDrupalWrapper $line_item_wrapper + * The wrapped line item object to add the discount price component to. + * @param EntityDrupalWrapper $order_wrapper + * The wrapped order entity. + * @param string $discount_name + * The discount machine name. + * @param integer $amount + * The (negative) discount amount to add as price component. + * + * @return bool + * A boolean indicating whether or not the discount price component has + * been added. + */ +function _commerce_discount_add_shipping_discount_price_component($line_item_wrapper, $order_wrapper, $discount_name, $amount) { + // Prevent the order total from going negative. + $order_total = $order_wrapper->commerce_order_total->value(); + if (-$amount > $order_total['amount']) { + $amount = -$order_total['amount']; + } + + if ($amount >= 0) { + return FALSE; + } + $discount_price = array( + 'amount' => $amount, + 'currency_code' => $line_item_wrapper->commerce_unit_price->currency_code->value(), + ); + commerce_discount_add_price_component($line_item_wrapper, $discount_name, $discount_price); + + if ($line_item_wrapper->getIdentifier() !== FALSE) { + // Save the line item so that it correctly appears in the subsequent + // order total calculation. + $line_item_wrapper->save(); + commerce_discount_calculate_order_total($order_wrapper); + return TRUE; } + return FALSE; } /** - * Rules action: Apply free products discount. + * Rules action: Apply free bonus products discount. * * @param EntityDrupalWrapper $wrapper * Wrapped commerce_order entity type. @@ -816,7 +1256,7 @@ function commerce_discount_free_products(EntityDrupalWrapper $wrapper, $discount } // Exit if the wrapper doesn't have a commerce_discounts property. - if (!$wrapper->__isset('commerce_discounts')) { + if (!isset($wrapper->commerce_discounts)) { return; } @@ -890,12 +1330,10 @@ function commerce_discount_free_products(EntityDrupalWrapper $wrapper, $discount commerce_line_item_save($line_item); - // Add the free product to order's line items. + // Add the free bonus product to order's line items. $wrapper->commerce_line_items[] = $line_item; + commerce_discount_calculate_order_total($wrapper); } - - // Update the total order price, for the next rules condition (if any). - commerce_order_calculate_total($order); } /** @@ -907,25 +1345,28 @@ function commerce_discount_free_products(EntityDrupalWrapper $wrapper, $discount * The name of the discount being applied. * @param array $discount_amount * The discount amount price array (amount, currency_code). + * @param array $data + * Optional. An associative array of parameters to include in the line item's + * data array along with the discount name. + * + * @return object + * The newly created discount line item. */ -function commerce_discount_add_line_item(EntityDrupalWrapper $order_wrapper, $discount_name, $discount_amount) { +function commerce_discount_add_line_item(EntityDrupalWrapper $order_wrapper, $discount_name, $discount_amount, $data = array()) { // Create a new line item. - $values = array( - 'type' => 'commerce_discount', - 'order_id' => $order_wrapper->order_id->value(), - 'quantity' => 1, - // Flag the line item. - 'data' => array('discount_name' => $discount_name), - ); - $discount_line_item = entity_create('commerce_line_item', $values); - $discount_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $discount_line_item); + $discount_line_item = commerce_line_item_new('commerce_discount', $order_wrapper->getIdentifier()); + $discount_line_item->data = array('discount_name' => $discount_name) + $data; + + // Setting the bundle to ensure the entity metadata stores it correctly. + $info = array('bundle' => 'commerce_discount'); + $discount_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $discount_line_item, $info); // Initialize the line item unit price. $discount_line_item_wrapper->commerce_unit_price->amount = 0; $discount_line_item_wrapper->commerce_unit_price->currency_code = $discount_amount['currency_code']; - // Reset the data array of the line item total field to only include a - // base price component, set the currency code from the order. + // Reset the data array of the line item total field to only include a base + // price component and set the currency code from the order. $base_price = array( 'amount' => 0, 'currency_code' => $discount_amount['currency_code'], @@ -936,9 +1377,12 @@ function commerce_discount_add_line_item(EntityDrupalWrapper $order_wrapper, $di // Add the discount price component. commerce_discount_add_price_component($discount_line_item_wrapper, $discount_name, $discount_amount); - // Save the line item and add it to the order. - $discount_line_item_wrapper->save(); - $order_wrapper->commerce_line_items[] = $discount_line_item_wrapper; + // Save the incoming line item now so we get its ID and add it to the oder's + // line item reference field value. + commerce_line_item_save($discount_line_item); + $order_wrapper->commerce_line_items[] = $discount_line_item; + + return $discount_line_item; } /** @@ -963,26 +1407,41 @@ function commerce_discount_add_price_component(EntityDrupalWrapper $line_item_wr // Calculate the updated amount and create a price array representing the // difference between it and the current amount. - $updated_amount = commerce_round(COMMERCE_ROUND_HALF_UP, $current_amount + $discount_amount['amount']); + $discount_amount['amount'] = commerce_round(COMMERCE_ROUND_HALF_UP, $discount_amount['amount']); + + $updated_amount = $current_amount + $discount_amount['amount']; $difference = array( - 'amount' => commerce_round(COMMERCE_ROUND_HALF_UP, $discount_amount['amount']), + 'amount' => $discount_amount['amount'], 'currency_code' => $discount_amount['currency_code'], 'data' => array( 'discount_name' => $discount_name, - 'discount_component_title' => empty($component_title) ? 'discount' : $component_title, + 'discount_component_title' => empty($component_title) ? t('Discount') : $component_title, ), ); // Set the new unit price. $line_item_wrapper->commerce_unit_price->amount = $updated_amount; + // Add the discount amount as a price component. - $price = $line_item_wrapper->commerce_unit_price->value(); + $unit_price = commerce_price_wrapper_value($line_item_wrapper, 'commerce_unit_price', TRUE); $type = empty($component_title) ? 'discount' : check_plain('discount|' . $discount_name); - $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add($price, $type, $difference, TRUE, TRUE); + $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add($unit_price, $type, $difference, TRUE, TRUE); +} - // Update the line item total. - _commerce_discount_update_commerce_total($line_item_wrapper); +/** + * Rules options list callback for product match options. + * + * @return array + * Rules option list. + */ +function _commerce_discount_rules_product_match_options() { + return array( + 'any' => t('any products'), + 'all' => t('all products'), + 'exactly' => t('exclusively all products'), + 'only' => t('exclusively any products'), + ); } /** @@ -1012,47 +1471,16 @@ function commerce_discount_set_price_component(EntityDrupalWrapper $line_item_wr // Set the new unit price. $line_item_wrapper->commerce_unit_price->amount = $discount_amount['amount']; - // Add the discount amount as a price component. - $price = $line_item_wrapper->commerce_unit_price->value(); - $type = empty($component_title) ? 'discount' : check_plain('discount|' . $discount_name); - $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add($price, $type, $discount_amount, TRUE, TRUE); - - // Update the line item total. - _commerce_discount_update_commerce_total($line_item_wrapper); -} - -/** - * Update commerce_total so that the order total can be refreshed without - * saving the line item. Taken from CommerceLineItemEntityController::save(). - */ -function _commerce_discount_update_commerce_total($line_item_wrapper) { - $quantity = $line_item_wrapper->quantity->value(); - - // Update the total of the line item based on the quantity and unit price. + // Add the discount amount as a price component. $unit_price = commerce_price_wrapper_value($line_item_wrapper, 'commerce_unit_price', TRUE); - - $line_item_wrapper->commerce_total->amount = $quantity * $unit_price['amount']; - $line_item_wrapper->commerce_total->currency_code = $unit_price['currency_code']; - - // Add the components multiplied by the quantity to the data array. - if (empty($unit_price['data']['components'])) { - $unit_price['data']['components'] = array(); - } - else { - foreach ($unit_price['data']['components'] as $key => &$component) { - $component['price']['amount'] *= $quantity; - } - } - - // Set the updated data array to the total price. - $line_item_wrapper->commerce_total->data = $unit_price['data']; - // Reset the cache because we aren't saving it. - entity_get_controller('commerce_line_item')->resetCache(array($line_item_wrapper->getIdentifier())); + $type = empty($component_title) ? 'discount' : check_plain('discount|' . $discount_name); + $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add($unit_price, $type, $discount_amount, TRUE, TRUE); } /** - * Updates the unit price of an existing discount line item. + * Updates the unit price of the discount line item matching the named discount. * + * Will be updated only first discount line item. * Non-discount line items are ignored. * * @param EntityDrupalWrapper $order_wrapper @@ -1062,23 +1490,122 @@ function _commerce_discount_update_commerce_total($line_item_wrapper) { * @param array $discount_price * The discount amount price array (amount, currency_code). * - * @return bool - * TRUE if an existing line item was successfully modified, FALSE otherwise. + * @return object|bool + * The modified line item or FALSE if not found. */ function commerce_discount_set_existing_line_item_price(EntityDrupalWrapper $order_wrapper, $discount_name, $discount_price) { - $modified_existing = FALSE; + $modified_line_item = FALSE; + foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { if ($line_item_wrapper->getBundle() == 'commerce_discount') { // Add the discount component price if the line item was originally // added by discount module. $line_item = $line_item_wrapper->value(); + if (isset($line_item->data['discount_name']) && $line_item->data['discount_name'] == $discount_name) { commerce_discount_set_price_component($line_item_wrapper, $discount_name, $discount_price); $line_item_wrapper->save(); - $modified_existing = TRUE; + $modified_line_item = $line_item; + break; } } } - return $modified_existing; + return $modified_line_item; +} + +/** + * Options list callback for condition. + */ +function _commerce_discount_operator_options() { + return array( + '<' => t('less than'), + '<=' => t('less than or equal to'), + '==' => t('equals'), + '>' => t('greater than'), + '>=' => t('greater than or equal to'), + ); +} + +/** + * Rules condition callback: evaluate maximum usage per-person of a discount. + */ +function commerce_discount_usage_max_usage_per_person($order, $discount_name) { + if (!$discount = entity_load_single('commerce_discount', $discount_name)) { + return FALSE; + } + $per_person_limit = FALSE; + + // Don't use the wrapper getter on purpose for performance reasons. + if (isset($discount->discount_usage_per_person[LANGUAGE_NONE])) { + $per_person_limit = $discount->discount_usage_per_person[LANGUAGE_NONE][0]['value']; + } + + // Nothing to count if the order does not have an email. + if (!$per_person_limit || !$order->mail) { + return TRUE; + } + + // Find other orders owned by same person that have same discount. + $usage = commerce_discount_usage_get_usage_by_mail($discount_name, $order->mail, $order->order_id); + + return $usage < $per_person_limit; +} + +/** + * Rules condition callback: evaluate maximum usage of a discount. + */ +function commerce_discount_usage_max_usage($order, $discount_name) { + if (!$discount = entity_load_single('commerce_discount', $discount_name)) { + return FALSE; + } + $limit = FALSE; + + // Don't use the wrapper getter on purpose for performance reasons. + if (isset($discount->discount_usage_limit[LANGUAGE_NONE])) { + $limit = $discount->discount_usage_limit[LANGUAGE_NONE][0]['value']; + } + + if (!$limit) { + return TRUE; + } + + // Find other orders that have same discount. + $usage = commerce_discount_usage_get_usage($discount_name, $order->order_id); + + return $usage < $limit; +} + +/** + * Rules condition: Check discount can be applied. + */ +function commerce_discount_date_condition($discount_name) { + if (!$discount = entity_load_single('commerce_discount', $discount_name)) { + return FALSE; + } + if (!isset($discount->commerce_discount_date[LANGUAGE_NONE])) { + return TRUE; + } + $discount_date = $discount->commerce_discount_date[LANGUAGE_NONE][0]; + $time = time(); + + $start_date = new DateTime(); + $start_date->setTimestamp($discount_date['value']); + $start_date->setTime(0, 0, 0); + $start_date_timestamp = $start_date->getTimestamp(); + + $end_date = new DateTime(); + $end_date->setTimestamp($discount_date['value2']); + $end_date->setTime(24, 0, 0); + $end_date_timestamp = $end_date->getTimestamp(); + + return $time >= $start_date_timestamp && $time <= $end_date_timestamp; +} + +/** + * Rules action: Remove discount components on product line items. + */ +function commerce_discount_remove_discount_components_on_products($line_item_wrapper) { + commerce_discount_remove_discount_components($line_item_wrapper->commerce_unit_price, array('product_discount')); + commerce_discount_remove_discount_components($line_item_wrapper->commerce_total, array('product_discount')); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules_defaults.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules_defaults.inc index de65708e..14e87a8a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules_defaults.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.rules_defaults.inc @@ -9,56 +9,45 @@ * Implements hook_default_rules_configuration(). */ function commerce_discount_default_rules_configuration() { - $types = commerce_discount_types(); - $offer_types = commerce_discount_offer_types(); - $rules = array(); // Create a rule for each commerce discount entity. entity_get_controller('commerce_discount')->resetCache(); + $discounts = entity_load('commerce_discount'); - foreach (entity_load('commerce_discount') as $discount) { - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - $wrapper_properties = $wrapper->getPropertyInfo(); - // Only for Commerce Discount wrappers with Commerce Discount Offer defined. - if (in_array('commerce_discount_offer', array_keys($wrapper_properties))) { - $type = $types[$discount->type]; - $offer_type = $offer_types[$wrapper->commerce_discount_offer->getBundle()]; - - $rule = rules_reaction_rule(); - - $rule->label = $discount->label; - $rule->active = $discount->status; - $rule->tags = array('Commerce Discount', check_plain($type['label'])); - - $rule - ->event(!empty($offer_type['event']) ? $offer_type['event'] : $type['event']) - ->action( - $offer_type['action'], array( - 'entity:select' => $type['entity type'], - 'commerce_discount' => $discount->name, - ) - ); - - // Let other modules alter the rule object, with configuration specific - // to commerce discount. We don't invoke an alter function, as it can - // be already achieved by implementing - // hook_default_rules_configuration_alter(). - module_invoke_all('commerce_discount_rule_build', $rule, $discount); - - // Let inline_conditions fields add their own conditions. - foreach ($wrapper_properties as $field_name => $property) { - if (stripos($property['type'], 'inline_conditions') !== FALSE) { - inline_conditions_build($rule, $wrapper->$field_name->value()); - } - } - - // Add the commerce discount to the rule configuration, so other may act - // according to it, in hook_default_rules_configuration_alter(). - $rule->commerce_discount = $discount; + // Build the discount rules. + if ($discounts) { + $rules += commerce_discount_build_discount_rules($discounts); + } - $rules['commerce_discount_rule_' . $discount->name] = $rule; - } + // Remove discount price components on product line items, prior to evaluating + // product discount rules. + $rule = rules_reaction_rule(); + $rule->active = TRUE; + $rule->weight = -100; + $rule + ->event('commerce_product_calculate_sell_price') + ->action('commerce_discount_remove_discount_components_on_products', array( + 'commerce_line_item:select' => 'commerce-line-item', + )); + $rules['commerce_discount_remove_discount_components_on_products'] = $rule; + + // Create a rule for shipping discounts if shipping is enabled. + if (module_exists('commerce_shipping')) { + $rule = rules_reaction_rule(); + + $rule->label = t('Discount shipping services'); + $rule->active = TRUE; + $rule->weight = 10; + + // Discount shipping services once all methods have been collected. + $rule + ->event('commerce_shipping_collect_rates') + ->action('commerce_discount_shipping_services', array( + 'commerce_order:select' => 'commerce-order', + )); + + $rules['commerce_discount_shipping_services'] = $rule; } return $rules; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.test deleted file mode 100644 index f1a28952..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/commerce_discount.test +++ /dev/null @@ -1,588 +0,0 @@ -sub_module) { - $modules[] = $this->sub_module; - } - parent::setUp($modules); - - // User creation for different operations. - $this->store_admin = $this->createStoreAdmin(); - $this->store_customer = $this->createStoreCustomer(); - - // Create a dummy product. - $this->product = $this->createDummyProduct('PROD-01', 'Product One', 1000); - - // Create a dummy product display content type. - $this->createDummyProductDisplayContentType(); - - // Create a product display node. - $this->product_node = $this->createDummyProductNode(array($this->product->product_id), 'Product One node'); - - // Set the default country to US. - variable_set('site_default_country', 'US'); - } - - /** - * Create a discount. - * - * @param $discount_type - * The discount type; Either 'order_discount' or 'product_discount'. - * @param $offer_type - * The discount offer type; Either 'fixed_amount' or 'percentage'. - * @param $amount - * The discount offer amount. - * @param $name - * Discount name - Optional. - * - * @return - * The newly created commerce_discount entity. - */ - protected function createDiscount($discount_type, $offer_type, $amount, $name = '', $component_title = '') { - // Create the discount offer. - $commerce_discount_offer = entity_create('commerce_discount_offer', array('type' => $offer_type)); - $wrapper = entity_metadata_wrapper('commerce_discount_offer', $commerce_discount_offer); - switch ($offer_type) { - case 'fixed_amount': - $wrapper->commerce_fixed_amount->amount = $amount; - $wrapper->commerce_fixed_amount->currency_code = 'USD'; - break; - - case 'percentage': - $wrapper->commerce_percentage = $amount; - break; - - case 'free_products': - // Product ids array should be provided for $amount. - $wrapper->commerce_free_products = $amount; - break; - } - - $wrapper->save(); - - // Provide default name. - $name = $name ? $name : $discount_type . '_' . $offer_type; - $component_title = $component_title ? $component_title : $name; - - // Create the discount. - $values = array( - 'name' => $name, - 'label' => $name, - 'type' => $discount_type, - 'component_title' => $component_title, - 'status' => TRUE, - 'export_status' => TRUE, - ); - $commerce_discount = entity_create('commerce_discount', $values); - $wrapper = entity_metadata_wrapper('commerce_discount', $commerce_discount); - $wrapper->commerce_discount_offer = $commerce_discount_offer; - $wrapper->save(); - - return $wrapper->value(); - } -} - -/** - * Testing commerce discounts UI and functionality. - */ -class CommerceDiscountTest extends CommerceDiscountTestBase { - /** - * Implementation of getInfo(). - */ - public static function getInfo() { - return array( - 'name' => 'Discounts', - 'description' => 'Test discounts UI and functionality', - 'group' => 'Commerce discounts', - ); - } - - /** - * Access to commerce discounts admin. - */ - public function testCommerceDiscountUIAccessDiscountsListing() { - // Login with customer. - $this->drupalLogin($this->store_customer); - // Check the access to the profiles listing. - $this->drupalGet('admin/commerce/store/discounts'); - $this->assertResponse(403, t('The store customer has no access to discounts administration.')); - - // Login with store admin. - $this->drupalLogin($this->store_admin); - // Check the access to the profiles listing. - $this->drupalGet('admin/commerce/store/discounts'); - $this->assertResponse(200, t('The store admin has access to discounts administration.')); - - // Check the message of no discounts available. - $this->assertText(t('No discounts found.'), t('\'No discounts found.\' message is displayed')); - // Check the add customer profile link. - $this->assertRaw(l('Add discount', 'admin/commerce/store/discounts/add'), t('\'Add discount\' link is present in the page')); - } - - /** - * Test the add discount UI. - */ - public function testCommerceDiscountUIAddDiscount() { - // Login with normal user. - $this->drupalLogin($this->store_customer); - - // Access to the admin discount creation page. - $this->drupalGet('admin/commerce/store/discounts/add'); - - $this->assertResponse(403, t('Normal user is not able to add a discount using the admin interface')); - - // Login with store admin. - $this->drupalLogin($this->store_admin); - - // Access to the admin discount creation page. - $this->drupalGet('admin/commerce/store/discounts/add'); - - $this->assertResponse(200, t('Store admin user is allowed to add a discount using the admin interface')); - - // Check the integrity of the add form. - $this->assertFieldByName('commerce_discount_type', NULL, t('Discount type field is present')); - $this->assertFieldByName('label', NULL, t('Label field is present')); - $this->assertFieldByName('component_title', NULL, t('Name field is present')); - $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][type]', NULL, t('Offer type field is present')); - $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]', NULL, t('Amount field is present')); - $this->assertFieldByName('status', NULL, t('Status field is present')); - $this->assertFieldById('edit-submit', t('Save discount'), t('Save discount button is present')); - - // Try to save the product and check validation messages. - $this->drupalPost(NULL, array(), t('Save discount')); - - $this->assertText(t('Admin title field is required.'), t('Validation message for missing label.')); - $this->assertText(t('Machine-readable name field is required.'), t('Validation message for missing machine-name.')); - $this->assertText(t('Fixed amount field is required.'), t('Validation message for missing amount.')); - - // Load a clean discount add form. - $this->drupalGet('admin/commerce/store/discounts/add'); - // Create a discount. - $values = array( - 'label' => 'Order discount - fixed', - 'name' => 'order_discount_fixed', - 'component_title' => 'Order discount', - 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, - ); - $this->drupalPost(NULL, $values, t('Save discount')); - - // Load the discount and wrap it. - $discount = entity_load_single('commerce_discount', 1); - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - // Check the stored discount. - $this->assertTrue($discount->label == $values['label'], t('Label stored correctly.')); - $this->assertTrue($discount->name == 'discount_' . $values['name'], t('Name stored correctly.')); - $this->assertTrue($discount->export_status == 1, t('Active stored correctly.')); - $this->assertTrue($discount->component_title == $values['component_title'], t('Name for customer stored correctly.')); - $this->assertTrue($discount->status == 1, t('Enabled stored correctly.')); - - $this->assertTrue($wrapper->commerce_discount_offer->type->value() == 'fixed_amount', t('Offer type stored correctly.')); - $this->assertTrue($wrapper->commerce_discount_offer->commerce_fixed_amount->amount->value() == 1277, t('Amount stored correctly.')); - - // Check the discounts listing - $this->assertTrue($this->url == url('admin/commerce/store/discounts', array('absolute' => TRUE)), t('Landing page after save is the discounts list.')); - $this->assertText($values['label'], t('Label of the discount is present.')); - $this->assertText($values['component_title'], t('Name of the discount is present.')); - } - - /** - * Test the Edit discount UI. - */ - // public function testCommerceDiscountUIEditDiscount() { - // Create a discount. - // $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); - - // // Login with normal user. - // $this->drupalLogin($this->store_customer); - - // // Access to the admin discount edit page. - // $this->drupalGet('admin/commerce/store/discounts/manage/' . $discount->name); - - // $this->assertResponse(403, t('Normal user is not able to edit a discount using the admin interface')); - - // // Login with store admin. - // $this->drupalLogin($this->store_admin); - - // // Access to the admin discount edit page. - // $this->drupalGet('admin/commerce/store/discounts/manage/' . $discount->name); - - // $this->assertResponse(200, t('Store admin user is allowed to edit a discount using the admin interface')); - - // // Check the integrity of the add form. - // $this->assertFieldByName('commerce_discount_type', NULL, t('Discount type field is present')); - // $this->assertFieldByName('label', NULL, t('Label field is present')); - // $this->assertFieldByName('component_title', NULL, t('Name field is present')); - // $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][type]', NULL, t('Offer type field is present')); - // $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]', NULL, t('Amount field is present')); - // $this->assertFieldByName('status', NULL, t('Status field is present')); - // $this->assertFieldById('edit-submit', t('Save discount'), t('Save discount button is present')); - // $this->assertFieldById('edit-delete', t('Delete discount'), t('Delete discount button is present')); - - // // Try to save the product and check validation messages. - // $this->drupalPost(NULL, array(), t('Save discount')); - - // @todo Fix these tests. - // $this->assertText(t('Admin title field is required.'), t('Validation message for missing label.')); - // $this->assertText(t('Machine-readable name field is required.'), t('Validation message for missing machine-name.')); - // $this->assertText(t('Fixed amount field is required.'), t('Validation message for missing amount.')); - - // // Discount new values. - // $values = array( - // 'label' => 'Order discount - fixed', - // 'name' => 'order_discount_fixed', - // 'component_title' => 'Order discount', - // 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, - // ); - // $this->drupalPost(NULL, $values, t('Save discount')); - - // // Load the discount and wrap it. - // $discount = entity_load_single('commerce_discount', 1); - // $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - // // Check the stored discount. - // $this->assertTrue($discount->label == $values['label'], t('Label stored correctly.')); - // $this->assertTrue($discount->name == 'discount_' . $values['name'], t('Name stored correctly.')); - // $this->assertTrue($discount->component_title == $values['component_title'], t('Name for customer stored correctly.')); - // $this->assertTrue($discount->status == 1, t('Enabled stored correctly.')); - - // $this->assertTrue($wrapper->commerce_discount_offer->type->value() == 'fixed_amount', t('Offer type stored correctly.')); - // $this->assertTrue($wrapper->commerce_discount_offer->commerce_fixed_amount->amount->value() == 1277, t('Amount stored correctly.')); - - // // Check the discounts listing. - // $this->assertTrue($this->url == url('admin/commerce/store/discounts', array('absolute' => TRUE)), t('Landing page after save is the discounts list.')); - // $this->assertText($values['label'], t('Label of the discount is present.')); - // $this->assertText($values['component_title'], t('Name of the discount is present.')); - // } - - /** - * Test the delete discount UI. - */ - // public function testCommerceDiscountUIDeleteDiscount() { - // Create a discount. - // $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); - - // // Login with normal user. - // $this->drupalLogin($this->store_customer); - - // // Access to the admin discount edit page. - // $this->drupalGet('admin/commerce/store/discounts/manage/' . $discount->name . '/delete'); - - // $this->assertResponse(403, t('Normal user is not able to delete a discount using the admin interface')); - - // // Login with store admin. - // $this->drupalLogin($this->store_admin); - - // // Access to the admin discount edit page. - // $this->drupalGet('admin/commerce/store/discounts/manage/' . $discount->name . '/delete'); - - // $this->assertResponse(200, t('Store admin user is allowed to delete a discount using the admin interface')); - - // // Check the integrity of the add form. - // $this->pass('Test the discount delete confirmation form:'); - // $this->assertTitle(t('Are you sure you want to delete the Commerce Discount !label?', array('!label' => $discount->label)) . ' | Drupal', t('The confirmation message is displayed')); - // $this->assertText(t('This action cannot be undone'), t("A warning notifying the user about the action can't be undone is displayed.")); - // $this->assertFieldById('edit-submit', t('Confirm'), t('Delete button is present')); - // $this->assertText(t('Cancel'), t('Cancel is present')); - - // // Try to save the product and check validation messages. - // $this->drupalPost(NULL, array(), t('Confirm')); - - // // Check the url after deleting and if the discount has been deleted in - // // database. - // $this->assertTrue($this->url == url('admin/commerce/store/discounts', array('absolute' => TRUE)), t('Landing page after deleting a discount is the discounts listing page')); - // $this->assertRaw(t('Deleted %type %label.', array('%type' => 'Commerce Discount', '%label' => $discount->label)), t("'Discount has been deleted' message is displayed")); - // $this->assertRaw(t('No discounts found.', array('@link' => url('admin/commerce/store/discounts/add'))), t('Empty discount listing message is displayed')); - // } - - /** - * Test the importing of commerce discounts. - */ - public function testCommerceDiscountImport() { - // Login store admin. - $this->drupalLogin($this->store_admin); - - // Access to the admin discount creation page. - $this->drupalGet('admin/commerce/store/discounts/import'); - $this->assertResponse(200, t('Store admin is allowed in the discounts import page')); - - $exported_discount = '{ - "name" : "pf", - "label" : "PF", - "type" : "product_discount", - "status" : "1", - "component_title" : "pf", - "commerce_discount_offer" : { - "type" : "fixed_amount", - "commerce_fixed_amount" : { "und" : [ - { - "amount" : "1200", - "currency_code" : "USD", - "data" : { "components" : [] } - } - ] - } - }, - "inline_conditions" : [] -}'; - - // Import the discount. - $import = entity_import('commerce_discount', $exported_discount); - $this->assertNotNull($import, t('Entity export JSON imported successfully.')); - entity_save('commerce_discount', $import); - - // Export the discount to make sure it's identical to the import string. - $discount = entity_load_single('commerce_discount', $import->discount_id); - $export = entity_export('commerce_discount', $discount); - $this->assertTrue($exported_discount == $export, t('Exported discount is identical to its origin.')); - } - - /** - * Test fixed order discounts. - */ - public function testCommerceDiscountFixedOrderDiscount() { - // Testing fixed discount. - // Create a fixed order discount of $3. - $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 700, t('Fixed order discount is deducted correctly.')); - - // Disable the discount. - $discount->status = FALSE; - entity_save('commerce_discount', $discount); - - // Resave the order. - // Check if the discount was applied on the order total price. - $wrapper = entity_metadata_wrapper('commerce_order', $order); - $wrapper->save(); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 1000, t('Fixed order discount is removed when it\'s not appliable.')); - } - - /** - * Test percentage order discounts. - */ - public function testCommerceDiscountPercentageOrderDiscount() { - // Testing percentage discount. - // Create a percentage order discount of 5%. - $discount = $this->createDiscount('order_discount', 'percentage', 5); - // Create a completed order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 950, t('Percentage order discount is deducted correctly.')); - - // Disable the discount. - $discount->status = FALSE; - entity_save('commerce_discount', $discount); - - // Resave the order. - // Check if the discount was applied on the order total price. - $wrapper = entity_metadata_wrapper('commerce_order', $order); - $wrapper->save(); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 1000, t("Percentage order discount is removed when it's not appliable.")); - } - - /** - * Test Free products order discounts. - */ - public function testCommerceDiscountFreeProductsOrderDiscount() { - // Testing Free products discount. - // Create a Free products product discount. - $discount = $this->createDiscount('product_discount', 'free_products', array($this->product->product_id)); - // Create a completed order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - // @todo Fix this assertion. - // $this->assertTrue($wrapper->commerce_order_total->amount->value() == 0, t('Free Products order discount is deducted correctly.')); - - // Disable the discount. - $discount->status = FALSE; - entity_save('commerce_discount', $discount); - - // Resave the order. - // Check if the discount was applied on the order total price. - $wrapper = entity_metadata_wrapper('commerce_order', $order); - $wrapper->save(); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 1000, t("Free Products order discount is removed when it's not appliable.")); - } - - /** - * Test fixed product discounts. - */ - public function testCommerceDiscountFixedProductDiscount() { - $discount = $this->createDiscount('product_discount', 'fixed_amount', 300); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - - $this->assertTrue($price_data['components'][1]['price']['amount'] == -300, t('Fixed product discount is added as a price component to the line item.')); - } - - /** - * Test percentage product discounts. - */ - public function testCommerceDiscountPercentageProductDiscount() { - $discount = $this->createDiscount('product_discount', 'percentage', 5); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - - $this->assertTrue($price_data['components'][1]['price']['amount'] == -50, t('Percentage product discount is added as a price component to the line item.')); - } - - /** - * Test discounted product price display. - */ - public function testCommerceDiscountDiscountedProductPriceDisplay() { - // Create a product discount. - $discount = $this->createDiscount('product_discount', 'fixed_amount', 300); - $formatted_discounted_price = '$7.00'; - - // Log as a normal user. - $this->drupalLogin($this->store_customer); - - $nid = $this->product_node->nid; - // View a product node. - $this->drupalGet("node/$nid"); - $product_price = $this->xpath('//div[contains(@class, "field-name-commerce-price")]/div[contains(@class, "field-item")]'); - $this->assertTrue(trim((string)$product_price[0]->div) == $formatted_discounted_price, t('Discounted product price is shown on product page.')); - - // Add a product to the cart. - $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart')); - - // View the cart. - $this->drupalGet('cart'); - $product_price = $this->xpath('//td[contains(@class, "views-field-commerce-unit-price")]'); - $this->assertTrue(trim((string)$product_price[0]->{0}) == $formatted_discounted_price, t('Discounted product price is shown on the cart.')); - } - - /** - * Test multiple fixed order discounts. - */ - public function testCommerceDiscountMultipleFixedOrderDiscounts() { - // Create two discounts. - $discount = $this->createDiscount('order_discount', 'fixed_amount', 300, 'of1'); - $discount = $this->createDiscount('order_discount', 'fixed_amount', 200, 'of2'); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - entity_get_controller('commerce_order')->resetCache(array($order->order_id)); - - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - $this->assertTrue($wrapper->commerce_discounts->count() == 2, t('2 discounts are listed as applied on the order.')); - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 500, t('Two fixed order discounts are applied on the total price.')); - $this->assertTrue($wrapper->commerce_line_items->count() == 3, t('An order with one product and two fixed order discounts has three line items.')); - $wrapper->save(); - $this->assertTrue($wrapper->commerce_line_items->count() == 3, t('After updating the order it still has three line items.')); - } - -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount-rtl.css b/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount-rtl.css deleted file mode 100644 index c0849e3c..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount-rtl.css +++ /dev/null @@ -1,155 +0,0 @@ -.form-item.form-type-radio { - position: relative; -} -.form-item.form-type-radio .progress-disabled { - float: none; -} -.form-item.form-type-radio .ajax-progress-throbber { - position: absolute; - right: -10px; -} - -html.js input.form-autocomplete { - background-position: 0% 2px; -} - -/** - * Discount form. - */ -.commerce-discount-form { - position: relative; -} -.commerce-discount-form .form-item-status { - background-color: #eee; - padding: 10px 10px 20px 10px; - position: absolute; - top: 0; - left: 0; - right: auto; - width: 180px; -} -.commerce-discount-form .form-item-label { - clear: both; -} -.commerce-discount-form .form-item-label .form-item { - padding: 0px; -} -.commerce-discount-form .form-item.form-type-radio { - position: relative; -} -.commerce-discount-form .form-item.form-type-radio .ajax-progress-throbber { - position: absolute; - right: -10px; -} -.commerce-discount-form #commerce-discount-fields-wrapper .form-wrapper > #inline-conditions-inline_conditions .form-wrapper .ajax-progress { - display: block; - /* RTL */ - margin-top: 10px; - /* RTL */ - width: 180px; - /* RTL */ -} -.commerce-discount-form #commerce-discount-fields-wrapper .form-wrapper > #inline-conditions-inline_conditions .form-wrapper .condition-wrapper .form-item .ajax-progress { - display: inline-block; - margin-top: 0; - width: auto; -} - -/** - * Choose discount type block. - */ -.form-item-commerce-discount-type .form-radios .form-item { - float: right; -} -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item { - margin-left: 20px; - margin-right: 0; -} -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item { - float: right; -} - -/** - * Choose offer type block. - */ -.field-name-commerce-discount-offer { - clear: both; -} -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item label { - padding-right: 5px; - padding-left: 0; -} -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios { - border-right: 0; - border-left: 1px dotted #cccccc; - float: right; - padding-right: 0; - padding-left: 20px; -} -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item { - margin-left: 20px; - margin-right: 0; -} -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios label { - padding-right: 5px; - padding-left: 0; -} -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-wrapper label { - padding-right: 5px; - padding-left: 0; -} -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-wrapper .form-item { - float: right; -} - -/** - * Discount dates block. - */ -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date { - float: right; -} -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .date-padding { - float: left; -} -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .date-padding > .form-item:first-child input.form-text { - background: white url("../images/calendar.png") no-repeat 2px 0px; -} -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date label { - float: right; - padding-right: 0px; - padding-left: 10px; -} -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date.end-date-wrapper { - margin-left: 0; - margin-right: 20px; -} - -/** - * Usage block. - */ -.commerce-discount-usage .form-item.form-type-select { - margin: 0; -} -.commerce-discount-usage .form-item.form-type-select label { - border-bottom: 1px solid #cccccc; -} -.commerce-discount-usage .form-item .form-select { - float: right; -} -.commerce-discount-usage .field-name-commerce-discount-max-uses { - padding: 0px; -} -.commerce-discount-usage .field-name-commerce-discount-max-uses > div { - float: left; - margin-left: 30px; -} -.commerce-discount-usage .field-name-commerce-discount-max-uses > div:first-child { - margin-left: 0; -} -.commerce-discount-usage .field-type-number-integer { - float: right; -} -.commerce-discount-usage .field-type-number-integer label { - margin-left: 0px; - margin-right: 10px; -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount.css b/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount.css index 1162834b..248d0b21 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount.css +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/css/commerce_discount.css @@ -4,19 +4,11 @@ .commerce-discount-form { position: relative; } -.commerce-discount-form .form-item-status { - background-color: #eee; - padding: 10px 10px 20px 10px; - position: absolute; - top: 0; - right: 0; - width: 180px; -} .commerce-discount-form .form-item-label { clear: both; } .commerce-discount-form .form-item-label .form-item { - padding: 0px; + padding: 0; } .commerce-discount-form .form-item.form-type-radio { position: relative; @@ -28,9 +20,13 @@ .commerce-discount-form .form-item.form-type-radio .ajax-progress-throbber .message { display: none; } -.commerce-discount-form #commerce-discount-fields-wrapper > .form-wrapper { +.commerce-discount-form #commerce-discount-fields-wrapper .form-wrapper { margin-bottom: 15px; } +.commerce-discount-form #commerce-discount-fields-wrapper .field-name-discount-usage-limit, .commerce-discount-form #commerce-discount-fields-wrapper .field-name-discount-usage-per-person { + float: none; + clear: both; +} .commerce-discount-form #edit-actions { clear: both; } @@ -44,10 +40,14 @@ .form-item-commerce-discount-type .form-radios .form-item { color: black; border-radius: 5px; - margin-right: 20px; margin-bottom: 5px; padding: 5px; - float: left; +} +[dir="ltr"] .form-item-commerce-discount-type .form-radios .form-item { + margin-right: 20px; +} +[dir="rtl"] .form-item-commerce-discount-type .form-radios .form-item { + margin-left: 20px; } .form-item-commerce-discount-type .form-radios .form-item:hover { background-color: #f6f6f6; @@ -56,44 +56,58 @@ .form-item-commerce-discount-type .form-radios .form-item:hover label { cursor: pointer; } +[dir="ltr"] .form-item-commerce-discount-type .form-radios .form-item { + float: left; +} +[dir="rtl"] .form-item-commerce-discount-type .form-radios .form-item { + float: right; +} .form-item-commerce-discount-type .form-radios .form-item.selected { background-color: #eee; } .form-item-commerce-discount-type .form-radios .form-item .ajax-progress { display: none; } -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type { - padding: 0px; + +#edit-commerce-discount-type { + padding: 0; clear: both; margin-top: 10px; clear: none; } -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item { +#edit-commerce-discount-type .form-item { color: black; border-radius: 5px; - margin-right: 20px; margin-bottom: 5px; padding: 5px; } -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item:hover { +[dir="ltr"] #edit-commerce-discount-type .form-item { + margin-right: 20px; +} +[dir="rtl"] #edit-commerce-discount-type .form-item { + margin-left: 20px; +} +#edit-commerce-discount-type .form-item:hover { background-color: #f6f6f6; cursor: pointer; } -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item:hover label { +#edit-commerce-discount-type .form-item:hover label { cursor: pointer; } -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item input { - display: inline; -} -.form-item-commerce-discount-type .form-radios .form-item #edit-commerce-discount-type .form-item { +[dir="ltr"] #edit-commerce-discount-type .form-item { float: left; + margin-right: 20px; +} +[dir="rtl"] #edit-commerce-discount-type .form-item { + float: right; + margin-left: 20px; } /** * Order/Product discount conditions block. */ .commerce-discount-box div.inline-conditions-container > label { - border-bottom: 1px solid #cccccc; + border-bottom: 1px solid #ccc; display: block; width: 100%; } @@ -102,69 +116,98 @@ * Choose offer type block. */ .field-name-commerce-discount-offer { - border: none; + border: 0; margin: 0; padding: 0; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item { - margin-bottom: 2px; - padding: 0; +.field-name-commerce-discount-offer:after { + content: ""; + display: table; + clear: both; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item label { - border-bottom: 1px solid #cccccc; - padding-left: 5px; +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios { + padding-left: 0; + padding-right: 0; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios { - padding: 0px; - clear: both; - margin-top: 10px; - background-color: transparent; - border-right: 1px dotted #cccccc; - /* LTR */ +[dir="ltr"] .field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios { float: left; - /* LTR */ - margin: 10px; - padding: 4px; + margin-right: 20px; padding-right: 20px; - /* LTR */ + border-right: 1px dotted #ccc; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item { +[dir="rtl"] .field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios { + float: right; + margin-left: 20px; + padding-left: 20px; + border-left: 1px dotted #ccc; +} +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios { + clear: both; + margin: 10px; + padding: 0; +} +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item { color: black; border-radius: 5px; - margin-right: 20px; margin-bottom: 5px; padding: 5px; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item:hover { +[dir="ltr"] .field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item { + margin-right: 20px; +} +[dir="rtl"] .field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item { + margin-left: 20px; +} +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item:hover { background-color: #f6f6f6; cursor: pointer; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item:hover label { +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item:hover label { cursor: pointer; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item input { +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item input { display: inline; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item.selected { +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item.selected { background-color: #eee; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios .form-item .ajax-progress { +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios .form-item .ajax-progress { display: none; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item div.form-radios label { +.field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios label { border-bottom: 0; +} +[dir="ltr"] .field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios label { padding-left: 0; } +[dir="rtl"] .field-name-commerce-discount-offer .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios .form-radios label { + padding-right: 0; +} +.field-name-commerce-discount-offer .commerce-offer-type-wrapper > .fieldset-wrapper:after { + content: ""; + display: table; + clear: both; +} +.field-name-commerce-discount-offer .commerce-offer-fields-wrapper { + padding: 0; +} +[dir="ltr"] .field-name-commerce-discount-offer .commerce-offer-fields-wrapper { + float: left; +} +[dir="rtl"] .field-name-commerce-discount-offer .commerce-offer-fields-wrapper { + float: right; +} +.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-item { + margin-bottom: 2px; +} .field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-wrapper label { border-bottom: 0; +} +[dir="ltr"] .field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-wrapper label { padding-left: 0; } -.field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-wrapper .form-item { - background-color: transparent; - float: left; - /* LTR */ - margin: 10px; - padding: 4px; +[dir="rtl"] .field-name-commerce-discount-offer .form-wrapper .form-wrapper .form-wrapper label { + padding-right: 0; } /** @@ -173,125 +216,130 @@ .field-name-commerce-discount-date { clear: both; } -.field-name-commerce-discount-date fieldset.form-wrapper { - border: none; -} -.field-name-commerce-discount-date fieldset.form-wrapper legend { - width: 100%; -} -.field-name-commerce-discount-date fieldset.form-wrapper .fieldset-legend { - border-bottom: 1px solid #cccccc; - display: block; - margin-top: 0; - padding-left: 0; - text-transform: none; - width: 100%; -} .field-name-commerce-discount-date .form-wrapper fieldset.form-wrapper { margin: 0; } .field-name-commerce-discount-date .fieldset-wrapper { - padding: 0px; background-color: transparent; display: inline-block; margin: 10px; padding: 4px; width: auto; } -.field-name-commerce-discount-date .fieldset-wrapper .form-type-checkbox { - display: none; -} -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date { +.field-name-commerce-discount-date .container-inline-date { clear: none; - float: left; - width: auto; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .description { - display: none; +[dir="ltr"] .field-name-commerce-discount-date .container-inline-date { + float: left; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .form-item { - padding: 0; +[dir="rtl"] .field-name-commerce-discount-date .container-inline-date { + float: right; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .date-padding { +[dir="ltr"] .field-name-commerce-discount-date .container-inline-date .date-padding { float: left; - padding: 0; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .date-padding input.form-text { - width: 55px; +[dir="rtl"] .field-name-commerce-discount-date .container-inline-date .date-padding { + float: right; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date .date-padding > .form-item:first-child input.form-text { - background: white url("../images/calendar.png") no-repeat 86px 0px; - width: 100px; +.field-name-commerce-discount-date .container-inline-date .form-text { + background-image: url(../images/calendar.svg); + background-repeat: no-repeat; + background-position: calc(100% - 0.25em) center; + background-size: 1.25em; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date > .form-item { - border: none; -} -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date label { - display: block; - float: left; - padding-right: 10px; +.field-name-commerce-discount-date fieldset.date-combo .container-inline-date .date-padding { + padding: 0; } -.field-name-commerce-discount-date .fieldset-wrapper .container-inline-date.end-date-wrapper { - margin-left: 20px; +.field-name-commerce-discount-date fieldset.date-combo .fieldset-wrapper > .container-inline-date { + padding-top: 0; } /** * Usage block. */ .commerce-discount-usage { - clear: both; + clear: both; } .commerce-discount-usage .form-item.form-type-select { margin: 0; } .commerce-discount-usage .form-item.form-type-select label { - border-bottom: 1px solid #cccccc; + border-bottom: 1px solid #ccc; } .commerce-discount-usage .form-item .form-select { - float: left; margin: 10px; } +[dir="ltr"] .commerce-discount-usage .form-item .form-select { + float: left; +} +[dir="rtl"] .commerce-discount-usage .form-item .form-select { + float: right; +} .commerce-discount-usage .field-name-commerce-discount-max-uses { - padding: 0px; + padding: 0; } -.commerce-discount-usage .field-name-commerce-discount-max-uses > div { +[dir="ltr"] .commerce-discount-usage .field-name-commerce-discount-max-uses > div { float: left; margin-left: 30px; } -.commerce-discount-usage .field-name-commerce-discount-max-uses > div:first-child { +[dir="ltr"] .commerce-discount-usage .field-name-commerce-discount-max-uses > div:first-child { margin-left: 0; } +[dir="rtl"] .commerce-discount-usage .field-name-commerce-discount-max-uses > div { + float: right; + margin-right: 30px; +} +[dir="rtl"] .commerce-discount-usage .field-name-commerce-discount-max-uses > div:first-child { + margin-right: 0; +} .commerce-discount-usage .field-name-commerce-discount-max-uses > div input[disabled] { - background: #eeeeee; + background: #eee; } -.commerce-discount-usage .field-type-number-integer { +[dir="ltr"] .commerce-discount-usage .field-type-number-integer { float: left; } +[dir="rtl"] .commerce-discount-usage .field-type-number-integer { + float: right; +} .commerce-discount-usage .field-type-number-integer .form-item { padding: 0; } .commerce-discount-usage .field-type-number-integer label { display: inline; font-weight: normal; +} +[dir="ltr"] .commerce-discount-usage .field-type-number-integer label { margin-left: 10px; } +[dir="rtl"] .commerce-discount-usage .field-type-number-integer label { + margin-right: 10px; +} .commerce-discount-usage .field-type-number-integer .form-disabled input { - border: none; - background: none; + border: 0; + background-color: transparent; } /** * Overview table. */ .view-commerce-discount-overview .views-table { - border: none; + border: 0; +} +.view-commerce-discount-overview .views-table caption { + font-size: 1.25em; + line-height: 2em; + font-weight: bold; + margin-top: 1em; } .view-commerce-discount-overview .views-table tr, .view-commerce-discount-overview .views-table td { - border: none; + border: 0; } .view-commerce-discount-overview .views-table th { text-transform: none; } +.view-commerce-discount-overview .views-table .views-field-label { + width: 30%; +} .view-commerce-discount-overview .views-table th .views-field-operations-dropbutton { width: 70px; } @@ -314,7 +362,6 @@ .view-commerce-discount-overview .views-table td .views-field-enable-disable .item-list ul li { display: block; width: 50%; - float: left; text-transform: uppercase; padding: 5px 0; font-weight: bold; @@ -323,6 +370,12 @@ color: white; line-height: 20px; } +[dir="ltr"] .view-commerce-discount-overview .views-table td .views-field-enable-disable .item-list ul li { + float: left; +} +[dir="rtl"] .view-commerce-discount-overview .views-table td .views-field-enable-disable .item-list ul li { + float: right; +} .view-commerce-discount-overview .views-table td .views-field-enable-disable .item-list ul li.enabled { background-color: #7cae20; } @@ -336,15 +389,17 @@ position: relative; } .view-commerce-discount-overview .views-table td .views-field-operations-dropbutton .ctools-dropbutton { - border: 1px solid #cccccc; + border: 1px solid #ccc; border-radius: 10px; - background-image: linear-gradient(top, #fcfdfc 36%, #f0f0f0 68%); - background-image: -o-linear-gradient(top, #fcfdfc 36%, #f0f0f0 68%); - background-image: -moz-linear-gradient(top, #fcfdfc 36%, #f0f0f0 68%); background-image: -webkit-linear-gradient(top, #fcfdfc 36%, #f0f0f0 68%); - background-image: -ms-linear-gradient(top, #fcfdfc 36%, #f0f0f0 68%); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0.36, #fcfdfc), color-stop(0.68, #f0f0f0)); + background-image: linear-gradient(top, #fcfdfc 36%, #f0f0f0 68%); } .view-commerce-discount-overview .views-table td .views-field-operations-dropbutton .ctools-dropbutton .ctools-content { - border-right: 1px solid #cccccc; + border-right: 1px solid #ccc; +} + +.field-name-commerce-compatibility-strategy .field-widget-options-buttons .form-wrapper { + clear: both; } + +/*# sourceMappingURL=commerce_discount.css.map */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.png b/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.png index 9d40d4c6..4fa837f4 100644 Binary files a/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.png and b/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.svg b/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.svg new file mode 100644 index 00000000..f27c8936 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar.svg @@ -0,0 +1,40 @@ + + + + Icon_Calendar + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar16.png b/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar16.png new file mode 100644 index 00000000..af6c3bc4 Binary files /dev/null and b/profiles/commerce_kickstart/modules/contrib/commerce_discount/images/calendar16.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.admin.inc index 3beb7320..917dcd60 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.admin.inc @@ -6,12 +6,12 @@ */ /** - * UI controller. + * Class CommerceDiscountUIController. */ class CommerceDiscountUIController extends EntityDefaultUIController { /** - * Overridden to customize the field location. + * {@inheritdoc} */ public function entityFormSubmitBuildEntity($form, &$form_state) { // We cannot use entity_form_submit_build_entity() any more. @@ -19,9 +19,10 @@ class CommerceDiscountUIController extends EntityDefaultUIController { // Extract form values. form_state_values_clean($form_state); + foreach ($form_state['values'] as $key => $value) { if ($key != 'commerce_discount_fields') { - $entity->$key = $value; + $entity->{$key} = $value; } else { $discount_offer = $value['commerce_discount_offer'][LANGUAGE_NONE]['form']; @@ -29,21 +30,22 @@ class CommerceDiscountUIController extends EntityDefaultUIController { $entity->commerce_discount_offer['commerce_discount_fields'] = $discount_offer; } } - // Invoke all specified builders for copying form values to entity - // properties. + + // Invoke all specified builders for copying values to entity properties. // @see entity_form_submit_build_entity() if (isset($form['#entity_builders'])) { foreach ($form['#entity_builders'] as $function) { $function('commerce_discount', $entity, $form, $form_state); } } + field_attach_submit('commerce_discount', $entity, $form['commerce_discount_fields'], $form_state); return $entity; } /** - * Overriden EntityDefaultUIController::hook_menu(). + * Overridden EntityDefaultUIController::hook_menu(). * * Call our own page callback to show the "commerce_discount_overview" * view, as this view has exposed filters which will not work inside @@ -64,10 +66,10 @@ class CommerceDiscountUIController extends EntityDefaultUIController { 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, ); - unset($items[$this->path . '/add']['title callback']); - unset($items[$this->path . '/import']['title callback']); - $items[$this->path . '/add']['title'] = t('Add discount'); - $items[$this->path . '/import']['title'] = t('Import discount'); + $items[$this->path . '/add']['title callback'] = 't'; + $items[$this->path . '/import']['title callback'] = 't'; + $items[$this->path . '/add']['title'] = 'Add discount'; + $items[$this->path . '/import']['title'] = 'Import discount'; $items[$this->path . '/settings'] = array( 'title' => 'Settings', @@ -80,26 +82,29 @@ class CommerceDiscountUIController extends EntityDefaultUIController { 'file path' => drupal_get_path('module', 'commerce_discount'), ); - $items[$this->path . '/rules'] = array( - 'title' => 'Discount rules', - 'description' => 'Manage discount rules', - 'page callback' => 'drupal_goto', - 'page arguments' => array( - 'admin/config/workflow/rules', - array( - 'query' => array( - 'event' => 0, - 'tag' => 'Commerce Discount', + if (module_exists('rules_admin')) { + $items[$this->path . '/rules'] = array( + 'title' => 'Discount rules', + 'description' => 'Manage discount rules', + 'page callback' => 'drupal_goto', + 'page arguments' => array( + 'admin/config/workflow/rules', + array( + 'query' => array( + 'event' => 0, + 'tag' => 'Commerce Discount', + ), ), - ) - ), - 'access arguments' => array('administer commerce discounts'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 10, - ); + ), + 'access arguments' => array('administer commerce discounts'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + ); + } return $items; } + } /** @@ -110,10 +115,7 @@ function commerce_discount_overview_form() { $view->override_url = $_GET['q']; $form['view'] = array('#markup' => $view->preview()); $form['#attached']['css'][] = drupal_get_path('module', 'commerce_discount') . '/css/commerce_discount.css'; - // Load rtl.css file if needed. - if ($GLOBALS['language']->direction == 1) { - $form['#attached']['css'][] = drupal_get_path('module', 'commerce_discount') . '/css/commerce_discount-rtl.css'; - } + return $form; } @@ -125,9 +127,12 @@ function commerce_discount_overview_form() { * @param array $form_state * Standard Drupal $form_state. * @param CommerceDiscount $commerce_discount - * Comment me. + * Commerce discount object. * @param string $op * Current operation. + * + * @return array + * A renderable form build array. */ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_discount, $op = 'edit') { // We might have gotten the commerce discount type via ajax, so set it @@ -152,14 +157,14 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ $form['component_title'] = array( '#title' => t('Name'), '#type' => 'textfield', - '#default_value' => $commerce_discount->component_title, - '#description' => t('Shown to customers.'), + '#default_value' => empty($commerce_discount->component_title) ? t('Discount') : $commerce_discount->component_title, + '#description' => t('Shown to customers. Defaults to "Discount".'), ); // Machine-readable type name. $form['name'] = array( '#type' => 'machine_name', - // Strip the 'discount_' prefix from the beginning of the string + // Strip the 'discount_' prefix from the beginning of the string. '#default_value' => isset($commerce_discount->name) ? substr($commerce_discount->name, 9) : '', '#disabled' => $commerce_discount->hasStatus(ENTITY_IN_CODE), '#machine_name' => array( @@ -174,17 +179,6 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ '#field_suffix' => '‎', ); - $form['status'] = array( - '#title' => t('Discount status'), - '#type' => 'select', - '#options' => array( - TRUE => t('Active'), - FALSE => t('Disabled'), - ), - '#required' => FALSE, - '#default_value' => $commerce_discount->status, - ); - // Show a list of commerce discounts. $form['commerce_discount_type'] = array( '#title' => t('Choose discount type'), @@ -193,8 +187,8 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ '#required' => FALSE, '#default_value' => $commerce_discount->type, '#ajax' => array( - 'callback' => 'commerce_discount_fields_ajax_callback', - 'wrapper' => 'commerce-discount-fields-wrapper', + 'callback' => 'commerce_discount_fields_ajax_callback', + 'wrapper' => 'commerce-discount-fields-wrapper', ), ); @@ -203,6 +197,46 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ '#suffix' => '', '#tree' => TRUE, '#parents' => array('commerce_discount_fields'), + '#pre_render' => array('commerce_discount_form_pre_render'), + ); + // Vertical tabs container. + $form['commerce_discount_fields']['additional_settings'] = array( + '#type' => 'vertical_tabs', + '#weight' => 99, + 'discount_options' => array( + '#type' => 'fieldset', + '#title' => t('Discount options'), + '#collapsible' => TRUE, + '#weight' => -10, + ), + 'commerce_discount_compatibility' => array( + '#type' => 'fieldset', + '#title' => t('Discount compatibility'), + '#collapsible' => TRUE, + ), + ); + + // Add radios for enabling or disabling this discount. + $form['commerce_discount_fields']['additional_settings']['discount_options']['status'] = array( + '#title' => t('Discount status'), + '#type' => 'radios', + '#parents' => array('status'), + '#options' => array( + TRUE => t('Active'), + FALSE => t('Disabled'), + ), + '#required' => FALSE, + '#default_value' => $commerce_discount->status, + ); + + // Add the sort order select list. + $form['commerce_discount_fields']['additional_settings']['discount_options']['sort_order'] = array( + '#type' => 'select', + '#parents' => array('sort_order'), + '#title' => t('Sort order'), + '#description' => t('Discounts will be sorted by this value to be evaluated in that order.'), + '#options' => drupal_map_assoc(range(1, 21)), + '#default_value' => !empty($commerce_discount->sort_order) ? $commerce_discount->sort_order : 10, ); field_attach_form('commerce_discount', $commerce_discount, $form['commerce_discount_fields'], $form_state); @@ -213,9 +247,10 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ // Add a class to custom fields for easier styling. // Ignore the offer reference field and any date fields. Date fields are // ignored because their markup makes them hard to style generically. + $date_field_types = array('date', 'datestamp', 'datetime'); foreach (element_get_visible_children($form['commerce_discount_fields']) as $field_name) { $field = field_info_field($field_name); - if ($field_name == 'commerce_discount_offer' || in_array($field['type'], array('date', 'datestamp', 'datetime'))) { + if ($field_name == 'commerce_discount_offer' || in_array($field['type'], $date_field_types)) { continue; } @@ -226,13 +261,61 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ $field_form['#attributes']['class'][] = 'commerce-discount-box'; } + // Give the compatibility strategy field a title and a description linking to + // the compatibility documentation based on the discount type. + switch ($form['commerce_discount_fields']['commerce_compatibility_strategy'][LANGUAGE_NONE]['#bundle']) { + case 'product_discount': + $discount_type_name = t('Product discounts'); + $description = t('This setting determines which product discounts this discount can be applied after or that can be applied to the same product line item after this one.'); + break; + case 'order_discount': + $discount_type_name = t('Order discounts'); + $description = t('This setting determines which order discounts this discount can be applied after or that can be applied to the same order after this one.'); + break; + default: + $discount_type_name = t('discounts of the same type'); + $description = t('This setting determines which discounts of the same type this discount can be applied after or that can be applied to the same item after this one.'); + break; + } + + $title = t('Compatibility with other %name', array('%name' => $discount_type_name)); + $form['commerce_discount_fields']['commerce_compatibility_strategy'][LANGUAGE_NONE]['#title'] = $title; + $form['commerce_discount_fields']['commerce_compatibility_strategy'][LANGUAGE_NONE]['#description'] = $description . '
' . t('For more information, refer to the discount compatibility !link on drupal.org.', array('!link' => '' . t('documentation page') . ''));; + + // Ensure the compatibility strategy field has a default value. + if (empty($form['commerce_discount_fields']['commerce_compatibility_strategy'][LANGUAGE_NONE]['#default_value'])) { + $form['commerce_discount_fields']['commerce_compatibility_strategy'][LANGUAGE_NONE]['#default_value'] = 'any'; + } + + // Add visibility states to the discount compatibility selection field. + $form['commerce_discount_fields']['commerce_compatibility_selection']['#states'] = array( + 'visible' => array( + ':input[name="commerce_discount_fields[commerce_compatibility_strategy][' . LANGUAGE_NONE . ']"]' => array( + array('value' => 'except'), + array('value' => 'only'), + ), + ), + ); + + // We have to do some extra adjustments to move compatibility fields into + // the vertical tabs. + $form['commerce_discount_fields']['additional_settings']['commerce_discount_compatibility']['commerce_compatibility_strategy'] = $form['commerce_discount_fields']['commerce_compatibility_strategy']; + $form['commerce_discount_fields']['additional_settings']['commerce_discount_compatibility']['commerce_compatibility_strategy']['#parents'] = array( + 'commerce_discount_fields', 'commerce_compatibility_strategy', + ); + $form['commerce_discount_fields']['additional_settings']['commerce_discount_compatibility']['commerce_compatibility_selection'] = $form['commerce_discount_fields']['commerce_compatibility_selection']; + $form['commerce_discount_fields']['additional_settings']['commerce_discount_compatibility']['commerce_compatibility_selection']['#parents'] = array( + 'commerce_discount_fields', 'commerce_compatibility_selection', + ); + unset($form['commerce_discount_fields']['commerce_compatibility_strategy'], $form['commerce_discount_fields']['commerce_compatibility_selection']); + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save discount'), '#weight' => 40, '#ief_submit_all' => TRUE, - '#submit' => null, + '#submit' => NULL, ); if (!$commerce_discount->hasStatus(ENTITY_IN_CODE) && $op != 'add') { @@ -242,17 +325,30 @@ function commerce_discount_form($form, &$form_state, CommerceDiscount $commerce_ '#weight' => 45, '#limit_validation_errors' => array(), '#submit' => array('commerce_discount_form_submit_delete'), + '#suffix' => l(t('Cancel'), 'admin/commerce/discounts'), ); } + else { + $form['actions']['submit']['#suffix'] = l(t('Cancel'), 'admin/commerce/discounts'); + } + + $form['commerce_discount_fields']['additional_settings']['commerce_discount_usage'] = array( + '#type' => 'fieldset', + '#title' => t('Maximum usage'), + '#collapsible' => TRUE, + ); + $form['commerce_discount_fields']['additional_settings']['discount_date'] = array( + '#type' => 'fieldset', + '#title' => t('Discount dates'), + '#collapsible' => TRUE, + '#weight' => -1, + ); // Add assets. $form['#attached']['js'][] = drupal_get_path('module', 'commerce_discount') . '/js/commerce_discount.js'; $form['#attached']['css'][] = drupal_get_path('module', 'commerce_discount') . '/css/commerce_discount.css'; - // Load rtl.css file if needed. - if ($GLOBALS['language']->direction == 1) { - $form['#attached']['css'][] = drupal_get_path('module', 'commerce_discount') . '/css/commerce_discount-rtl.css'; - } $form['#attributes']['class'][] = 'commerce-discount-form'; + return $form; } @@ -275,6 +371,14 @@ function commerce_discount_form_validate($form, &$form_state) { if (!empty($form_state['values']['name'])) { form_set_value($form['name'], 'discount_' . $form_state['values']['name'], $form_state); } + + // Check if this is a percentage offer. + if (isset($form_state['values']['commerce_discount_fields']['commerce_discount_offer'][LANGUAGE_NONE]['form']['commerce_percentage'])) { + $percentage = $form_state['values']['commerce_discount_fields']['commerce_discount_offer'][LANGUAGE_NONE]['form']['commerce_percentage'][LANGUAGE_NONE][0]['value']; + if (!empty($percentage) && $percentage <= 0) { + form_set_error('commerce_discount_fields][commerce_discount_offer][' . LANGUAGE_NONE . '][form][commerce_percentage', t('Percentage should be a positive, non-zero number.')); + } + } } /** @@ -283,23 +387,30 @@ function commerce_discount_form_validate($form, &$form_state) { function commerce_discount_form_submit(&$form, &$form_state) { $commerce_discount = entity_ui_form_submit_build_entity($form, $form_state); $commerce_discount->save(); - $form_state['redirect'] = 'admin/commerce/store/discounts'; + + // Set the redirect based on the type of the discount. + if ($commerce_discount->type == 'order_discount') { + $form_state['redirect'] = array('admin/commerce/discounts', array('query' => array('type' => 'order_discount'))); + } + else { + $form_state['redirect'] = 'admin/commerce/discounts'; + } } /** * Form API submit callback for the delete button. */ function commerce_discount_form_submit_delete(&$form, &$form_state) { - $form_state['redirect'] = 'admin/commerce/store/discounts/manage/' . $form_state['commerce_discount']->name . '/delete'; + $form_state['redirect'] = 'admin/commerce/discounts/manage/' . $form_state['commerce_discount']->name . '/delete'; } /** * Checks if a Discount machine name is taken. * - * @param $value + * @param string $value * The machine name, not prefixed with 'discount_'. * - * @return + * @return object|bool * Whether or not the field machine name is taken. */ function _commerce_discount_name_exists($value) { @@ -318,9 +429,67 @@ function commerce_discount_settings() { $form['commerce_discount_line_item_types'] = array( '#type' => 'checkboxes', '#title' => t('Line item types to use for discounts'), - '#default_value' => variable_get('commerce_discount_line_item_types', array('product')), + '#default_value' => variable_get('commerce_discount_line_item_types', array_diff(commerce_product_line_item_types(), array('product_discount'))), '#options' => $types, '#description' => t('Select the line item types that will be taken into account for calculating the discount amounts in percentage offers.'), ); return system_settings_form($form); } + +/** + * Add fields to the additional_settings vertical tabs container. + */ +function commerce_discount_form_pre_render($fields) { + $fields['additional_settings']['commerce_discount_usage']['discount_usage_per_person'] = $fields['discount_usage_per_person']; + $fields['additional_settings']['commerce_discount_usage']['discount_usage_limit'] = $fields['discount_usage_limit']; + $fields['additional_settings']['discount_date']['commerce_discount_date'] = $fields['commerce_discount_date']; + + unset($fields['discount_usage_per_person']); + unset($fields['discount_usage_limit']); + unset($fields['commerce_discount_date']); + + return $fields; +} + +/** + * Implements hook_date_combo_process_alter(). + */ +function commerce_discount_date_combo_process_alter(&$element, &$form_state, $context) { + if ($element['#field_name'] == 'commerce_discount_date') { + $element['#attributes'] = 'container-inline'; + + // Hide the checkbox for showing the end date. + $element['show_todate']['#type'] = 'value'; + $element['show_todate']['#default_value'] = TRUE; + unset($element['show_todate']['#prefix']); + unset($element['show_todate']['#suffix']); + + // Ignore the "Show end date" checkbox. + unset($element['value2']['#states']); + + // Remove fieldset description. + $element['#fieldset_description'] = ''; + } +} + +/** + * Implements hook_date_popup_process_alter(). + */ +function commerce_discount_date_popup_process_alter(&$element, &$form_state, $context) { + if ($element['#field']['field_name'] == 'commerce_discount_date') { + // Date-clear. + unset($element['#attributes']['class']); + array_shift($element['#wrapper_attributes']['class']); + // Reset the titles and descriptions of the start and end date fields. + $field_name = end($element['#parents']); + if ($field_name == 'value') { + $element['date']['#title'] = t('Start date'); + $element['date']['#description'] = t('Starts at 12:00:00 AM'); + } + else { + $element['date']['#title'] = t('End date'); + $element['date']['#description'] = t('Ends after 11:59:59 PM'); + } + $element['#title'] = ''; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.class.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.class.inc index c1a1351b..e27724e8 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.class.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.class.inc @@ -1,5 +1,10 @@ component_title = $this->getTranslation('component_title'); } + /** + * {@inheritdoc} + */ protected function defaultUri() { $entity_info = entity_get_info('commerce_discount'); $path = $entity_info['admin ui']['path'] . '/manage'; return array('path' => $path . '/' . $this->name); } + + /** + * Overridden, to introduce the method for old entity API versions (BC). + * + * @todo Remove once we bump the required entity API version. + */ + public function getTranslation($property, $langcode = NULL) { + if (module_exists('i18n_string')) { + return parent::getTranslation($property, $langcode); + } + return $this->$property; + } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.controller.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.controller.inc index f53a62db..ec2e6a91 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount.controller.inc @@ -11,13 +11,15 @@ class CommerceDiscountControllerExportable extends EntityAPIControllerExportable { /** - * Override export, to allow embedding the commerce discount offer - * inside the export of the comemrce discount. + * {@inheritdoc} + * + * Override export, to allow embedding the commerce discount offer inside the + * export of the commerce discount. */ public function export($entity, $prefix = '') { $vars = get_object_vars($entity); - // Add commerce_discout_offer full entity export. + // Add commerce_discount_offer full entity export. $wrapper = entity_metadata_wrapper('commerce_discount', $entity); $discount_offer = $wrapper->commerce_discount_offer->value(); if (!empty($discount_offer)) { @@ -38,7 +40,7 @@ class CommerceDiscountControllerExportable extends EntityAPIControllerExportable * @param string $export * An exported entity as serialized string. * - * @return + * @return object * An entity object not yet saved. */ public function import($export) { @@ -52,7 +54,7 @@ class CommerceDiscountControllerExportable extends EntityAPIControllerExportable } /** - * Overridden to handle embedded dicount_offer update. + * Overridden to handle embedded discount_offer update. */ public function save($entity, DatabaseTransaction $transaction = NULL) { // If overwriting an existing discount, delete the referenced @@ -75,7 +77,9 @@ class CommerceDiscountControllerExportable extends EntityAPIControllerExportable */ public function delete($ids, DatabaseTransaction $transaction = NULL) { parent::delete($ids, $transaction); - // Rebuild entities. + + // Rebuild rules config. entity_defaults_rebuild(array('rules_config')); } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount_offer.class.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount_offer.class.inc index c0c8a2f8..7da6c5a6 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount_offer.class.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/commerce_discount_offer.class.inc @@ -1,5 +1,10 @@ type = $form_state['commerce_dicount_offer_selection_' . $entity_form['#ief_id']]; + if (isset($form_state['commerce_discount_offer_selection_' . $entity_form['#ief_id']])) { + $offer->type = $form_state['commerce_discount_offer_selection_' . $entity_form['#ief_id']]; } // Get discount type. $discount_type = commerce_discount_type($form_state['commerce_discount']->type); - $options = array(); + $offer_types = array(); foreach (commerce_discount_offer_types() as $type => $info) { if (in_array($discount_type['entity type'], $info['entity types'])) { - $options[$type] = $info['label']; + $offer_types[$type] = $info['label']; } } // Ensures the discount type includes the passed offer type. If not, set the // offer type to the first found valid offer. - if (!in_array($offer->type, array_keys($options))) { - $offer->type = reset(array_keys($options)); + if (!in_array($offer->type, array_keys($offer_types))) { + $offer->type = reset(array_keys($offer_types)); + } + + $ief_id = $entity_form['#ief_id']; + field_attach_form('commerce_discount_offer', $offer, $entity_form, $form_state, LANGUAGE_NONE); + + $fields_wrapper = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array('commerce-offer-fields-wrapper'), + ), + '#weight' => 2, + '#parents' => array( + 'commerce_discount_fields', + 'commerce_discount_offer', + LANGUAGE_NONE, + 'form', + ), + ); + + // Transfer field elements onto fields container. + foreach (element_children($entity_form) as $key) { + $fields_wrapper[$key] = $entity_form[$key]; + unset($entity_form[$key]); } + // Set the fields container back to the form. + $entity_form['commerce_discount_offer_fields'] = $fields_wrapper; + // Add offer type options. $entity_form['type'] = array( - '#title' => t('Choose offer type'), '#type' => 'radios', - '#options' => $options, + '#title' => t('Choose offer type'), + '#title_display' => 'invisible', + '#options' => $offer_types, '#required' => FALSE, '#default_value' => $offer->type, '#ajax' => array( - 'callback' => 'inline_entity_form_get_element', - 'wrapper' => 'inline-entity-form-' . $entity_form['#ief_id'], + 'callback' => 'inline_entity_form_get_element', + 'wrapper' => 'inline-entity-form-' . $ief_id, ), + '#weight' => 1, ); - field_attach_form('commerce_discount_offer', $offer, $entity_form, $form_state, LANGUAGE_NONE); + // Add wrapper class for CSS to avoid form item collisions. + $entity_form['#attributes']['class'][] = 'commerce-offer-type-wrapper'; + $entity_form['#type'] = 'fieldset'; + $entity_form['#title'] = t('Choose offer type'); return $entity_form; } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views.inc index 5fd510fb..0d3273d0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views.inc @@ -16,26 +16,26 @@ class CommerceDiscountViewsController extends EntityDefaultViewsController { public function views_data() { $data = parent::views_data(); - // Expose the product status. - $data['commerce_discount']['status'] = array( - 'title' => t('Status'), - 'help' => t('Whether or not the discount is active.'), - 'field' => array( - 'handler' => 'views_handler_field_boolean', - 'click sortable' => TRUE, - 'output formats' => array( - 'active-disabled' => array(t('Active'), t('Disabled')), + // Expose the product status. + $data['commerce_discount']['status'] = array( + 'title' => t('Status'), + 'help' => t('Whether or not the discount is active.'), + 'field' => array( + 'handler' => 'views_handler_field_boolean', + 'click sortable' => TRUE, + 'output formats' => array( + 'active-disabled' => array(t('Active'), t('Disabled')), + ), ), - ), - 'filter' => array( - 'handler' => 'views_handler_filter_boolean_operator', - 'label' => t('Active'), - 'type' => 'yes-no', - ), - 'sort' => array( - 'handler' => 'views_handler_sort', - ), - ); + 'filter' => array( + 'handler' => 'views_handler_filter_boolean_operator', + 'label' => t('Active'), + 'type' => 'yes-no', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); // Show the operations using CTools dropbutton menu. $data['commerce_discount']['operations_dropbutton'] = array( @@ -52,3 +52,163 @@ class CommerceDiscountViewsController extends EntityDefaultViewsController { } } + +/** + * Implements hook_views_data_alter(). + */ +function commerce_discount_views_data_alter(&$data) { + if (isset($data['commerce_discount'])) { + + // Usage analytics field. + $data['commerce_discount']['commerce_discount_usage'] = array( + 'title' => t('Analytics'), + 'help' => t('Show discount usage and usage limit.'), + 'field' => array( + 'handler' => 'commerce_discount_handler_field_commerce_discount_analytics', + ), + 'real field' => 'discount_id', + ); + + // Usage relationship. + $data['commerce_discount']['discount_usage'] = array( + 'relationship' => array( + 'title' => t('Discount usage'), + 'label' => t('Discount usage'), + 'help' => t('Relate this discount to its usage statistics.'), + 'handler' => 'views_handler_relationship', + 'base' => 'commerce_discount_usage', + 'base field' => 'discount', + 'field' => 'name', + ), + ); + } + + // Order table: discount usage relationship. + if (isset($data['commerce_order'])) { + $data['commerce_order']['discount_usage'] = array( + 'relationship' => array( + 'title' => t('Discount usage'), + 'label' => t('Discount usage'), + 'help' => t('Relate this order to its discount usage statistics.'), + 'handler' => 'views_handler_relationship', + 'base' => 'commerce_discount_usage', + 'base field' => 'order_id', + 'field' => 'order_id', + ), + ); + } + + // User table: discount usage relationship. + $data['users']['discount_usage'] = array( + 'relationship' => array( + 'title' => t('Discount usage'), + 'label' => t('Discount usage'), + 'help' => t('Relate this user to its discount usage statistics.'), + 'handler' => 'views_handler_relationship', + 'base' => 'commerce_discount_usage', + 'base field' => 'mail', + 'field' => 'mail', + ), + ); +} + +/** + * Implements hook_views_data(). + */ +function commerce_discount_usage_views_data() { + $data = array(); + $data['commerce_discount_usage']['table']['group'] = t('Commerce discount usage'); + + // Base table. + $data['commerce_discount_usage']['table']['base'] = array( + 'field' => 'mail', + 'title' => t('Commerce Discount Usage'), + 'help' => t('Usage statistics about discounts.'), + ); + + // Discount name. + $data['commerce_discount_usage']['discount'] = array( + 'title' => t('Discount'), + 'help' => t('The unique human-readable identifier of the discount.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + 'relationship' => array( + 'title' => t('Discount'), + 'label' => t('Discount'), + 'help' => t('Relate a usage statistics record to its discount.'), + 'handler' => 'views_handler_relationship', + 'base' => 'commerce_discount', + 'base field' => 'name', + 'field' => 'discount', + ), + ); + + // Customer mail. + $data['commerce_discount_usage']['mail'] = array( + 'title' => t('User'), + 'help' => t('The email address of the customer who used the discount.'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + 'relationship' => array( + 'title' => t('Customer email'), + 'label' => t('Customer email'), + 'help' => t('Relate a usage statistics record to its user.'), + 'handler' => 'views_handler_relationship', + 'base' => 'users', + 'base field' => 'mail', + 'field' => 'mail', + ), + ); + + // Order ID. + $data['commerce_discount_usage']['order_id'] = array( + 'title' => t('Order ID'), + 'help' => t('The unique internal identifier of the order where the discount was used.'), + 'field' => array( + 'handler' => 'commerce_order_handler_field_order', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'commerce_order_handler_argument_order_order_id', + ), + 'relationship' => array( + 'title' => t('Order'), + 'label' => t('Order'), + 'help' => t('Relate a usage statistics record to its order.'), + 'handler' => 'views_handler_relationship', + 'base' => 'commerce_order', + 'base field' => 'order_id', + 'field' => 'order_id', + ), + ); + + return $data; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views_default.inc index cd77219f..09638c1b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views_default.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/commerce_discount.views_default.inc @@ -30,15 +30,29 @@ function commerce_discount_views_default_views() { $handler->display->display_options['query']['type'] = 'views_query'; $handler->display->display_options['query']['options']['query_comment'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['exposed_form']['options']['reset_button'] = TRUE; + $handler->display->display_options['exposed_form']['options']['autosubmit'] = TRUE; $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['pager']['options']['items_per_page'] = '25'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['pager']['options']['id'] = '0'; + $handler->display->display_options['pager']['options']['quantity'] = '9'; $handler->display->display_options['style_plugin'] = 'table'; + $handler->display->display_options['style_options']['grouping'] = array( + 0 => array( + 'field' => 'status', + 'rendered' => 1, + 'rendered_strip' => 0, + ), + ); $handler->display->display_options['style_options']['columns'] = array( 'label' => 'label', 'type_1' => 'type_1', 'type' => 'type', - 'status' => 'status', + 'commerce_discount_usage' => 'commerce_discount_usage', 'commerce_discount_date' => 'commerce_discount_date', 'commerce_discount_date_1' => 'commerce_discount_date_1', + 'status' => 'status', 'operations_dropbutton' => 'operations_dropbutton', ); $handler->display->display_options['style_options']['default'] = '-1'; @@ -69,23 +83,23 @@ function commerce_discount_views_default_views() { 'separator' => '', 'empty_column' => 0, ), - 'status' => array( + 'commerce_discount_date' => array( 'sortable' => 1, - 'default_sort_order' => 'desc', + 'default_sort_order' => 'asc', 'align' => '', 'separator' => '', 'empty_column' => 0, ), - 'commerce_discount_date' => array( + 'commerce_discount_date_1' => array( 'sortable' => 1, 'default_sort_order' => 'asc', 'align' => '', 'separator' => '', 'empty_column' => 0, ), - 'commerce_discount_date_1' => array( + 'status' => array( 'sortable' => 1, - 'default_sort_order' => 'asc', + 'default_sort_order' => 'desc', 'align' => '', 'separator' => '', 'empty_column' => 0, @@ -97,56 +111,88 @@ function commerce_discount_views_default_views() { ), ); $handler->display->display_options['style_options']['empty_table'] = TRUE; - /* No results behavior: Global: Text area */ $handler->display->display_options['empty']['area']['id'] = 'area'; $handler->display->display_options['empty']['area']['table'] = 'views'; $handler->display->display_options['empty']['area']['field'] = 'area'; $handler->display->display_options['empty']['area']['content'] = 'No discounts found.'; - /* Relationship: Entity Reference: Referenced Entity */ $handler->display->display_options['relationships']['commerce_discount_offer_target_id']['id'] = 'commerce_discount_offer_target_id'; $handler->display->display_options['relationships']['commerce_discount_offer_target_id']['table'] = 'field_data_commerce_discount_offer'; $handler->display->display_options['relationships']['commerce_discount_offer_target_id']['field'] = 'commerce_discount_offer_target_id'; $handler->display->display_options['relationships']['commerce_discount_offer_target_id']['required'] = TRUE; - /* Field: Commerce Discount: Label */ + /* Field: Commerce Discount: Admin title */ $handler->display->display_options['fields']['label']['id'] = 'label'; $handler->display->display_options['fields']['label']['table'] = 'commerce_discount'; $handler->display->display_options['fields']['label']['field'] = 'label'; - $handler->display->display_options['fields']['label']['label'] = 'Name'; /* Field: Commerce Discount Offer: Type */ $handler->display->display_options['fields']['type_1']['id'] = 'type_1'; $handler->display->display_options['fields']['type_1']['table'] = 'commerce_discount_offer'; $handler->display->display_options['fields']['type_1']['field'] = 'type'; $handler->display->display_options['fields']['type_1']['relationship'] = 'commerce_discount_offer_target_id'; - $handler->display->display_options['fields']['type_1']['label'] = 'Offer'; - /* Field: Commerce Discount: Type */ - $handler->display->display_options['fields']['type']['id'] = 'type'; - $handler->display->display_options['fields']['type']['table'] = 'commerce_discount'; - $handler->display->display_options['fields']['type']['field'] = 'type'; - $handler->display->display_options['fields']['type']['alter']['alter_text'] = TRUE; - $handler->display->display_options['fields']['type']['alter']['text'] = '[type]'; + $handler->display->display_options['fields']['type_1']['label'] = 'Offer type'; + /* Field: Commerce Discount: Discount dates */ + $handler->display->display_options['fields']['commerce_discount_date']['id'] = 'commerce_discount_date'; + $handler->display->display_options['fields']['commerce_discount_date']['table'] = 'field_data_commerce_discount_date'; + $handler->display->display_options['fields']['commerce_discount_date']['field'] = 'commerce_discount_date'; + $handler->display->display_options['fields']['commerce_discount_date']['label'] = 'Valid dates'; + $handler->display->display_options['fields']['commerce_discount_date']['empty'] = 'Any'; + $handler->display->display_options['fields']['commerce_discount_date']['settings'] = array( + 'format_type' => 'short', + 'custom_date_format' => '', + 'fromto' => 'both', + 'multiple_number' => '', + 'multiple_from' => '', + 'multiple_to' => '', + 'show_remaining_days' => 0, + ); + /* Field: Commerce Discount: Analytics */ + $handler->display->display_options['fields']['commerce_discount_usage']['id'] = 'commerce_discount_usage'; + $handler->display->display_options['fields']['commerce_discount_usage']['table'] = 'commerce_discount'; + $handler->display->display_options['fields']['commerce_discount_usage']['field'] = 'commerce_discount_usage'; /* Field: Commerce Discount: Status */ $handler->display->display_options['fields']['status']['id'] = 'status'; $handler->display->display_options['fields']['status']['table'] = 'commerce_discount'; $handler->display->display_options['fields']['status']['field'] = 'status'; + $handler->display->display_options['fields']['status']['exclude'] = TRUE; $handler->display->display_options['fields']['status']['type'] = 'active-disabled'; $handler->display->display_options['fields']['status']['not'] = 0; + /* Field: Commerce Discount: Sort order */ + $handler->display->display_options['fields']['sort_order']['id'] = 'sort_order'; + $handler->display->display_options['fields']['sort_order']['table'] = 'commerce_discount'; + $handler->display->display_options['fields']['sort_order']['field'] = 'sort_order'; /* Field: Commerce Discount: Operations */ $handler->display->display_options['fields']['operations_dropbutton']['id'] = 'operations_dropbutton'; $handler->display->display_options['fields']['operations_dropbutton']['table'] = 'commerce_discount'; $handler->display->display_options['fields']['operations_dropbutton']['field'] = 'operations_dropbutton'; - /* Filter criterion: Commerce Discount: Label */ - $handler->display->display_options['filters']['label']['id'] = 'label'; - $handler->display->display_options['filters']['label']['table'] = 'commerce_discount'; - $handler->display->display_options['filters']['label']['field'] = 'label'; - $handler->display->display_options['filters']['label']['operator'] = 'contains'; - $handler->display->display_options['filters']['label']['group'] = 1; - $handler->display->display_options['filters']['label']['exposed'] = TRUE; - $handler->display->display_options['filters']['label']['expose']['operator_id'] = 'label_op'; - $handler->display->display_options['filters']['label']['expose']['label'] = 'Name'; - $handler->display->display_options['filters']['label']['expose']['operator'] = 'label_op'; - $handler->display->display_options['filters']['label']['expose']['identifier'] = 'label'; + /* Sort criterion: Commerce Discount: Status */ + $handler->display->display_options['sorts']['status']['id'] = 'status'; + $handler->display->display_options['sorts']['status']['table'] = 'commerce_discount'; + $handler->display->display_options['sorts']['status']['field'] = 'status'; + $handler->display->display_options['sorts']['status']['order'] = 'DESC'; + /* Sort criterion: Commerce Discount: Sort order */ + $handler->display->display_options['sorts']['sort_order']['id'] = 'sort_order'; + $handler->display->display_options['sorts']['sort_order']['table'] = 'commerce_discount'; + $handler->display->display_options['sorts']['sort_order']['field'] = 'sort_order'; + /* Filter criterion: Commerce Discount: Type */ + $handler->display->display_options['filters']['type']['id'] = 'type'; + $handler->display->display_options['filters']['type']['table'] = 'commerce_discount'; + $handler->display->display_options['filters']['type']['field'] = 'type'; + $handler->display->display_options['filters']['type']['value'] = array( + 'product_discount' => 'product_discount', + ); + $handler->display->display_options['filters']['type']['group'] = 1; + $handler->display->display_options['filters']['type']['exposed'] = TRUE; + $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op'; + $handler->display->display_options['filters']['type']['expose']['label'] = 'Discount type'; + $handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op'; + $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type'; + $handler->display->display_options['filters']['type']['expose']['required'] = TRUE; + $handler->display->display_options['filters']['type']['expose']['remember_roles'] = array( + 2 => '2', + 1 => 0, + 3 => 0, + ); /* Filter criterion: Commerce Discount Offer: Type */ $handler->display->display_options['filters']['type_1']['id'] = 'type_1'; $handler->display->display_options['filters']['type_1']['table'] = 'commerce_discount_offer'; @@ -155,54 +201,13 @@ function commerce_discount_views_default_views() { $handler->display->display_options['filters']['type_1']['group'] = 1; $handler->display->display_options['filters']['type_1']['exposed'] = TRUE; $handler->display->display_options['filters']['type_1']['expose']['operator_id'] = 'type_1_op'; - $handler->display->display_options['filters']['type_1']['expose']['label'] = 'Offer'; + $handler->display->display_options['filters']['type_1']['expose']['label'] = 'Offer type'; $handler->display->display_options['filters']['type_1']['expose']['operator'] = 'type_1_op'; $handler->display->display_options['filters']['type_1']['expose']['identifier'] = 'type_1'; - /* Filter criterion: Commerce Discount: Type */ - $handler->display->display_options['filters']['type']['id'] = 'type'; - $handler->display->display_options['filters']['type']['table'] = 'commerce_discount'; - $handler->display->display_options['filters']['type']['field'] = 'type'; - $handler->display->display_options['filters']['type']['group'] = 1; - $handler->display->display_options['filters']['type']['exposed'] = TRUE; - $handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op'; - $handler->display->display_options['filters']['type']['expose']['label'] = 'Type'; - $handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op'; - $handler->display->display_options['filters']['type']['expose']['identifier'] = 'type'; - /* Filter criterion: Commerce Discount: Status */ - $handler->display->display_options['filters']['status']['id'] = 'status'; - $handler->display->display_options['filters']['status']['table'] = 'commerce_discount'; - $handler->display->display_options['filters']['status']['field'] = 'status'; - $handler->display->display_options['filters']['status']['value'] = 'All'; - $handler->display->display_options['filters']['status']['exposed'] = TRUE; - $handler->display->display_options['filters']['status']['expose']['operator_id'] = ''; - $handler->display->display_options['filters']['status']['expose']['label'] = 'Active'; - $handler->display->display_options['filters']['status']['expose']['operator'] = 'status_op'; - $handler->display->display_options['filters']['status']['expose']['identifier'] = 'status'; - $translatables['commerce_discount_overview'] = array( - t('Master'), - t('more'), - t('Apply'), - t('Reset'), - t('Sort by'), - t('Asc'), - t('Desc'), - t('Items per page'), - t('- All -'), - t('Offset'), - t('« first'), - t('‹ previous'), - t('next ›'), - t('last »'), - t('No discounts found.'), - t('Commerce Discount Offer entity referenced from commerce_discount_offer'), - t('Name'), - t('Offer'), - t('Type'), - t('[type]'), - t('Status'), - t('Operations'), - t('Name'), - t('Active'), + $handler->display->display_options['filters']['type_1']['expose']['remember_roles'] = array( + 2 => '2', + 1 => 0, + 3 => 0, ); $views['commerce_discount_overview'] = $view; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/handlers/commerce_discount_usage_handler_field_commerce_discount_analytics.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_commerce_discount_analytics.inc similarity index 71% rename from profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/handlers/commerce_discount_usage_handler_field_commerce_discount_analytics.inc rename to profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_commerce_discount_analytics.inc index 48a5adb6..7521ec71 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/handlers/commerce_discount_usage_handler_field_commerce_discount_analytics.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_commerce_discount_analytics.inc @@ -1,11 +1,19 @@ additional_fields['discount_id'] = 'discount_id'; @@ -13,7 +21,10 @@ class commerce_discount_usage_handler_field_commerce_discount_analytics extends $this->real_field = 'discount_id'; } - function render($values) { + /** + * {@inheritdoc} + */ + public function render($values) { // Wrap the discount entity found. $wrapper = entity_metadata_wrapper('commerce_discount', entity_load_single('commerce_discount', $this->get_value($values, 'discount_id'))); @@ -23,9 +34,10 @@ class commerce_discount_usage_handler_field_commerce_discount_analytics extends 'items' => array( $wrapper->discount_usage_limit->value() ? t('@max available', array('@max' => $wrapper->discount_usage_limit->value())) : t('Unlimited'), $wrapper->discount_usage_per_person->value() ? t('Limit @max per customer', array('@max' => $wrapper->discount_usage_per_person->value())) : t('Unlimited usage per customer'), - t('Used @used times', array('@used' => commerce_discount_usage_get_usage($wrapper->name->value()))) - ) + t('Used @used times', array('@used' => commerce_discount_usage_get_usage($wrapper->name->value()))), + ), ) ); } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_operations_dropbutton.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_operations_dropbutton.inc index 8d290692..0009c437 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_operations_dropbutton.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/includes/views/handlers/commerce_discount_handler_field_operations_dropbutton.inc @@ -1,11 +1,19 @@ additional_fields['discount_id'] = 'discount_id'; @@ -13,7 +21,10 @@ class commerce_discount_handler_field_operations_dropbutton extends views_handle $this->real_field = 'discount_id'; } - function render($values) { + /** + * {@inheritdoc} + */ + public function render($values) { // Load the line item and return its title. $discount_id = $this->get_value($values, 'discount_id'); $commerce_discount = entity_load_single('commerce_discount', $discount_id); @@ -21,7 +32,7 @@ class commerce_discount_handler_field_operations_dropbutton extends views_handle $entity_info = entity_get_info('commerce_discount'); $path = $entity_info['admin ui']['path'] . '/manage'; - // Preapre the links. + // Prepare the links. $links = array(); $links[] = array( 'href' => $path . '/' . $commerce_discount->name, @@ -53,6 +64,14 @@ class commerce_discount_handler_field_operations_dropbutton extends views_handle 'title' => t('export'), ); + if (module_exists('i18n_string')) { + $links[] = array( + 'href' => $path . '/' . $commerce_discount->name . '/translate', + 'title' => t('translate'), + ); + } + return theme('links__ctools_dropbutton', array('links' => $links)); } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/js/commerce_discount.js b/profiles/commerce_kickstart/modules/contrib/commerce_discount/js/commerce_discount.js index a9e069df..3a083150 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/js/commerce_discount.js +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/js/commerce_discount.js @@ -1,12 +1,108 @@ -(function ($) { - -Drupal.behaviors.commerceDiscount = {}; -Drupal.behaviors.commerceDiscount.attach = function(context) { - $('input:radio').change(function() { - $('input:radio:not(:checked)').closest('div').removeClass('selected'); - $(this).closest('div').addClass('selected'); - }) - .filter(':checked').closest('div').addClass('selected'); -}; - -})(jQuery); +/** + * @file + * Commerce discount admin helper. + */ + +(function ($, Drupal) { + Drupal.behaviors.commerceDiscount = {}; + Drupal.behaviors.commerceDiscount.attach = function(context) { + $('input:radio').change(function() { + $('input:radio:not(:checked)').closest('div').removeClass('selected'); + $(this).closest('div').addClass('selected'); + }).filter(':checked').closest('div').addClass('selected'); + + // Provide the vertical tab summaries. + $('fieldset#edit-commerce-discount-fields-additional-settings-discount-options', context).drupalSetSummary(function(context) { + var values = []; + $("input[name^='status']:checked", context).parent().each(function() { + values.push(Drupal.checkPlain($(this).text().trim())); + }); + values.push(Drupal.t("Sort order: @Sort order", {'@Sort order': $("select[name^='sort_order'] option:selected").val()})); + return values.join(', '); + }); + $('fieldset#edit-commerce-discount-fields-additional-settings-commerce-discount-compatibility', context).drupalSetSummary(function(context) { + var values = [], + value; + + value = $('input[name="commerce_discount_fields[commerce_compatibility_strategy][und]"]:checked', context).val(); + if (value == 'any') { + return Drupal.t('Compatible with all'); + } + else if (value == 'only' || value == 'except') { + selected = $('input[name^="commerce_discount_fields[commerce_compatibility_selection]"][name$="[target_id]"]', context).each(function (context) { + var ruleName = Drupal.checkPlain($(this).val().replace(/ \(\d+\)/, '').trim()); + if (ruleName != '') { + values.push(ruleName); + } + }); + if (value == 'only') { + if (values.length == 0) { + return Drupal.t('Incompatible with all'); + } + else if (values.length == 1) { + return Drupal.t('Only with @selected', {'@selected': values.shift()}); + } + else { + return Drupal.t('Only with @selected and @remaining more...', {'@selected': values.shift(), '@remaining': values.length}); + } + } + else { + if (values.length == 0) { + return Drupal.t('Compatible with all'); + } + else if (values.length == 1) { + return Drupal.t('All except @selected', {'@selected': values.shift()}); + } + else { + return Drupal.t('All except @selected and @remaining more...', {'@selected': values.shift(), '@remaining': values.length}); + } + } + } + else if (value == 'none') { + return Drupal.t('Incompatible with all'); + } + }); + $('fieldset#edit-commerce-discount-fields-additional-settings-discount-date', context).drupalSetSummary(function(context) { + var fromDate, + toDate, + fromDateTS, + toDateTS; + + fromDate = $('input[name="commerce_discount_fields[commerce_discount_date][und][0][value][date]"]').val(); + fromDateTS = fromDate ? new Date(fromDate) : 0; + toDate = $('input[name="commerce_discount_fields[commerce_discount_date][und][0][value2][date]"]').val(); + toDateTS = toDate ? new Date(toDate) : 0; + + options = { + '!from': fromDateTS < Date.now() ? Drupal.t('Started') : Drupal.t('Starts'), + '!to': toDateTS < Date.now() ? Drupal.t('Ended') : Drupal.t('Ends'), + '@fromDate': fromDate, + '@toDate': toDate + }; + + if (fromDate && toDate) { + options['!to'] = options['!to'].toLowerCase(); + return Drupal.t('!from @fromDate and !to @toDate', options); + } + else if (fromDate && !toDate) { + return Drupal.t('!from @fromDate', options); + } + else if (!fromDate && toDate) { + return Drupal.t('!to @toDate', options); + } + else { + return Drupal.t('Always active'); + } + }); + $('fieldset#edit-commerce-discount-fields-additional-settings-commerce-discount-usage', context).drupalSetSummary(function(context) { + var usagePerPerson = $('input[name="commerce_discount_fields[discount_usage_per_person][und][0][value]"]').val(), + overallUsage = $('input[name="commerce_discount_fields[discount_usage_limit][und][0][value]"]').val(), + values = []; + + values.push(Drupal.t('!usagePerPerson per person', {'!usagePerPerson': usagePerPerson > 0 ? Drupal.checkPlain(usagePerPerson) : '∞'})); + values.push(Drupal.t('!overallUsage total', {'!overallUsage': overallUsage > 0 ? Drupal.checkPlain(overallUsage) : '∞'})); + + return values.join(', '); + }); + }; +}(jQuery, Drupal)); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.info b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.info deleted file mode 100644 index fc121f8a..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.info +++ /dev/null @@ -1,19 +0,0 @@ -name = Commerce Discount Date -description = Provides date fields for the Commerce discount entity. -core = 7.x -package = Commerce (contrib) - -dependencies[] = commerce_discount -dependencies[] = date -dependencies[] = date_popup - -; Simple tests -files[] = commerce_discount_date.test - - -; Information added by drush on 2015-08-20 -version = "7.x-1.0-alpha5+0-dev" -core = "7.x" -project = "commerce_discount" -datestamp = "1440110608" - diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.install b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.install deleted file mode 100644 index e4b7e89b..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.install +++ /dev/null @@ -1,43 +0,0 @@ - array('commerce_discount'), - 'settings' => array( - 'granularity' => array( - 'month' => 'month', - 'day' => 'day', - 'year' => 'year', - ), - 'tz_handling' => 'none', - 'timezone_db' => '', - 'todate' => 'optional', - 'handler' => 'base', - 'target_type' => 'commerce_discount', - 'handler_settings' => array( - 'target_bundles' => array(), - ), - ), - 'field_name' => 'commerce_discount_date', - 'type' => 'datestamp', - 'locked' => TRUE, - ); - field_create_field($field); -} - -/** - * Implements hook_uninstall(). - */ -function commerce_discount_date_uninstall() { - field_delete_field('commerce_discount_date'); -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.module b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.module deleted file mode 100644 index 7c3ae6fa..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.module +++ /dev/null @@ -1,99 +0,0 @@ - $value) { - if (!field_info_instance('commerce_discount', 'commerce_discount_date', $type)) { - $instance = array( - 'field_name' => 'commerce_discount_date', - 'entity_type' => 'commerce_discount', - 'bundle' => $type, - 'label' => t('Discount dates'), - 'widget' => array( - 'module' => 'date', - 'type' => 'date_popup', - 'weight' => -11, - ), - 'settings' => array( - 'default_value' => 'blank', - 'default_value2' => 'blank', - ), - ); - field_create_instance($instance); - } - } -} - -/** - * Implements hook_views_api(). - */ -function commerce_discount_date_views_api($module, $api) { - if ($module == 'views') { - return array( - 'version' => 2, - 'path' => drupal_get_path('module', 'commerce_discount_date') . '/includes/views', - ); - } -} - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function commerce_discount_date_form_commerce_discount_form_alter(&$form, $form_state) { - $form['commerce_discount_fields']['commerce_discount_date']['#after_build'][] = 'commerce_discount_date_after_build'; -} - -/** - * After-build callback for altering the date field. - * - * @param $element - * The date field. - * - * @return - * The altered date field. - */ -function commerce_discount_date_after_build($element, $form_state) { - // Reset the titles. - $element[LANGUAGE_NONE][0]['value']['#title'] = t('Start date'); - $element[LANGUAGE_NONE][0]['value2']['#title'] = t('End date'); - $element[LANGUAGE_NONE][0]['value']['date']['#title'] = ''; - $element[LANGUAGE_NONE][0]['value']['time']['#title'] = ''; - $element[LANGUAGE_NONE][0]['value2']['date']['#title'] = ''; - $element[LANGUAGE_NONE][0]['value2']['time']['#title'] = ''; - $element[LANGUAGE_NONE][0]['#fieldset_description'] = ''; - - // Ignore the "Show end date" checkbox. - unset($element[LANGUAGE_NONE][0]['value2']['#states']); - - // Check the "show end date" checkbox. - $element[LANGUAGE_NONE][0]['show_todate']['#checked'] = TRUE; - - return $element; -} - -/** - * Implements hook_commerce_discount_rule_build(). - */ -function commerce_discount_date_commerce_discount_rule_build($rule, $discount) { - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - if (!$wrapper->commerce_discount_date->value()) { - // No need to add a condition. - return; - } - - // Add condition to check usage didn't reach max uses. - $rule->condition('commerce_discount_date_condition', array( - 'commerce_discount' => $discount->name, - )); -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.rules.inc deleted file mode 100644 index bed6296b..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.rules.inc +++ /dev/null @@ -1,38 +0,0 @@ - t('Check discount dates'), - 'group' => t('Commerce Discount'), - 'parameter' => array( - 'commerce_discount' => array( - 'label' => t('Commerce Discount'), - 'type' => 'token', - 'options list' => 'commerce_discount_entity_list', - ), - ), - 'base' => 'commerce_discount_date_condition', - ); - - return $items; -} - -/** - * Rules condition: Check discount can be applied. - */ -function commerce_discount_date_condition($discount_name) { - $wrapper = entity_metadata_wrapper('commerce_discount', $discount_name); - $time = time(); - return $time >= $wrapper->commerce_discount_date->value->value() && $time <= $wrapper->commerce_discount_date->value2->value(); -} - diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.test deleted file mode 100644 index 00e5d9fc..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/commerce_discount_date.test +++ /dev/null @@ -1,254 +0,0 @@ - 'Discounts date', - 'description' => 'Test discounts date UI and functionality', - 'group' => 'Commerce discounts', - ); - } - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - } - - /** - * Test date specific elements in the add discount UI. - */ - public function testDiscountDateUIAddDiscount() { - // Login with store admin. - $this->drupalLogin($this->store_admin); - - // Access to the admin discount creation page. - $this->drupalGet('admin/commerce/store/discounts/add'); - - // Check the integrity of the add form. - $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value][date]', NULL, t('Start date field is present')); - $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value2][date]', NULL, t('End date field is present')); - - // Create a discount valid from yesterday until tomorrow. - $date_format = 'm/d/Y'; - $start_time = time() - 86400; - $start_date = date($date_format, $start_time); - $end_time = time() + 86400; - $end_date = date($date_format, $end_time); - - $values = array( - 'label' => 'Order discount - fixed', - 'name' => 'order_discount_fixed', - 'component_title' => 'Order discount', - 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, - 'commerce_discount_fields[commerce_discount_date][und][0][value][date]' => $start_date, - 'commerce_discount_fields[commerce_discount_date][und][0][value2][date]' => $end_date, - ); - $this->drupalPost(NULL, $values, t('Save discount')); - - // Load the discount and wrap it. - $discount = entity_load_single('commerce_discount', 1); - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - // Check the usage fields of the stored discount. - $this->assertTrue(date($date_format, $wrapper->commerce_discount_date->value->value()) == $start_date, t('Start date is stored correctly.')); - $this->assertTrue(date($date_format, $wrapper->commerce_discount_date->value2->value()) == $end_date, t('End date is stored correctly.')); - - // // Check the discounts listing - // $this->assertText($start_date, t('Start date is shown')); - // $this->assertText($end_date, t('End date is shown')); - } - - /** - * Test the Edit discount UI. - */ - public function testDiscountDateUIEditDiscount() { - // Create a discount valid from yesterday until tomorrow. - $start_time = time() - 86400; - $end_time = time() + 86400; - $discount = $this->createDateDiscount('order_discount', 'fixed_amount', 300, $start_time, $end_time); - - // Login with store admin. - $this->drupalLogin($this->store_admin); - - // Access to the admin discount edit page. - $this->drupalGet('admin/commerce/store/discounts/manage/' . $discount->name); - - // Check the integrity of the add form. - $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value][date]', NULL, t('Start date field is present')); - $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value2][date]', NULL, t('End date field is present')); - - // Try to save the product and check validation messages. - $this->drupalPost(NULL, array(), t('Save discount')); - - // Change the discount date, to be valid from tomorrow. - $date_format = 'm/d/Y'; - $start_time = time() + 86400; - $start_date = date($date_format, $start_time); - $end_time = time() + 2 * 86400; - $end_date = date($date_format, $end_time); - - $values = array( - 'label' => 'Order discount - fixed', - 'name' => 'order_discount_fixed', - 'component_title' => 'Order discount', - 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, - 'commerce_discount_fields[commerce_discount_date][und][0][value][date]' => $start_date, - 'commerce_discount_fields[commerce_discount_date][und][0][value2][date]' => $end_date, - ); - - // $this->drupalPost(NULL, $values, t('Save discount')); - - // Load the discount and wrap it. - $discount = entity_load_single('commerce_discount', 1); - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - // // Check the usage fields of the stored discount. - // $this->assertTrue(date($date_format, $wrapper->commerce_discount_date->value->value()) == $start_date, t('Start date is stored correctly.')); - // $this->assertTrue(date($date_format, $wrapper->commerce_discount_date->value2->value()) == $end_date, t('End date is stored correctly.')); - - // // Check the discounts listing - // $this->assertText($start_date, t('Start date is shown')); - // $this->assertText($end_date, t('End date is shown')); - } - - /** - * Test order discount in timespan. - */ - public function testDiscountDateOrderDiscountInTime() { - // Create a discount valid from yesterday until tomorrow. - $start_time = time() - 86400; - $end_time = time() + 86400; - - // Testing fixed discount. - $discount = $this->createDateDiscount('order_discount', 'fixed_amount', 300, $start_time, $end_time); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 700, t('Order discount is deducted when in time frame.')); - } - - /** - * Test order discount out of timespan. - */ - public function testDiscountDateOrderDiscountOutOfTime() { - - // Create a discount valid from tomorrow. - $start_time = time() + 86400; - $end_time = time() + 2 * 86400; - - // Testing fixed discount. - // Create a fixed order discount of $3 limited to one use. - $discount = $this->createDateDiscount('order_discount', 'fixed_amount', 300, $start_time, $end_time); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 1000, t('Order discount is ignored when out of time frame.')); - } - - /** - * Test product discount in timespan. - */ - public function testDiscountDateProductDiscountInTime() { - // Create a discount valid from yesterday until tomorrow. - $start_time = time() - 86400; - $end_time = time() + 86400; - - $discount = $this->createDateDiscount('product_discount', 'fixed_amount', 300, $start_time, $end_time); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - $this->assertTrue($price_data['components'][1]['price']['amount'] == -300, t('Product discount is applied when discount is in time frame.')); - } - - /** - * Test product discount out of timespan. - */ - public function testDiscountDateProductDiscountOutOfTime() { - // Create a discount valid from tomorrow. - $start_time = time() + 86400; - $end_time = time() + 2 * 86400; - - $discount = $this->createDateDiscount('product_discount', 'fixed_amount', 300, $start_time, $end_time); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - $this->assertTrue(count($price_data['components']) == 1, t('Product discount is ignored when discount is out of time frame.')); - } - - /** - * Create a date discount. - * - * @param string $discount_type - * The discount type; Either 'order_discount' or 'product_discount'. - * @param string $offer_type - * The discount offer type; Either 'fixed_amount' or 'percentage'. - * @param integer $amount - * The discount offer amount. - * @param $start_time - * Discount valid from. - * @param $end_time - * Discount valid until. - * - * @return object - * The newly created commerce_discount entity. - */ - protected function createDateDiscount($discount_type, $offer_type, $amount, $start_time, $end_time) { - // Use the base class to create a discount. - $discount = parent::createDiscount($discount_type, $offer_type, $amount); - - // Populate the date fields. - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - $date_data = array( - 'value' => $start_time, - 'value2' => $end_time, - 'date_type' => 'datestamp', - ); - - $wrapper->commerce_discount_date->set($date_data); - $wrapper->save(); - - return $wrapper->value(); - } -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/includes/views/commerce_discount_date.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/includes/views/commerce_discount_date.views_default.inc deleted file mode 100644 index 9c4f770e..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_date/includes/views/commerce_discount_date.views_default.inc +++ /dev/null @@ -1,111 +0,0 @@ -display['default']->handler; - - // In order to insert the date fields in the middle of the fields array, - // rebuild it while inserting the date fields after the 'rendered_entity' - // field. - if (empty($handler->display->display_options['fields']) || !is_array($handler->display->display_options['fields'])) { - return; - } - foreach ($handler->display->display_options['fields'] as $field_id => $field) { - $fields[$field_id] = $field; - - if ($field_id == 'rendered_entity') { - $fields += $date_fields; - } - } - - // Overwrite the original fields array. - $handler->display->display_options['fields'] = $fields; -} - -/** - * Helper function to get the Views import. - */ -function _commerce_discount_date_views_get_view_delta() { - $handler = new stdClass(); - - /* Field: Commerce Discount: Discount dates */ - $handler->display->display_options['fields']['commerce_discount_date']['id'] = 'commerce_discount_date'; - $handler->display->display_options['fields']['commerce_discount_date']['table'] = 'field_data_commerce_discount_date'; - $handler->display->display_options['fields']['commerce_discount_date']['field'] = 'commerce_discount_date'; - $handler->display->display_options['fields']['commerce_discount_date']['label'] = 'Start date'; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['alter_text'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['make_link'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['absolute'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['external'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['replace_spaces'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['trim_whitespace'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['nl2br'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['word_boundary'] = 1; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['ellipsis'] = 1; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['more_link'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['strip_tags'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['trim'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['alter']['html'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['element_label_colon'] = 1; - $handler->display->display_options['fields']['commerce_discount_date']['element_default_classes'] = 1; - $handler->display->display_options['fields']['commerce_discount_date']['hide_empty'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['empty_zero'] = 0; - $handler->display->display_options['fields']['commerce_discount_date']['hide_alter_empty'] = 1; - $handler->display->display_options['fields']['commerce_discount_date']['settings'] = array( - 'format_type' => 'short', - 'fromto' => 'value', - 'multiple_number' => '', - 'multiple_from' => '', - 'multiple_to' => '', - ); - $handler->display->display_options['fields']['commerce_discount_date']['field_api_classes'] = 0; - /* Field: Commerce Discount: Discount dates */ - $handler->display->display_options['fields']['commerce_discount_date_1']['id'] = 'commerce_discount_date_1'; - $handler->display->display_options['fields']['commerce_discount_date_1']['table'] = 'field_data_commerce_discount_date'; - $handler->display->display_options['fields']['commerce_discount_date_1']['field'] = 'commerce_discount_date'; - $handler->display->display_options['fields']['commerce_discount_date_1']['label'] = 'End date'; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['alter_text'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['make_link'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['absolute'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['external'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['replace_spaces'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['trim_whitespace'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['nl2br'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['word_boundary'] = 1; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['ellipsis'] = 1; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['more_link'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['strip_tags'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['trim'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['alter']['html'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['element_label_colon'] = 1; - $handler->display->display_options['fields']['commerce_discount_date_1']['element_default_classes'] = 1; - $handler->display->display_options['fields']['commerce_discount_date_1']['hide_empty'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['empty_zero'] = 0; - $handler->display->display_options['fields']['commerce_discount_date_1']['hide_alter_empty'] = 1; - $handler->display->display_options['fields']['commerce_discount_date_1']['settings'] = array( - 'format_type' => 'short', - 'fromto' => 'value2', - 'multiple_number' => '', - 'multiple_from' => '', - 'multiple_to' => '', - ); - $handler->display->display_options['fields']['commerce_discount_date_1']['field_api_classes'] = 0; - - return $handler->display->display_options['fields']; -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.info b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.info deleted file mode 100644 index 5f886b3d..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.info +++ /dev/null @@ -1,21 +0,0 @@ -name = Commerce Discount Usage -description = Provide usage tracking and limits for Commerce Discounts -package = Commerce (contrib) -core = 7.x - -; Dependencies -dependencies[] = commerce_discount - -; Views handlers -files[] = includes/views/handlers/commerce_discount_usage_handler_field_commerce_discount_analytics.inc - -; Simple tests -files[] = commerce_discount_usage.test - - -; Information added by drush on 2015-08-20 -version = "7.x-1.0-alpha5+0-dev" -core = "7.x" -project = "commerce_discount" -datestamp = "1440110608" - diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.install b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.install deleted file mode 100644 index f7b3b07f..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.install +++ /dev/null @@ -1,158 +0,0 @@ - array('commerce_discount'), - 'field_name' => 'discount_usage_per_person', - 'type' => 'number_integer', - 'locked' => TRUE, - ); - field_create_field($field); - } - foreach (commerce_discount_types() as $type => $info) { - if (empty($instances['commerce_discount'][$type]['discount_usage_per_person'])) { - $instance = array( - 'field_name' => 'discount_usage_per_person', - 'entity_type' => 'commerce_discount', - 'bundle' => $type, - 'label' => t('Maximum usage per customer'), - 'description' => t('Enter the maximum number of times a specific person (as identified by email) may use this discount. Leave blank for unlimited.'), - 'required' => FALSE, - 'widget' => array( - 'weight' => 100, - ), - 'settings' => array( - 'min' => 0 - ) - ); - field_create_instance($instance); - } - } - - // Discount usage. - if (empty($fields['discount_usage_limit'])) { - // Create entity reference field. - $field = array( - 'entity_types' => array('commerce_discount'), - 'field_name' => 'discount_usage_limit', - 'type' => 'number_integer', - 'locked' => TRUE, - ); - field_create_field($field); - } - foreach (commerce_discount_types() as $type => $info) { - if (empty($instances['commerce_discount'][$type]['discount_usage_limit'])) { - $instance = array( - 'field_name' => 'discount_usage_limit', - 'entity_type' => 'commerce_discount', - 'bundle' => $type, - 'label' => t('Maximum overall usage'), - 'description' => t('Enter the maximum number of times this discount may be used on the site, by anyone. Leave blank for unlimited.'), - 'required' => FALSE, - 'widget' => array( - 'weight' => 100, - ), - 'settings' => array( - 'min' => 0 - ) - ); - field_create_instance($instance); - } - } -} - -/** - * Implements hook_schema(). - */ -function commerce_discount_usage_schema() { - $schema['commerce_discount_usage'] = array( - 'fields' => array( - 'discount' => array( - 'type' => 'varchar', - 'length' => 64, - 'not null' => TRUE, - 'description' => 'Discount name.', - ), - 'mail' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The email of the customer that used this discount.', - ), - 'order_id' => array( - 'type' => 'int', - 'not null' => TRUE, - 'description' => 'The order id that this discount was used with.', - ), - ), - 'unique keys' => array( - 'discount_mail_order_id' => array('discount', 'mail', 'order_id'), - ), - 'foreign keys' => array( - 'discount' => array( - 'table' => 'commerce_discount', - 'column' => array('discount' => 'name') - ), - 'order_id' => array( - 'table' => 'commerce_order', - 'column' => array('order_id' => 'order_id') - ), - 'mail' => array( - 'table' => 'users', - 'column' => array('mail' => 'mail') - ) - ), - 'indexes' => array( - 'mail' => array('mail'), - 'discount' => array('discount'), - 'order_id' => array('order_id') - ) - ); - - return $schema; -} - -/** - * Create usage table - */ -function commerce_discount_usage_update_7001() { - // Add usage table. - if (!db_table_exists('commerce_discount_usage')) { - db_create_table('commerce_discount_usage', drupal_get_schema_unprocessed('commerce_discount_usage', 'commerce_discount_usage')); - } -} - -/** - * Migrate from commerce_discount_max_uses to discount_usage_limit. - */ -function commerce_discount_usage_update_7002() { - foreach (entity_load('commerce_discount') as $discount) { - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - if (!isset($wrapper->discount_usage_limit) || $wrapper->discount_usage_limit->value() == FALSE - && isset($wrapper->commerce_discount_max_uses) && $wrapper->commerce_discount_max_uses->value()) { - $wrapper->discount_usage_limit = $wrapper->commerce_discount_max_uses->value(); - entity_save('commerce_discount', $discount); - } - } - - field_delete_field('commerce_discount_max_uses'); -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.module b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.module deleted file mode 100644 index 6833c71b..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.module +++ /dev/null @@ -1,216 +0,0 @@ - 'commerce-order', - 'product_discount' => 'commerce-line-item:order' - ); - - if (isset($map[$discount->type])) { - // Add condition for per-person usage - if ($wrapper->discount_usage_per_person->value()) { - $rule->condition( - 'commerce_discount_usage_max_usage_per_person', - array( - 'commerce_discount' => $discount->name, - 'order:select' => $map[$discount->type], - 'usage' => $wrapper->discount_usage_per_person->value(), - ) - ); - } - // For normal usage - if ($wrapper->discount_usage_limit->value()) { - $rule->condition( - 'commerce_discount_usage_max_usage', - array( - 'commerce_discount' => $discount->name, - 'order:select' => $map[$discount->type], - 'usage' => $wrapper->discount_usage_limit->value(), - ) - ); - } - } -} - -/* - * Implements hook_commerce_discount_usages(). - */ -function commerce_discount_usage_flush_caches() { - module_load_install('commerce_discount_usage'); - _commerce_discount_usage_install_helper(); -} - -/** - * Get usage of a discount for a user, excluding a certain order id. - * - * @param string $discount_name - * The discount name. - * @param string $mail - * The user mail. - * @param bool $exclude_order_id - * If TRUE, the order id will be excluded from the SQL request. - * - * @return int - * Return the number of usage by mail. - */ -function commerce_discount_usage_get_usage_by_mail($discount_name, $mail, $exclude_order_id = FALSE) { - $query = db_select('commerce_discount_usage', 'g') - ->fields('g') - ->condition('g.mail', $mail) - ->condition('g.discount', $discount_name); - - if ($exclude_order_id) { - $query->condition('g.order_id', $exclude_order_id, '<>'); - } - - return $query->execute()->rowCount(); -} - -/** - * Get usage of a discount, excluding a certain order id. - * - * @param string $discount_name - * The discount name. - * @param bool $exclude_order_id - * If TRUE, the order id will be excluded from the SQL request. - * - * @return int - * Return the number of usage. - */ -function commerce_discount_usage_get_usage($discount_name, $exclude_order_id = FALSE) { - $query = db_select('commerce_discount_usage', 'g') - ->fields('g') - ->condition('g.discount', $discount_name); - - if ($exclude_order_id) { - $query->condition('g.order_id', $exclude_order_id, '<>'); - } - - return $query->execute()->rowCount(); -} - -/** - * Reset usage statistics for an entire order - * - * @param object $order - * A fully qualified order object. - * - * @return DeleteQuery - * Return a new DeleteQuery object. - */ -function commerce_discount_usage_reset_order_usage($order) { - return db_delete('commerce_discount_usage') - ->condition('order_id', $order->order_id) - ->execute(); -} - -/** - * Record order usage - * - * @param object $order - * A fully qualified order object. - * - * @return void - */ -function commerce_discount_usage_record_order_usage($order) { - // Reset usage for this order first. - commerce_discount_usage_reset_order_usage($order); - - // Only record discount usage if the order has an email. - $discount_names = commerce_discount_usage_order_discounts($order); - - foreach ($discount_names as $discount_name) { - $record = array( - 'discount' => $discount_name, - 'mail' => $order->mail ? $order->mail : '', - 'order_id' => $order->order_id - ); - drupal_write_record('commerce_discount_usage', $record); - } -} - -/** - * Load all discounts connected to an order, including line item level discounts - * traced through line item unit price components. - * - * @param object $order - * A fully qualified order object. - * - * @return array - * - */ -function commerce_discount_usage_order_discounts($order) { - $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - $order_discounts = array(); - - foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) { - $data = $line_item_wrapper->commerce_unit_price->data->value(); - - foreach ($data['components'] as $key => $component) { - if ($component['name'] == 'discount' || !empty($component['price']['data']['discount_name'])) { - $order_discount_name = $component['price']['data']['discount_name']; - $order_discount_wrapper = entity_metadata_wrapper('commerce_discount', $order_discount_name); - // Make a list of discounts present via the order's line item price - // components. - if ($order_discount_wrapper->value()) { - $order_discounts[] = $order_discount_wrapper->name->value(); - } - } - } - } - - // Add the set of discounts directly referenced on the order. - if (!empty($order->commerce_discounts)) { - foreach ($order_wrapper->commerce_discounts->value() as $discount) { - $order_discounts[] = $discount->name; - } - } - - $order_discounts = array_unique($order_discounts); - - return $order_discounts; -} - -/* - * Implements hook_commerce_order_update(). - */ -function commerce_discount_usage_commerce_order_update($order) { - commerce_discount_usage_record_order_usage($order); -} - -/* - * Implements hook_commerce_order_insert(). - */ -function commerce_discount_usage_commerce_order_insert($order) { - commerce_discount_usage_record_order_usage($order); -} - -/* - * Implements hook_commerce_order_delete(). - */ -function commerce_discount_usage_commerce_order_delete($order) { - commerce_discount_usage_reset_order_usage($order); -} - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function commerce_discount_usage_form_commerce_discount_form_alter(&$form, $form_state) { - $form['#attached']['css'][] = drupal_get_path('module', 'commerce_discount_usage') . '/css/commerce_discount_usage.css'; -} - -/* - * Implements hook_views_api(). - */ -function commerce_discount_usage_views_api() { - return array( - 'version' => 2, - 'path' => drupal_get_path('module', 'commerce_discount_usage') . '/includes/views' - ); -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.rules.inc deleted file mode 100644 index 52655204..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.rules.inc +++ /dev/null @@ -1,88 +0,0 @@ - t('Max usage per person'), - 'parameter' => array( - 'order' => array( - 'type' => 'commerce_order', - 'label' => t('Order') - ), - 'commerce_discount' => array( - 'label' => t('Commerce Discount'), - 'type' => 'token', - 'options list' => 'commerce_discount_entity_list', - ), - 'usage' => array( - 'type' => 'integer', - 'label' => t('Maximum usage per customer'), - 'description' => t('Enter the maximum number of times a specific person (as identified by email) may use this discount. Leave blank for unlimited.'), - ), - ), - 'group' => t('Commerce Discount'), - ); - - // Max usage - $conditions['commerce_discount_usage_max_usage'] = array( - 'label' => t('Max usage'), - 'parameter' => array( - 'order' => array( - 'type' => 'commerce_order', - 'label' => t('Order') - ), - 'commerce_discount' => array( - 'label' => t('Commerce Discount'), - 'type' => 'token', - 'options list' => 'commerce_discount_entity_list', - ), - 'usage' => array( - 'type' => 'integer', - 'label' => t('Maximum overall usage'), - 'description' => t('Enter the maximum number of times this discount may be used on the site, by anyone. Leave blank for unlimited.'), - ), - ), - 'group' => t('Commerce Discount'), - ); - - return $conditions; -} - -/* - * Rules condition callback: evaluate maximum usage per-person of a discount. - */ -function commerce_discount_usage_max_usage_per_person($order, $discount_name) { - $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount_name); - $per_person_limit = $discount_wrapper->discount_usage_per_person->value(); - - // Nothing to count if the order does not have an email. - if (!$per_person_limit || !$order->mail) { - return TRUE; - } - - // Find other orders owned by same person that have same discount. - $usage = commerce_discount_usage_get_usage_by_mail($discount_name, $order->mail, $order->order_id); - - return $usage < $per_person_limit; -} - -/* - * Rules condition callback: evaluate maximum usage of a discount. - */ -function commerce_discount_usage_max_usage($order, $discount_name) { - $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount_name); - $limit = $discount_wrapper->discount_usage_limit->value(); - - if (!$limit) { - return TRUE; - } - - // Find other orders that have same discount. - $usage = commerce_discount_usage_get_usage($discount_name, $order->order_id); - - return $usage < $limit; -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.test deleted file mode 100644 index 57365f16..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/commerce_discount_usage.test +++ /dev/null @@ -1,271 +0,0 @@ - 'Discounts usage', - 'description' => 'Test discounts usage UI and functionality', - 'group' => 'Commerce discounts', - ); - } - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - $this->store_customer2 = $this->createStoreCustomer(); - } - - /** - * Test usage specific elements in the add discount UI. - */ - public function testCommerceDiscountUsageUIAddDiscount() { - // Login with store admin. - $this->drupalLogin($this->store_admin); - - // Access to the admin discount creation page. - $this->drupalGet('admin/commerce/store/discounts/add'); - - // Check the integrity of the add form. - // $this->assertFieldByName('commerce_discount_fields[discount_usage_limit][limit_uses]', NULL, t('Quantity field is present')); - $this->assertFieldByName('commerce_discount_fields[discount_usage_limit][und][0][value]', NULL, t('Amount field is present')); - - // Create a discount. - $values = array( - 'label' => 'Order discount - fixed', - 'name' => 'order_discount_fixed', - 'component_title' => 'Order discount', - 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, - 'commerce_discount_fields[discount_usage_limit][und][0][value]' => 5, - ); - $this->drupalPost(NULL, $values, t('Save discount')); - - // Load the discount and wrap it. - $discount = entity_load_single('commerce_discount', 1); - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - // Check the usage fields of the stored discount. - $this->assertTrue($wrapper->discount_usage_per_person->value() == 0, t('Discount uses field is empty.')); - $this->assertTrue($wrapper->discount_usage_limit->value() == 5, t('Discount max uses stored correctly.')); - - // Check the discounts listing - $this->assertText(t('@amount available', array('@amount' => 5)), t('Analytics - Max usage is shown.')); - $this->assertText(t('Used @amount times', array('@amount' => 0)), t('Analytics - Usage is shown.')); - } - - /** - * Test usage specific elements in the edit discount UI. - */ - public function testCommerceDiscountUsageUIEditDiscount() { - // Testing fixed discount. - // Create a fixed order discount of $3 limited to one use. - $discount = $this->createUsageDiscount('order_discount', 'fixed_amount', 300, 1); - - // Login with store admin. - $this->drupalLogin($this->store_admin); - - // Access to the admin discount edit page. - $this->drupalGet('admin/commerce/store/discounts/manage/' . $discount->name); - - // Check the integrity of the add form. - // $this->assertFieldByName('commerce_discount_fields[discount_usage_limit][limit_uses]', NULL, t('Quantity field is present')); - $this->assertFieldByName('commerce_discount_fields[discount_usage_limit][und][0][value]', NULL, t('Amount field is present')); - - // Change the discount values. - $values = array( - 'label' => 'Order discount - fixed', - 'name' => 'order_discount_fixed', - 'component_title' => 'Order discount', - 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, - 'commerce_discount_fields[discount_usage_limit][und][0][value]' => 5, - ); - $this->drupalPost(NULL, $values, t('Save discount')); - - // Load the discount and wrap it. - $discount = entity_load_single('commerce_discount', 1); - $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); - - // Check the usage fields of the stored discount. - $this->assertTrue($discount_wrapper->discount_usage_per_person->value() == 0, t('Discount uses field is empty.')); - // $this->assertTrue($discount_wrapper->discount_usage_limit->value() == 5, t('Discount max uses stored correctly.')); - - // Check the discounts listing - $this->assertText(t('@amount available', array('@amount' => 5)), t('Analytics - Max usage is shown.')); - $this->assertText(t('Used @amount times', array('@amount' => 0)), t('Analytics - Usage is shown.')); - } - - /** - * Test fixed order discounts. - */ - public function testCommerceDiscountUsageFixedOrderDiscount() { - // Testing fixed discount. - // Create a fixed order discount of $3 limited to one use. - $discount = $this->createUsageDiscount('order_discount', 'fixed_amount', 300, 1); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 700, t('Fixed order discount is deducted correctly on the first use.')); - - // Create another order to make sure the discount isn't applied again. - $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 1000, t('Fixed order discount is ignored after maximal usage.')); - } - - /** - * Test percentage order discounts. - */ - public function testCommerceDiscountUsagePercentageOrderDiscount() { - // Testing percentage discount. - // Create a percentage order discount of 5% limited to one use. - $discount = $this->createUsageDiscount('order_discount', 'percentage', 5, 1); - // Create a completed order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 950, t('Percentage order discount is deducted correctly.')); - - // Create another order to make sure the discount isn't applied again. - $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Check if the discount was applied on the order total price. - $this->assertTrue($wrapper->commerce_order_total->amount->value() == 1000, t('Percentage order discount is ignored after maximal usage.')); - } - - /** - * Test fixed product discounts. - */ - public function testCommerceDiscountUsageFixedProductDiscount() { - $discount = $this->createUsageDiscount('product_discount', 'fixed_amount', 300, 1); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - $this->assertTrue($price_data['components'][1]['price']['amount'] == -300, t('Fixed product discount is added as a price compoennt to the line item.')); - - // Create another order to make sure the discount isn't applied again. - $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - // $this->assertTrue(count($price_data['components']) === 1, t('Fixed product discount is ignored after maximal usage.')); - } - - /** - * Test percentage product discounts. - */ - public function testCommerceDiscountUsagePercentageProductDiscount() { - $discount = $this->createUsageDiscount('product_discount', 'percentage', 5, 1); - - // Create an order. - $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - // $this->assertTrue($price_data['components'][1]['price']['amount'] == -500, t('Percentage product discount is added as a price compoennt to the line item.')); - - // Create another order to make sure the discount isn't applied again. - $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'complete'); - $wrapper = entity_metadata_wrapper('commerce_order', $order); - - // Recalculate discounts. - commerce_cart_order_refresh($order); - - // Invoke line item price re-calculation. - $line_item = $wrapper->commerce_line_items->get(0)->value(); - rules_invoke_event('commerce_product_calculate_sell_price', $line_item); - // Check if the discount was added as a component to the line item. - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $price_data = $line_item_wrapper->commerce_unit_price->data->value(); - $this->assertTrue(count($price_data['components']) == 1, t('Percentage product discount is ignored after maximal usage.')); - } - - /** - * Create a discount. - * - * @param string $discount_type - * The discount type; Either 'order_discount' or 'product_discount'. - * @param string $offer_type - * The discount offer type; Either 'fixed_amount' or 'percentage'. - * @param integer $amount - * The discount offer amount. - * @param integer $max_usage - * Maximal uses for the discount. - * - * @return object - * The newly created commerce_discount entity. - */ - protected function createUsageDiscount($discount_type, $offer_type, $amount, $max_usage) { - // Use the base class to create a discount. - $discount = parent::createDiscount($discount_type, $offer_type, $amount); - - // Populate the max usage field. - $wrapper = entity_metadata_wrapper('commerce_discount', $discount); - $wrapper->discount_usage_limit = $max_usage; - $wrapper->save(); - - return $wrapper->value(); - } - -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/css/commerce_discount_usage.css b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/css/commerce_discount_usage.css deleted file mode 100644 index d0b9f383..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/css/commerce_discount_usage.css +++ /dev/null @@ -1,5 +0,0 @@ -#commerce-discount-fields-wrapper .field-name-discount-usage-limit, -#commerce-discount-fields-wrapper .field-name-discount-usage-per-person{ - float:none; - clear:both; -} \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/commerce_discount_usage.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/commerce_discount_usage.views.inc deleted file mode 100644 index 1a66e8e1..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/commerce_discount_usage.views.inc +++ /dev/null @@ -1,161 +0,0 @@ - t('Analytics'), - 'help' => t('Show discount usage and usage limit.'), - 'field' => array( - 'handler' => 'commerce_discount_usage_handler_field_commerce_discount_analytics', - ), - 'real field' => 'discount_id', - ); - - // Usage relationship - $data['commerce_discount']['discount_usage'] = array( - 'relationship' => array( - 'title' => t('Discount usage'), - 'label' => t('Discount usage'), - 'help' => t('Relate this discount to its usage statistics.'), - 'handler' => 'views_handler_relationship', - 'base' => 'commerce_discount_usage', - 'base field' => 'discount', - 'field' => 'name', - ), - ); - } - - // Order table: discount usage relationship - if (isset($data['commerce_order'])) { - $data['commerce_order']['discount_usage'] = array( - 'relationship' => array( - 'title' => t('Discount usage'), - 'label' => t('Discount usage'), - 'help' => t('Relate this order to its discount usage statistics.'), - 'handler' => 'views_handler_relationship', - 'base' => 'commerce_discount_usage', - 'base field' => 'order_id', - 'field' => 'order_id', - ), - ); - } - - // User table: discount usage relationship - $data['users']['discount_usage'] = array( - 'relationship' => array( - 'title' => t('Discount usage'), - 'label' => t('Discount usage'), - 'help' => t('Relate this user to its discount usage statistics.'), - 'handler' => 'views_handler_relationship', - 'base' => 'commerce_discount_usage', - 'base field' => 'mail', - 'field' => 'mail', - ), - ); -} - -/* - * Implements hook_views_data(). - */ -function commerce_discount_usage_views_data() { - $data = array(); - $data['commerce_discount_usage']['table']['group'] = t('Commerce discount usage'); - - // Base table - $data['commerce_discount_usage']['table']['base'] = array( - 'field' => 'mail', - 'title' => t('Commerce Discount Usage'), - 'help' => t('Usage statistics about discounts.'), - ); - - // Discount name. - $data['commerce_discount_usage']['discount'] = array( - 'title' => t('Discount'), - 'help' => t('The unique human-readable identifier of the discount.'), - 'field' => array( - 'handler' => 'views_handler_field', - 'click sortable' => TRUE, - ), - 'filter' => array( - 'handler' => 'views_handler_filter_string', - ), - 'sort' => array( - 'handler' => 'views_handler_sort', - ), - 'argument' => array( - 'handler' => 'views_handler_argument_string', - ), - 'relationship' => array( - 'title' => t('Discount'), - 'label' => t('Discount'), - 'help' => t('Relate a usage statistics record to its discount.'), - 'handler' => 'views_handler_relationship', - 'base' => 'commerce_discount', - 'base field' => 'name', - 'field' => 'discount', - ) - ); - - // Customer mail. - $data['commerce_discount_usage']['mail'] = array( - 'title' => t('User'), - 'help' => t('The email address of the customer who used the discount.'), - 'field' => array( - 'handler' => 'views_handler_field', - 'click sortable' => TRUE, - ), - 'filter' => array( - 'handler' => 'views_handler_filter_string', - ), - 'sort' => array( - 'handler' => 'views_handler_sort', - ), - 'argument' => array( - 'handler' => 'views_handler_argument_string', - ), - 'relationship' => array( - 'title' => t('Customer email'), - 'label' => t('Customer email'), - 'help' => t('Relate a usage statistics record to its user.'), - 'handler' => 'views_handler_relationship', - 'base' => 'users', - 'base field' => 'mail', - 'field' => 'mail', - ) - ); - - // Order ID. - $data['commerce_discount_usage']['order_id'] = array( - 'title' => t('Order ID'), - 'help' => t('The unique internal identifier of the order where the discount was used.'), - 'field' => array( - 'handler' => 'commerce_order_handler_field_order', - 'click sortable' => TRUE, - ), - 'filter' => array( - 'handler' => 'views_handler_filter_numeric', - ), - 'sort' => array( - 'handler' => 'views_handler_sort', - ), - 'argument' => array( - 'handler' => 'commerce_order_handler_argument_order_order_id', - ), - 'relationship' => array( - 'title' => t('Order'), - 'label' => t('Order'), - 'help' => t('Relate a usage statistics record to its order.'), - 'handler' => 'views_handler_relationship', - 'base' => 'commerce_order', - 'base field' => 'order_id', - 'field' => 'order_id', - ) - ); - - return $data; -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/commerce_discount_usage.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/commerce_discount_usage.views_default.inc deleted file mode 100644 index 6f271ad5..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/modules/commerce_discount_usage/includes/views/commerce_discount_usage.views_default.inc +++ /dev/null @@ -1,66 +0,0 @@ -display['default']->handler; - - if (empty($handler->display->display_options['fields']) || !is_array($handler->display->display_options['fields'])) { - return; - } - - $fields = array(); - foreach ($handler->display->display_options['fields'] as $field_id => $field) { - // Add the usage column after the status column. - if ($field_id == 'status') { - $fields += $usage_field; - } - $fields[$field_id] = $field; - } - - // Overwrite the original fields array. - $handler->display->display_options['fields'] = $fields; -} - -/** - * Helper function to get the Views import. - */ -function _commerce_discount_usage_views_get_view_delta() { - $handler = new stdClass(); - - /* Field: Commerce Discount: Analytics */ - $handler->display->display_options['fields']['commerce_discount_usage']['id'] = 'commerce_discount_usage'; - $handler->display->display_options['fields']['commerce_discount_usage']['table'] = 'commerce_discount'; - $handler->display->display_options['fields']['commerce_discount_usage']['field'] = 'commerce_discount_usage'; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['alter_text'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['make_link'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['absolute'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['external'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['replace_spaces'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['trim_whitespace'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['nl2br'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['word_boundary'] = 1; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['ellipsis'] = 1; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['more_link'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['strip_tags'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['trim'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['alter']['html'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['element_label_colon'] = 1; - $handler->display->display_options['fields']['commerce_discount_usage']['element_default_classes'] = 1; - $handler->display->display_options['fields']['commerce_discount_usage']['hide_empty'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['empty_zero'] = 0; - $handler->display->display_options['fields']['commerce_discount_usage']['hide_alter_empty'] = 1; - - return $handler->display->display_options['fields']; -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/sass/_mixins.scss b/profiles/commerce_kickstart/modules/contrib/commerce_discount/sass/_mixins.scss new file mode 100644 index 00000000..7374cf0d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/sass/_mixins.scss @@ -0,0 +1,85 @@ +$gray: #f0f0f0; +$gray2: #ccc; +$gray3: #e7e8e3; +$gray4: #4d4e49; +$grayLight: #f6f6f6; +$grayDark: #6d6d6d; +$blue: #0089c9; +$green: #7cae20; +$azure: #d2eeff; + +@mixin gradient { + // http://caniuse.com/#search=linear-gradient + background-image: -webkit-linear-gradient(top, rgb(252, 253, 252) 36%, rgb(240, 240, 240) 68%); + background-image: linear-gradient(top, rgb(252, 253, 252) 36%, rgb(240, 240, 240) 68%); +} + +@mixin box { + padding: 0; +} + +// https://css-tricks.com/snippets/css/clear-fix/ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin btn { + color: black; + border-radius: 5px; + [dir="ltr"] & { + margin-right: 20px; + } + [dir="rtl"] & { + margin-left: 20px; + } + + margin-bottom: 5px; + padding: 5px; + &:hover { + background-color: $grayLight; + cursor: pointer; + label { + cursor: pointer; + } + } +} + +@mixin radio-buttons { + clear: both; + + .form-item { + @include btn; + + input { + display: inline; + } + } +} + +@mixin fieldset-title { + fieldset.form-wrapper { + border: none; + + legend { + width: 100%; + } + .fieldset-legend { + border-bottom: 1px solid $gray2; + display: block; + margin-top: 0; + [dir="ltr"] & { + padding-left: 0; + } + [dir="rtl"] & { + padding-right: 0; + } + + text-transform: none; + width: 100%; + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/commerce_discount.scss b/profiles/commerce_kickstart/modules/contrib/commerce_discount/sass/commerce_discount.scss similarity index 51% rename from profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/commerce_discount.scss rename to profiles/commerce_kickstart/modules/contrib/commerce_discount/sass/commerce_discount.scss index da2295fe..b81aee5e 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/commerce_discount.scss +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/sass/commerce_discount.scss @@ -1,4 +1,4 @@ -@import "_mixins.scss"; +@import "mixins"; /** * Discount form. @@ -6,37 +6,41 @@ .commerce-discount-form { // Position the discount status block on top right region. position: relative; - .form-item-status { - background-color: #eee; - padding: 10px 10px 20px 10px; - position: absolute; - top: 0; - right: 0; // LTR - width: 180px; - } - // Reset + + // Reset. .form-item-label { clear: both; + .form-item { @include box; } } + .form-item.form-type-radio { position: relative; + .ajax-progress-throbber { position: absolute; right: -10px; + .message { display: none; } } } - // general block margins + + // General block margins. #commerce-discount-fields-wrapper { - & > .form-wrapper { + .form-wrapper { margin-bottom: 15px; } + + .field-name-discount-usage-limit, .field-name-discount-usage-per-person { + float: none; + clear: both; + } } + #edit-actions { clear: both; } @@ -48,24 +52,46 @@ .form-item-commerce-discount-type { .form-radios { margin-top: 5px; + .form-item { @include btn; - float: left; + + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } &.selected { background-color: #eee; } + .ajax-progress { display: none; } - #edit-commerce-discount-type { - @include radio-buttons; - clear: none; + } + } +} - .form-item { - float: left; // LTR - } - } +#edit-commerce-discount-type { + padding: 0; + + @include radio-buttons; + + margin-top: 10px; + clear: none; + + .form-item { + [dir="ltr"] & { + float: left; + margin-right: 20px; + } + + [dir="rtl"] & { + float: right; + margin-left: 20px; } } } @@ -85,55 +111,93 @@ * Choose offer type block. */ .field-name-commerce-discount-offer { - border: none; + // Help with floaties inside. + @include clearfix; + + border: 0; margin: 0; padding: 0; - .form-wrapper .form-wrapper { - .form-item { - margin-bottom: 2px; + // Offer type options. + .form-item-commerce-discount-fields-commerce-discount-offer-und-form-type.form-type-radios { + padding-left: 0; + padding-right: 0; + [dir="ltr"] & { + float: left; + margin-right: 20px; + padding-right: 20px; + border-right: 1px dotted $gray2; + } + + [dir="rtl"] & { + float: right; + margin-left: 20px; + padding-left: 20px; + border-left: 1px dotted $gray2; + } + + .form-radios { + @include radio-buttons; + margin: 10px; padding: 0; - label { - border-bottom: 1px solid $gray2; - padding-left: 5px; // LTR - } - div.form-radios { - @include radio-buttons; - - background-color: transparent; - border-right: 1px dotted $gray2; /* LTR */ - float: left; /* LTR */ - margin: 10px; - padding: 4px; - padding-right: 20px; /* LTR */ - - .form-item { - &.selected { - background-color: #eee; - } - .ajax-progress { - display: none; - } + .form-item { + &.selected { + background-color: #eee; + } + + .ajax-progress { + display: none; } + } + + label { + border-bottom: 0; - label { - border-bottom: 0; + [dir="ltr"] & { padding-left: 0; } + + [dir="rtl"] & { + padding-right: 0; + } } } + } + + // Avoid collision with fields below. + .commerce-offer-type-wrapper > .fieldset-wrapper { + @include clearfix; + } + + // The two columns for the offer type. + .commerce-offer-fields-wrapper { + padding: 0; + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } + } + + .form-wrapper .form-wrapper { + .form-item { + margin-bottom: 2px; + } + .form-wrapper { label { border-bottom: 0; - padding-left: 0; - } - &.form-item { - background-color: transparent; - float: left; /* LTR */ - margin: 10px; - padding: 4px; + [dir="ltr"] & { + padding-left: 0; + } + + [dir="rtl"] & { + padding-right: 0; + } } } } @@ -144,19 +208,56 @@ */ .field-name-commerce-discount-date { clear: both; - @include fieldset-title; + .form-wrapper fieldset.form-wrapper { margin: 0; } - .fieldset-wrapper { - @include fieldset-content; + .fieldset-wrapper { background-color: transparent; display: inline-block; margin: 10px; padding: 4px; width: auto; } + + .container-inline-date { + clear: none; + + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } + + .date-padding { + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } + } + + .form-text { + background-image: url(../images/calendar.svg); + background-repeat: no-repeat; + background-position: calc(100% - 0.25em) center; + background-size: 1.25em; + } + } + + // Date.css override. + fieldset.date-combo .container-inline-date .date-padding { + padding: 0; + } + // Vertical tab fix. + fieldset.date-combo .fieldset-wrapper > .container-inline-date { + padding-top: 0; + } } /** @@ -172,40 +273,76 @@ border-bottom: 1px solid $gray2; } } + .form-item .form-select { - float: left; // LTR + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } + margin: 10px; } + .field-name-commerce-discount-max-uses { @include box; & > div { - float: left; // LTR - margin-left: 30px; // LTR + [dir="ltr"] & { + float: left; + margin-left: 30px; + + &:first-child { + margin-left: 0; + } + } + + [dir="rtl"] & { + float: right; + margin-right: 30px; - &:first-child { - margin-left: 0; + &:first-child { + margin-right: 0; + } } input[disabled] { - background: #eeeeee; + background: #eee; } } } + .field-type-number-integer { - float: left; // LTR + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } .form-item { padding: 0; } + label { display: inline; font-weight: normal; - margin-left: 10px; // LTR + + [dir="ltr"] & { + margin-left: 10px; + } + + [dir="rtl"] & { + margin-right: 10px; + } } + .form-disabled input { - border: none; - background: none; + border: 0; + background-color: transparent; } } } @@ -215,22 +352,24 @@ */ .view-commerce-discount-overview { .views-table { - border: none; + border: 0; tr, td { - border: none; + border: 0; } + th { text-transform: none; - .views-field-enable-disable {} .views-field-operations-dropbutton { width: 70px; } } + td { .views-field-type { padding: 10px 20px; } + .views-field-commerce-discount-usage li { list-style-type: none; @@ -239,6 +378,7 @@ font-style: italic; } } + .views-field-enable-disable .item-list ul { width: 140px; border: 2px solid $grayDark; @@ -248,7 +388,15 @@ li { display: block; width: 50%; - float: left; // LTR + + [dir="ltr"] & { + float: left; + } + + [dir="rtl"] & { + float: right; + } + text-transform: uppercase; padding: 5px 0; font-weight: bold; @@ -270,6 +418,7 @@ } } } + .views-field-operations-dropbutton { position: relative; @@ -287,3 +436,7 @@ } } } + +.field-name-commerce-compatibility-strategy .field-widget-options-buttons .form-wrapper { + clear: both; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/_mixins.scss b/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/_mixins.scss deleted file mode 100644 index cad8a772..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/_mixins.scss +++ /dev/null @@ -1,114 +0,0 @@ -$gray: #f0f0f0; -$gray2: #cccccc; -$gray3: #e7e8e3; -$gray4: #4d4e49; -$grayLight: #f6f6f6; -$grayDark: #6d6d6d; -$blue: #0089c9; -$green: #7cae20; -$azure: #d2eeff; - -@mixin gradient { - background-image: linear-gradient(top, rgb(252,253,252) 36%, rgb(240,240,240) 68%); - background-image: -o-linear-gradient(top, rgb(252,253,252) 36%, rgb(240,240,240) 68%); - background-image: -moz-linear-gradient(top, rgb(252,253,252) 36%, rgb(240,240,240) 68%); - background-image: -webkit-linear-gradient(top, rgb(252,253,252) 36%, rgb(240,240,240) 68%); - background-image: -ms-linear-gradient(top, rgb(252,253,252) 36%, rgb(240,240,240) 68%); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0.36, rgb(252,253,252)), color-stop(0.68, rgb(240,240,240))); -} - -@mixin box { - padding: 0px; -} - -@mixin btn { - color: black; - border-radius: 5px; - margin-right: 20px; // LTR - margin-bottom: 5px; - padding: 5px; - &:hover { - background-color: $grayLight; - cursor: pointer; - label { - cursor: pointer; - } - } -} - -@mixin radio-buttons { - @include box; - clear: both; - margin-top: 10px; - - .form-item { - @include btn; - - input { - display: inline; - } - } -} - -@mixin fieldset-title { - fieldset.form-wrapper { - border: none; - - legend { - width: 100%; - } - .fieldset-legend { - border-bottom: 1px solid $gray2; - display: block; - margin-top: 0; - padding-left: 0; - text-transform: none; - width: 100%; - } - } -} - -@mixin fieldset-content { - @include box; - - .form-type-checkbox { - // Hide Show end date checkbox. - display: none; - } - .container-inline-date { - clear: none; - float: left; // LTR - width: auto; - - .description { - display: none; - } - .form-item { - padding: 0; - } - .date-padding { - float: left; // LTR - padding: 0; - - input.form-text { - width: 55px; - } - & > .form-item:first-child input.form-text { - // TODO: For some reason this icon isn't added. - background: white url('../images/calendar.png') no-repeat 86px 0px; // LTR - width: 100px; - } - } - & > .form-item { - border: none; - } - label { - display: block; - float: left; // LTR - padding-right: 10px; // LTR - } - &.end-date-wrapper { - margin-left: 20px; // LTR - } - } -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/commerce_discount-rtl.scss b/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/commerce_discount-rtl.scss deleted file mode 100644 index 8c810909..00000000 --- a/profiles/commerce_kickstart/modules/contrib/commerce_discount/scss/commerce_discount-rtl.scss +++ /dev/null @@ -1,200 +0,0 @@ -@import "_mixins.scss"; - -@mixin btn { - margin-left: 20px; - margin-right: 0; // RTL -} -.form-item.form-type-radio { - position: relative; - .progress-disabled { - float: none; - } - .ajax-progress-throbber { - position: absolute; - right: -10px; - } -} - -@mixin radio-buttons { - .form-item { - @include btn; - } -} - -@mixin fieldset-content { - .container-inline-date { - float: right; - - .date-padding { - float: left; // LTR - - - & > .form-item:first-child input.form-text { - // TODO: For some reason this icon isn't added. - background: white url('../images/calendar.png') no-repeat 2px 0px; - } - } - label { - float: right; - padding-right: 0px; - padding-left: 10px; // RTL - } - &.end-date-wrapper { - margin-left: 0; // RTL - margin-right: 20px; - } - } -} - -html.js input.form-autocomplete { - background-position: 0% 2px; // RTL -} - -/** - * Discount form. - */ -.commerce-discount-form { - // Position the discount status block on top right region. - position: relative; - .form-item-status { - background-color: #eee; - padding: 10px 10px 20px 10px; - position: absolute; - top: 0; - left: 0; - right: auto; // RTL - width: 180px; - } - // Reset - .form-item-label { - clear: both; - .form-item { - @include box; - } - } - .form-item.form-type-radio { - position: relative; - .ajax-progress-throbber { - position: absolute; - right: -10px; - - } - } - // general block margins - #commerce-discount-fields-wrapper .form-wrapper { - & > #inline-conditions-inline_conditions .form-wrapper .ajax-progress { - display: block; /* RTL */ - margin-top: 10px; /* RTL */ - width: 180px; /* RTL */ - } - & > #inline-conditions-inline_conditions .form-wrapper .condition-wrapper .form-item { - .ajax-progress { - display: inline-block; - margin-top: 0; - width: auto; - } - } - } -} -/** - * Choose discount type block. - */ -.form-item-commerce-discount-type { - .form-radios { - .form-item { - float: right; - - #edit-commerce-discount-type { - @include radio-buttons; - - .form-item { - float: right; - } - } - } - } -} -/** - * Choose offer type block. - */ -.field-name-commerce-discount-offer { - clear: both; // RTL - - .form-wrapper .form-wrapper { - .form-item { - label { - padding-right: 5px; - padding-left: 0; // RTL - } - div.form-radios { - @include radio-buttons; - - border-right: 0; // RTL - border-left: 1px dotted $gray2; - float: right; - padding-right: 0; // RTL - padding-left: 20px; - - label { - padding-right: 5px; - padding-left: 0; // RTL - } - } - } - .form-wrapper { - label { - padding-right: 5px; - padding-left: 0; // RTL - } - &.form-item { - float: right; - } - } - } -} - -/** - * Discount dates block. - */ -.field-name-commerce-discount-date { - .fieldset-wrapper { - @include fieldset-content; - } -} - -/** - * Usage block. - */ -.commerce-discount-usage { - .form-item.form-type-select { - margin: 0; - - label { - border-bottom: 1px solid $gray2; - } - } - .form-item .form-select { - float: right; - } - .field-name-commerce-discount-max-uses { - @include box; - - & > div { - float: left; // LTR - margin-left: 30px; // LTR - - &:first-child { - margin-left: 0; - } - - } - } - .field-type-number-integer { - float: right; - - label { - margin-left: 0px; - margin-right: 10px; // RTL - } - } -} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount.test new file mode 100644 index 00000000..02e0288f --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount.test @@ -0,0 +1,587 @@ + 'Discounts', + 'description' => 'Test discounts functionality', + 'group' => 'Commerce Discount', + ); + } + + /** + * Test the importing of commerce discounts. + */ + public function testCommerceDiscountImport() { + $exported_discount = '{ + "name" : "pf", + "label" : "PF", + "type" : "product_discount", + "status" : "1", + "component_title" : "pf", + "sort_order" : "10", + "commerce_discount_offer" : { + "type" : "fixed_amount", + "commerce_fixed_amount" : { "und" : [ + { + "amount" : "1200", + "currency_code" : "USD", + "data" : { "components" : [] } + } + ] + } + }, + "commerce_compatibility_strategy" : { "und" : [ { "value" : "any" } ] }, + "commerce_compatibility_selection" : [], + "commerce_discount_date" : [], + "inline_conditions" : [], + "discount_usage_per_person" : [], + "discount_usage_limit" : [] +}'; + + // Import the discount. + $import = entity_import('commerce_discount', $exported_discount); + $this->assertNotNull($import, 'Entity export JSON imported successfully.'); + entity_save('commerce_discount', $import); + + // Export the discount to make sure it's identical to the import string. + $discount = entity_load_single('commerce_discount', $import->discount_id); + $export = entity_export('commerce_discount', $discount); + $this->assertTrue($exported_discount == $export, 'Exported discount is identical to its origin.'); + } + + /** + * Test order wrapper cache from order refresh. + */ + public function testCommerceDiscountOrderRefreshWrapper() { + // Create a 'free bonus products' product discount. + $discount = $this->createDiscount('order_discount', 'free_products', array($this->product->product_id)); + // Create a completed order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + $line_items = $order_wrapper->value()->commerce_line_items[LANGUAGE_NONE]; + $this->assertEqual($order_wrapper->commerce_line_items->count(), count($line_items), 'Number of line items matched'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + $order_wrapper = commerce_cart_order_refresh($order); + $line_items = $order_wrapper->value()->commerce_line_items[LANGUAGE_NONE]; + $this->assertEqual($order_wrapper->commerce_line_items->count(), count($line_items), 'Number of line items matched'); + } + + /** + * Test fixed order discounts. + */ + public function testCommerceDiscountFixedOrderDiscount() { + // Testing fixed discount. + // Create a fixed order discount of $3. + $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 700, 'Fixed order discount is deducted correctly.'); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 700, 'Fixed order discount is deducted correctly even after order refresh.'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + // Re-save the order. + // Check if the discount was applied on the order total price. + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_wrapper->save(); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 1000, "Fixed order discount is removed when it's not applicable."); + } + + /** + * Test percentage order discounts. + */ + public function testCommerceDiscountPercentageOrderDiscount() { + // Testing percentage discount. + // Create a percentage order discount of 5%. + $discount = $this->createDiscount('order_discount', 'percentage', 5); + // Create a completed order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 950, 'Percentage order discount is deducted correctly.'); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 950, 'Percentage order discount is deducted correctly even after refresh.'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 1000, "Percentage order discount is removed when it's not applicable."); + } + + /** + * Test a 100% off product percentage discount. + * + * A 100% off product-level discount differs from the "Free product" discount + * offer type since those are normally used as an "add-on" that is added + * without the user's interaction (maybe as part of a package, or bonus); + * whereas a 100% off discount should be used for any product purposely added + * to a cart order. + */ + public function testCommerceDiscountOneHundredPercentOff() { + // Login as the store admin. + $this->drupalLogin($this->store_admin); + + // Create a 100% off discount and create a test product. + $discount = $this->createDiscount('product_discount', 'percentage', 100, 'freebie'); + + $product = $this->createDummyProduct('TEST-PRODUCT', 'Test Product', 999); + + // Create the order and apply the freebie discount. + $order = $this->createDummyOrder($this->store_admin->uid, array($product->product_id => 1)); + $order_wrapper = commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('freebie', $order); + $this->assertTrue($properly_applied, t('100% off discount applied to a product.')); + + // Verify that the product is now free. + $unit_price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + + $this->assertEqual($unit_price['amount'], 0, 'Product line item unit price amount is properly set to 0.'); + $this->assertEqual($unit_price['data']['components'][1]['price']['amount'], -999, 'Product line item unit price discount component properly set to 100% of the product price.'); + + $order_wrapper = commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('freebie', $order); + $this->assertTrue($properly_applied, t('100% off discount applied to a product even after refresh.')); + + // Verify that the product is now free. + $unit_price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + + $this->assertEqual($unit_price['amount'], 0, 'Product line item unit price amount is properly set to 0 even after refresh.'); + $this->assertEqual($unit_price['data']['components'][1]['price']['amount'], -999, 'Product line item unit price discount component properly set to 100% of the product price even after refresh.'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + $unit_price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + + $properly_applied = $this->discountAppliedToOrder('freebie', $order); + $this->assertFalse($properly_applied, t('100% off discount not applied to a product.')); + + $this->assertEqual($unit_price['amount'], 999, 'Product line item unit price amount is 999.'); + } + + /** + * Test free bonus products order discounts. + */ + public function testCommerceDiscountFreeProductsOrderDiscount() { + // Create 'free bonus products' product discount. + $discount = $this->createDiscount('order_discount', 'free_products', array($this->product->product_id)); + // Create a completed order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertEqual($order_wrapper->commerce_order_total->amount->value(), 1000, 'Free Bonus Products order discount has the price of only one product.'); + $this->assertEqual($order_wrapper->commerce_line_items->count(), 2, 'Free Bonus Products order discount is added as a line item.'); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertEqual($order_wrapper->commerce_order_total->amount->value(), 1000, 'Free Bonus Products order discount has the price of only one product even after refresh.'); + $this->assertEqual($order_wrapper->commerce_line_items->count(), 2, 'Free Bonus Products order discount is added as a line item even after refresh.'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + // Re-save the order. + // Check if the discount was applied on the order total price. + $order_wrapper = commerce_cart_order_refresh($order); + $this->assertEqual($order_wrapper->commerce_order_total->amount->value(), 1000, "Free Bonus Products order discount is removed when it's not applicable and price is the same."); + $this->assertEqual($order_wrapper->commerce_line_items->count(), 1, "Free Bonus Products order discount is removed when it's not applicable and line item count is only 1"); + } + + /** + * Test fixed product discounts. + */ + public function testCommerceDiscountFixedProductDiscount() { + $discount = $this->createDiscount('product_discount', 'fixed_amount', 300); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was added as a component to the line item. + $price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + $this->assertTrue($price['data']['components'][1]['price']['amount'] == -300, 'Fixed product discount is added as a price component to the line item.'); + $this->assertEqual($price['amount'], 700, 'Line item price with fixed product discount is correct.'); + + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was added as a component to the line item. + $price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + $this->assertTrue($price['data']['components'][1]['price']['amount'] == -300, 'Fixed product discount is added as a price component to the line item even after refresh.'); + $this->assertEqual($price['amount'], 700, 'Line item price with fixed product discount is correct even after refresh.'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount is not applied. + $price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + $this->assertEqual($price['amount'], 1000, 'Disabled fixed product discount is does not appear in the price.'); + } + + /** + * Test percentage product discounts. + */ + public function testCommerceDiscountPercentageProductDiscount() { + $discount = $this->createDiscount('product_discount', 'percentage', 5); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was added as a component to the line item. + $price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + $this->assertEqual($price['data']['components'][1]['price']['amount'], -50, 'Percentage product discount is added as a price component to the line item.'); + $this->assertEqual($price['amount'], 950, 'Line item amount with Percentage product discount is correct.'); + + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was added as a component to the line item. + $price = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + $this->assertEqual($price['data']['components'][1]['price']['amount'], -50, 'Percentage product discount is added as a price component to the line item even after refresh.'); + $this->assertEqual($price['amount'], 950, 'Line item amount with Percentage product discount is correct even after refresh.'); + + // Disable the discount. + $discount->status = FALSE; + entity_save('commerce_discount', $discount); + + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was added as a component to the line item. + $price_data = $order_wrapper->commerce_line_items->get(0)->commerce_unit_price->value(); + $this->assertEqual($price_data['amount'], 1000, 'Line item amount without Percentage product discount is correct.'); + } + + /** + * Test discounted product price display. + */ + public function testCommerceDiscountDiscountedProductPriceDisplay() { + // Create a product discount. + $this->createDiscount('product_discount', 'fixed_amount', 300); + $formatted_discounted_price = '$7.00'; + + // Log in as a normal user. + $this->drupalLogin($this->store_customer); + + $nid = $this->product_node->nid; + // View a product node. + $this->drupalGet("node/$nid"); + $product_price = $this->xpath('//div[contains(@class, "field-name-commerce-price")]/div[contains(@class, "field-item")]'); + $this->assertTrue(trim((string) $product_price[0]->div) == $formatted_discounted_price, 'Discounted product price is shown on product page.'); + + // Add a product to the cart. + $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart')); + + // View the cart. + $this->drupalGet('cart'); + $product_price = $this->xpath('//td[contains(@class, "views-field-commerce-unit-price")]'); + $this->assertTrue(trim((string) $product_price[0]->{0}) == $formatted_discounted_price, 'Discounted product price is shown on the cart.'); + } + + /** + * Test multiple fixed order discounts. + */ + public function testCommerceDiscountMultipleFixedOrderDiscounts() { + // Create two discounts. + $this->createDiscount('order_discount', 'fixed_amount', 300, 'of1'); + $this->createDiscount('order_discount', 'fixed_amount', 200, 'of2'); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + $this->assertTrue($order_wrapper->commerce_discounts->count() == 2, '2 discounts are listed as applied on the order.'); + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 500, 'Two fixed order discounts are applied on the total price.'); + $this->assertTrue($order_wrapper->commerce_line_items->count() == 3, 'An order with one product and two fixed order discounts has three line items.'); + $order_wrapper->save(); + $this->assertTrue($order_wrapper->commerce_line_items->count() == 3, 'After updating the order it still has three line items.'); + } + + /** + * Test rounding in percentage based product discounts. + * + * To test the rounding used when adding discount price components to product + * line items, we use a 30% discount on a product that costs $10.25. When + * rounding was not working correctly, the unit price amount would be set to + * $7.18 even though the sum of the unit price's price components would in + * fact be $7.17. In reality, since the actual discount amount SHOULD be + * rounded up to $3.08 from $3.075, the unit price amount SHOULD be $7.17. + * This test ensures that is the case in conjunction with a patch from the + * linked issue below. + * + * @link https://www.drupal.org/node/2468943#comment-10476486 + */ + public function testCommerceProductPercentageDiscountRounding() { + // Create the 30% discount and $10.25 product. + $this->createDiscount('product_discount', 'percentage', 30, 'discount_30_off'); + $product = $this->createDummyProduct('TEST-PRODUCT', 'Test Product', 1025); + + // Create the order and apply discount. + $order = $this->createDummyOrder($this->store_customer->uid, array($product->product_id => 1), 'completed'); + $order_wrapper = commerce_cart_order_refresh($order); + + // Verify rounding came out properly. + $line_item_wrapper = $order_wrapper->commerce_line_items->get(0); + $unit_price = $line_item_wrapper->commerce_unit_price->value(); + + $this->assertEqual($unit_price['amount'], 717, 'Product line item unit price amount rounded properly for a 30% discount.'); + $this->assertEqual($unit_price['data']['components'][1]['price']['amount'], -308, 'Product line item unit price discount component properly rounded for a 30% discount.'); + } + + /** + * Test discount compatibility strategies. + * + * Currently implemented strategies include: + * - any: discount is compatible with any other discount. + * - except: discount is compatible with any discount except selected ones. + * - only: discount is only compatible with selected ones. + * - none: discount is not compatible with any other discount. + * + * Compatibility is checked first to ensure that discounts already on an order + * are not incompatible with the discount being added. It is then checked to + * ensure the discount being added is not incompatible with any discount that + * has already been added to the order. + */ + public function testCommerceDiscountCompatibilityStrategies() { + // Create two discounts set to execute one after the other. + $discount_one = $this->createDiscount('order_discount', 'fixed_amount', 100, 'of1', 1); + $discount_one_wrapper = entity_metadata_wrapper('commerce_discount', $discount_one); + $discount_two = $this->createDiscount('order_discount', 'fixed_amount', 200, 'of2', 2); + $discount_two_wrapper = entity_metadata_wrapper('commerce_discount', $discount_two); + + // Create an order and recalculate discounts. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + + // Test compatibility with both discounts using the "any" strategy. Both + // discounts should be applied. + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Discount one and two applied when both are compatible with any discount.')); + + // Test compatibility with only discount one using the "none" strategy. Only + // discount one should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'none'; + $discount_one_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && !$this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Only discount one applied when it is not compatible with any other discount.')); + + // Test compatibility with only discount two using the "none" strategy. Only + // discount one should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'any'; + $discount_one_wrapper->save(); + $discount_two_wrapper->commerce_compatibility_strategy = 'none'; + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && !$this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Only discount one applied when discount two is not compatible with any other discount.')); + + // Test compatibility with discount one compatible with any discount + // except discount two. Only discount one should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'except'; + $discount_one_wrapper->commerce_compatibility_selection = array($discount_two->discount_id); + $discount_one_wrapper->save(); + $discount_two_wrapper->commerce_compatibility_strategy = 'any'; + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && !$this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Only discount one applied when it is compatible with any discount except discount two.')); + + // Test compatibility with discount two compatible with any discount + // except discount one. Only discount one should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'any'; + $discount_one_wrapper->save(); + $discount_two_wrapper->commerce_compatibility_strategy = 'except'; + $discount_two_wrapper->commerce_compatibility_selection = array($discount_one->discount_id); + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && !$this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Only discount one applied when it is compatible with any discount and discount two is compatible with any discount except discount one.')); + + // Test compatibility with discount two compatible with only discount + // one. Both discounts should be applied. + $discount_two_wrapper->commerce_compatibility_strategy = 'only'; + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Both discounts applied when discount one is compatible with any discount and discount two is compatible only with discount one.')); + + // Test compatibility with discount two compatible with only discount + // one. Both discounts should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'only'; + $discount_one_wrapper->save(); + $discount_two_wrapper->commerce_compatibility_strategy = 'any'; + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Both discounts applied when discount one is only compatible with discount two and discount two is compatible with any discount.')); + } + + /** + * Test discount deletion. + * + * Discount deletion should not cause fatal errors on cart refresh. + * + * @link https://www.drupal.org/node/2538812 + */ + public function testCartWithDiscountsDeleted() { + // Testing fixed discount. + // Create a fixed order discount of $3. + /** @var CommerceDiscount $discount */ + $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 700, 'Fixed order discount is deducted correctly.'); + + // Delete the discount. + $discount->delete(); + + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 1000, "Fixed order discount is removed when it's not applicable."); + } + + /** + * Test discount compatibility regression https://www.drupal.org/node/2621526. + * + * Discount "toggles" when cart page is refreshed. + */ + public function testCommerceDiscountCompatibilityStrategiesRefresh() { + // Create two discounts set to execute one after the other. + $discount_one = $this->createDiscount('product_discount', 'percentage', 10, 'of1'); + $discount_one_wrapper = entity_metadata_wrapper('commerce_discount', $discount_one); + $discount_two = $this->createDiscount('product_discount', 'percentage', 20, 'of2', 2); + $discount_two_wrapper = entity_metadata_wrapper('commerce_discount', $discount_two); + + // Create an order and recalculate discounts. + $order = $this->createDummyOrder($this->store_customer->uid, array( + $this->product->product_id => 1 + ), 'completed'); + + // Test compatibility with discount one compatible only with discount two + // And discount two compatible with any discount. Both should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'only'; + $discount_one_wrapper->commerce_compatibility_selection = array( + $discount_two->discount_id + ); + $discount_one_wrapper->save(); + $discount_two_wrapper->commerce_compatibility_strategy = 'any'; + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Both discounts applied when discount one is compatible with any discount and discount two is compatible only with discount one.')); + + // Regression test for discount compatibility with itself. + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($properly_applied, t('Both discounts applied when discount one is compatible with any discount, discount two is compatible only with discount one and the order refreshed one more time.')); + } + + /** + * Test discount compatibility regression https://www.drupal.org/node/2621526. + * + * Two "none" compatible discounts "toggles" when cart page is refreshed. + */ + public function testCommerceDiscountCompatibilityStrategiesRefreshNone() { + // Create two discounts set to execute one after the other. + $discount_one = $this->createDiscount('product_discount', 'percentage', 10, 'of1'); + $discount_one_wrapper = entity_metadata_wrapper('commerce_discount', $discount_one); + $discount_two = $this->createDiscount('product_discount', 'percentage', 20, 'of2', 2); + $discount_two_wrapper = entity_metadata_wrapper('commerce_discount', $discount_two); + + // Create an order and recalculate discounts. + $order = $this->createDummyOrder($this->store_customer->uid, array( + $this->product->product_id => 1 + ), 'completed'); + + // Test compatibility with both discounts compatible with no other + // discounts. + // Only first discount should be applied. + $discount_one_wrapper->commerce_compatibility_strategy = 'none'; + $discount_one_wrapper->save(); + $discount_two_wrapper->commerce_compatibility_strategy = 'none'; + $discount_two_wrapper->save(); + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && ! $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($this->discountAppliedToOrder('of1', $order), t('Discount 1.')); + $this->assertFalse($this->discountAppliedToOrder('of2', $order), t('Discount 2.')); + $this->assertTrue($properly_applied, t('Only first discount applied when both discounts are incompatible with any discounts.')); + + // Regression test for discount compatibility with itself. + commerce_cart_order_refresh($order); + + $properly_applied = $this->discountAppliedToOrder('of1', $order) && ! $this->discountAppliedToOrder('of2', $order); + $this->assertTrue($this->discountAppliedToOrder('of1', $order), t('Discount 1.')); + $this->assertFalse($this->discountAppliedToOrder('of2', $order), t('Discount 2.')); + $this->assertTrue($properly_applied, t('Only first discount applied when both discounts are incompatible with any discounts and order refreshed once again.')); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_base.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_base.test new file mode 100644 index 00000000..5c1570e1 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_base.test @@ -0,0 +1,253 @@ +sub_module) { + $modules[] = $this->sub_module; + } + parent::setUp($modules); + + // User creation for different operations. + $this->store_admin = $this->createStoreAdmin(); + $this->store_customer = $this->createStoreCustomer(); + + // Create a dummy product. + $this->product = $this->createDummyProduct('PROD-01', 'Product One', 1000); + + // Create a dummy product display content type. + $this->createDummyProductDisplayContentType(); + + // Create a product display node. + $this->product_node = $this->createDummyProductNode(array($this->product->product_id), 'Product One node'); + + // Set the default country to US. + variable_set('site_default_country', 'US'); + } + + /** + * Create a discount. + * + * @param string $discount_type + * The discount type; Either 'order_discount' or 'product_discount'. + * @param string $offer_type + * The discount offer type; One of 'fixed_amount', 'percentage', + * 'free_products', 'free_shipping', 'percent_off_shipping' or + * 'shipping_upgrade'. + * @param int|array $amount + * In case of 'fixed_amount' a number: the discount amount. + * In case of 'percentage' a number: the percentage (a number less than 1). + * In case of 'free_products' an array of the product ids. + * In case of 'free_shipping' a string of the free shipping service name. + * In case of 'percent_off_shipping' an array with the percentage amount + * (key: 'percent', should be < 100) and the shipping service name string + * (key: 'service'). + * In case of 'shipping_upgrade' an array with the source service (key: ' + * source') and the target service (key: 'target'). + * @param string $name + * Discount name - Optional. If given, CANNOT start with a number. + * @param string $component_title + * Component title - Optional. + * + * @return object + * The newly created commerce_discount entity. + */ + protected function createDiscount($discount_type, $offer_type, $amount, $name = '', $component_title = '', $sort_order = 10) { + // Create the discount offer. + $commerce_discount_offer = entity_create('commerce_discount_offer', array('type' => $offer_type)); + $offer_wrapper = entity_metadata_wrapper('commerce_discount_offer', $commerce_discount_offer); + switch ($offer_type) { + case 'fixed_amount': + $offer_wrapper->commerce_fixed_amount->amount = $amount; + $offer_wrapper->commerce_fixed_amount->currency_code = 'USD'; + break; + + case 'percentage': + $offer_wrapper->commerce_percentage = $amount; + break; + + case 'free_products': + // Product ids array should be provided for $amount. + $offer_wrapper->commerce_free_products = $amount; + break; + + case 'free_shipping': + $offer_wrapper->commerce_free_shipping = $amount; + $offer_wrapper->commerce_free_shipping_strategy = 'only_selected'; + break; + + case 'percent_off_shipping': + $offer_wrapper->commerce_percent_off_shipping = $amount['percent']; + if (!empty($amount[1])) { + $offer_wrapper->commerce_percent_off_ship_serv = $amount['service']; + } + break; + + case 'shipping_upgrade': + $offer_wrapper->commerce_shipping_upgrade_target = $amount['target']; + $offer_wrapper->commerce_shipping_upgrade_source = $amount['source']; + break; + } + + $offer_wrapper->save(); + + // Provide default name. + $name = $name ? $name : $discount_type . '_' . $offer_type; + $component_title = $component_title ? $component_title : $name; + + // Create the discount. + $values = array( + 'name' => $name, + 'label' => $name, + 'type' => $discount_type, + 'sort_order' => $sort_order, + 'component_title' => $component_title, + 'status' => TRUE, + 'export_status' => TRUE, + ); + $commerce_discount = entity_create('commerce_discount', $values); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $commerce_discount); + $discount_wrapper->commerce_discount_offer = $commerce_discount_offer; + $discount_wrapper->save(); + + return $discount_wrapper->value(); + } + + /** + * Determines whether or not a discount has been applied to an order. + * + * @param string $discount_name + * The machine-name of the discount to look for. + * @param object $order + * The order object to inspect for the discount. + * + * @return bool + * Boolean indicating whether or not the discount is applied to the order. + */ + public function discountAppliedToOrder($discount_name, $order) { + // Fetch the list of discounts applied to the order based on the price + // components in its order total array. + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $order_total = $order_wrapper->commerce_order_total->value(); + $applied_discounts = commerce_discount_get_discounts_applied_to_price($order_total); + + // Look for the given discount in the list of applied discounts. + return in_array($discount_name, $applied_discounts); + } + + /** + * Create a discount. + * + * @param string $discount_type + * The discount type; Either 'order_discount' or 'product_discount'. + * @param string $offer_type + * The discount offer type; Either 'fixed_amount' or 'percentage'. + * @param int $amount + * The discount offer amount. + * @param int $max_usage + * Maximal uses for the discount. + * + * @return object + * The newly created commerce_discount entity. + */ + protected function createUsageDiscount($discount_type, $offer_type, $amount, $max_usage) { + // Use the base class to create a discount. + $discount = $this->createDiscount($discount_type, $offer_type, $amount); + + // Populate the max usage field. + $wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $wrapper->discount_usage_limit = $max_usage; + $wrapper->save(); + + return $wrapper->value(); + } + + /** + * Create a date discount. + * + * @param string $discount_type + * The discount type; Either 'order_discount' or 'product_discount'. + * @param string $offer_type + * The discount offer type; Either 'fixed_amount' or 'percentage'. + * @param int $amount + * The discount offer amount. + * @param int $start_time + * Discount valid from. + * @param int $end_time + * Discount valid until. + * + * @return object + * The newly created commerce_discount entity. + */ + protected function createDateDiscount($discount_type, $offer_type, $amount, $start_time, $end_time) { + // Use the base class to create a discount. + $discount = $this->createDiscount($discount_type, $offer_type, $amount); + + // Populate the date fields. + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $discount_wrapper->commerce_discount_date = array( + 'value' => $start_time, + 'value2' => $end_time, + 'date_type' => 'datestamp', + ); + $discount_wrapper->save(); + + return $discount_wrapper->value(); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_conditions.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_conditions.test new file mode 100644 index 00000000..bb7f90c7 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_conditions.test @@ -0,0 +1,184 @@ + 'Discounts inline conditions', + 'description' => 'Test discounts inline conditions', + 'group' => 'Commerce Discount', + ); + } + + /** + * Set up our tests by adding some additional products. + */ + public function setUp() { + parent::setUp(); + + // We need a bunch of products. + $this->products['b'] = $this->createDummyProduct('PROD-B', 'Product B', 1000); + $this->products['c'] = $this->createDummyProduct('PROD-C', 'Product C', 1000); + $this->products['d'] = $this->createDummyProduct('PROD-D', 'Product D', 1000); + $this->products['e'] = $this->createDummyProduct('PROD-E', 'Product E', 1000); + $this->products['f'] = $this->createDummyProduct('PROD-F', 'Product F', 1000); + } + + /** + * Tests the 'commerce_order_contains_products' rule. + */ + public function testOrderContainsProductsCondition() { + // Build a test matrix. + // @see https://www.drupal.org/node/2398113 + $test_matrix = array( + // All of the products, including other products. + 'all inclusive' => array( + 'products_in_order' => array('b', 'c', 'd', 'e', 'f'), + 'results' => array( + 'any' => TRUE, + 'all' => TRUE, + 'exactly' => FALSE, + 'only' => FALSE, + ), + ), + // All of the products, exclusive of other products. + 'all exclusive' => array( + 'products_in_order' => array('c', 'e', 'f'), + 'results' => array( + 'any' => TRUE, + 'all' => TRUE, + 'exactly' => TRUE, + 'only' => TRUE, + ), + ), + // Some of the products, including one other product. + 'some inclusive' => array( + 'products_in_order' => array('b', 'c'), + 'results' => array( + 'any' => TRUE, + 'all' => FALSE, + 'exactly' => FALSE, + 'only' => FALSE, + ), + ), + // Some of the products, exclusively. + 'some exclusive' => array( + 'products_in_order' => array('e', 'f'), + 'results' => array( + 'any' => TRUE, + 'all' => FALSE, + 'exactly' => FALSE, + 'only' => TRUE, + ), + ), + // None of the products. + 'none' => array( + 'products_in_order' => array('b', 'd'), + 'results' => array( + 'any' => FALSE, + 'all' => FALSE, + 'exactly' => FALSE, + 'only' => FALSE, + ), + ), + ); + + // Set up all of the discount rules. + foreach ($test_matrix['none']['results'] as $operator => $result) { + $discount = $this->createDiscount('order_discount', 'fixed_amount', 100, 'ic_' . $operator, 1); + $discount->inline_conditions[LANGUAGE_NONE][0] = array( + 'condition_name' => 'commerce_order_contains_products', + 'condition_settings' => array( + 'operator' => $operator, + 'products' => array( + array('product_id' => $this->products['c']->product_id), + array('product_id' => $this->products['e']->product_id), + array('product_id' => $this->products['f']->product_id), + ), + ), + ); + entity_save('commerce_discount', $discount); + } + + // Loop through each element of the test matrix and run the tests. + foreach ($test_matrix as $set => $test_details) { + $order_products = array(); + + // Only add products we want for our test. + foreach ($test_details['products_in_order'] as $product_letter) { + $order_products[$this->products[$product_letter]->product_id] = 1; + } + + // Create a discount with our dummy products. + $order = $this->createDummyOrder($this->store_customer->uid, $order_products); + + // Refresh the order to apply the discounts. + commerce_cart_order_refresh($order); + + // Check if the discount was applied on the order total price. + foreach ($test_details['results'] as $operator => $result) { + $this->assertTrue(($this->discountAppliedToOrder('ic_' . $operator, $order) === $result), 'Order discount properly applies with "' . $operator . '" operator to product set: ' . $set . '.', 'Discount'); + } + + // Delete the order. + commerce_order_delete($order->order_id); + } + } + + /** + * Test commerce_product_contains_products() condition. + */ + public function testCommerceProductContainsProducts() { + $discount = $this->createDiscount('product_discount', 'fixed_amount', 100, 'product_sku_discount'); + $discount->inline_conditions[LANGUAGE_NONE][0] = array( + 'condition_name' => 'commerce_product_contains_products', + 'condition_settings' => array( + 'sku' => array( + array('product_id' => $this->products['c']->product_id), + ), + ), + ); + entity_save('commerce_discount', $discount); + + // Calculated price has discount. + $calculated_data = commerce_product_calculate_sell_price($this->products['c']); + $discounts = commerce_discount_get_discounts_applied_to_price($calculated_data); + $this->assertTrue(!empty($discounts)); + + $calculated_data = commerce_product_calculate_sell_price($this->products['b']); + $discounts = commerce_discount_get_discounts_applied_to_price($calculated_data); + $this->assertTrue(empty($discounts)); + + // Orders have discount. + $order = $this->createDummyOrder($this->store_customer->uid, array( + $this->products['c']->product_id => 1, + )); + // Refresh the order to apply the discounts. + commerce_cart_order_refresh($order); + $this->assertTrue($this->discountAppliedToOrder('product_sku_discount', $order)); + + // Delete order createDummyOrder method uses commerce_cart_order_load. + commerce_order_delete($order->order_id); + + $order2 = $this->createDummyOrder($this->store_customer->uid, array( + $this->products['b']->product_id => 1, + )); + // Refresh the order to apply the discounts. + commerce_cart_order_refresh($order2); + $this->assertFalse($this->discountAppliedToOrder('product_sku_discount', $order2)); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_date.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_date.test new file mode 100644 index 00000000..418e3599 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_date.test @@ -0,0 +1,144 @@ + 'Discounts date', + 'description' => 'Test discounts date functionality', + 'group' => 'Commerce Discount', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + } + + /** + * Test order discount in timespan. + */ + public function testDiscountDateOrderDiscountInTime() { + // Create a discount valid from yesterday until tomorrow. + $start_time = time() - $this->dayInSeconds; + $end_time = time() + $this->dayInSeconds; + + // Testing fixed discount. + $this->createDateDiscount('order_discount', 'fixed_amount', 300, $start_time, $end_time); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 700, 'Order discount is deducted when in time frame.'); + } + + /** + * Test order discount out of timespan. + */ + public function testDiscountDateOrderDiscountOutOfTime() { + // Create a discount valid from tomorrow. + $start_time = time() + $this->dayInSeconds; + $end_time = time() + 2 * $this->dayInSeconds; + + // Testing fixed discount. + // Create a fixed order discount of $3 limited to one use. + $this->createDateDiscount('order_discount', 'fixed_amount', 300, $start_time, $end_time); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 1000, 'Order discount is ignored when out of time frame.'); + } + + /** + * Test product discount in timespan. + */ + public function testDiscountDateProductDiscountInTime() { + // Create a discount valid from yesterday until tomorrow. + $start_time = time() - $this->dayInSeconds; + $end_time = time() + $this->dayInSeconds; + + $this->createDateDiscount('product_discount', 'fixed_amount', 300, $start_time, $end_time); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + // Invoke line item price re-calculation. + $line_item = $order_wrapper->commerce_line_items->get(0)->value(); + rules_invoke_event('commerce_product_calculate_sell_price', $line_item); + // Check if the discount was added as a component to the line item. + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $price_data = $line_item_wrapper->commerce_unit_price->data->value(); + $this->assertEqual($price_data['components'][1]['price']['amount'], -300, 'Product discount is applied when discount is in time frame.'); + } + + /** + * Test product discount out of timespan. + */ + public function testDiscountDateProductDiscountOutOfTime() { + // Create a discount valid from tomorrow. + $start_time = time() + $this->dayInSeconds; + $end_time = time() + (2 * $this->dayInSeconds); + + $this->createDateDiscount('product_discount', 'fixed_amount', 300, $start_time, $end_time); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + // Invoke line item price re-calculation. + $line_item = $order_wrapper->commerce_line_items->get(0)->value(); + rules_invoke_event('commerce_product_calculate_sell_price', $line_item); + // Check if the discount was added as a component to the line item. + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $price_data = $line_item_wrapper->commerce_unit_price->data->value(); + $this->assertTrue(count($price_data['components']) == 1, 'Product discount is ignored when discount is out of time frame.'); + } + + /** + * Test product discount on the same day. + */ + public function testDiscountSameDay() { + // Create a discount valid for today only. + $start_date = new DateTime(); + $start_date->setTime(0, 0, 0); + $start_time = $start_date->getTimestamp(); + $end_time = $start_time; + + $this->createDateDiscount('product_discount', 'fixed_amount', 300, $start_time, $end_time); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + // Invoke line item price re-calculation. + $line_item = $order_wrapper->commerce_line_items->get(0)->value(); + rules_invoke_event('commerce_product_calculate_sell_price', $line_item); + // Check if the discount was added as a component to the line item. + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $price_data = $line_item_wrapper->commerce_unit_price->data->value(); + $this->assertEqual($price_data['components'][1]['price']['amount'], -300, 'Product discount is applied when discount is in time frame.'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_date_ui.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_date_ui.test new file mode 100644 index 00000000..a3ec38f2 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_date_ui.test @@ -0,0 +1,141 @@ + 'Discounts date UI', + 'description' => 'Test discounts date UI', + 'group' => 'Commerce Discount UI', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + } + + /** + * Test date specific elements in the add discount UI. + */ + public function testDiscountDateUIAddDiscount() { + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount creation page. + $this->drupalGet('admin/commerce/discounts/add'); + + // Check the integrity of the add form. + $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value][date]', NULL, 'Start date field is present'); + $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value2][date]', NULL, 'End date field is present'); + + // Create a discount valid from yesterday until tomorrow. + $start_time = time() - $this->dayInSeconds; + $start_date = date($this->dateFormat, $start_time); + $end_time = time() + $this->dayInSeconds; + $end_date = date($this->dateFormat, $end_time); + + $values = array( + 'label' => 'Order discount - fixed', + 'name' => 'order_discount_fixed', + 'component_title' => 'Order discount', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, + 'commerce_discount_fields[commerce_discount_date][und][0][value][date]' => $start_date, + 'commerce_discount_fields[commerce_discount_date][und][0][value2][date]' => $end_date, + ); + $this->drupalPost(NULL, $values, t('Save discount')); + + // Load the discount and wrap it. + $discount = entity_load_single('commerce_discount', 1); + $wrapper = entity_metadata_wrapper('commerce_discount', $discount); + + // Check the usage fields of the stored discount. + $result_start_date = date($this->dateFormat, $wrapper->commerce_discount_date->value->value()); + $result_end_date = date($this->dateFormat, $wrapper->commerce_discount_date->value2->value()); + $this->assertEqual($result_start_date, $start_date, 'Start date is stored correctly.'); + $this->assertEqual($result_end_date, $end_date, 'End date is stored correctly.'); + + // Check the discounts listing. + $this->assertText($start_date, 'Start date is shown'); + $this->assertText($end_date, 'End date is shown'); + } + + /** + * Test the Edit discount UI. + */ + public function testDiscountDateUIEditDiscount() { + // Create a discount valid from yesterday until tomorrow. + $start_time = time() - $this->dayInSeconds; + $end_time = time() + $this->dayInSeconds; + $discount = $this->createDateDiscount('order_discount', 'fixed_amount', 300, $start_time, $end_time); + + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount edit page. + $this->drupalGet('admin/commerce/discounts/manage/' . $discount->name); + + // Check the integrity of the add form. + $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value][date]', NULL, 'Start date field is present'); + $this->assertFieldByName('commerce_discount_fields[commerce_discount_date][und][0][value2][date]', NULL, 'End date field is present'); + + // Change the discount date, to be valid from tomorrow. + $start_time = time() + $this->dayInSeconds; + $end_time = time() + (2 * $this->dayInSeconds); + $start_date = date($this->dateFormat, $start_time); + $end_date = date($this->dateFormat, $end_time); + + $values = array( + 'label' => 'Order discount - fixed', + 'name' => 'order_discount_fixed', + 'component_title' => 'Order discount', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, + 'commerce_discount_fields[commerce_discount_date][und][0][value][date]' => $start_date, + 'commerce_discount_fields[commerce_discount_date][und][0][value2][date]' => $end_date, + ); + + $this->drupalPost(NULL, $values, t('Save discount')); + + // Load the discount from the database and wrap it. + $discounts = entity_load('commerce_discount', array($discount->discount_id), $conditions = array(), $reset = TRUE); + $wrapper = entity_metadata_wrapper('commerce_discount', reset($discounts)); + + // Check the usage fields of the stored discount. + $result_start_date = date($this->dateFormat, $wrapper->commerce_discount_date->value->value()); + $result_end_date = date($this->dateFormat, $wrapper->commerce_discount_date->value2->value()); + $this->assertEqual($result_start_date, $start_date, 'Start date is stored correctly.'); + $this->assertEqual($result_end_date, $end_date, 'End date is stored correctly.'); + + // Check the discounts listing. + $this->assertText($start_date, 'Start date is shown'); + $this->assertText($end_date, 'End date is shown'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping.test new file mode 100644 index 00000000..1c099015 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping.test @@ -0,0 +1,229 @@ + 'Shipping discounts', + 'description' => 'Test shipping discounts functionality', + 'group' => 'Commerce Discount', + ); + } + + /** + * @inheritdoc + */ + public function setUp() { + parent::setUp(); + module_enable(array('commerce_discount_shipping_test')); + $this->resetAll(); + } + + /** + * Test shipping discounts. + */ + public function testCommerceDiscountShippingDiscounts() { + $shipping_service_name = 'commerce_discount_cheap_shipping'; + $decent_shipping_service_name = 'commerce_discount_decent_shipping'; + $expensive_shipping_service_name = 'commerce_discount_expensive_shipping'; + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'checkout_review', NULL); + + // Check shipping prices shown in checkout. + $this->drupalLogin($this->store_customer); + $this->drupalGet('checkout/' . $order->order_id . '/shipping'); + $this->assertText('Cheap shipping: $5.00', t('Cheap shipping price displayed correctly on checkout without a discount.')); + $this->assertText('Decent shipping: $15.00', t('Decent shipping price displayed correctly on checkout without a discount.')); + $this->assertText('Expensive shipping: $25.00', t('Expensive shipping price displayed correctly on checkout without a discount.')); + $this->assertText('Deluxe shipping: $55.00', t('Deluxe shipping price displayed correctly on checkout without a discount.')); + + // Select cheap shipping. + $edit = array( + 'commerce_shipping[shipping_service]' => $shipping_service_name, + ); + $this->drupalPostAJAX(NULL, $edit, 'commerce_shipping[shipping_service]'); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$5.00', t('Cheap shipping price is correctly displayed on checkout review.')); + $this->assertText('Order total$15.00', t('Undiscounted order total with cheap shipping is correctly displayed on checkout review.')); + + // Create a discount. + $discount = $this->createDiscount('order_discount', 'free_shipping', $shipping_service_name, 'free_shipping', 'Free cheap shipping'); + // Go back to shipping checkout step. + $this->drupalPost(NULL, array(), t('Go back')); + $this->assertText('Cheap shipping: $0.00', t('Cheap shipping price displayed correctly on checkout shipping with a cheap discount.')); + $this->assertText('Decent shipping: $15.00', t('Decent shipping price displayed correctly on checkout shipping with a cheap discount.')); + $this->assertText('Expensive shipping: $25.00', t('Expensive shipping price displayed correctly on checkout shipping with a cheap discount.')); + $this->assertText('Deluxe shipping: $55.00', t('Deluxe shipping price displayed correctly on checkout shipping with a cheap discount.')); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$5.00', t('Cheap shipping price is correctly displayed on checkout review.')); + $this->assertText('Free cheap shipping-$5.00', t('Free cheap shipping discount amount is correctly displayed on checkout review.')); + $this->assertText('Order total$10.00', t('Order total with free cheap shipping discount is correctly displayed on checkout review.')); + + $this->drupalPost(NULL, array(), t('Go back')); + // Select decent shipping. + $edit = array( + 'commerce_shipping[shipping_service]' => $decent_shipping_service_name, + ); + $this->drupalPostAJAX(NULL, $edit, 'commerce_shipping[shipping_service]'); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$15.00', t('Decent shipping price is correctly displayed.')); + $this->assertText('Order total$25.00', t('Undiscounted order total with decent shipping and with unapplied cheap shipping discount is correctly displayed.')); + + // Update discount to have a 'discount_all' strategy. + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $discount_wrapper->commerce_discount_offer->commerce_free_shipping_strategy = 'discount_all'; + $discount_wrapper->commerce_discount_offer->save(); + + $this->drupalPost(NULL, array(), t('Go back')); + $this->assertText('Cheap shipping: $0.00', t('Cheap shipping price displayed correctly on checkout shipping with a cheap discount with discount_all strategy.')); + $this->assertText('Decent shipping: $10.00', t('Decent shipping price displayed correctly on checkout shipping with a cheap discount with discount_all strategy.')); + $this->assertText('Expensive shipping: $20.00', t('Expensive shipping price displayed correctly on checkout shipping with a cheap discount with discount_all strategy.')); + $this->assertText('Deluxe shipping: $50.00', t('Deluxe shipping price displayed correctly on checkout shipping with a cheap discount with discount_all strategy.')); + + // Select expensive shipping. + $edit = array( + 'commerce_shipping[shipping_service]' => $expensive_shipping_service_name, + ); + $this->drupalPostAJAX(NULL, $edit, 'commerce_shipping[shipping_service]'); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$25.00', t('Expensive shipping price is correctly displayed on checkout review.')); + $this->assertText('Free cheap shipping-$5.00', t('Discount amount on expensive shipping is correctly displayed in case of a discount_all cheap shipping on checkout review.')); + $this->assertText('Order total$30.00', t('Undiscounted order total with decent shipping and with unapplied discounted cheap shipping is correctly displayed on checkout review.')); + + // Remove discount. + entity_delete('commerce_discount', $discount->discount_id); + + // Create discount for the expensive shipping method with 'discount_all' strategy. + $discount = $this->createDiscount('order_discount', 'free_shipping', $expensive_shipping_service_name, 'free_shipping', 'Free expensive shipping'); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $discount_wrapper->commerce_discount_offer->commerce_free_shipping_strategy = 'discount_all'; + $discount_wrapper->commerce_discount_offer->save(); + + $this->drupalPost(NULL, array(), t('Go back')); + $this->assertText('Cheap shipping: $0.00', t('Cheap shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + $this->assertText('Decent shipping: $0.00', t('Decent shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + $this->assertText('Expensive shipping: $0.00', t('Expensive shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + $this->assertText('Deluxe shipping: $30.00', t('Deluxe shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + + // Select deluxe shipping. + $edit = array( + 'commerce_shipping[shipping_service]' => 'commerce_discount_deluxe_shipping', + ); + $this->drupalPostAJAX(NULL, $edit, 'commerce_shipping[shipping_service]'); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$55.00', t('Deluxe shipping price is correctly displayed on checkout review.')); + $this->assertText('Free expensive shipping-$25.00', t('Discount amount on deluxe shipping is correctly displayed in case of a discount_all expensive shipping on checkout review.')); + $this->assertText('Order total$40.00', t('Order total with deluxe shipping and with discounted expensive shipping discount is correctly displayed on checkout review.')); + + // Remove discount. + entity_delete('commerce_discount', $discount->discount_id); + + // Create a free cheap shipping discount with discount_all strategy. + $discount = $this->createDiscount('order_discount', 'free_shipping', $shipping_service_name, 'free_shipping', 'Free cheap shipping'); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + $discount_wrapper->commerce_discount_offer->commerce_free_shipping_strategy = 'discount_all'; + $discount_wrapper->commerce_discount_offer->save(); + + // Create a 20% percent off shipping discount. + $offer = array( + 'percent' => 20, + 'service' => '', + ); + $percent_discount = $this->createDiscount('order_discount', 'percent_off_shipping', $offer, 'percent_off_shipping', '20% off shipping'); + $percent_discount->sort_order = 9; + entity_save('commerce_discount', $percent_discount); + + $this->drupalPost(NULL, array(), t('Go back')); + $this->assertText('Cheap shipping: $0.00', t('Cheap shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + $this->assertText('Decent shipping: $7.00', t('Decent shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + $this->assertText('Expensive shipping: $15.00', t('Expensive shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + $this->assertText('Deluxe shipping: $39.00', t('Deluxe shipping price displayed correctly on checkout shipping with an expensive discount with discount_all strategy.')); + + // Select deluxe shipping. + $edit = array( + 'commerce_shipping[shipping_service]' => 'commerce_discount_deluxe_shipping', + ); + $this->drupalPostAJAX(NULL, $edit, 'commerce_shipping[shipping_service]'); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$55.00', t('Deluxe shipping price is correctly displayed on checkout review.')); + $this->assertText('Free cheap shipping-$5.00', t('Discount amount on deluxe shipping is correctly displayed in case of a discount_all cheap shipping discount on checkout review.')); + $this->assertText('20% off shipping-$11.00', t('Discount amount on deluxe shipping is correctly displayed in case of an "all" 20% shipping discount on checkout review.')); + $this->assertText('Order total$49.00', t('Order total with deluxe shipping and with a discount_all cheap shipping discount and a 20% "all" shipping discount is correctly displayed on checkout review.')); + + // Remove free shipping discount. + entity_delete('commerce_discount', $discount->discount_id); + + // Create shipping upgrade discount. + $offer = array( + 'source' => $shipping_service_name, + 'target' => $expensive_shipping_service_name, + ); + $this->createDiscount('order_discount', 'shipping_upgrade', $offer, 'shipping_upgrade', 'Shipping upgrade', 11); + + $this->drupalPost(NULL, array(), t('Go back')); + $this->assertText('Cheap shipping: $4.00', t('Cheap shipping price displayed correctly on checkout shipping with a 20% "all" shipping discount and a cheap->expensive upgrade discount.')); + $this->assertText('Decent shipping: $12.00', t('Decent shipping price displayed correctly on checkout shipping with a 20% "all" shipping discount and a cheap->expensive upgrade discount.')); + $this->assertText('Expensive shipping: $4.00', t('Expensive shipping price displayed correctly on checkout shipping with a 20% "all" shipping discount and a cheap->expensive upgrade discount.')); + $this->assertText('Deluxe shipping: $44.00', t('Deluxe shipping price displayed correctly on checkout shipping with a 20% "all" shipping discount and a cheap->expensive upgrade discount.')); + + // Select expensive shipping. + $edit = array( + 'commerce_shipping[shipping_service]' => $expensive_shipping_service_name, + ); + $this->drupalPostAJAX(NULL, $edit, 'commerce_shipping[shipping_service]'); + // Go to next checkout step. + $this->drupalPost(NULL, $edit, t('Continue to next step')); + $this->assertText('Shipping$25.00', t('Expensive shipping price is correctly displayed on checkout review with a 20% "all" shipping discount and a cheap->expensive upgrade discount.')); + $this->assertText('Shipping upgrade-$16.00', t('Discount amount on expensive shipping is correctly displayed in case of cheap->expensive upgrade discount on checkout review.')); + $this->assertText('20% off shipping-$5.00', t('20% off discount amount on expensive shipping is correctly displayed on checkout review.')); + $this->assertText('Order total$14.00', t('Order total with deluxe shipping and with a 20% "all" shipping discount and a cheap->expensive upgrade discount is correctly displayed on checkout review.')); + } + + /** + * Test a free shipping discount combined with a fixed amount discount. + */ + public function testCommerceDiscountShippingAndFixedAmount() { + $shipping_service_name = 'commerce_discount_cheap_shipping'; + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed', NULL); + module_load_include('inc', 'commerce_shipping', 'commerce_shipping.rules'); + commerce_shipping_rate_apply($order, $shipping_service_name); + commerce_order_save($order); + unset($order->shipping_rates); + $free_shipping_discount = $this->createDiscount('order_discount', 'free_shipping', $shipping_service_name, 'free_shipping', 'Free cheap shipping', 1); + $this->createDiscount('order_discount', 'fixed_amount', 1500, 'of1'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Order total before discount is 1000, assert that applying both a shipping + // discount and a fixed amount discount isn't causing the order total to + // be negative (make sure order total is properly recalculated after the + // free shipping discount is applied). + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 0, 'Order total is correct when the free shipping discount has a lower weight than the fixed amount.'); + + // Update the free shipping discount weight to make sure the order total + // is correct when the shipping discount is evaluated after the fixed amount + // discount. + $free_shipping_discount->sort_order = 20; + $free_shipping_discount->save(); + $order_wrapper = commerce_cart_order_refresh($order); + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 0, 'Order total is correct when the free shipping discount has a higher weight than the fixed amount.'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping_test/commerce_discount_shipping_test.info b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping_test/commerce_discount_shipping_test.info new file mode 100644 index 00000000..74eda69d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping_test/commerce_discount_shipping_test.info @@ -0,0 +1,13 @@ +name = Commerce discount test shipping method +description = Provides shipping methods for commerce discount tests. +package = Commerce (shipping) +dependencies[] = commerce +dependencies[] = commerce_shipping +core = 7.x + +; Information added by Drupal.org packaging script on 2017-12-19 +version = "7.x-1.0-beta5" +core = "7.x" +project = "commerce_discount" +datestamp = "1513724288" + diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping_test/commerce_discount_shipping_test.module b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping_test/commerce_discount_shipping_test.module new file mode 100644 index 00000000..db34ecb9 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_shipping_test/commerce_discount_shipping_test.module @@ -0,0 +1,110 @@ + t('Discount test shipping method'), + 'description' => t('Defines 3 shipping services.'), + ); + + $shipping_methods['commerce_discount_test_other_method'] = array( + 'title' => t('Discount test other shipping method'), + 'description' => t('Defines a shipping service.'), + ); + + return $shipping_methods; +} + +/** + * Implements hook_commerce_shipping_service_info(). + */ +function commerce_discount_shipping_test_commerce_shipping_service_info() { + $shipping_services = array(); + + $shipping_services['commerce_discount_cheap_shipping'] = array( + 'title' => t('Cheap shipping service'), + 'description' => t('A cheap flat rate service.'), + 'display_title' => t('Cheap shipping'), + 'shipping_method' => 'commerce_discount_test_shipping_method', + 'price_component' => 'shipping', + 'callbacks' => array( + 'rate' => 'commerce_discount_test_shipping_service_rate', + ), + ); + + $shipping_services['commerce_discount_decent_shipping'] = array( + 'title' => t('Decent shipping service'), + 'description' => t('A decent flat rate service.'), + 'display_title' => t('Decent shipping'), + 'shipping_method' => 'commerce_discount_test_shipping_method', + 'price_component' => 'shipping', + 'callbacks' => array( + 'rate' => 'commerce_discount_test_shipping_service_rate', + ), + ); + + $shipping_services['commerce_discount_expensive_shipping'] = array( + 'title' => t('Expensive shipping service'), + 'description' => t('An expensive flat rate service.'), + 'display_title' => t('Expensive shipping'), + 'shipping_method' => 'commerce_discount_test_shipping_method', + 'price_component' => 'shipping', + 'callbacks' => array( + 'rate' => 'commerce_discount_test_shipping_service_rate', + ), + ); + + $shipping_services['commerce_discount_deluxe_shipping'] = array( + 'title' => t('Deluxe shipping service'), + 'description' => t('A deluxe flat rate service.'), + 'display_title' => t('Deluxe shipping'), + 'shipping_method' => 'commerce_discount_test_other_method', + 'price_component' => 'shipping', + 'callbacks' => array( + 'rate' => 'commerce_discount_test_shipping_service_rate', + ), + ); + + return $shipping_services; +} + +/** + * Shipping service callback. + * + * Returns a base price array for a shipping service + * calculated for the given order. + */ +function commerce_discount_test_shipping_service_rate($shipping_service, $order) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + switch ($shipping_service['name']) { + case 'commerce_discount_cheap_shipping': + $amount = 500; + break; + + case 'commerce_discount_decent_shipping': + $amount = 1500; + break; + + case 'commerce_discount_expensive_shipping': + $amount = 2500; + break; + + case 'commerce_discount_deluxe_shipping': + $amount = 5500; + break; + } + return array( + 'amount' => $amount, + 'currency_code' => $order_wrapper->commerce_order_total->currency_code->value(), + 'data' => array(), + ); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_ui.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_ui.test new file mode 100644 index 00000000..dc436847 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_ui.test @@ -0,0 +1,272 @@ + 'Discounts', + 'description' => 'Test discounts UI and functionality', + 'group' => 'Commerce Discount UI', + ); + } + + /** + * Test the importing of commerce discounts. + */ + public function testCommerceDiscountImportUI() { + // Login store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount creation page. + $this->drupalGet('admin/commerce/discounts/import'); + $this->assertResponse(200, 'Store admin is allowed in the discounts import page'); + } + + /** + * Access to commerce discounts admin. + */ + public function testCommerceDiscountUIAccessDiscountsListing() { + // Login with customer. + $this->drupalLogin($this->store_customer); + // Check the access to the profiles listing. + $this->drupalGet('admin/commerce/discounts'); + $this->assertResponse(403, 'The store customer has no access to discounts administration.'); + + // Login with store admin. + $this->drupalLogin($this->store_admin); + // Check the access to the profiles listing. + $this->drupalGet('admin/commerce/discounts'); + $this->assertResponse(200, 'The store admin has access to discounts administration.'); + + // Check the message of no discounts available. + $this->assertText(t('No discounts found.'), "'No discounts found.' message is displayed"); + // Check the add customer profile link. + $this->assertRaw(l(t('Add discount'), 'admin/commerce/discounts/add'), "'Add discount' link is present in the page"); + } + + /** + * Test the add discount UI. + */ + public function testCommerceDiscountUIAddDiscount() { + // Login with normal user. + $this->drupalLogin($this->store_customer); + + // Access to the admin discount creation page. + $this->drupalGet('admin/commerce/discounts/add'); + + $this->assertResponse(403, 'Normal user is not able to add a discount using the admin interface'); + + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount creation page. + $this->drupalGet('admin/commerce/discounts/add'); + + $this->assertResponse(200, 'Store admin user is allowed to add a discount using the admin interface'); + + // Check the integrity of the add form. + $this->assertFieldByName('commerce_discount_type', NULL, 'Discount type field is present'); + $this->assertFieldByName('label', NULL, 'Label field is present'); + $this->assertFieldByName('component_title', NULL, 'Name field is present'); + $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][type]', NULL, 'Offer type field is present'); + $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]', NULL, 'Amount field is present'); + $this->assertFieldByName('status', NULL, 'Status field is present'); + $this->assertFieldById('edit-submit', t('Save discount'), 'Save discount button is present'); + + // Try to save the product and check validation messages. + $this->drupalPost(NULL, array(), t('Save discount')); + + $this->assertText(t('Admin title field is required.'), 'Validation message for missing label.'); + $this->assertText(t('Machine-readable name field is required.'), 'Validation message for missing machine-name.'); + $this->assertText(t('Fixed amount field is required.'), 'Validation message for missing amount.'); + + // Load a clean discount add form. + $this->drupalGet('admin/commerce/discounts/add'); + // Create a discount. + $values = array( + 'label' => 'Order discount - fixed', + 'name' => 'order_discount_fixed', + 'component_title' => 'Order discount', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, + ); + $this->drupalPost(NULL, $values, t('Save discount')); + + // Load the discount and wrap it. + $discount = entity_load_single('commerce_discount', 1); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + + // Check the stored discount. + $this->assertEqual($discount->label, $values['label'], 'Label stored correctly.'); + $this->assertEqual($discount->name, 'discount_' . $values['name'], 'Name stored correctly.'); + $this->assertEqual($discount->export_status, 1, 'Active stored correctly.'); + $this->assertEqual($discount->component_title, $values['component_title'], 'Name for customer stored correctly.'); + $this->assertEqual($discount->status, 1, 'Enabled stored correctly.'); + + $this->assertEqual($discount_wrapper->commerce_discount_offer->getBundle(), 'fixed_amount', 'Offer type stored correctly.'); + $this->assertEqual($discount_wrapper->commerce_discount_offer->commerce_fixed_amount->amount->value(), 1277, 'Amount stored correctly.'); + + // Check the discounts listing. + $this->assertUrl('admin/commerce/discounts', array('query' => array('type' => 'order_discount')), 'Landing page after save is the order discounts list.'); + $this->assertText($values['label'], 'Label of the discount is present.'); + } + + /** + * Test the Edit discount UI. + */ + public function testCommerceDiscountUIEditDiscount() { + // Create a discount. + $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); + + // Login with normal user. + $this->drupalLogin($this->store_customer); + + // Access to the admin discount edit page. + $this->drupalGet('admin/commerce/discounts/manage/' . $discount->name); + $this->assertResponse(403, 'Normal user is not able to edit a discount using the admin interface'); + + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount edit page. + $this->drupalGet('admin/commerce/discounts/manage/' . $discount->name); + $this->assertResponse(200, 'Store admin user is allowed to edit a discount using the admin interface'); + + // Check the integrity of the add form. + $this->assertFieldByName('commerce_discount_type', NULL, 'Discount type field is present'); + $this->assertFieldByName('label', NULL, 'Label field is present'); + $this->assertFieldByName('component_title', NULL, 'Name field is present'); + $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][type]', NULL, 'Offer type field is present'); + $this->assertFieldByName('commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]', NULL, 'Amount field is present'); + $this->assertFieldByName('status', NULL, 'Status field is present'); + $this->assertFieldById('edit-submit', t('Save discount'), 'Save discount button is present'); + $this->assertFieldById('edit-delete', t('Delete discount'), 'Delete discount button is present'); + + // Empty values for validation assertions. + $values = array( + 'label' => '', + 'name' => '', + 'component_title' => '', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => '', + ); + + // Try to save the product and check validation messages. + $this->drupalPost(NULL, $values, t('Save discount')); + + $this->assertText(t('Admin title field is required.'), 'Validation message for missing label.'); + $this->assertText(t('Machine-readable name field is required.'), 'Validation message for missing machine-name.'); + $this->assertText(t('Fixed amount field is required.'), 'Validation message for missing amount.'); + + // Discount new values. + $values = array( + 'label' => 'Order discount - fixed', + 'name' => 'order_discount_fixed', + 'component_title' => 'Order discount', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, + ); + $this->drupalPost(NULL, $values, t('Save discount')); + + // Load the discount and wrap it. + $discounts = entity_load('commerce_discount', array($discount->discount_id), $conditions = array(), $reset = TRUE); + $discount = reset($discounts); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + + // Check the stored discount. + $this->assertEqual($discount->label, $values['label'], 'Label stored correctly.'); + $this->assertEqual($discount->name, 'discount_' . $values['name'], 'Name stored correctly.'); + $this->assertEqual($discount->component_title, $values['component_title'], 'Name for customer stored correctly.'); + $this->assertEqual($discount->status, 1, 'Enabled stored correctly.'); + + $this->assertEqual($discount_wrapper->commerce_discount_offer->getBundle(), 'fixed_amount', 'Offer type stored correctly.'); + $this->assertEqual($discount_wrapper->commerce_discount_offer->commerce_fixed_amount->amount->value(), 1277, 'Amount stored correctly.'); + + // Check the discounts listing. + $this->assertUrl('admin/commerce/discounts', array('query' => array('type' => 'order_discount')), 'Landing page after save is the order discounts list.'); + $this->assertText($values['label'], 'Label of the discount is present.'); + } + + /** + * Test the delete discount UI. + */ + public function testCommerceDiscountUIDeleteDiscount() { + // Create a discount. + $discount = $this->createDiscount('order_discount', 'fixed_amount', 300); + + // Login with normal user. + $this->drupalLogin($this->store_customer); + + // Access to the admin discount edit page. + $this->drupalGet('admin/commerce/discounts/manage/' . $discount->name . '/delete'); + + $this->assertResponse(403, 'Normal user is not able to delete a discount using the admin interface'); + + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount edit page. + $this->drupalGet('admin/commerce/discounts/manage/' . $discount->name . '/delete'); + + $this->assertResponse(200, 'Store admin user is allowed to delete a discount using the admin interface'); + + // Check the integrity of the add form. + $this->pass('Test the discount delete confirmation form:'); + $this->assertTitle(t('Are you sure you want to delete the Commerce Discount !label?', array('!label' => $discount->label)) . ' | Drupal', 'The confirmation message is displayed'); + $this->assertText(t('This action cannot be undone'), "A warning notifying the user about the action can't be undone is displayed."); + $this->assertFieldById('edit-submit', t('Confirm'), 'Delete button is present'); + $this->assertText(t('Cancel'), 'Cancel is present'); + + // Try to save the product and check validation messages. + $this->drupalPost(NULL, array(), t('Confirm')); + + // Check the url after deleting and if the discount has been deleted in + // database. + $this->assertUrl('admin/commerce/discounts', array(), 'Landing page after deleting a discount is the discounts listing page'); + $this->assertRaw(t('Deleted %type %label.', array('%type' => 'Commerce Discount', '%label' => $discount->label)), "'Discount has been deleted' message is displayed"); + $this->assertRaw(t('No discounts found.', array('@link' => url('admin/commerce/discounts/add'))), 'Empty discount listing message is displayed'); + } + + /** + * Test disabled line item types. + */ + public function testCommerceDiscountUIDisabledLineItemTypes() { + // Create the 30% discount and $1 product. + $this->createDiscount('product_discount', 'percentage', 30, 'discount_30_off'); + $product = $this->createDummyProduct('TEST-PRODUCT', 'Test Product', 100); + + // Create the order and apply discount. + $order = $this->createDummyOrder($this->store_customer->uid, array($product->product_id => 1)); + $order_wrapper = commerce_cart_order_refresh($order); + commerce_order_calculate_total($order); + + // Ensure discount applied. + $line_item_wrapper = $order_wrapper->commerce_line_items->get(0); + $unit_price = $line_item_wrapper->commerce_unit_price->value(); + $this->assertEqual($unit_price['amount'], 70, 'Product line item unit price amount rounded properly for a 30% discount.'); + + // Login with store admin and turn off all line item types. + $this->drupalLogin($this->store_admin); + $this->drupalGet('admin/commerce/discounts/settings'); + $values = array( + 'commerce_discount_line_item_types[product_discount]' => FALSE, + 'commerce_discount_line_item_types[product]' => FALSE, + ); + $this->drupalPost(NULL, $values, t('Save configuration')); + + // Refresh the order and ensure the discount is no longer applicable. + $order_wrapper = commerce_cart_order_refresh($order); + $line_item_wrapper = $order_wrapper->commerce_line_items->get(0); + $unit_price = $line_item_wrapper->commerce_unit_price->value(); + $this->assertEqual($unit_price['amount'], 100, 'Product line item unit price is the original amount'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_usage.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_usage.test new file mode 100644 index 00000000..77ea6a03 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_usage.test @@ -0,0 +1,128 @@ + 'Discounts usage', + 'description' => 'Test discounts usage functionality', + 'group' => 'Commerce Discount', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->store_customer2 = $this->createStoreCustomer(); + } + + /** + * Test fixed order discounts. + */ + public function testCommerceDiscountUsageFixedOrderDiscount() { + // Testing fixed discount. + // Create a fixed order discount of $3 limited to one use. + $this->createUsageDiscount('order_discount', 'fixed_amount', 300, 1); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 700, 'Fixed order discount is deducted correctly on the first use.'); + + // Create another order to make sure the discount isn't applied again. + $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 1000, 'Fixed order discount is ignored after maximal usage.'); + } + + /** + * Test percentage order discounts. + */ + public function testCommerceDiscountUsagePercentageOrderDiscount() { + // Testing percentage discount. + // Create a percentage order discount of 5% limited to one use. + $this->createUsageDiscount('order_discount', 'percentage', 5, 1); + + // Create a completed order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 950, 'Percentage order discount is deducted correctly.'); + + // Create another order to make sure the discount isn't applied again. + $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was applied on the order total price. + $this->assertTrue($order_wrapper->commerce_order_total->amount->value() == 1000, 'Percentage order discount is ignored after maximal usage.'); + } + + /** + * Test fixed product discounts. + */ + public function testCommerceDiscountUsageFixedProductDiscount() { + $this->createUsageDiscount('product_discount', 'fixed_amount', 300, 1); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was added as a component to the line item. + $price = commerce_price_wrapper_value($order_wrapper->commerce_line_items->get(0), 'commerce_unit_price'); + $this->assertTrue($price['data']['components'][1]['price']['amount'] == -300, 'Fixed product discount is added as a price component to the line item.'); + commerce_order_save($order); + + // Create another order to make sure the discount isn't applied again. + $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was added as a component to the line item. + $price = commerce_price_wrapper_value($order_wrapper->commerce_line_items->get(0), 'commerce_unit_price'); + $this->assertTrue(count($price['data']['components']) === 1, 'Fixed product discount is ignored after maximal usage.'); + } + + /** + * Test percentage product discounts. + */ + public function testCommerceDiscountUsagePercentageProductDiscount() { + $this->createUsageDiscount('product_discount', 'percentage', 5, 1); + + // Create an order. + $order = $this->createDummyOrder($this->store_customer->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was added as a component to the line item. + $price = commerce_price_wrapper_value($order_wrapper->commerce_line_items->get(0), 'commerce_unit_price'); + $this->assertEqual($price['data']['components'][1]['price']['amount'], -50, 'Percentage product discount is added as a price component to the line item.'); + commerce_order_save($order); + + // Create another order to make sure the discount isn't applied again. + $order = $this->createDummyOrder($this->store_customer2->uid, array($this->product->product_id => 1), 'completed'); + // Recalculate discounts. + $order_wrapper = commerce_cart_order_refresh($order); + // Check if the discount was added as a component to the line item. + $price = commerce_price_wrapper_value($order_wrapper->commerce_line_items->get(0), 'commerce_unit_price'); + $this->assertTrue(count($price['data']['components']) === 1, 'Percentage product discount is ignored after maximal usage.'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_usage_ui.test b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_usage_ui.test new file mode 100644 index 00000000..f89db85b --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_discount/tests/commerce_discount_usage_ui.test @@ -0,0 +1,109 @@ + 'Discounts usage', + 'description' => 'Test discounts usage UI', + 'group' => 'Commerce Discount UI', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + } + + /** + * Test usage specific elements in the add discount UI. + */ + public function testCommerceDiscountUsageUIAddDiscount() { + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount creation page. + $this->drupalGet('admin/commerce/discounts/add'); + + // Check the integrity of the add form. + $this->assertFieldByName('commerce_discount_fields[discount_usage_per_person][und][0][value]', NULL, 'Maximum usage per customer field is present'); + $this->assertFieldByName('commerce_discount_fields[discount_usage_limit][und][0][value]', NULL, 'Maximum overall usage field is present'); + + // Create a discount. + $values = array( + 'label' => 'Order discount - fixed', + 'name' => 'order_discount_fixed', + 'component_title' => 'Order discount', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, + 'commerce_discount_fields[discount_usage_limit][und][0][value]' => 5, + ); + $this->drupalPost(NULL, $values, t('Save discount')); + + // Load the discount and wrap it. + $discount = entity_load_single('commerce_discount', 1); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount); + + // Check the usage fields of the stored discount. + $this->assertTrue($discount_wrapper->discount_usage_per_person->value() == 0, 'Discount uses field is empty.'); + $this->assertTrue($discount_wrapper->discount_usage_limit->value() == 5, 'Discount max uses stored correctly.'); + + // Check the discounts listing. + $this->assertText(t('@amount available', array('@amount' => 5)), 'Analytics - Max usage is shown.'); + $this->assertText(t('Used @amount times', array('@amount' => 0)), 'Analytics - Usage is shown.'); + } + + /** + * Test usage specific elements in the edit discount UI. + */ + public function testCommerceDiscountUsageUIEditDiscount() { + // Testing fixed discount. + // Create a fixed order discount of $3 limited to one use. + $discount = $this->createUsageDiscount('order_discount', 'fixed_amount', 300, 1); + + // Login with store admin. + $this->drupalLogin($this->store_admin); + + // Access to the admin discount edit page. + $this->drupalGet('admin/commerce/discounts/manage/' . $discount->name); + + // Check the integrity of the add form. + $this->assertFieldByName('commerce_discount_fields[discount_usage_per_person][und][0][value]', NULL, 'Maximum usage per customer field is present'); + $this->assertFieldByName('commerce_discount_fields[discount_usage_limit][und][0][value]', NULL, 'Maximum overall usage field is present'); + + // Change the discount values. + $values = array( + 'label' => 'Order discount - fixed', + 'name' => 'order_discount_fixed', + 'component_title' => 'Order discount', + 'commerce_discount_fields[commerce_discount_offer][und][form][commerce_fixed_amount][und][0][amount]' => 12.77, + 'commerce_discount_fields[discount_usage_limit][und][0][value]' => 5, + ); + $this->drupalPost(NULL, $values, t('Save discount')); + + // Load the discount from the database and wrap it. + $discounts = entity_load('commerce_discount', array($discount->discount_id), $conditions = array(), $reset = TRUE); + $discount_wrapper = entity_metadata_wrapper('commerce_discount', reset($discounts)); + + // Check the usage fields of the stored discount. + $this->assertEqual($discount_wrapper->discount_usage_per_person->value(), 0, 'Discount uses field is empty.'); + $this->assertEqual($discount_wrapper->discount_usage_limit->value(), 5, 'Discount max uses stored correctly.'); + + // Check the discounts listing. + $this->assertText(t('@amount available', array('@amount' => 5)), 'Analytics - Max usage is shown.'); + $this->assertText(t('Used @amount times', array('@amount' => 0)), 'Analytics - Usage is shown.'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_extra_price_formatters/commerce_extra_price_formatters.info b/profiles/commerce_kickstart/modules/contrib/commerce_extra_price_formatters/commerce_extra_price_formatters.info index 6ec0b566..4d173c0a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_extra_price_formatters/commerce_extra_price_formatters.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_extra_price_formatters/commerce_extra_price_formatters.info @@ -5,10 +5,7 @@ dependencies[] = commerce dependencies[] = commerce_price package = Commerce (contrib) - -; Information added by drush on 2015-08-20 +; Information added by drush on 2013-01-04 version = "7.x-1.1+7-dev" -core = "7.x" project = "commerce_extra_price_formatters" -datestamp = "1440110607" - +datestamp = "1357314508" \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_checkout_panes.features.inc b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_checkout_panes.features.inc new file mode 100644 index 00000000..378f5b81 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_checkout_panes.features.inc @@ -0,0 +1,103 @@ + $pane) { + $options[$pane_id] = sprintf('%s (%s)', $pane['title'], $pane['name']); + } + + return $options; +} + +/** + * Implements hook_features_export(). + */ +function commerce_checkout_panes_features_export(array $data, array &$export, $module) { + $panes = commerce_checkout_panes(); + + foreach ($data as $pane_id) { + $export['features'][COMMERCE_CHECKOUT_PANES_INTEGRATION_NAME][$pane_id] = $pane_id; + $export['dependencies'][] = $panes[$pane_id]['module']; + } + + $export['dependencies'][] = 'commerce_checkout'; + + return array(); +} + +/** + * Implements hook_features_export_render(). + */ +function commerce_checkout_panes_features_export_render($module, array $data, $export = NULL) { + // Need to reset static cache, otherwise Features will not be able to check + // a state of components properly. + commerce_checkout_panes_reset(); + + $schema = drupal_get_schema('commerce_checkout_pane'); + $fields = array_fill_keys(array_keys($schema['fields']), NULL); + $panes = commerce_checkout_panes(); + $code = array(); + + $code[] = ' $panes = array();'; + + foreach ($data as $pane_id) { + if (isset($panes[$pane_id])) { + $code[] = ''; + $code[] = sprintf(' $panes[%s] = %s;', "'$pane_id'", features_var_export(array_intersect_key($panes[$pane_id], $fields), ' ')); + } + } + + $code[] = ''; + $code[] = ' return $panes;'; + + return array(COMMERCE_CHECKOUT_PANES_DEFAULT_HOOK => implode("\n", $code)); +} + +/** + * Implements hook_features_revert(). + */ +function commerce_checkout_panes_features_revert($module) { + $defaults = module_invoke($module, COMMERCE_CHECKOUT_PANES_DEFAULT_HOOK); + + if (!empty($defaults)) { + commerce_checkout_panes_reset(); + $existing = commerce_checkout_panes(); + + foreach ($defaults as $pane_id => $pane) { + $pane['saved'] = isset($existing[$pane_id]['saved']) ? $existing[$pane_id]['saved'] : FALSE; + + commerce_checkout_pane_save($pane); + } + } +} + +/** + * Implements hook_features_rebuild(). + */ +function commerce_checkout_panes_features_rebuild($module) { + commerce_checkout_panes_features_revert($module); +} + +/** + * Implements hook_features_disable_feature(). + */ +function commerce_checkout_panes_features_disable_feature($module) { + $defaults = module_invoke($module, COMMERCE_CHECKOUT_PANES_DEFAULT_HOOK); + + if (!empty($defaults)) { + foreach ($defaults as $pane_id => $pane) { + commerce_checkout_pane_reset($pane_id); + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_customer.features.inc b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_customer.features.inc index 1b538c9c..34354bda 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_customer.features.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_customer.features.inc @@ -20,7 +20,7 @@ function commerce_customer_features_export($data, &$export, $module_name = '') { foreach ($data as $type) { // Add module dependencies. $export['dependencies'][$info[$type]['module']] = $info[$type]['module']; - $export['features']['commerce_customer_type'][$type] = $type; + $export['features']['commerce_customer'][$type] = $type; // Fetch fields of the profile type and add them as dependency. $fields = field_info_instances('commerce_customer_profile', $type); @@ -50,7 +50,22 @@ function commerce_customer_features_export_options() { */ function commerce_customer_features_export_render($module, $data) { // Return nothing, since the appropriate code has to exist already. - return array(); + $info = commerce_customer_profile_types(); + $output = array(); + $output[] = ' $items = array('; + foreach ($data as $type) { + if (isset($info[$type]) && $item = $info[$type]) { + $output[] = " '{$type}' => array("; + foreach ($item as $key => $value) { + $output[] = " '{$key}' => " . features_var_export($value, ' ') . ","; + } + $output[] = " ),"; + } + } + $output[] = ' );'; + $output[] = ' return $items;'; + $output = implode("\n", $output); + return array('commerce_customer_profile_type_info' => $output); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.info b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.info index 7ed7ab33..9d94a9d1 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.info @@ -5,9 +5,9 @@ core = 7.x dependencies[] = features dependencies[] = commerce -; Information added by Drupal.org packaging script on 2015-07-21 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2017-11-13 +version = "7.x-1.3" core = "7.x" project = "commerce_features" -datestamp = "1437515562" +datestamp = "1510599186" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.module b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.module index a5addadf..f6635069 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_features.module @@ -10,43 +10,34 @@ */ function commerce_features_features_api() { $features = array(); + $path = drupal_get_path('module', 'commerce_features'); if (module_exists('commerce_product') && module_exists('commerce_product_ui')) { $features['commerce_product_type'] = array( 'name' => t('Commerce product types'), - 'feature_source' => TRUE, 'default_hook' => 'commerce_product_default_types', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_product_type.features.inc', ); } if (module_exists('commerce_tax')) { $features['commerce_tax_type'] = array( 'name' => t('Commerce tax types'), - 'feature_source' => TRUE, 'default_hook' => 'commerce_tax_default_types', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_tax_type.features.inc', ); $features['commerce_tax_rate'] = array( 'name' => t('Commerce tax rates'), - 'feature_source' => TRUE, 'default_hook' => 'commerce_tax_default_rates', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_tax_rate.features.inc', ); } if (module_exists('commerce_order') && module_exists('commerce_order_types')) { $features['commerce_order_type'] = array( 'name' => t('Commerce order types'), - 'features_source' => TRUE, 'default_hook' => 'commerce_order_default_types', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_order_type.features.inc', ); } if (module_exists('commerce_line_item') && module_exists('commerce_custom_product')) { $features['commerce_line_item_type'] = array( 'name' => t('Commerce line item types'), - 'feature_source' => TRUE, 'default_hook' => 'commerce_line_item_default_types', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_line_item_type.features.inc', ); } if (module_exists('commerce_coupon')) { @@ -54,26 +45,50 @@ function commerce_features_features_api() { if (version_compare($module_info['version'], '7.x-2', '<')) { $features['commerce_coupon_type'] = array( 'name' => t('Commerce coupon types'), - 'feature_source' => TRUE, 'default_hook' => 'commerce_coupon_default_types', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_coupon_type.features.inc', ); } } if (module_exists('commerce_customer')) { $features['commerce_customer'] = array( 'name' => t('Commerce customer profile types'), - 'features_source' => TRUE, 'default_hook' => 'commerce_customer_default_types', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_customer.features.inc', ); } if (module_exists('commerce_flat_rate')) { $features['commerce_flat_rate_services'] = array( 'name' => t('Commerce flat rate services'), - 'features_source' => TRUE, 'default_hook' => 'commerce_flat_rate_default_services', - 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_flat_rate_services.features.inc', + ); + } + if (module_exists('commerce_checkout')) { + $features['commerce_checkout_panes'] = array( + 'name' => t('Commerce checkout panes'), + 'default_hook' => 'commerce_checkout_panes_default', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ); + } + + foreach ($features as $name => $integration) { + $features[$name] = $integration + array( + 'file' => "$path/$name.features.inc", + 'feature_source' => TRUE, + ); + } + if (module_exists('commerce_payment')) { + $features['commerce_payment_method'] = array( + 'name' => t('Commerce payment methods'), + 'feature_source' => TRUE, + 'default_hook' => 'commerce_payment_default_methods', + 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_payment_method.features.inc', + ); + } + if (module_exists('commerce_fees')) { + $features['commerce_fees_type'] = array( + 'name' => t('Commerce fees types'), + 'feature_source' => TRUE, + 'default_hook' => 'commerce_fees_default_types', + 'file' => drupal_get_path('module', 'commerce_features') . '/commerce_fees.features.inc', ); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_fees.features.inc b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_fees.features.inc new file mode 100644 index 00000000..9391d3e3 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_fees.features.inc @@ -0,0 +1,97 @@ + $type) { + $feature_types[$key] = $type['display_title']; + } + } + return $feature_types; +} + +/** + * Implements hook_features_export_render(). + */ +function commerce_fees_type_features_export_render($module, $data, $export = NULL) { + $info = commerce_fees_types(); + $output = array(); + $output[] = ' $items = array('; + foreach ($data as $type) { + if (isset($info[$type]) && $fees_type = $info[$type]) { + $output[] = " '{$type}' => " . features_var_export($fees_type, ' ') . ","; + } + } + $output[] = ' );'; + $output[] = ' return $items;'; + $output = implode("\n", $output); + return array('commerce_fees_default_types' => $output); +} + +/** + * Implements hook_features_revert(). + */ +function commerce_fees_type_features_revert($module = NULL) { + // Get default fees types + if (module_hook($module, 'commerce_fees_default_types')) { + $default_types = module_invoke($module, 'commerce_fees_default_types'); + // Read the fees types from the database instead of calling all + $existing_types = commerce_fees_types(); + foreach ($default_types as $key => $type) { + // Add or update. + if (!isset($existing_types[$key])) { + $type['is_new'] = TRUE; + } + commerce_fees_save($type, TRUE, TRUE); + } + } + else { + drupal_set_message(t('Could not load default fees types.'), 'error'); + return FALSE; + } + + // Reset the static cache. + commerce_fees_reset(); + // Schedule a menu rebuild. + variable_set('menu_rebuild_needed', TRUE); + + return TRUE; +} + +/** + * Implements hook_features_rebuild(). + */ +function commerce_fees_type_features_rebuild($module) { + return commerce_fees_type_features_revert($module); +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_payment_method.features.inc b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_payment_method.features.inc new file mode 100644 index 00000000..ac4cc198 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_features/commerce_payment_method.features.inc @@ -0,0 +1,38 @@ + 1, CURLOPT_POSTFIELDS => $request_content, CURLOPT_RETURNTRANSFER => 1, - CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_SSL_VERIFYPEER => 1, CURLOPT_NOPROGRESS => 1, CURLOPT_FOLLOWLOCATION => 0, CURLOPT_FRESH_CONNECT => 1, @@ -583,6 +583,19 @@ public function request(&$request_state) { CURLOPT_HTTPHEADER => array_values($request_headers), ); + // Commerce First Data GGE4 requires SSL peer verification, which may prevent + // out of date servers from successfully processing API requests. If you get + // an error related to peer verification, you may need to download the CA + // certificate bundle file from http://curl.haxx.se/docs/caextract.html, + // place it in a safe location on your web server, and update your + // settings.php to set the commerce_firstdata_gge4_cacert variable to contain + // the absolute path of the file. + // Alternately, you may be able to update your php.ini to point to the file + // with the curl.cainfo setting. + if ($cert_path = variable_get('commerce_firstdata_gge4_cacert', FALSE)) { + $curl_options[CURLOPT_CAINFO] = $cert_path; + } + $ch = curl_init(); curl_setopt_array($ch, $curl_options); $raw_response = curl_exec($ch); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_message/LICENSE.txt old mode 100755 new mode 100644 diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.info b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.info index 1f252e36..e5aac410 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.info @@ -12,10 +12,11 @@ dependencies[] = message dependencies[] = message_notify files[] = includes/views/handlers/commerce_message_handler_area_add_message.inc +files[] = commerce_message.test -; Information added by Drupal.org packaging script on 2014-06-11 -version = "7.x-1.0-rc3" +; Information added by Drupal.org packaging script on 2017-06-26 +version = "7.x-1.0" core = "7.x" project = "commerce_message" -datestamp = "1402495128" +datestamp = "1498488844" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.module b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.module index 892bbb5e..0b3a5ff1 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.module @@ -25,6 +25,7 @@ function commerce_message_menu() { 'access callback' => 'commerce_order_access', 'access arguments' => array('view', 3), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, 'weight' => 11, ); @@ -56,8 +57,10 @@ function commerce_message_module_implements_alter(&$implementations, $hook) { * * @link http://drupal.org/node/1272560 */ -function commerce_message_message_presave($message) { - if (!empty($message->mid) || $message->type != 'commerce_order_order_confirmation') { +function commerce_message_message_presave(Message $message) { + // Only support unsaved messages that re-use our order reference field. + $wrapper = entity_metadata_wrapper('message', $message); + if (!empty($message->mid) || !isset($wrapper->message_commerce_order)) { return; } @@ -70,14 +73,22 @@ function commerce_message_message_presave($message) { /** * Message callback; Show order summary. * - * @param $message + * @param Message $message * The Message entity. + * + * @return string + * The output of the View. */ function commerce_message_order_summary(Message $message) { $wrapper = entity_metadata_wrapper('message', $message); $view = views_get_view('commerce_cart_summary'); $view->set_arguments(array($wrapper->message_commerce_order->getIdentifier())); $view->hide_admin_links = TRUE; + + // Disable SQL query rewrite so this renders properly for token. + // @link https://www.drupal.org/node/1895418 + $view->display['default']->display_options['query']['options']['disable_sql_rewrite'] = TRUE; + return $view->preview(); } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.rules_defaults.inc b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.rules_defaults.inc index 8253d657..1b5410fb 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.rules_defaults.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.rules_defaults.inc @@ -217,6 +217,39 @@ function commerce_message_default_rules_configuration() { } }'); + $rules['rules_commerce_order_message_admin_order_notification'] = entity_import('rules_config', '{ "rules_commerce_order_message_admin_order_notification" : { + "LABEL" : "Commerce order message: admin order notification e-mail", + "PLUGIN" : "reaction rule", + "WEIGHT" : "3", + "OWNER" : "rules", + "REQUIRES" : [ "rules", "message_notify", "commerce_checkout" ], + "ON" : { "commerce_checkout_complete" : [] }, + "DO" : [ + { "entity_create" : { + "USING" : { + "type" : "message", + "param_type" : "commerce_order_admin_order_confirmation", + "param_user" : [ "commerce-order:owner" ] + }, + "PROVIDE" : { "entity_created" : { "entity_created" : "Created entity" } } + } + }, + { "data_set" : { + "data" : [ "entity-created:message-commerce-order" ], + "value" : [ "commerce-order" ] + } + }, + { "entity_save" : { "data" : [ "entity-created" ], "immediate" : 1 } }, + { "message_notify_process" : { + "message" : [ "entity-created" ], + "save_on_fail" : 0, + "save_on_success" : 0 + } + } + ] + } + }'); + return $rules; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.test b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.test new file mode 100644 index 00000000..c119480e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_message/commerce_message.test @@ -0,0 +1,560 @@ +resetAll(); + + $this->store_admin = $this->createStoreAdmin(); + + if (!$this->store_admin) { + $this->fail(t('Failed to create Store Admin user for test.')); + } + + // Set the default country to US. + variable_set('site_default_country', 'US'); + } + + /** + * Loads message entities referencing an order. + * + * @param object $order + * The commerce_order entity. + * + * @return Message[] + * Array of entities. + */ + protected function loadMessagesForOrder($order) { + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'message') + ->fieldCondition('message_commerce_order', 'target_id', $order->order_id) + ->propertyOrderBy('timestamp', 'ASC'); + $results = $query->execute(); + + if (!empty($results)) { + return entity_load('message', array_keys($results['message'])); + } + + return array(); + } + + /** + * Asserts if a Message entity's bundle matches. + * + * @param \Message $message + * The Message entity. + * @param string $bundle + * The bundle. + */ + protected function assertBundle(Message $message, $bundle) { + $this->assertEqual($bundle, $message->bundle(), t('Message type @expected expected, got: @actual', array( + '@expected' => $bundle, + '@actual' => $message->bundle(), + ) + )); + } +} + +/** + * Class CommerceMessageOrderHistoryTest + */ +class CommerceMessageOrderHistoryTest extends CommerceMessageTestBase { + + public static function getInfo() { + return array( + 'name' => 'Commerce Message order history', + 'description' => 'Tests messages as order history', + 'group' => 'Commerce Message', + 'dependencies' => array('rules'), + ); + } + + /** + * Test order history programmatically. + */ + public function testOrderHistory() { + $order = $this->createDummyOrder(); + commerce_order_status_update($order, 'pending'); + commerce_order_status_update($order, 'completed'); + RulesLog::logger()->checkLog(); + + $messages = $this->loadMessagesForOrder($order); + + $this->assertEqual(4, count($messages)); + + // Assert order created. + $message = reset($messages); + array_shift($messages); + $this->assertBundle($message, 'commerce_order_created'); + $this->assertEqual('Order has been created.', $message->getText(LANGUAGE_NONE)); + + // Assert product added to cart. + $message = reset($messages); + array_shift($messages); + $this->assertBundle($message, 'commerce_order_cart_add'); + $this->assertEqual('Product PROD-01 added to the cart.', $message->getText(LANGUAGE_NONE)); + + // Assert status updates. + $message = reset($messages); + array_shift($messages); + $this->assertBundle($message, 'commerce_order_state'); + $this->assertEqual('Status has been set to Pending (previously: Shopping cart).', $message->getText(LANGUAGE_NONE)); + + $message = reset($messages); + array_shift($messages); + $this->assertBundle($message, 'commerce_order_state'); + $this->assertEqual('Status has been set to Completed (previously: Pending).', $message->getText(LANGUAGE_NONE)); + } + + /** + * Test order history UI. + */ + public function testOrderHistoryUi() { + $order = $this->createDummyOrder(); + commerce_order_status_update($order, 'pending'); + commerce_order_status_update($order, 'completed'); + RulesLog::logger()->checkLog(); + + $this->drupalLogin($this->store_admin); + + $this->drupalGet('admin/commerce/orders/' . $order->order_id . '/history'); + $this->assertText('Order has been created.'); + $this->assertText('Product PROD-01 added to the cart.'); + $this->assertText('Status has been set to Pending (previously: Shopping cart).'); + $this->assertText('Status has been set to Completed (previously: Pending).'); + + $this->drupalLogout(); + $this->drupalGet('admin/commerce/orders/' . $order->order_id . '/history'); + $this->assertResponse(403, 'Anonymoous users cannot view order history.'); + } +} + +/** + * Class CommerceMessageOrderNotificationTest + * + * @see MailTestCase + */ +class CommerceMessageOrderNotificationTest extends CommerceMessageTestBase implements MailSystemInterface { + /** + * The most recent message that was sent through the test case. + */ + private static $sent_message; + + public static function getInfo() { + return array( + 'name' => 'Commerce Message order notification', + 'description' => 'Tests messages as order history', + 'group' => 'Commerce Message', + 'dependencies' => array('rules'), + ); + } + + public function setUp() { + parent::setUp(); + // Set MailTestCase (i.e. this class) as the SMTP library. + variable_set('mail_system', array('default-system' => 'CommerceMessageOrderNotificationTest')); + } + + /** + * Concatenate and wrap the e-mail body for plain-text mails. + * + * @see DefaultMailSystem + */ + public function format(array $message) { + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + // Convert any HTML to plain-text. + $message['body'] = drupal_html_to_text($message['body']); + // Wrap the mail body for sending. + $message['body'] = drupal_wrap_mail($message['body']); + return $message; + } + + /** + * Send function that is called through the mail system. + */ + public function mail(array $message) { + self::$sent_message = $message; + } + + + /** + * Test the order message's content. + */ + public function testOrderNotificationMessageContent() { + $order = $this->createDummyOrder(); + $message = message_create('commerce_order_order_confirmation', array(), $this->store_admin); + $message->wrapper()->message_commerce_order = $order; + $message->save(); + $mid = $message->mid; + + // Reload the message to see it was saved. + $message = message_load($mid); + $this->assertTrue(!empty($message->mid), t('Message was saved to the database.')); + + $message = $message->buildContent(); + + global $language; + $url_options = array( + 'absolute' => TRUE, + 'language' => $language, + ); + + $this->assertEqual( + t('Order @order_id at @site', array( + '@order_id' => $order->order_id, + '@site' => variable_get('site_name', 'Drupal'), + )), + $message['message__message_text__0']['#markup']); + + $body = t('Thanks for your order @order_id at @site. + +If this is your first order with us, you will receive a separate e-mail with login instructions. You can view your order history with us at any time by logging into our website at: + +@login-url + +You can find the status of your current order at: + +@order-view + +Please contact us if you have any questions about your order.', array( + '@order_id' => $order->order_id, + '@site' => variable_get('site_name', 'Drupal'), + '@login-url' => url('user', $url_options), + '@order-view' => url('user/' . $order->uid . '/orders/' . $order->order_id, $url_options), + )); + $this->assertEqual($body, $message['message__message_text__1']['#markup']); + } + + /** + * Tests that the notification is generated on checkout complete. + */ + public function testOrderNotificationMessageCreation() { + $order = $this->createDummyOrder(); + commerce_checkout_complete($order); + + $messages = $this->loadMessagesForOrder($order); + foreach ($messages as $message) { + if ($message->bundle() == 'commerce_order_order_confirmation') { + $this->pass('Order that completed checkout had an order notification generated.'); + } + } + + $this->assertNotNull(self::$sent_message); + } +} + +/** + * Class CommerceMessageAdminOrderNotificationTest + * + * @see MailTestCase + */ +class CommerceMessageAdminOrderNotificationTest extends CommerceMessageTestBase implements MailSystemInterface { + /** + * The most recent message that was sent through the test case. + */ + private static $sent_message; + + public static function getInfo() { + return array( + 'name' => 'Commerce Message admin order notification', + 'description' => 'Tests messages as order history', + 'group' => 'Commerce Message', + 'dependencies' => array('rules'), + ); + } + + public function setUp() { + parent::setUp(); + // Set MailTestCase (i.e. this class) as the SMTP library. + variable_set('mail_system', array('default-system' => 'CommerceMessageAdminOrderNotificationTest')); + } + + /** + * Concatenate and wrap the e-mail body for plain-text mails. + * + * @see DefaultMailSystem + */ + public function format(array $message) { + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + // Convert any HTML to plain-text. + $message['body'] = drupal_html_to_text($message['body']); + // Wrap the mail body for sending. + $message['body'] = drupal_wrap_mail($message['body']); + return $message; + } + + /** + * Send function that is called through the mail system. + */ + public function mail(array $message) { + self::$sent_message = $message; + } + + + /** + * Test the order message's content. + */ + public function testOrderNotificationMessageContent() { + $order = $this->createDummyOrder(); + $message = message_create('commerce_order_admin_order_confirmation', array(), $this->store_admin); + $message->wrapper()->message_commerce_order = $order; + $message->save(); + $mid = $message->mid; + + // Reload the message to see it was saved. + $message = message_load($mid); + $this->assertTrue(!empty($message->mid), t('Message was saved to the database.')); + + $message = $message->buildContent(); + + global $language; + $url_options = array( + 'absolute' => TRUE, + 'language' => $language, + ); + + $this->assertEqual( + t('Order @order_id at @site', array( + '@order_id' => $order->order_id, + '@site' => variable_get('site_name', 'Drupal'), + )), + $message['message__message_text__0']['#markup']); + + $body = t('A new order (@order_id) has been placed at @site. +Here is a link to the order: + +@order-view', array( + '@order_id' => $order->order_id, + '@site' => variable_get('site_name', 'Drupal'), + '@login-url' => url('user', $url_options), + '@order-view' => url('user/' . $order->uid . '/orders/' . $order->order_id, $url_options), + )); + $this->assertEqual($body, $message['message__message_text__1']['#markup']); + } + + /** + * Tests that the notification is generated on checkout complete. + */ + public function testOrderNotificationMessageCreation() { + $order = $this->createDummyOrder(); + commerce_checkout_complete($order); + + $messages = $this->loadMessagesForOrder($order); + foreach ($messages as $message) { + if ($message->bundle() == 'commerce_order_admin_order_confirmation') { + $this->pass('Order that completed checkout had an admin order notification generated.'); + } + } + + $this->assertNotNull(self::$sent_message); + } +} + +class CommerceMessageTokenTest extends CommerceMessageTestBase { + + public static function getInfo() { + return array( + 'name' => 'Commerce Message order summary token', + 'description' => 'Tests the order summary token', + 'group' => 'Commerce Message', + ); + } + + public function testOrderSummaryToken() { + $order = $this->createDummyOrder(); + $message = message_create('commerce_order_order_confirmation', array(), $this->store_admin); + $message->wrapper()->message_commerce_order = $order; + $message->save(); + + $this->assertTrue(isset($message->arguments['!order-summary'])); + + $message = message_create('commerce_order_admin_order_confirmation', array(), $this->store_admin); + $message->wrapper()->message_commerce_order = $order; + $message->save(); + + $this->assertTrue(isset($message->arguments['!order-summary'])); + } + + public function testTokenCustomMessageType() { + $message_type = message_type_create('foo', array('message_text' => array(LANGUAGE_NONE => array(array('value' => 'Example text.\n\n!order-summary'))))); + $message_type->save(); + + $message = message_create('foo', array(), $this->store_admin); + $message->save(); + + $this->assertFalse(isset($message->arguments['!order-summary'])); + + // Attach field to message type. + $fields['message_commerce_order']['field'] = array( + 'type' => 'entityreference', + 'module' => 'entityreference', + 'cardinality' => '1', + 'translatable' => FALSE, + 'settings' => array( + 'target_type' => 'commerce_order', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array(), + 'sort' => array( + 'type' => 'property', + 'property' => 'order_id', + 'direction' => 'ASC', + ), + ), + ), + 'locked' => TRUE, + ); + $fields['message_commerce_order']['instances'][] = array( + 'entity_type' => 'message', + 'bundles' => array('foo'), + 'label' => 'Order', + 'required' => TRUE, + 'widget' => array( + 'type' => 'entityreference_autocomplete', + 'module' => 'entityreference', + 'settings' => array( + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'path' => '', + ), + ), + 'settings' => array(), + 'display' => array( + 'default' => array( + 'label' => 'above', + 'type' => 'entityreference_label', + 'settings' => array( + 'link' => FALSE, + ), + 'module' => 'entityreference', + 'weight' => 0, + ), + ), + ); + foreach ($fields as $field_name => $info) { + $field = $info['field']; + $field += array( + 'field_name' => $field_name, + ); + if (!field_info_field($field_name)) { + field_create_field($field); + } + + foreach ($info['instances'] as $instance) { + foreach ($instance['bundles'] as $bundle) { + $instance['bundle'] = $bundle; + unset($instance['bundles']); + $instance['field_name'] = $field_name; + if (!field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle'])) { + field_create_instance($instance); + } + } + } + } + + $order = $this->createDummyOrder(); + $message = message_create('foo', array(), $this->store_admin); + $message->wrapper()->message_commerce_order = $order; + $message->save(); + + $this->assertTrue(isset($message->arguments['!order-summary'])); + + } + + public function testCloneHasToken() { + $this->drupalLogin($this->store_admin); + $edit = array( + 'name' => 'commerce_order_order_confirmation_cloned_', + 'description' => 'Commerce Order: order confirmation (cloned)', + ); + $this->drupalPost('admin/structure/messages/manage/commerce_order_order_confirmation/clone', $edit, t('Save message type')); + + if (!message_type_load('commerce_order_order_confirmation_cloned_')) { + $this->fail('Failed to clone the message type'); + } + + $this->drupalGet('admin/structure/messages/manage/commerce_order_order_confirmation_cloned_/fields'); + $this->assertText('message_commerce_order'); + + } +} + +class CommerceMessageOrderSummaryTest extends CommerceMessageTestBase { + public static function getInfo() { + return array( + 'name' => 'Commerce Message order summary token', + 'description' => 'Tests the order summary token', + 'group' => 'Commerce Message', + ); + } + + public function testViewAsOrderOwner() { + $original_user = $GLOBALS['user']; + + $customer = $this->createStoreCustomer(); + $order = $this->createDummyOrder($customer->uid); + $message = message_create('commerce_order_order_confirmation', array(), $customer); + $message->wrapper()->message_commerce_order = $order; + $message->save(); + + $GLOBALS['user'] = $customer; + $this->assertIdentical($GLOBALS['user']->uid, $customer->uid); + $summary = commerce_message_order_summary($message); + // The view-dom-id-* class is dynamic each time, so we need to check for + // the order total area class in our returned markup. + $this->assertTrue(strpos($summary, 'commerce-order-handler-area-order-total') !== FALSE); + + $GLOBALS['user'] = $this->store_admin; + $this->assertIdentical($GLOBALS['user']->uid, $this->store_admin->uid); + $summary = commerce_message_order_summary($message); + $this->assertTrue(strpos($summary, 'commerce-order-handler-area-order-total') !== FALSE); + + // Now there is a chance a payment gateway's IPN returns when there is not + // and active session. That means it'll run as anonymous and ruin our fun. + // Here we test SQL rewrite is disabled so our messages work. + $GLOBALS['user'] = drupal_anonymous_user(); + $this->assertIdentical($GLOBALS['user']->uid, 0); + $summary = commerce_message_order_summary($message); + $this->assertTrue(strpos($summary, 'commerce-order-handler-area-order-total') !== FALSE); + + // Reset just in case. + $GLOBALS['user'] = $original_user; + + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/commerce_message.message.inc b/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/commerce_message.message.inc index df6e18e2..e8496b93 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/commerce_message.message.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/commerce_message.message.inc @@ -202,6 +202,31 @@ function commerce_message_default_message_type() { "rdf_mapping" : [] }'); + $items['commerce_order_admin_order_confirmation'] = entity_import('message_type', '{ + "name" : "commerce_order_admin_order_confirmation", + "description" : "Commerce Order: admin order confirmation", + "argument_keys" : [], + "argument" : [], + "category" : "message_type", + "data" : { "purge" : { "override" : 0, "enabled" : 0, "quota" : "", "days" : "" } }, + "language" : "", + "arguments" : null, + "message_text" : { "und" : [ + { + "value" : "Order @{message:message-commerce-order:order-number} at [site:name]", + "format" : null, + "safe_value" : "Order @{message:message-commerce-order:order-number} at [site:name]" + }, + { + "value" : "A new order (@{message:message-commerce-order:order-number}) has been placed at [site:name].\r\nHere is a link to the order:\r\n\r\n[message:message-commerce-order:view-url]", + "format" : "commerce_order_message", + "safe_value" : "A new order (@{message:message-commerce-order:order-number}) has been placed at [site:name].\r\nHere is a link to the order:\r\n\r\n[message:message-commerce-order:view-url]" + } + ] + }, + "rdf_mapping" : [] + }'); + return $items; } @@ -212,9 +237,10 @@ function commerce_message_default_message_type() { function commerce_message_default_message_type_alter(&$items) { if (module_exists('locale')) { $languages = locale_language_list(); + $default_messages = array_keys(commerce_message_default_message_type()); foreach ($languages as $langcode => $langname) { foreach ($items as $message_type => $item) { - if ($item->module != 'commerce_message') { + if (!in_array($message_type, $default_messages)) { continue; } if (isset($items[$message_type]->message_text[LANGUAGE_NONE])) { @@ -261,6 +287,7 @@ function commerce_message_message_field_refresh() { 'commerce_order_user_comment', 'commerce_order_admin_comment', 'commerce_order_order_confirmation', + 'commerce_order_admin_order_confirmation', ); $fields['message_commerce_order']['instances'][] = array( 'entity_type' => 'message', @@ -563,6 +590,8 @@ function commerce_message_message_form_validate($form, &$form_state) { * Submit callback for commerce_message_message_form(). */ function commerce_message_message_form_submit($form, &$form_state) { + // Update date to current one + $form['#entity']->timestamp = REQUEST_TIME; // Notify field widgets. field_attach_submit('message', $form['#entity'], $form['content'], $form_state); entity_save('message', $form['#entity']); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/views/commerce_message.views_default.inc b/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/views/commerce_message.views_default.inc index 0b8d789e..c284b302 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/views/commerce_message.views_default.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_message/includes/views/commerce_message.views_default.inc @@ -263,17 +263,19 @@ function commerce_message_views_default_views() { 'commerce_order_message' => 'commerce_order_message', ); - /* Display: Entity content */ - $handler = $view->new_display('entity_view', 'Entity content', 'order_view'); - $handler->display->display_options['defaults']['pager'] = FALSE; - $handler->display->display_options['pager']['type'] = 'some'; - $handler->display->display_options['pager']['options']['items_per_page'] = '4'; - $handler->display->display_options['pager']['options']['offset'] = '0'; - $handler->display->display_options['entity_type'] = 'commerce_order'; - $handler->display->display_options['bundles'] = array( - 0 => 'commerce_order', - ); - $handler->display->display_options['show_title'] = 1; + if (module_exists('eva')) { + /* Display: Entity content */ + $handler = $view->new_display('entity_view', 'Entity content', 'order_view'); + $handler->display->display_options['defaults']['pager'] = FALSE; + $handler->display->display_options['pager']['type'] = 'some'; + $handler->display->display_options['pager']['options']['items_per_page'] = '4'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['entity_type'] = 'commerce_order'; + $handler->display->display_options['bundles'] = array( + 0 => 'commerce_order', + ); + $handler->display->display_options['show_title'] = 1; + } /* Display: Block */ $handler = $view->new_display('block', 'Block', 'block_1'); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/PATCHES.txt index 29bb797d..e71b2e11 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/PATCHES.txt +++ b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/PATCHES.txt @@ -1,4 +1,4 @@ The following patches have been applied to this project: -- https://www.drupal.org/files/commerce_products_source_migration-1931302-2.patch +- https://www.drupal.org/files/issues/reference_fields_should-2701333-3.patch -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file +This file was automatically generated by Drush Make (http://drupal.org/project/drush). diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/README.txt b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/README.txt index 5e9d8edf..f41460a4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/README.txt @@ -1,16 +1,18 @@ -Commerce Migrate is a general-purpose migration framework extending Migrate Module -for bringing store information into Drupal Commerce. +Commerce Migrate is a general-purpose migration framework extending Migrate +Module for bringing store information into Drupal Commerce. -- Migrate destination field handlers for commerce fields (reference fields, price field) +- Migrate destination field handlers for commerce fields (reference fields, + price field) - Migrate destination plugin for commerce product types. -Commerce Migrate depends on Migrate Extras for Entity API and Address Field integration. + Commerce Migrate depends on Migrate Extras for Entity API and Address Field + integration. Ubercart migration ------------------ Commerce Migrate Ubercart has moved to its own project, http://drupal.org/project/commerce_migrate_ubercart -It can migrate 6.x and 7.x Ubercart stores from either the existing Drupal database -or a remote database. +It can migrate 6.x and 7.x Ubercart stores from either the existing Drupal +database or a remote database. Price fields ------------ @@ -41,4 +43,3 @@ The Migrate handbook page at http://drupal.org/node/415260 http://cyrve.com/import http://www.gizra.com/content/data-migration-part-1 http://www.gizra.com/content/data-migration-part-2 - diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.info b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.info index db5164ac..cca541de 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.info @@ -7,13 +7,14 @@ dependencies[] = migrate_extras dependencies[] = commerce_product ; Destination plugins +files[] = plugins/destinations/commerce_license.inc files[] = plugins/destinations/commerce_product_type.inc files[] = plugins/destinations/commerce_product.inc files[] = plugins/destinations/fields.inc -; Information added by drupal.org packaging script on 2012-12-06 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2015-11-14 +version = "7.x-1.2" core = "7.x" project = "commerce_migrate" -datestamp = "1354758712" +datestamp = "1447468741" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.module b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.module index 99af93f2..da245c0c 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate.module @@ -1,6 +1,6 @@ 2, + 'groups' => array( + 'commerce_example' => array( + 'title' => t('Commerce Example Imports'), + ), + ), + 'migrations' => array( + 'CommerceExampleProduct' => array( + 'class_name' => 'CommerceMigrateExampleProductMigration', + 'group' => 'commerce_example', + ), + 'CommerceExampleProductDisplay' => array( + 'class_name' => 'CommerceMigrateExampleDisplayNodeMigration', + 'group' => 'commerce_example', + 'dependencies' => array( + 'CommerceExampleProduct', + ), + ), + ), + ); + + return $api; +} + +/** + * A simple base class that contains things common to all + * CommerceMigrateExample classes. + */ +abstract class CommerceMigrateExampleMigration extends Migration { + public function __construct($arguments = array()) { + // Always call the parent constructor first for basic setup + parent::__construct($arguments); + } + + /** + * Provide the names of the incoming CSV file columns. + */ + function csvcolumns() { + $columns[0] = array('SKU', 'SKU'); + $columns[1] = array('Title', 'Title'); + $columns[2] = array('Price', 'Price'); + $columns[3] = array('Image', 'Image'); + $columns[4] = array('Description', 'Description'); + return $columns; + } +} + +/** + * Import Products into the "product" product type. + * + * This simple migration assumes that a product type called 'product' + * already exists, and has the sku, title, commerce_price, and field_image + * fields. + */ +class CommerceMigrateExampleProductMigration extends CommerceMigrateExampleMigration { + public function __construct($arguments = array()) { + parent::__construct($arguments); + $this->description = t('Import products from CSV file (with no header).'); + + // Create a map object for tracking the relationships between source rows + $this->map = new MigrateSQLMap($this->machineName, + array( + // The 'SKU' field in the CSV file is the key. + 'SKU' => array('type' => 'varchar', + 'length' => 24, + 'not null' => TRUE, + 'description' => 'SKU', + ), + ), + // Rather than specifying the type directly here, we would probably use + // arguments, but instead this just specifies the 'product' product type + // to make it obvious what's going on. + MigrateDestinationEntityAPI::getKeySchema('commerce_product', 'product') + ); + + // Create a MigrateSource object, which manages retrieving the input data. + $csv_file = drupal_get_path('module', 'commerce_migrate_example') . '/commerce_migrate_example.csv'; + $this->source = new MigrateSourceCSV($csv_file, $this->csvcolumns()); + + // Again here we're hardwiring the product type to 'product'. + $this->destination = new MigrateDestinationEntityAPI('commerce_product', 'product'); + + $this->addFieldMapping('sku', 'SKU'); + $this->addFieldMapping('title', 'Title'); + $this->addFieldMapping('commerce_price', 'Price'); + $this->addFieldMapping('field_image', 'Image'); + $this->addFieldMapping('field_image:file_class') + ->defaultValue('MigrateFileUri'); + $this->addFieldMapping('field_image:source_dir') + ->defaultValue(drupal_get_path('module', 'commerce_migrate_example') . '/images'); + } +} + +/** + * Import nodes of type 'product_display' from the same CSV file. + * + * This is hard-wired to the node type 'product_display' with a + * product reference field of type field_product. + */ +class CommerceMigrateExampleDisplayNodeMigration extends CommerceMigrateExampleMigration { + public function __construct($arguments = array()) { + parent::__construct($arguments); + $this->description = t('Import product display nodes from CSV file (with no header).'); + $this->dependencies = array('CommerceExampleProduct'); + + // Create a map object for tracking the relationships between source rows + $this->map = new MigrateSQLMap($this->machineName, + array( + 'SKU' => array('type' => 'varchar', + 'length' => 24, + 'not null' => TRUE, + 'description' => 'SKU', + ), + ), + MigrateDestinationNode::getKeySchema() + ); + + // Create a MigrateSource object, which manages retrieving the input data. + $csv_file = drupal_get_path('module', 'commerce_migrate_example') . '/commerce_migrate_example.csv'; + $this->source = new MigrateSourceCSV($csv_file, $this->csvcolumns()); + + $this->destination = new MigrateDestinationNode('product_display'); + + // For a multivalued import, see the field_migrate_example_country mapping + // in the Migrate Example in beer.inc. + // Here we do a single SKU per product node. + $this->addFieldMapping('field_product', 'SKU')->sourceMigration('CommerceExampleProduct'); + + $this->addFieldMapping('title', 'Title'); + $this->addFieldMapping('body', 'Description'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate_example/commerce_migrate_example.module b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate_example/commerce_migrate_example.module new file mode 100644 index 00000000..a4abe2da --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/commerce_migrate_example/commerce_migrate_example.module @@ -0,0 +1,2 @@ + $field_name, @@ -245,8 +252,8 @@ class MigrateDestinationCommerceProductType extends MigrateDestination { 'type' => 'image', 'settings' => array('image_style' => 'large', 'image_link' => ''), 'weight' => -1, - ), - 'teaser' => array( + ), + 'teaser' => array( 'label' => 'hidden', 'type' => 'image', 'settings' => array('image_style' => 'medium', 'image_link' => 'content'), @@ -257,6 +264,7 @@ class MigrateDestinationCommerceProductType extends MigrateDestination { field_create_instance($instance); } } + /** * Implementation of __toString(). * @@ -264,6 +272,6 @@ class MigrateDestinationCommerceProductType extends MigrateDestination { * Description of the destination being migrated into */ public function __toString() { - return 'Creates commerce product types'; + return 'Creates commerce product types'; } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/plugins/destinations/fields.inc b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/plugins/destinations/fields.inc index 2562de1f..fc3149ca 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_migrate/plugins/destinations/fields.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_migrate/plugins/destinations/fields.inc @@ -2,76 +2,41 @@ /** * @file - * Support for processing commerce fields (product reference, customer profile reference, price) + * Support for processing commerce fields (product reference, customer profile + * reference, price) */ -class MigrateCommerceCustomerProfileReferenceFieldHandler extends MigrateFieldHandler { +class MigrateCommerceCustomerProfileReferenceFieldHandler extends MigrateSimpleFieldHandler { public function __construct() { + parent::__construct(array( + 'value_key' => 'profile_id', + 'skip_empty' => TRUE, + )); $this->registerTypes(array('commerce_customer_profile_reference')); } - public function prepare($entity, array $field_info, array $instance, array $values) { - $migration = Migration::currentMigration(); - $arguments = (isset($values['arguments']))? $values['arguments']: array(); - $language = $this->getFieldLanguage($entity, $field_info, $arguments); - // Setup the standard Field API array for saving. - $delta = 0; - foreach (array_filter($values) as $value) { - $return[$language][$delta]['profile_id'] = $value; - $delta++; - } - if (!isset($return)) { - $return = NULL; - } - return $return; - } } -class MigrateCommerceLineItemReferenceFieldHandler extends MigrateFieldHandler { +class MigrateCommerceLineItemReferenceFieldHandler extends MigrateSimpleFieldHandler { public function __construct() { + parent::__construct(array( + 'value_key' => 'line_item_id', + 'skip_empty' => TRUE, + )); $this->registerTypes(array('commerce_line_item_reference')); } - public function prepare($entity, array $field_info, array $instance, array $values) { - $migration = Migration::currentMigration(); - $arguments = (isset($values['arguments']))? $values['arguments']: array(); - $language = $this->getFieldLanguage($entity, $field_info, $arguments); - // Setup the standard Field API array for saving. - $delta = 0; - foreach (array_filter($values) as $value) { - $return[$language][$delta]['line_item_id'] = $value; - $delta++; - } - if (!isset($return)) { - $return = NULL; - } - return $return; - } } -class MigrateCommerceProductReferenceFieldHandler extends MigrateFieldHandler { +class MigrateCommerceProductReferenceFieldHandler extends MigrateSimpleFieldHandler { public function __construct() { + parent::__construct(array( + 'value_key' => 'product_id', + 'skip_empty' => TRUE, + )); $this->registerTypes(array('commerce_product_reference')); } - public function prepare($entity, array $field_info, array $instance, array $values) { - $migration = Migration::currentMigration(); - $arguments = (isset($values['arguments']))? $values['arguments']: array(); - $language = $this->getFieldLanguage($entity, $field_info, $arguments); - // Setup the standard Field API array for saving. - $delta = 0; - if(!is_array(reset($values))) { - $values = array($values); - } - foreach (array_filter($values) as $value) { - $return[$language][$delta]['product_id'] = reset($value); - $delta++; - } - if (!isset($return)) { - $return = NULL; - } - return $return; - } } class MigrateCommercePriceFieldHandler extends MigrateFieldHandler { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_moneybookers/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/commerce_moneybookers/PATCHES.txt index 2eaca867..9f97ebf0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_moneybookers/PATCHES.txt +++ b/profiles/commerce_kickstart/modules/contrib/commerce_moneybookers/PATCHES.txt @@ -1,4 +1,4 @@ The following patches have been applied to this project: - http://drupal.org/files/commerce_moneybookers-disable_payment_method_by_default-1962226-3.patch -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file +This file was automatically generated by Drush Make (http://drupal.org/project/drush). diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.info b/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.info index fdaec056..0d2dfb89 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.info @@ -10,9 +10,9 @@ core = 7.x package = Commerce (contrib) configure = admin/config/services/nostotagging -; Information added by drupal.org packaging script on 2013-09-10 -version = "7.x-1.0" +; Information added by Drupal.org packaging script on 2016-01-07 +version = "7.x-1.1" core = "7.x" project = "commerce_nosto_tagging" -datestamp = "1378843042" +datestamp = "1452154140" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.module b/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.module index 4344917b..bdb2cd7a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_nosto_tagging/commerce_nosto_tagging.module @@ -836,18 +836,20 @@ function _commerce_nosto_tagging_get_commerce_product_image_url($commerce_produc */ function _commerce_nosto_tagging_get_commerce_product_availability($commerce_product) { if (module_exists('commerce_stock') && isset($commerce_product->commerce_stock)) { - $remaining_stock = 0; $out_of_stock = FALSE; // Commerce Stock 1.x-branch if (function_exists('commerce_stock_product_check_out_of_stock')) { + $remaining_stock = 0; $out_of_stock = commerce_stock_product_check_out_of_stock($commerce_product->product_id, 1, $remaining_stock); } // Commerce Stock 2.x-branch elseif (function_exists('commerce_stock_check_product_checkout_rule')) { - $stock_state = TRUE; + $stock_state = 0; $message = NULL; - $out_of_stock_status = commerce_stock_check_product_checkout_rule($commerce_product, 1, $stock_state, $message); - $out_of_stock = $stock_state; + commerce_stock_check_product_checkout_rule($commerce_product, 1, $stock_state, $message); + if ($stock_state == 1 || $stock_state == 2) { + $out_of_stock = TRUE; + } } if ($out_of_stock) { return COMMERCE_NOSTO_TAGGING_PRODUCT_OUT_OF_STOCK; diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/LICENSE.txt old mode 100755 new mode 100644 diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.info b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.info index 8f3cd1bf..59fa5a59 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.info @@ -10,9 +10,9 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal.test -; Information added by Drupal.org packaging script on 2014-01-14 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2017-06-19 +version = "7.x-2.4" core = "7.x" project = "commerce_paypal" -datestamp = "1389740908" +datestamp = "1497908944" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.module b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.module index 87678336..5e659c15 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/commerce_paypal.module @@ -91,18 +91,42 @@ function commerce_paypal_process_ipn($payment_method = NULL, $debug_ipn = array( $host = 'https://www.paypal.com/cgi-bin/webscr'; } - // Process the HTTP request to validate the IPN. - $response = drupal_http_request($host, array('method' => 'POST', 'data' => implode('&', $variables))); + // Setup the cURL request. + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $host); + curl_setopt($ch, CURLOPT_VERBOSE, 0); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $variables)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_NOPROGRESS, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + + // Commerce PayPal requires SSL peer verification, which may prevent out of + // date servers from successfully processing API requests. If you get an error + // related to peer verification, you may need to download the CA certificate + // bundle file from http://curl.haxx.se/docs/caextract.html, place it in a + // safe location on your web server, and update your settings.php to set the + // commerce_paypal_cacert variable to contain the absolute path of the file. + // Alternately, you may be able to update your php.ini to point to the file + // with the curl.cainfo setting. + if (variable_get('commerce_paypal_cacert', FALSE)) { + curl_setopt($ch, CURLOPT_CAINFO, variable_get('commerce_paypal_cacert', '')); + } + + $response = curl_exec($ch); // If an error occurred during processing, log the message and exit. - if (property_exists($response, 'error')) { - watchdog('commerce_paypal', 'Attempt to validate IPN failed with error @code: @error', array('@code' => $response->code, '@error' => $response->error), WATCHDOG_ERROR); + if ($error = curl_error($ch)) { + watchdog('commerce_paypal', 'Attempt to validate IPN failed with cURL error: @error', array('@error' => $error), WATCHDOG_ERROR); return FALSE; } + curl_close($ch); - // If the IPN was invalid, log a message and exit. - if ($response->data == 'INVALID') { - watchdog('commerce_paypal', 'Invalid IPN received and ignored.', array(), WATCHDOG_ALERT); + // inspect IPN validation result and act accordingly + if (strcmp ($response, "INVALID") == 0) { + // If the IPN was invalid, log a message and exit. + watchdog('commerce_paypal', 'Invalid IPN received and ignored. Response: @response', array('@response' => $response), WATCHDOG_ALERT); return FALSE; } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.info b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.info index f053f001..7e965d81 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.info @@ -8,9 +8,9 @@ dependencies[] = commerce_order dependencies[] = commerce_paypal core = 7.x -; Information added by Drupal.org packaging script on 2014-01-14 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2017-06-19 +version = "7.x-2.4" core = "7.x" project = "commerce_paypal" -datestamp = "1389740908" +datestamp = "1497908944" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.module b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.module index f6ea1b86..d179591b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/ec/commerce_paypal_ec.module @@ -875,7 +875,7 @@ function commerce_paypal_ec_paypal_ipn_process($order, $payment_method, &$ipn) { } // Exit when we don't get a payment status we recognize. - if (!in_array($ipn['payment_status'], array('Failed', 'Voided', 'Pending', 'Completed', 'Refunded'))) { + if (!in_array($ipn['payment_status'], array('Failed', 'Voided', 'Pending', 'Completed', 'Refunded', 'Denied'))) { commerce_payment_redirect_pane_previous_page($order); return FALSE; } @@ -908,6 +908,24 @@ function commerce_paypal_ec_paypal_ipn_process($order, $payment_method, &$ipn) { $transaction = commerce_payment_transaction_new('paypal_ec', $order->order_id); $transaction->instance_id = $payment_method['instance_id']; } + elseif (in_array($ipn['payment_status'], array('Completed','Denied')) && $ipn['payment_type'] === 'echeck') { + // E-checks use the same ipn transaction id, with an updated status. Check + // for existing transaction with a status that's not the same. + // Ensure there is an existing transaction. + $transaction = commerce_paypal_payment_transaction_load($ipn['txn_id']); + if (! $transaction) { + watchdog('commerce_paypal_ec', 'IPN for Order @order_number ignored: transaction not found.', array('@order_number' => $order->order_number), WATCHDOG_NOTICE); + return FALSE; + } + + // The e-check status has updated, so change the transaction details. + if ($ipn['payment_status'] == 'Completed') { + $transaction->message = '' . t('eCheck payment successful') . ''; + } + else { + $transaction->message = '' . t('eCheck payment denied') . ''; + } + } else { // In other circumstances, exit the processing, because we handle those // cases directly during API response processing. @@ -953,6 +971,11 @@ function commerce_paypal_ec_paypal_ipn_process($order, $payment_method, &$ipn) { $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; $transaction->message .= t('Refund for transaction @txn_id', array('@txn_id' => $ipn['parent_txn_id'])); break; + + case 'Denied' : + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + $transaction->message .= t("The payment has been denied. This happens only if the payment was previously pending."); + break; } // Save the transaction information. @@ -1049,7 +1072,7 @@ function commerce_paypal_ec_set_express_checkout($payment_method, $order, $flow) if (module_exists('commerce_shipping')) { // If we have a shipping address, pass it to PayPal and do not allow the // customer to set a new one at PayPal. - if (!empty($order_wrapper->commerce_customer_shipping->commerce_customer_address)) { + if (isset($order_wrapper->commerce_customer_shipping) && !empty($order_wrapper->commerce_customer_shipping->commerce_customer_address)) { $shipping_address = $order_wrapper->commerce_customer_shipping->commerce_customer_address->value(); // Ensure there's a name_line. @@ -1383,7 +1406,9 @@ function commerce_paypal_ec_customer_profile($order, $profile_type, $response, $ ); // Prepare an addressfield array to set to the customer profile. - $address = addressfield_default_values(); + $field = field_info_field('commerce_customer_address'); + $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', $profile_type); + $address = addressfield_default_values($field, $instance); // Use the first name and last name if the profile is a billing profile. if ($profile_type == 'billing') { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info index 8b4ea845..6acd0c70 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.info @@ -11,9 +11,9 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal_wps.test -; Information added by Drupal.org packaging script on 2014-01-14 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2017-06-19 +version = "7.x-2.4" core = "7.x" project = "commerce_paypal" -datestamp = "1389740908" +datestamp = "1497908944" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.module b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.module index 3d34fa70..5ed1a0e7 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/payflow/commerce_payflow.module @@ -1447,6 +1447,7 @@ function commerce_payflow_link_result_message($result) { case 0: return t('Transaction approved.'); case 1: + return t('Account authentication error. Please contact an administrator to resolve this issue.'); case 5: case 26: return t('The Payflow hosted checkout page is not configured for use. Please contact an administrator to resolve this issue.'); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info index e986ac66..8c1055e4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/commerce_paypal_wpp.info @@ -11,9 +11,9 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal_wps.test -; Information added by Drupal.org packaging script on 2014-01-14 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2017-06-19 +version = "7.x-2.4" core = "7.x" project = "commerce_paypal" -datestamp = "1389740908" +datestamp = "1497908944" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/includes/commerce_paypal_wpp.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/includes/commerce_paypal_wpp.admin.inc index 9325ac80..ee7e3498 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/includes/commerce_paypal_wpp.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wpp/includes/commerce_paypal_wpp.admin.inc @@ -98,10 +98,21 @@ function commerce_paypal_wpp_capture_form_submit($form, &$form_state) { $order = $form_state['order']; $payment_method = $form_state['payment_method']; $authorization_valid = TRUE; + $reauth_needed = TRUE; + + // Find a previous successful reauthorization. This is needed in edge cases + // where reauthorization was successful but capture failed. + foreach ($transaction->payload as $key => $payload) { + $is_reauth = strpos($key, 'reauthorization'); + if ($is_reauth && $payload['ACK'] == 'Success') { + $reauth_needed = FALSE; + break; + } + } // If the original authorization was more than 3 days ago, PayPal's honor // period is over and a reauthorization is required before capturing. - if (REQUEST_TIME - $transaction->created > 86400 * 3) { + if (REQUEST_TIME - $transaction->created > 86400 * 3 && $reauth_needed) { // Build a name-value pair array for the reauthorization. $nvp = array( 'METHOD' => 'DoReauthorization', @@ -112,7 +123,7 @@ function commerce_paypal_wpp_capture_form_submit($form, &$form_state) { // Submit the reauthorization request. $response = commerce_paypal_api_request($payment_method, $nvp, $order); - $transaction->payload[REQUEST_TIMESTAMP . '-reauthorization'] = $response; + $transaction->payload[REQUEST_TIME . '-reauthorization'] = $response; // If the response contains an authorization ID... if (!empty($response['AUTHORIZATIONID'])) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info index a2da517d..307a135b 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.info @@ -11,9 +11,9 @@ core = 7.x ; Simple tests ; files[] = tests/commerce_paypal_wps.test -; Information added by Drupal.org packaging script on 2014-01-14 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2017-06-19 +version = "7.x-2.4" core = "7.x" project = "commerce_paypal" -datestamp = "1389740908" +datestamp = "1497908944" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module index 7b761f8e..6eb51376 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_paypal/modules/wps/commerce_paypal_wps.module @@ -350,10 +350,15 @@ function commerce_paypal_wps_paypal_ipn_process($order, $payment_method, &$ipn) // Create the new profile now. $profile = commerce_customer_profile_new('billing', $order->uid); + // Prepare an addressfield array to set to the customer profile. + $field = field_info_field('commerce_customer_address'); + $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', 'billing'); + $address = addressfield_default_values($field, $instance); + // Add the address value. $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); - $profile_wrapper->commerce_customer_address = array_merge(addressfield_default_values(), array( + $profile_wrapper->commerce_customer_address = array_merge($address, array( 'country' => $ipn['residence_country'], 'name_line' => $ipn['first_name'] . ' ' . $ipn['last_name'], 'first_name' => $ipn['first_name'], @@ -472,7 +477,7 @@ function commerce_paypal_wps_order_form($form, &$form_state, $order, $settings) $form['#action'] = commerce_paypal_wps_server_url($settings['server']); foreach ($data as $name => $value) { - if (!empty($value)) { + if (isset($value)) { $form[$name] = array('#type' => 'hidden', '#value' => $value); } } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.info b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.info index 64c0d0b9..fe7f1d84 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.info @@ -8,9 +8,7 @@ dependencies[] = commerce_product_reference dependencies[] = physical core = 7.x - -; Information added by drush on 2015-08-20 -version = "" +; Information added by drush on 2016-10-22 +version = "7.x-1.x-dev" project = "commerce_physical" -datestamp = "1440110605" - +datestamp = "1477171345" \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.inline_conditions.inc b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.inline_conditions.inc new file mode 100644 index 00000000..21564626 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.inline_conditions.inc @@ -0,0 +1,75 @@ + t('Total order weight'), + 'entity type' => 'commerce_order', + 'callbacks' => array( + 'configure' => 'commerce_physical_order_weight_comparison_configure', + ), + ); + + return $conditions; +} + + /** + * Configuration callback for commerce_physical_order_weight_comparison_configure. + * + * @param array $settings + * An array of rules condition settings. + * + * @return array + * A form element array. + */ +function commerce_physical_order_weight_comparison_configure($settings) { + $form = array(); + + // Ensure the settings array contains default values. + $settings += array( + 'operator' => '>=', + 'value' => '', + 'unit' => '', + ); + + $form['operator'] = array( + '#type' => 'select', + '#multiple' => FALSE, + '#options' => commerce_numeric_comparison_operator_options_list(), + '#title' => t('Operator'), + '#title_display' => 'invisible', + '#default_value' => $settings['operator'], + '#require' => TRUE, + ); + + $form['value'] = array( + '#type' => 'textfield', + '#title' => t('Value'), + '#title_display' => 'invisible', + '#default_value' => $settings['value'], + '#size' => 5, + '#required' => TRUE, + '#element_validate' => array('element_validate_number'), + ); + + $form['unit'] = array( + '#type' => 'select', + '#multiple' => FALSE, + '#options' => physical_weight_unit_options(), + '#title' => t('Weight unit'), + '#title_display' => 'invisible', + '#default_value' => $settings['unit'], + '#require' => TRUE, + ); + + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.module b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.module index dde39e28..c5b040d0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.module @@ -2,52 +2,95 @@ /** * @file - * Provides API support for working with physical product types in Drupal Commerce. + * API for working with physical product types in Drupal Commerce. */ /** * Determines the weight field to use for a given entity. * - * @param $entity_type + * @param string $entity_type * The type of entity passed to the function. - * @param $entity + * @param object $entity * The actual entity whose weight field name should be determined. * - * @return + * @return string * The name of the field to use on the entity to find a weight value or NULL * if none was found. */ function commerce_physical_entity_weight_field_name($entity_type, $entity) { + + $field_name = commerce_physical_entity_field_name($entity_type, $entity, 'physical_weight'); + + // Allow other modules to specify a different field name. + drupal_alter('commerce_physical_entity_weight_field_name', $field_name, $entity_type, $entity); + + return $field_name; +} + +/** + * Determines the dimensions field to use for a given entity. + * + * @param string $entity_type + * The type of entity passed to the function. + * @param object $entity + * The actual entity whose weight field name should be determined. + * + * @return string + * The name of the field to use on the entity to find a weight value or NULL + * if none was found. + */ +function commerce_physical_entity_dimensions_field_name($entity_type, $entity) { + + $field_name = commerce_physical_entity_field_name($entity_type, $entity, 'physical_dimensions'); + + // Allow other modules to specify a different field name. + drupal_alter('commerce_physical_entity_dimensions_field_name', $field_name, $entity_type, $entity); + + return $field_name; +} + +/** + * Determines the field of a certain type to use for a given entity. + * + * @param string $entity_type + * The type of entity passed to the function. + * @param object $entity + * The actual entity whose weight field name should be determined. + * @param string $field_type + * The field type to use. + * + * @return string + * The name of the field to use on the entity to find a weight value or NULL + * if none was found. + */ +function commerce_physical_entity_field_name($entity_type, $entity, $field_type) { $bundle = field_extract_bundle($entity_type, $entity); $field_name = NULL; - // Look for the first weight field available for the entity. + // Look for the first field available for the entity. foreach (field_info_instances($entity_type, $bundle) as $instance_name => $instance) { // Load the field info for the current instance. $field = field_info_field($instance['field_name']); // If it's of the proper type... - if ($field['type'] == 'physical_weight') { + if ($field['type'] == $field_type) { // Save its name and exit the loop. $field_name = $instance_name; break; } } - // Allow other modules to specify a different field name. - drupal_alter('commerce_physical_entity_weight_field_name', $field_name, $entity_type, $entity); - return $field_name; } /** * Determines the weight to use for a product line item on an order. * - * @param $line_item + * @param commerce_line_item $line_item * A product line item whose weight should be determined. * - * @return + * @return array * A weight field value array representing the weight of the product line item * or NULL if none was found. */ @@ -80,15 +123,57 @@ function commerce_physical_product_line_item_weight($line_item) { return $weight; } +/** + * Determines the dimensions of a product line item on an order. + * + * @param commerce_line_item $line_item + * A product line item whose dimensions should be determined. + * + * @return array + * An array of dimensions field value arrays. There'll be one entry in the + * array per product, with the entry being an array of that product's + * dimensions. If this line item contains no products with dimensions, an + * empty array will be returned. + */ +function commerce_physical_product_line_item_dimensions($line_item) { + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $dimensions = array(); + + // If the line item references a valid product... + if (!empty($line_item_wrapper->commerce_product)) { + $product = $line_item_wrapper->commerce_product->value(); + + if (!empty($product)) { + // If the product has a valid dimensions field... + $field_name = commerce_physical_entity_dimensions_field_name('commerce_product', $product); + + if (!empty($field_name) && !empty($product->{$field_name})) { + $product_wrapper = entity_metadata_wrapper('commerce_product', $product); + + // Add dimension values per product in the line item. + for ($i = 0; $i < $line_item_wrapper->quantity->value(); $i++) { + $dimensions[] = $product_wrapper->{$field_name}->value(); + } + } + } + } + + // Allow other modules to alter the weight if necessary. + drupal_alter('commerce_physical_product_line_item_dimensions', $dimensions, $line_item); + + return $dimensions; +} + + /** * Determines the weight of an entire order. * - * @param $order + * @param commerce_order $order * The order object whose weight value should be calculated. - * @param $unit + * @param string $unit * The unit of measurement to use for the returned weight of the order. * - * @return + * @return array * A weight field value array representing the total weight of the order using * the specified unit of measurement or NULL if no weight could be determined. */ @@ -99,7 +184,7 @@ function commerce_physical_order_weight($order, $unit = 'lb') { // Loop over each line item on the order. foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { // Get the weight value of product line items. - if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { + if (in_array($line_item_wrapper->getBundle(), commerce_product_line_item_types())) { $line_item_weight = commerce_physical_product_line_item_weight($line_item_wrapper->value()); // Add it to the running total converting it to the required weight unit. @@ -123,87 +208,16 @@ function commerce_physical_order_weight($order, $unit = 'lb') { } -/** - * Determines the dimensions field to use for a given entity. - * - * @param $entity_type - * The type of entity passed to the function. - * @param $entity - * The actual entity whose dimensions field name should be determined. - * - * @return - * The name of the field to use on the entity to find a dimensions value or - * NULL if none was found. - */ -function commerce_physical_entity_dimensions_field_name($entity_type, $entity) { - $bundle = field_extract_bundle($entity_type, $entity); - $field_name = NULL; - - // Look for the first dimensions field available for the entity. - foreach (field_info_instances($entity_type, $bundle) as $instance_name => $instance) { - // Load the field info for the current instance. - $field = field_info_field($instance['field_name']); - - // If it's of the proper type... - if ($field['type'] == 'physical_dimensions') { - // Save its name and exit the loop. - $field_name = $instance_name; - break; - } - } - - // Allow other modules to specify a different field name. - drupal_alter('commerce_physical_entity_dimensions_field_name', $field_name, $entity_type, $entity); - - return $field_name; -} - -/** - * Determines the dimensions to use for a product line item on an order. - * - * @param $line_item - * A product line item whose single quantity dimensions should be determined. - * - * @return - * A dimensions field value array representing the dimensions of a single - * product referenced by the line item or NULL if no dimensions were found. - */ -function commerce_physical_product_line_item_dimensions($line_item) { - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $dimensions = NULL; - - // If the line item references a valid product... - if (!empty($line_item_wrapper->commerce_product)) { - $product = $line_item_wrapper->commerce_product->value(); - - if (!empty($product)) { - // If the product has a valid dimensions field... - $field_name = commerce_physical_entity_dimensions_field_name('commerce_product', $product); - - if (!empty($field_name) && !empty($product->{$field_name})) { - // Extract the dimensions value from the product. - $product_wrapper = entity_metadata_wrapper('commerce_product', $product); - $dimensions = $product_wrapper->{$field_name}->value(); - } - } - } - - // Allow other modules to alter the volume if necessary. - drupal_alter('commerce_physical_product_line_item_dimensions', $dimensions, $line_item); - - return $dimensions; -} - /** * Determines the volume of an entire order. * - * @param $order + * @param commerce_order $order * The order object whose volume should be calculated. - * @param $unit + * @param string $unit * The unit of measurement to convert dimensions to before calculating the * volume of the order in the related cubic unit. * - * @return + * @return array * A volume value array with keys representing the total 'volume' of the order * in the 'unit' specified or NULL if no volume could be determined. */ @@ -216,6 +230,9 @@ function commerce_physical_order_volume($order, $unit = 'in') { // Get the dimensions value of product line items. if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { $line_item_dimensions = commerce_physical_product_line_item_dimensions($line_item_wrapper->value()); + // Each product in a line item has the same dimensions, so we index into + // only the first product. + $line_item_dimensions = reset($line_item_dimensions); // Add it to the running total converting it to the required weight unit. if (!physical_field_is_empty($line_item_dimensions, array('type' => 'physical_dimensions'))) { @@ -242,13 +259,57 @@ function commerce_physical_order_volume($order, $unit = 'in') { return $volume; } + +/** + * Determines the dimensions of each product in an entire order. + * + * Other code can then use this data to figure out things like what the maximum + * dimensions of any product in the order is, or what size shipping container + * everything will fit into. + * + * @param commerce_order $order + * The order object whose dimensions should be returned. + * @param string $unit + * The unit of measurement to use for the returned dimensions. + * + * @return array + * An array of dimension arrays. One per product in the order. + * weight field value array representing the total weight of the order using + * the specified unit of measurement or NULL if no weight could be determined. + */ +function commerce_physical_order_dimensions($order, $unit = 'cm') { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + + $order_dimensions = array(); + + // Loop over each line item on the order. + foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { + // Get the weight value of product line items. + if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) { + $line_item_dimensions = commerce_physical_product_line_item_dimensions($line_item_wrapper->value()); + + $order_dimensions = array_merge($order_dimensions, $line_item_dimensions); + } + } + + // Now ensure that all dimensions supplied are in the requested units. + foreach ($order_dimensions as $key => $dimensions) { + $order_dimensions[$key] = physical_dimensions_convert($dimensions, $unit); + } + + // Allow other modules to alter the weight if necessary. + drupal_alter('commerce_physical_order_dimensions', $order_dimensions, $order, $unit); + + return $order_dimensions; +} + /** * Determines whether or not a line item should be considered shippable. * - * @param $line_item + * @param commerce_line_item $line_item * The line item object whose shippability is being determined. * - * @return + * @return bool * Boolean indicating whether or not the given line item represents something * shippable; defaults to FALSE unless the line item represents a product line * item with a discernible weight. @@ -275,10 +336,10 @@ function commerce_physical_line_item_shippable($line_item) { /** * Determines whether or not an order should be considered shippable. * - * @param $order + * @param commerce_order $order * The order object whose shippability should be determined. * - * @return + * @return bool * Boolean indicating whether or not the given order is shippable; defaults to * FALSE unless any line item on the order is determined to be shippable. */ @@ -302,14 +363,13 @@ function commerce_physical_order_shippable($order) { } /** - * Determines the name of the shipping customer profile reference field for the - * given order. + * Name of the shipping customer profile reference field for the given order. * - * @param $order + * @param commerce_order $order * The order whose shipping customer profile reference field name should be * determined. * - * @return + * @return string * The name of the field to use on the order to find shipping information or * NULL if none was found; defaults to commerce_customer_shipping if available * or another customer profile reference if not (preferring profiles other @@ -348,10 +408,10 @@ function commerce_physical_order_shipping_field_name($order) { /** * Determines the name of the phone number field of a customer profile. * - * @param $profile + * @param commerce_cutomer_profile $profile * The customer profile whose phone number field name should be determined. * - * @return + * @return string * The name of the field to use on the customer profile to find a phone number * or NULL if none was found. Defaults to field_phone or field_phone_number if * available; otherwise it's up to you to alter it in a custom module since we @@ -375,13 +435,12 @@ function commerce_physical_customer_profile_phone_number_field_name($profile) { } /** - * Determines whether or not a shipping customer profile should be considered as - * a residential address. + * Whether or not a shipping customer profile is a residential address. * - * @param $profile + * @param commerce_customer_profile $profile * The customer profile whose residential status should be determined. * - * @return + * @return bool * Boolean indicating whether or not the given profile has a residential * address; defaults to TRUE unless otherwise determined by a custom module. */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.rules.inc new file mode 100644 index 00000000..807867e3 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_physical/commerce_physical.rules.inc @@ -0,0 +1,186 @@ + t("Order total weight comparison"), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + ), + 'operator' => array( + 'type' => 'text', + 'label' => t('Operator'), + 'description' => t('The comparison operator.'), + 'options list' => 'commerce_numeric_comparison_operator_options_list', + 'restriction' => 'input', + ), + 'value' => array( + 'type' => 'decimal', + 'label' => t('Value'), + ), + 'unit' => array( + 'type' => 'text', + 'label' => t('Weight unit'), + 'options list' => 'physical_weight_unit_options', + 'restriction' => 'input', + 'default value' => 'kg', + ), + ), + 'group' => t('Commerce Physical'), + ); + + $conditions['commerce_physical_rules_order_max_dimension_comparison'] = array( + 'label' => t("Maximum product dimension comparison"), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + ), + 'operator' => array( + 'type' => 'text', + 'label' => t('Operator'), + 'description' => t('The comparison operator.'), + 'options list' => 'commerce_numeric_comparison_operator_options_list', + 'restriction' => 'input', + ), + 'value' => array( + 'type' => 'decimal', + 'label' => t('Value'), + ), + 'unit' => array( + 'type' => 'text', + 'label' => t('Dimension unit'), + 'options list' => 'physical_dimension_unit_options', + 'restriction' => 'input', + 'default value' => 'cm', + ), + ), + 'group' => t('Commerce Physical'), + ); + + $conditions['commerce_physical_rules_order_is_shippable'] = array( + 'label' => t("The order contains shippable products"), + 'parameter' => array( + 'commerce_order' => array( + 'type' => 'commerce_order', + 'label' => t('Order'), + ), + ), + 'group' => t('Commerce Physical'), + ); + + $conditions['commerce_physical_rules_line_item_is_shippable'] = array( + 'label' => t("The line item is a shippable product"), + 'parameter' => array( + 'commerce_line_item' => array( + 'type' => 'commerce_line_item', + 'label' => t('Line Item'), + ), + ), + 'group' => t('Commerce Physical'), + ); + + return $conditions; +} + + +/** + * Fetches the max. dimension in the order and compares it with a given value. + * + * Calculates the maximum single dimension of any product in the order and + * performs a comparison on it. + */ +function commerce_physical_rules_order_max_dimension_comparison($order, $operator, $value, $unit) { + + $max_dimension = 0; + + $dimension_keys = array( + 'length', + 'width', + 'height', + ); + + // Get the dimensions of every product in the order. + foreach (commerce_physical_order_dimensions($order, $unit) as $dimension) { + + // Check each of length / width / height. + foreach ($dimension_keys as $dimension_key) { + + // If this dimension's bigger than the current max, it's the new max. + if ($dimension[$dimension_key] > $max_dimension) { + $max_dimension = $dimension[$dimension_key]; + } + } + } + + switch ($operator) { + case '<': + return $max_dimension < $value; + + case '<=': + return $max_dimension <= $value; + + case '==': + return $max_dimension == $value; + + case '>=': + return $max_dimension >= $value; + + case '>': + return $max_dimension > $value; + } + + return FALSE; +} + +/** + * Calculates the order's total weight and performs a comparison on it. + */ +function commerce_physical_rules_order_weight_comparison($order, $operator, $value, $unit) { + + $order_weight = commerce_physical_order_weight($order, $unit); + + switch ($operator) { + case '<': + return $order_weight['weight'] < $value; + + case '<=': + return $order_weight['weight'] <= $value; + + case '==': + return $order_weight['weight'] == $value; + + case '>=': + return $order_weight['weight'] >= $value; + + case '>': + return $order_weight['weight'] > $value; + } + + return FALSE; +} + +/** + * Rules condition: check if the order contains shippable products. + */ +function commerce_physical_rules_order_is_shippable($order) { + return commerce_physical_order_shippable($order); +} + +/** + * Rules condition: check if the line item is a shippable product. + */ +function commerce_physical_rules_line_item_is_shippable($line_item) { + return commerce_physical_line_item_shippable($line_item); +} \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/commerce_search_api.facetapi_defaults.inc b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/commerce_search_api.facetapi_defaults.inc index eb734d51..efc28110 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/commerce_search_api.facetapi_defaults.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/commerce_search_api.facetapi_defaults.inc @@ -1,12 +1,17 @@ 'Configure the generated product display index.', 'page callback' => 'drupal_get_form', 'page arguments' => array('commerce_search_api_admin_settings_form'), - 'access callback' => 'commerce_search_api_admin_settings_access', - 'access arguments' => array('administer site configuration'), + 'access arguments' => array('configure store'), 'type' => MENU_NORMAL_ITEM, 'file' => 'includes/commerce_search_api.admin.inc', ); return $items; } -/** - * Access calback for the index admin settings form. - */ -function commerce_search_api_admin_settings_access($permission) { - return search_api_index_load('product_display') && user_access($permission); -} - /** * Implements hook_default_search_api_index(). * * Create an automatic product display index. */ function commerce_search_api_default_search_api_index() { + // Don't generate a default Search Index if unnecessary. + if (!variable_get('commerce_search_api_provide_default_index', TRUE)) { + return; + } + module_load_include('inc', 'commerce_search_api', 'includes/commerce_search_api.callbacks'); $server = search_api_server_load('frontend'); if (empty($server)) { $servers = search_api_server_load_multiple(FALSE); @@ -52,256 +50,10 @@ function commerce_search_api_default_search_api_index() { } $server = reset($servers); } - $items['product_display'] = commerce_search_api_generate_product_display_index($server, 'product_display'); + $items[COMMERCE_SEARCH_API_INDEX] = commerce_search_api_generate_product_display_index($server, COMMERCE_SEARCH_API_INDEX); return $items; } -/** - * Generate a SearchApiIndex with common defaults configuration. - * - * @param SearchApiServer $server - * The server the generated index will resides on. - * - * @param $index_machine_name - * The machine name of the generated index. - * - * @return SearchApiIndex - * The generated SearchApiIndex. - */ -function commerce_search_api_generate_product_display_index(SearchApiServer $server, $index_machine_name) { - $product_reference_fields = commerce_info_fields('commerce_product_reference', 'node'); - // We only generate an index if there are product display types. - if (empty($product_reference_fields) || !isset($server->machine_name)) { - return entity_create('search_api_index', array()); - } - $title = 'title'; - // Check if we should index the translated title or not. - if (module_exists('title') && $title_field = title_field_replacement_info('node', 'title')) { - $title = $title_field['field']['field_name']; - } - // We prepend temp_ to the machine name to prevent Search API from caching - // the wrong fields when calling the getFields() method. - $values = array( - 'name' => t('Product display'), - 'machine_name' => 'temp_' . $index_machine_name, - 'server' => $server->machine_name, - 'item_type' => 'node', - 'enabled' => 1, - 'read_only' => 0, - 'options' => array( - 'index_directly' => 1, - 'cron_limit' => 50, - 'fields' => array( - 'nid' => array( - 'type' => 'integer', - ), - 'search_api_language' => array( - 'type' => 'string', - ), - 'type' => array( - 'type' => 'string', - ), - $title => array( - 'type' => 'string', - ), - 'created' => array( - 'type' => 'date', - ), - 'changed' => array( - 'type' => 'date', - ), - 'status' => array( - 'type' => 'integer', - ), - 'search_api_aggregation_1' => array( - 'type' => 'text', - ), - ), - 'data_alter_callbacks' => array( - 'commerce_search_api_product_display_filter' => array( - 'status' => 1, - 'weight' => -10, - ), - 'commerce_search_api_alter_product_status' => array( - 'status' => 1, - 'weight' => -9, - ), - 'search_api_alter_add_aggregation' => array( - 'status' => 1, - 'weight' => 0, - 'settings' => array( - 'fields' => array( - 'search_api_aggregation_1' => array( - 'name' => t('Title (Fulltext)'), - 'type' => 'fulltext', - 'fields' => array( - $title, - ), - 'description' => t('A Fulltext aggregation of the title field.'), - ), - ), - ), - ), - ), - ), - ); - // If a product reference field is called field_product, take it. - if (isset($product_reference_fields['field_product'])) { - $product_reference_field = $product_reference_fields['field_product']; - } - // Else, take the first one. - else { - $product_reference_field = reset($product_reference_fields); - } - $type = $product_reference_field['cardinality'] != 1 ? 'list' : 'integer'; - $values['options']['fields'] += array( - $product_reference_field['field_name'] => array( - 'type' => $type, - 'entity_type' => 'commerce_product', - ), - ); - // Adding fields in the additional fields array will alow us to browse them. - $values['options']['additional fields'][$product_reference_field['field_name']] = $product_reference_field['field_name']; - $values['options']['additional fields'][$product_reference_field['field_name'] . ':commerce_price'] = $product_reference_field['field_name'] . ':commerce_price'; - - // Create a temporary node index that allows us to browse the fields. - $index = entity_create('search_api_index', $values); - $aggregation_index = 2; - foreach ($index->getFields(FALSE) as $field_name => $field) { - // Index term reference fields. - if (isset($field['entity_type']) && $field['entity_type'] == 'taxonomy_term') { - $values['options']['fields'] += array( - $field_name => array( - 'type' => $field['type'], - 'entity_type' => $field['entity_type'], - ), - // We're also indexing the name property of the taxonomy term. - $field_name . ':name' => array( - 'type' => search_api_nest_type('string', $field['type']), - ), - ); - // Check if the fulltext search is enabled for this taxonomy field. - if (strpos($field_name, ':') === FALSE && variable_get('commerce_search_api_' . $field_name . '_fulltext', 1)) { - $values['options']['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields'] += array( - 'search_api_aggregation_' . $aggregation_index => array( - 'name' => t('@field name (Fulltext)', array('@field' => $field['name'])), - 'type' => 'fulltext', - 'fields' => array( - $field_name . ':name', - ), - 'description' => t('Name property of @field as fulltext', array('@field' => $field['name'])), - ), - ); - $values['options']['fields']['search_api_aggregation_' . $aggregation_index] = array( - 'type' => 'text', - ); - } - $aggregation_index++; - } - else { - // Index the price decimal property. - if ($field_name == $product_reference_field['field_name'] . ':commerce_price:amount_decimal') { - $values['options']['fields'] += array( - $field_name => array( - 'type' => $field['type'], - ), - ); - // Ranges support, we're using the decimal amount (Useful for facets). - if (search_api_is_list_type($field['type']) && module_exists('search_api_ranges')) { - $prefix = str_replace(':', '_', $field_name); - $values['options']['fields'][$prefix . '_asc'] = array( - 'type' => 'decimal', - ); - $values['options']['fields'][$prefix . '_desc'] = array( - 'type' => 'decimal', - ); - $values['options']['data_alter_callbacks'] += array( - 'search_api_ranges_alter' => array( - 'status' => 1, - 'weight' => 1, - 'settings' => array( - 'fields' => drupal_map_assoc(array($field_name)), - ), - ), - ); - } - } - } - } - $index->options = $values['options']; - $index->machine_name = $index_machine_name; - return $index; -} - -/** - * Generate and enable facetapi facets with some defaults. - * - * @param $index_machine_name - * The machine name of the index. - * - * @return array - * An array of facets exports with some default configurations. - */ -function commerce_search_api_generate_facets($index_machine_name) { - $export = array(); - $searcher = 'search_api@' . $index_machine_name; - $index = search_api_index_load($index_machine_name); - $adapter = facetapi_adapter_load($searcher); - if ((empty($adapter) || empty($index)) || (!empty($index) && !$index->server()->supportsFeature('search_api_facets'))) { - return $export; - } - $product_attributes = commerce_info_fields('taxonomy_term_reference', 'commerce_product'); - $realm = facetapi_realm_load('block'); - $search_api_ranges_exists = module_exists('search_api_ranges'); - foreach (facetapi_get_facet_info($searcher) as $field_name => $facet_info) { - $export_facet = FALSE; - if (isset($facet_info['field type']) && $facet_info['field type'] == 'taxonomy_term') { - $widget = 'facetapi_checkbox_links'; - $export_facet = TRUE; - } - if ($search_api_ranges_exists && strpos($field_name, ':amount_decimal') !== FALSE) { - $widget = 'search_api_ranges_ui_slider'; - $export_facet = TRUE; - } - if ($export_facet) { - $facet = $adapter->getFacetSettings($facet_info, $realm); - if ($facet->export_type == EXPORT_IN_DATABASE) { - continue; - } - $facet->settings['widget'] = $widget; - $facet->settings['filters'] += array( - 'active_items' => array( - 'status' => 1, - 'weight' => -1, - ), - ); - // Adding extra filters to product attribute fields. - if (!empty($product_attributes)) { - if (strpos($field_name, ':') !== FALSE) { - list($prefix, $suffix) = explode(':', $field_name, 2); - if (isset($product_attributes[$suffix])) { - $facet->settings['filters'] += array( - 'useless_searches' => array( - 'status' => 1, - 'weight' => 0, - ), - 'hide_search_start' => array( - 'status' => 1, - 'weight' => 1, - ), - ); - } - } - } - $facet->api_version = 1; - $facet->disabled = FALSE; - $facet->enabled = TRUE; - $export[$facet->name] = $facet; - } - } - return $export; -} - /** * Implements hook_entity_property_info_alter(). */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/callback_product_status.inc b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/callback_product_status.inc index 01bb44e2..aeb99378 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/callback_product_status.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/callback_product_status.inc @@ -22,7 +22,7 @@ class CommerceSearchApiAlterProductStatus extends SearchApiAbstractAlterCallback */ public function supportsIndex(SearchApiIndex $index) { $bundles = commerce_product_reference_node_types(); - return ($index->item_type == 'node' && !empty($bundles)); + return ($index->getEntityType() == 'node' && !empty($bundles)); } /** @@ -49,7 +49,7 @@ class CommerceSearchApiAlterProductStatus extends SearchApiAbstractAlterCallback } else { foreach ($node_wrapper->{$field_name} as $delta => $product_wrapper) { - if ($product_wrapper->status->raw() == 0) { + if (!$product_wrapper->value() || $product_wrapper->status->raw() == 0) { $products_removed++; if ($products_removed != $nb_products) { $node_wrapper->{$field_name}->offsetUnset($delta); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.admin.inc index 16fdf0ce..08ec6c62 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.admin.inc @@ -13,35 +13,33 @@ * @ingroup forms */ function commerce_search_api_admin_settings_form($form, &$form_state) { - if ($index = search_api_index_load('product_display')) { - $form['#entity'] = $index; - $form['description'] = array( - '#type' => 'item', - '#title' => t('Commerce Search API'), - '#markup' => t('This page allows you to change the behavior of the generated search index.'), - ); - // Try to find taxonomy terms on - foreach ($index->getFields(FALSE) as $field_name => $field) { - if (!isset($field['entity_type']) || $field['entity_type'] != 'taxonomy_term' || strpos($field_name, ':') !== FALSE) { - continue; - } - $form['commerce_search_api_' . $field_name . '_fulltext'] = array( - '#title' => t('Allow users to search products by @field name', array('@field' => $field['name'])), - '#type' => 'checkbox', - '#default_value' => variable_get('commerce_search_api_' . $field_name . '_fulltext', 1), - ); - } - $form = system_settings_form($form); - $form['#submit'][] = 'commerce_search_api_admin_settings_form_submit'; - return $form; - } + $form['commerce_search_api_provide_default_index'] = array( + '#title' => t('Provide a default Search Index'), + '#description' => t('If checked, a default Search API Product display index will be generated with sensible defaults configuration.'), + '#type' => 'checkbox', + '#default_value' => variable_get('commerce_search_api_provide_default_index', TRUE), + ); + $form['commerce_search_api_generate_facets'] = array( + '#title' => t('Generate default facets'), + '#description' => t('If checked, defaults Facet API facets will be generated for the Product display index.'), + '#type' => 'checkbox', + '#default_value' => variable_get('commerce_search_api_generate_facets', TRUE), + '#states' => array( + 'visible' => array( + ':input[name="commerce_search_api_provide_default_index"]' => array('checked' => TRUE), + ), + ), + ); + $form = system_settings_form($form); + $form['#submit'][] = 'commerce_search_api_admin_settings_form_submit'; + return $form; } /** - * Submit callback for the commerce_search_api_admin_settings_form. + * Form submission handler for commerce_search_api_admin_settings_form(). + * + * @see commerce_search_api_admin_settings_form(). */ function commerce_search_api_admin_settings_form_submit($form, &$form_state) { entity_defaults_rebuild(array('search_api_index', 'search_api_server')); - drupal_set_message(t('You need to reindex your items because we added/remove fields, Click on "Index now".')); - $form_state['redirect'] = 'admin/config/search/search_api/index/product_display/status'; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.callbacks.inc b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.callbacks.inc new file mode 100644 index 00000000..141e253d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api.callbacks.inc @@ -0,0 +1,257 @@ +machine_name)) { + return entity_create('search_api_index', array()); + } + $title = 'title'; + // Check if we should index the translated title or not. + if (module_exists('title') && $title_field = title_field_replacement_info('node', 'title')) { + $title = $title_field['field']['field_name']; + } + // We prepend temp_ to the machine name to prevent Search API from caching + // the wrong fields when calling the getFields() method. + $values = array( + 'name' => t('Product display'), + 'machine_name' => 'temp_' . $index_machine_name, + 'server' => $server->machine_name, + 'item_type' => 'node', + 'enabled' => 1, + 'read_only' => 0, + 'options' => array( + 'index_directly' => 1, + 'cron_limit' => 50, + 'fields' => array( + 'nid' => array( + 'type' => 'integer', + ), + 'search_api_language' => array( + 'type' => 'string', + ), + 'type' => array( + 'type' => 'string', + ), + $title => array( + 'type' => 'string', + ), + 'created' => array( + 'type' => 'date', + ), + 'changed' => array( + 'type' => 'date', + ), + 'status' => array( + 'type' => 'integer', + ), + 'search_api_aggregation_1' => array( + 'type' => 'text', + ), + ), + 'data_alter_callbacks' => array( + 'commerce_search_api_product_display_filter' => array( + 'status' => 1, + 'weight' => -10, + ), + 'commerce_search_api_alter_product_status' => array( + 'status' => 1, + 'weight' => -9, + ), + 'search_api_alter_add_aggregation' => array( + 'status' => 1, + 'weight' => 0, + 'settings' => array( + 'fields' => array( + 'search_api_aggregation_1' => array( + 'name' => t('Title (Fulltext)'), + 'type' => 'fulltext', + 'fields' => array( + $title, + ), + 'description' => t('A Fulltext aggregation of the title field.'), + ), + ), + ), + ), + ), + ), + ); + // If a product reference field is called field_product, take it. + if (isset($product_reference_fields['field_product'])) { + $product_reference_field = $product_reference_fields['field_product']; + } + // Else, take the first one. + else { + $product_reference_field = reset($product_reference_fields); + } + $type = $product_reference_field['cardinality'] != 1 ? 'list' : 'integer'; + $values['options']['fields'] += array( + $product_reference_field['field_name'] => array( + 'type' => $type, + 'entity_type' => 'commerce_product', + ), + ); + // Adding fields in the additional fields array will alow us to browse them. + $values['options']['additional fields'][$product_reference_field['field_name']] = $product_reference_field['field_name']; + $values['options']['additional fields'][$product_reference_field['field_name'] . ':commerce_price'] = $product_reference_field['field_name'] . ':commerce_price'; + + // Create a temporary node index that allows us to browse the fields. + $index = entity_create('search_api_index', $values); + $aggregation_index = 2; + foreach ($index->getFields(FALSE) as $field_name => $field) { + // Index term reference fields. + if (isset($field['entity_type']) && $field['entity_type'] == 'taxonomy_term') { + $field_info = field_info_field($field_name); + // Check if the field belongs to a product display bundle to include it + // in the index and create the aggregated field. + if (!empty($field_info['bundles']['node']) && !array_intersect($field_info['bundles']['node'], $product_reference_field['bundles']['node'])) { + continue; + } + $values['options']['fields'] += array( + $field_name => array( + 'type' => $field['type'], + 'entity_type' => $field['entity_type'], + ), + // We're also indexing the name property of the taxonomy term. + $field_name . ':name' => array( + 'type' => search_api_nest_type('string', $field['type']), + ), + ); + // Check if the fulltext search is enabled for this taxonomy field. + if (strpos($field_name, ':') === FALSE) { + $values['options']['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields'] += array( + 'search_api_aggregation_' . $aggregation_index => array( + 'name' => t('@field name (Fulltext)', array('@field' => $field['name'])), + 'type' => 'fulltext', + 'fields' => array( + $field_name . ':name', + ), + 'description' => t('Name property of @field as fulltext', array('@field' => $field['name'])), + ), + ); + $values['options']['fields']['search_api_aggregation_' . $aggregation_index] = array( + 'type' => 'text', + ); + } + $aggregation_index++; + } + else { + // Index the price decimal property. + if ($field_name == $product_reference_field['field_name'] . ':commerce_price:amount_decimal') { + $values['options']['fields'] += array( + $field_name => array( + 'type' => $field['type'], + ), + ); + // Ranges support, we're using the decimal amount (Useful for facets). + if (search_api_is_list_type($field['type']) && module_exists('search_api_ranges')) { + $prefix = str_replace(':', '_', $field_name); + $values['options']['fields'][$prefix . '_asc'] = array( + 'type' => 'decimal', + ); + $values['options']['fields'][$prefix . '_desc'] = array( + 'type' => 'decimal', + ); + $values['options']['data_alter_callbacks'] += array( + 'search_api_ranges_alter' => array( + 'status' => 1, + 'weight' => 1, + 'settings' => array( + 'fields' => drupal_map_assoc(array($field_name)), + ), + ), + ); + } + } + } + } + $index->options = $values['options']; + $index->machine_name = $index_machine_name; + return $index; +} + +/** + * Generate and enable Facetapi facets with some defaults. + * + * @param $index_machine_name + * The machine name of the index. + * + * @return array + * An array of facets exports with some default configurations. + */ +function commerce_search_api_generate_facets($index_machine_name) { + $export = array(); + $searcher = 'search_api@' . $index_machine_name; + $index = search_api_index_load($index_machine_name); + $adapter = facetapi_adapter_load($searcher); + if ((empty($adapter) || empty($index)) || (!empty($index) && !$index->server()->supportsFeature('search_api_facets'))) { + return $export; + } + $product_attributes = commerce_info_fields('taxonomy_term_reference', 'commerce_product'); + $realm = facetapi_realm_load('block'); + $search_api_ranges_exists = module_exists('search_api_ranges'); + foreach (facetapi_get_facet_info($searcher) as $field_name => $facet_info) { + $export_facet = FALSE; + if (isset($facet_info['field type']) && $facet_info['field type'] == 'taxonomy_term') { + $widget = 'facetapi_checkbox_links'; + $export_facet = TRUE; + } + if ($search_api_ranges_exists && strpos($field_name, ':amount_decimal') !== FALSE) { + $widget = 'search_api_ranges_ui_slider'; + $export_facet = TRUE; + } + if ($export_facet) { + $facet = $adapter->getFacetSettings($facet_info, $realm); + if ($facet->export_type == EXPORT_IN_DATABASE) { + continue; + } + $facet->settings['widget'] = $widget; + $facet->settings['filters'] += array( + 'active_items' => array( + 'status' => 1, + 'weight' => -1, + ), + ); + // Adding extra filters to product attribute fields. + if (!empty($product_attributes)) { + if (strpos($field_name, ':') !== FALSE) { + list($prefix, $suffix) = explode(':', $field_name, 2); + if (isset($product_attributes[$suffix])) { + $facet->settings['filters'] += array( + 'useless_searches' => array( + 'status' => 1, + 'weight' => 0, + ), + 'hide_search_start' => array( + 'status' => 1, + 'weight' => 1, + ), + ); + } + } + } + $facet->api_version = 1; + $facet->disabled = FALSE; + $facet->enabled = TRUE; + $export[$facet->name] = $facet; + } + } + return $export; +} diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api_product_display_filter.inc b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api_product_display_filter.inc index f51e13ea..17b86209 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api_product_display_filter.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_search_api/includes/commerce_search_api_product_display_filter.inc @@ -22,7 +22,7 @@ class CommerceSearchApiProductDisplayFilter extends SearchApiAbstractAlterCallba */ public function supportsIndex(SearchApiIndex $index) { $bundles = commerce_product_reference_node_types(); - return ($index->item_type == 'node' && !empty($bundles)); + return ($index->getEntityType() == 'node' && !empty($bundles)); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.api.php b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.api.php index d815f567..468977f4 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.api.php +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.api.php @@ -1,19 +1,24 @@ shipping_rates array containing shipping line items for rates * calculated for the order. * - * @param $options + * @param array $options * The options array used to select a calculated shipping rate. - * @param $order + * @param object $order * The order the options array was generated for. * * @see commerce_shipping_service_rate_options() @@ -192,10 +212,12 @@ function hook_commerce_shipping_service_rate_options_alter(&$options, $order, &$ } /** - * Allows modules to alter newly created shipping line items after their service - * and unit price values have been set. + * Allows modules to alter newly created shipping line items. + * + * Shipping line items will be altered after their service and + * unit price values have been set. * - * @param $line_item + * @param object $line_item * The newly created shipping line item. * * @see commerce_shipping_line_item_new() diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.info b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.info index ed4ae4dd..2657ffec 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.info @@ -10,9 +10,9 @@ core = 7.x files[] = includes/views/handlers/commerce_shipping_handler_relationship_shipping_line_item_representative.inc -; Information added by Drupal.org packaging script on 2015-04-08 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2017-11-28 +version = "7.x-2.3" core = "7.x" project = "commerce_shipping" -datestamp = "1428479882" +datestamp = "1511853188" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.install b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.install index 3ec450e7..57396c61 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.install +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.install @@ -1,10 +1,10 @@ fields('r', array('data', 'id')) ->condition('r.name', $rules_actions_to_rename, 'IN') @@ -96,7 +96,7 @@ function commerce_shipping_update_7002(&$sandbox) { preg_match('/(\d+):"commerce_shipping_enable_plugin-/', $row->data, $matches); if (!empty($matches[1])) { $replace = $matches[1] - 7; - $data = preg_replace('/\d+(:"commerce_shipping_enable_)plugin-/', $replace . '$1' , $row->data); + $data = preg_replace('/\d+(:"commerce_shipping_enable_)plugin-/', $replace . '$1', $row->data); db_update('rules_config') ->fields(array('data' => $row->data)) ->condition('id', $row->id) @@ -108,39 +108,41 @@ function commerce_shipping_update_7002(&$sandbox) { } /** - * Rename the shipping_method field on the shipping line item to commerce_shipping_method. - * This should actually never be run on 2.x but is here for reference. + * Rename the shipping_method field on the shipping line item. */ function commerce_shipping_update_7003(&$sandbox) { + // This should actually never be run on 2.x but is here for reference. /* - commerce_shipping_line_item_configuration(array('type' => 'shipping')); - foreach (commerce_line_item_load_multiple(FALSE, array('type' => 'shipping')) as $line_item) { - $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $line_item_wrapper->commerce_shipping_method = $line_item_wrapper->shipping_method->value(); - $line_item_wrapper->save(); - } - field_delete_field('shipping_method'); + commerce_shipping_line_item_configuration(array('type' => 'shipping')); + foreach (commerce_line_item_load_multiple(FALSE, array('type' => 'shipping')) as $line_item) { + $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); + $line_item_wrapper->commerce_shipping_method = $line_item_wrapper->shipping_method->value(); + $line_item_wrapper->save(); + } + field_delete_field('shipping_method'); */ } /** * Install the shipping rate cache table for Shipping 2.x if necessary. - * This should actually never be run on 2.x but is here for reference. */ function commerce_shipping_update_7004($sandbox) { + // This should actually never be run on 2.x but is here for reference. /* - if (!db_table_exists('cache_commerce_shipping_rates')) { - $table = drupal_get_schema_unprocessed('system', 'cache'); - $table['description'] = 'Cache table for the temporary storage of base calculated shipping rates for orders.'; - $table['fields']['cid']['description'] = 'Primary Key: Order ID and shipping method the rates are for.'; - db_create_table('cache_commerce_shipping_rates', $table); - return t('Created shipping rate cache table for Shipping 2.x.'); - } - return t('Skipped creating the shipping rate cache table; it already exists.'); + if (!db_table_exists('cache_commerce_shipping_rates')) { + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for the temporary storage of base calculated shipping rates for orders.'; + $table['fields']['cid']['description'] = 'Primary Key: Order ID and shipping method the rates are for.'; + db_create_table('cache_commerce_shipping_rates', $table); + return t('Created shipping rate cache table for Shipping 2.x.'); + } + return t('Skipped creating the shipping rate cache table; it already exists.'); */ } /** + * Upgrade from Commerce Shipping 1.x. + * * Upgrade from Commerce Shipping 1.x by renaming the commerce_shipping_method * field on the shipping line item type to commerce_shipping_service and * converting price components on shipping line items to the generic Shipping diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.module b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.module index c6542997..0a6974f0 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.module @@ -16,7 +16,6 @@ * an order during the checkout process. */ - /** * Implements hook_hook_info(). */ @@ -156,7 +155,7 @@ function commerce_shipping_modules_enabled($modules) { /** * Resets default Rules if necessary when modules are enabled or disabled. * - * @param $modules + * @param array $modules * An array of module names that have been enabled or disabled. */ function _commerce_shipping_default_rules_reset($modules) { @@ -200,7 +199,7 @@ function commerce_shipping_module_implements_alter(&$implementations, $hook) { /** * Returns an array of shipping methods defined by enabled modules. * - * @return + * @return array * An associative array of shipping method arrays keyed by the method_id. */ function commerce_shipping_methods() { @@ -248,10 +247,10 @@ function commerce_shipping_methods_reset() { /** * Returns a shipping method array. * - * @param $name + * @param string $name * The machine-name of the shipping method to return. * - * @return + * @return array|bool * The fully loaded shipping method array or FALSE if not found. */ function commerce_shipping_method_load($name) { @@ -262,13 +261,13 @@ function commerce_shipping_method_load($name) { /** * Returns the human readable title of any or all shipping methods. * - * @param $name + * @param string $name * The machine-name of the shipping method whose title should be returned. If * left NULL, an array of all titles will be returned. - * @param $title_type + * @param string $title_type * The type of title to return: 'title' or 'display_title'. * - * @return + * @return array|string|bool * Either an array of all shipping method titles keyed by the machine-name or * a string containing the human readable title for the specified method. If a * method is specified that does not exist, this function returns FALSE. @@ -298,9 +297,12 @@ function commerce_shipping_method_get_title($name = NULL, $title_type = 'title') } /** - * Wraps commerce_shipping_method_get_title() for the Entity module and Field API. + * Prepares commerce_shipping_method_get_title(). + * + * Wraps commerce_shipping_method_get_title() for the Entity module + * and Field API. * - * @return + * @return array * An array of shipping method titles keyed by machine-name for use in options * lists and allowed values lists. */ @@ -311,8 +313,11 @@ function commerce_shipping_method_options_list() { /** * Returns an array of shipping services keyed by name. * - * @param $method + * @param string $method * The machine-name of a shipping method to filter the return value by. + * + * @return array + * Array of shipping services. */ function commerce_shipping_services($method = NULL) { // First check the static cache for a shipping services array. @@ -344,7 +349,13 @@ function commerce_shipping_services($method = NULL) { $shipping_service = array_merge($defaults, $shipping_service); // Merge in default callbacks. - foreach (array('rate', 'details_form', 'details_form_validate', 'details_form_submit') as $callback) { + $callbacks = array( + 'rate', + 'details_form', + 'details_form_validate', + 'details_form_submit', + ); + foreach ($callbacks as $callback) { if (!isset($shipping_service['callbacks'][$callback])) { $shipping_service['callbacks'][$callback] = $shipping_service['base'] . '_' . $callback; } @@ -385,27 +396,27 @@ function commerce_shipping_services_reset() { /** * Returns a single shipping service array. * - * @param $name + * @param string $name * The machine-name of the shipping service to return. * - * @return + * @return array|bool * The specified shipping service array or FALSE if it did not exist. */ function commerce_shipping_service_load($name) { - $shipping_services= commerce_shipping_services(); + $shipping_services = commerce_shipping_services(); return empty($shipping_services[$name]) ? FALSE : $shipping_services[$name]; } /** * Returns the human readable title of any or all shipping services. * - * @param $name + * @param string $name * The machine-name of the shipping service whose title should be returned. If * left NULL, an array of all titles will be returned. - * @param $title_type + * @param string $title_type * The type of title to return: 'title' or 'display_title'. * - * @return + * @return array|string|bool * Either an array of all shipping service titles keyed by the machine-name or * a string containing the human readable title for the specified service. If * a service is specified that does not exist, this function returns FALSE. @@ -443,7 +454,7 @@ function commerce_shipping_service_get_title($name = NULL, $title_type = 'title' /** * Wraps commerce_shipping_service_get_title() for the Entity module. * - * @return + * @return array * An array of shipping service titles keyed by machine-name as needed for * options lists. */ @@ -451,20 +462,19 @@ function commerce_shipping_service_options_list() { return commerce_shipping_service_get_title(); } - /** * Returns the specified callback for the given shipping service if one exists. * - * @param $shipping_service + * @param array $shipping_service * The shipping service info array. - * @param $callback + * @param string $callback * The callback function to return, one of: * - rate * - details_form * - details_form_validate - * - details_form_submit + * - details_form_submit. * - * @return + * @return string|bool * A string containing the name of the callback function or FALSE if it could * not be found. */ @@ -480,10 +490,12 @@ function commerce_shipping_service_callback($shipping_service, $callback) { } /** - * Collects available shipping rates for an order, adding them to the order - * object via an unsaved shipping_rates property. + * Collects available shipping rates for an order. + * + * Collect shipping rates for an order, adding them to the order object via + * an unsaved shipping_rates property. * - * @param $order + * @param object $order * The order for which rates will be collected. */ function commerce_shipping_collect_rates($order) { @@ -497,7 +509,9 @@ function commerce_shipping_collect_rates($order) { } /** - * Sorts shipping rates based on the weight property added to shipping line + * Sorts shipping rates. + * + * Sort shipping rates based on the weight property added to shipping line * items in an order's data array. */ function commerce_shipping_sort_rates($a, $b) { @@ -517,9 +531,9 @@ function commerce_shipping_sort_rates($a, $b) { * This function is typically called via the Rules action "Collect rates for a * shipping method" attached to a default Rule. * - * @param $method + * @param string $method * The machine-name of the shipping method whose services should be collected. - * @param $order + * @param object $order * The order to which the services should be made available. */ function commerce_shipping_method_collect_rates($method, $order) { @@ -528,7 +542,8 @@ function commerce_shipping_method_collect_rates($method, $order) { // Loop over each shipping service in search of matching components. foreach (commerce_shipping_services() as $name => $shipping_service) { - // If the current service matches the method and specifies a default component... + // If the current service matches the method and specifies + // a default component... if ($shipping_service['shipping_method'] == $method && $shipping_service['rules_component']) { $component_name = 'commerce_shipping_service_' . $name; @@ -548,9 +563,9 @@ function commerce_shipping_method_collect_rates($method, $order) { /** * Adds a shipping rate to the given order object for the specified service. * - * @param $service + * @param string $service * The machine-name of the shipping service to rate. - * @param $order + * @param object $order * The order for which the shipping service should be rated. */ function commerce_shipping_service_rate_order($service, $order) { @@ -568,7 +583,8 @@ function commerce_shipping_service_rate_order($service, $order) { $line_item = commerce_shipping_service_rate_calculate($service, $price, $order->order_id); $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - // Add the rate to the order as long as it doesn't have a NULL price amount. + // Add the rate to the order as long as it doesn't have + // a NULL price amount. if (!is_null($line_item_wrapper->commerce_unit_price->amount->value())) { // Include a weight property on the line item object from the shipping // service for sorting rates. @@ -580,17 +596,19 @@ function commerce_shipping_service_rate_order($service, $order) { } /** + * Creates a shipping line item and passes it through Rules. + * * Creates a shipping line item with the specified initial price and passes it * through Rules for additional calculation. * - * @param $service + * @param string $service * The machine-name of the shipping service the rate is for. - * @param $price + * @param array $price * A price array used to establish the base unit price for the shipping. - * @param $order + * @param int $order_id * If available, the order to which the shipping line item will belong. * - * @return + * @return object * The shipping line item with a calculated shipping rate. */ function commerce_shipping_service_rate_calculate($service, $price, $order_id = 0) { @@ -621,11 +639,11 @@ function commerce_shipping_service_rate_calculate($service, $price, $order_id = /** * Turns an array of shipping rates into a form element options array. * - * @param $order + * @param object $order * An order object with a shipping_rates property defined as an array of * shipping rate price arrays keyed by shipping service name. * - * @return + * @return array * An options array of calculated shipping rates labeled using the display * title of the shipping services. */ @@ -650,11 +668,11 @@ function commerce_shipping_service_rate_options($order, &$form_state) { /** * Caches shipping rates for an order. * - * @param $method + * @param string $method * The name of the shipping method the rates are being cached for. - * @param $order + * @param object $order * The order the rates were calculated for. - * @param $rates + * @param array $rates * An array of base rate price arrays keyed by shipping service name. */ function commerce_shipping_rates_cache_set($method, $order, $rates) { @@ -664,16 +682,16 @@ function commerce_shipping_rates_cache_set($method, $order, $rates) { /** * Retrieves cached shipping rates for an order. * - * @param $method + * @param string $method * The name of the shipping method the rates are being cached for. - * @param $order + * @param object $order * The order the rates were calculated for. - * @param $timeout + * @param int $timeout * Number of seconds after which cached rates should be considered invalid. * Defaults to 0, meaning cached rates are only good for the current page * request. * - * @return + * @return array|bool * A cached array of base rate price arrays keyed by shipping service name or * FALSE if no cache existed or the cache is invalid based on the timeout * parameter if specified. @@ -795,6 +813,8 @@ function commerce_shipping_line_item_title($line_item) { } /** + * Helper function. + * * Returns the elements necessary to add a shipping line item through the line * item manager widget. */ @@ -869,16 +889,16 @@ function commerce_shipping_line_item_add_form($form, &$form_state) { /** * Adds the selected shipping information to a new shipping line item. * - * @param $line_item + * @param object $line_item * The newly created line item object. - * @param $element + * @param array $element * The array representing the widget form element. - * @param $form_state + * @param array $form_state * The present state of the form upon the latest submission. - * @param $form + * @param array $form * The actual form array. * - * @return + * @return bool * NULL if all is well or an error message if something goes wrong. */ function commerce_shipping_line_item_add_form_submit($line_item, $element, &$form_state, $form) { @@ -923,18 +943,18 @@ function commerce_shipping_line_item_add_form_submit($line_item, $element, &$for /** * Creates a new shipping line item populated with the proper shipping values. * - * @param $service + * @param string $service * The machine-name of the shipping service the line item represents. - * @param $unit_price + * @param array $unit_price * A price array used to initialize the value of the line item's unit price. - * @param $order_id + * @param int $order_id * The ID of the order the line item belongs to. - * @param $data + * @param array $data * An array value to initialize the line item's data array with. - * @param $type + * @param string $type * The name of the line item type being created; defaults to 'shipping'. * - * @return + * @return Object * The shipping line item for the specified service initialized to the given * unit price. */ @@ -965,9 +985,9 @@ function commerce_shipping_line_item_new($service, $unit_price, $order_id = 0, $ /** * Populates a shipping line item with the specified values. * - * @param $service + * @param string $service * The machine-name of the shipping service the line item represents. - * @param $unit_price + * @param array $unit_price * A price array used to initialize the value of the line item's unit price. */ function commerce_shipping_line_item_populate($line_item, $service, $unit_price) { @@ -987,22 +1007,23 @@ function commerce_shipping_line_item_populate($line_item, $service, $unit_price) /** * Deletes all shipping line items on an order. * - * @param $order - * The order object to delete the shipping line items from. - * @param $skip_save - * Boolean indicating whether or not to skip saving the order in this function. + * @param object $order + * The order object to delete the shipping line items from. + * @param bool $skip_save + * Boolean indicating whether or not to skip saving the order + * in this function. */ function commerce_shipping_delete_shipping_line_items($order, $skip_save = FALSE) { $order_wrapper = entity_metadata_wrapper('commerce_order', $order); // When deleting more than one line item, metadata_wrapper will give problems - // if deleting while looping through the line items. So first remove from order - // and then delete the line items. + // if deleting while looping through the line items. So first remove from + // order and then delete the line items. $line_item_ids = array(); foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { // If this line item is a shipping line item... - if ($line_item_wrapper->type->value() == 'shipping') { + if ($line_item_wrapper->getBundle() == 'shipping') { // Store its ID for later deletion and remove the reference from the line // item reference field. $line_item_ids[] = $line_item_wrapper->line_item_id->value(); @@ -1022,28 +1043,18 @@ function commerce_shipping_delete_shipping_line_items($order, $skip_save = FALSE } } -/** - * Implements hook_commerce_order_update(). - * - * When an order is updated, we have no way of knowing if new information on the - * order demands a recalculation of shipping rates. To be safe, we simply clear - * the cached rates for an order any time that order is updated. - */ -function commerce_shipping_commerce_order_update($order) { - commerce_shipping_rates_cache_clear($order); -} - /** * Adds a shipping line item to an order. * - * @param $line_item + * @param object $line_item * An unsaved shipping line item that should be added to the order. - * @param $order + * @param object $order * The order to add the shipping line item to. - * @param $skip_save - * Boolean indicating whether or not to skip saving the order in this function. + * @param bool $skip_save + * Boolean indicating whether or not to skip saving the order + * in this function. * - * @return + * @return object|bool * The saved shipping line item object or FALSE on failure. */ function commerce_shipping_add_shipping_line_item($line_item, $order, $skip_save = FALSE) { @@ -1083,7 +1094,16 @@ function commerce_shipping_form_commerce_checkout_form_alter(&$form, &$form_stat if (commerce_shipping_recalculate_services($form)) { // Build an array to limit validation errors to customer profile data and // the currently selected shipping service. - $limit_validation = array(array('commerce_shipping', 'shipping_service'), array('commerce_shipping', 'service_details')); + $limit_validation = array( + array( + 'commerce_shipping', + 'shipping_service', + ), + array( + 'commerce_shipping', + 'service_details', + ), + ); foreach (commerce_customer_profile_types() as $type => $profile_type) { $limit_validation[] = array('customer_profile_' . $type); @@ -1133,6 +1153,8 @@ function commerce_shipping_commerce_customer_profile_copy_refresh_alter(&$comman } /** + * Validates shipping service recalculations. + * * Determines whether or not automatic shipping service recalculation is valid * for the given form. */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules.inc b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules.inc index 83c20248..5782f451 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules.inc @@ -5,6 +5,7 @@ * Rules integration for shipping. * * @addtogroup rules + * * @{ */ @@ -27,7 +28,8 @@ function commerce_shipping_rules_event_info() { 'access callback' => 'commerce_order_rules_access', ); - // Include the Line Item module's Rules integration to reuse its access callback. + // Include the Line Item module's Rules integration to reuse + // its access callback. module_load_include('inc', 'commerce_line_item', 'commerce_line_item.rules'); $events['commerce_shipping_calculate_rate'] = array( @@ -88,9 +90,9 @@ function commerce_shipping_service_rules_options_list() { /** * Checks an order for the existence of a shipping line item. * - * @param $order + * @param object $order * The order to check for a shipping line item. - * @param $service + * @param string $service * The machine-name of a particular shipping service to search for; if '-any-' * the condition returns TRUE for any found shipping line item. */ @@ -204,7 +206,6 @@ function commerce_shipping_rate_apply($order, $service_name = NULL) { $service_name = key($order->shipping_rates); } - // Delete any existing shipping line items from the order. commerce_shipping_delete_shipping_line_items($order, TRUE); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules_defaults.inc b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules_defaults.inc index 1f23a830..3cf64e2a 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules_defaults.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping.rules_defaults.inc @@ -5,17 +5,16 @@ * Default rules configurations for Shipping. */ - /** * Implements hook_default_rules_configuration(). */ function commerce_shipping_default_rules_configuration() { $rules = array(); - // When an order's status is updating to "Shopping cart" from any other status, - // its shipping line items will be deleted. This captures any cart contents - // change via Add to Cart forms / the Shopping cart Views form. It also happens - // when the customer cancels out of the checkout form. + // When an order's status is updating to "Shopping cart" from any other + // status, its shipping line items will be deleted. This captures any + // cart contents change via Add to Cart forms / the Shopping cart Views form. + // It also happens when the customer cancels out of the checkout form. $rule = rules_reaction_rule(); $rule->label = t('Delete shipping line items on shopping cart updates'); @@ -73,9 +72,9 @@ function commerce_shipping_default_rules_configuration() { $rule ->event('commerce_shipping_collect_rates') ->action('commerce_shipping_method_collect_rates', array( - 'shipping_method_name' => $name, - 'commerce_order:select' => 'commerce-order', - )); + 'shipping_method_name' => $name, + 'commerce_order:select' => 'commerce-order', + )); $rules['commerce_shipping_method_' . $name] = $rule; } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.info b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.info index c90c6453..db0d7bf8 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.info @@ -7,9 +7,9 @@ dependencies[] = commerce_shipping core = 7.x configure = admin/commerce/config/shipping -; Information added by Drupal.org packaging script on 2015-04-08 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2017-11-28 +version = "7.x-2.3" core = "7.x" project = "commerce_shipping" -datestamp = "1428479882" +datestamp = "1511853188" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.module b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.module index c6f0a21b..87ae7c84 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/commerce_shipping_ui.module @@ -212,7 +212,6 @@ function commerce_shipping_ui_help($path, $arg) { } } - /** * Implements hook_menu_local_tasks_alter(). */ diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping.checkout_pane.inc b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping.checkout_pane.inc index fed4752b..0b97407f 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping.checkout_pane.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping.checkout_pane.inc @@ -5,7 +5,6 @@ * Callback functions for the shipping module's checkout panes. */ - /** * Checkout pane callback: returns the shipping service pane's settings form. */ @@ -141,7 +140,8 @@ function commerce_shipping_pane_checkout_form($form, &$form_state, $checkout_pan $message = t('No valid shipping rates found for your order, and we require shipping service selection to complete checkout.') . '
' . t('Please verify you have supplied all required information or contact us to resolve the issue.') . ''; } else { - // If live shipping rate recalculation is required, check if we can show shipping options. + // If live shipping rate recalculation is required, check if we can show + // shipping options. if (commerce_shipping_recalculate_services($form, NULL, TRUE) && empty($form_state['recalculate'])) { $message = t('Please supply all of the required information requested above to reveal your shipping options.'); } @@ -160,7 +160,9 @@ function commerce_shipping_pane_checkout_form($form, &$form_state, $checkout_pan } /** - * Ajax callback: Returns the shipping details form elements that match the + * Ajax callback. + * + * Returns the shipping details form elements that match the * currently selected shipping service. */ function commerce_shipping_pane_service_details_refresh($form, $form_state) { @@ -178,20 +180,6 @@ function commerce_shipping_recalculate_services_refresh($form, $form_state) { * Validate callback for recalculating shipping services. */ function commerce_shipping_recalculate_services_validate($form, &$form_state) { - - // Load latest version of the order. - $order = commerce_order_load($form_state['order']->order_id); - // Check if the order is still on the same checkout step. - $checkout_page = $form_state['checkout_page']; - if ($form_state['order']->changed < $order->changed || !commerce_checkout_page_access($checkout_page, $order)) { - // Clear all errors. - form_clear_error(); - watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); - drupal_set_message(t('You are not authorized to access this command.'), 'error'); - $form_state['rebuild'] = TRUE; - return FALSE; - } - // Call all validation callbacks. $recalculate = commerce_shipping_recalculate_services($form, $form_state); @@ -210,7 +198,8 @@ function commerce_shipping_recalculate_services_validate($form, &$form_state) { $form_state['rebuild'] = TRUE; } - // Store the validation result so that it can be examined in the submit handler. + // Store the validation result so that it can be examined + // in the submit handler. $form_state['recalculate'] = $recalculate; return TRUE; @@ -261,8 +250,8 @@ function commerce_shipping_recalculate_services_submit($form, &$form_state) { $profile = $wrapper->{$source_field_name}->value(); } elseif (!empty($form_state['order']->data['profiles'][$source_id])) { - // Otherwise fallback to the customer profile referenced in the order - // data array. + // Otherwise fallback to the customer profile referenced + // in the order data array. $profile = commerce_customer_profile_load($form_state['order']->data['profiles'][$source_id]); } @@ -275,9 +264,14 @@ function commerce_shipping_recalculate_services_submit($form, &$form_state) { } // Unset any cached addressfield data for this customer profile. - foreach ($form_state['addressfield'] as $key => $value) { - if (strpos($key, 'commerce_customer_profile|' . $type) === 0) { - unset($form_state['addressfield'][$key]); + foreach (field_read_fields(array('type' => 'addressfield')) as $field) { + if (isset($form_state['input']['customer_profile_' . $type][$field['field_name']])) { + foreach ($form_state['input']['customer_profile_' . $type][$field['field_name']][LANGUAGE_NONE][0] as $key => &$value) { + if ($key == 'country') { + continue; + } + $value = ''; + } } } } @@ -298,10 +292,12 @@ function commerce_shipping_recalculate_services_submit($form, &$form_state) { // Save the order and rebuild the form to reflect the updated customer data. if ($rebuild) { // Avoid overwriting an already updated order. - $stored_order = commerce_order_load($order->order_id); + $stored_order = entity_load_unchanged('commerce_order', $order->order_id); + if ($stored_order->changed <= $order->changed) { commerce_order_save($order); } + $form_state['rebuild'] = TRUE; } } @@ -342,9 +338,9 @@ function commerce_shipping_pane_checkout_form_validate($form, &$form_state, $che if ($callback = commerce_shipping_service_callback($shipping_service, 'details_form_validate')) { $result = $callback($pane_form['service_details'], $pane_values['service_details'], $shipping_service, $order, array($checkout_pane['pane_id'], 'service_details')); - // To prevent payment method validation routines from having to return TRUE - // explicitly, only return FALSE if it was specifically returned. Otherwise - // default to TRUE. + // To prevent payment method validation routines from having to return + // TRUE explicitly, only return FALSE if it was specifically returned. + // Otherwise default to TRUE. return $result === FALSE ? FALSE : TRUE; } } @@ -406,7 +402,9 @@ function commerce_shipping_pane_checkout_form_submit($form, &$form_state, $check } /** - * Checkout pane callback: show the selected shipping service on the review pane. + * Checkout pane callback. + * + * Show the selected shipping service on the review pane. */ function commerce_shipping_pane_review($form, $form_state, $checkout_pane, $order) { $order_wrapper = entity_metadata_wrapper('commerce_order', $order); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping_ui.admin.inc b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping_ui.admin.inc index 1f2d705f..80450e97 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping_ui.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/commerce_shipping_ui.admin.inc @@ -57,7 +57,7 @@ function commerce_shipping_ui_overview($type, $method = NULL) { array( 'data' => $empty_text, 'colspan' => 2, - ) + ), ); } @@ -67,7 +67,7 @@ function commerce_shipping_ui_overview($type, $method = NULL) { /** * Builds an overview of a shipping method for display to an administrator. * - * @param $variables + * @param array $variables * An array of variables used to generate the display; by default includes the * shipping_method key with a value of the shipping method info array. * @@ -87,7 +87,7 @@ function theme_shipping_method_admin_overview($variables) { /** * Builds an overview of a shipping service for display to an administrator. * - * @param $variables + * @param array $variables * An array of variables used to generate the display; by default includes the * shipping_service key with a value of the shipping service info array. * @@ -112,7 +112,11 @@ function commerce_shipping_ui_rate_calculation_rules() { $content['enabled']['title']['#markup'] = '

' . t('Enabled shipping rate calculation rules') . '

'; - $conditions = array('event' => 'commerce_shipping_calculate_rate', 'plugin' => 'reaction rule', 'active' => TRUE); + $conditions = array( + 'event' => 'commerce_shipping_calculate_rate', + 'plugin' => 'reaction rule', + 'active' => TRUE, + ); $content['enabled']['rules'] = RulesPluginUI::overviewTable($conditions, $options); $content['enabled']['rules']['#empty'] = t('There are no active shipping rate calculation rules.'); diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/commerce_shipping.views.inc b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/commerce_shipping.views.inc index 2205dfaf..eeda8393 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/commerce_shipping.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/commerce_shipping.views.inc @@ -5,9 +5,8 @@ * Defines Views integration for Commerce Shipping. */ - /** - * Implements hook_views_data_alter() + * Implements hook_views_data_alter(). */ function commerce_shipping_views_data_alter(&$data) { // Add a relationship to the order table to join to a representative shipping diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/handlers/commerce_shipping_handler_relationship_shipping_line_item_representative.inc b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/handlers/commerce_shipping_handler_relationship_shipping_line_item_representative.inc index 3e4c979e..c5b1f6a8 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/handlers/commerce_shipping_handler_relationship_shipping_line_item_representative.inc +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/includes/views/handlers/commerce_shipping_handler_relationship_shipping_line_item_representative.inc @@ -6,14 +6,15 @@ */ class commerce_shipping_handler_relationship_shipping_line_item_representative extends views_handler_relationship { - function label() { + + public function label() { if (!isset($this->options['label'])) { return $this->ui_name(); } return $this->options['label']; } - function query() { + public function query() { // Get the JOIN type from the relationship settings. $join_type = empty($this->options['required']) ? 'LEFT' : 'INNER'; @@ -40,4 +41,5 @@ class commerce_shipping_handler_relationship_shipping_line_item_representative e $alias = 'commerce_order_shipping_line_item_representiative'; $this->alias = $this->query->add_relationship($alias, $join, 'commerce_line_item', $this->relationship); } + } diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.info b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.info index 16cc4590..39927ff5 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.info @@ -5,9 +5,9 @@ dependencies[] = commerce dependencies[] = commerce_shipping core = 7.x -; Information added by Drupal.org packaging script on 2015-04-08 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2017-11-28 +version = "7.x-2.3" core = "7.x" project = "commerce_shipping" -datestamp = "1428479882" +datestamp = "1511853188" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.module b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.module index 0c75e74a..dcfa75a6 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_shipping/modules/commerce_shipping_example.module @@ -5,7 +5,6 @@ * Defines an example shipping method for testing and development. */ - /** * Implements hook_commerce_shipping_method_info(). */ @@ -44,7 +43,9 @@ function commerce_shipping_example_commerce_shipping_service_info() { } /** - * Shipping service callback: returns a base price array for a shipping service + * Shipping service callback. + * + * Returns a base price array for a shipping service * calculated for the given order. */ function commerce_shipping_example_service_rate($shipping_service, $order) { @@ -100,7 +101,9 @@ function commerce_shipping_example_service_details_form_validate($details_form, } /** - * Shipping service callback: increases the shipping line item's unit price if + * Shipping service callback. + * + * Increases the shipping line item's unit price if * express delivery was selected. */ function commerce_shipping_example_service_details_form_submit($details_form, $details_values, $line_item) { diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.info b/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.info index 4561a201..be10e063 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.info +++ b/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.info @@ -9,10 +9,9 @@ dependencies[] = commerce dependencies[] = commerce_order dependencies[] = commerce_product - -; Information added by drush on 2015-08-20 -version = "7.x-1.1+9-dev" +; Information added by Drupal.org packaging script on 2015-05-07 +version = "7.x-1.2" core = "7.x" project = "commerce_yotpo" -datestamp = "1440110603" +datestamp = "1431015501" diff --git a/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.module b/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.module index baee1431..bfa71233 100644 --- a/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.module +++ b/profiles/commerce_kickstart/modules/contrib/commerce_yotpo/commerce_yotpo.module @@ -265,6 +265,10 @@ function commerce_yotpo_create_purchase($commerce_order) { $data['products'] = $products; $response = $yotpo->create_purchase($data); + if (!isset($response->code) || $response->code != 200) { + $code = isset($response->code) ? $response->code : 'NULL'; + watchdog('commerce_yotpo', 'Unexpected response code: @code', array('@code' => $code)); + } } else { watchdog('commerce_yotpo', 'Could not load Yotpo library'); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/CHANGELOG.txt b/profiles/commerce_kickstart/modules/contrib/ctools/CHANGELOG.txt deleted file mode 100644 index c5bd5e6d..00000000 --- a/profiles/commerce_kickstart/modules/contrib/ctools/CHANGELOG.txt +++ /dev/null @@ -1,82 +0,0 @@ -Current API VERSION: 2.0. See API.txt for more information. - -ctools 7.x-1.x-dev -================== -#1008120: "New custom content" shows empty form if custom content panes module is not enabled. -#999302 by troky: Fix jump menu. Apparently this wasn't actually committed the last time it was committed. -#1065976 by tekante and David_Rothstein: Reset plugin static cache during module enable to prevent stale data from harming export ui. -#1016510 by EclipseGC: Make the taxonomy system page functional. - -ctools 7.x-1.x-alpha2 (05-Jan-2011) -=================================== - -#911396 by alex_b: Prevent notices in export UI. -#919768 by mikey_p: Allow url options to be sent to ctools_ajax_command_url(). -#358953 by cedarm: Allow term context to return lowercase, spaces to dashes versions of terms. -#931434 by EclipseGc: Argument plugin for node revision ID. -#910656: CTools AJAX sample wizard demo "domesticated" checkbox value not stored. -#922442 by EugenMayer, neclimdul and voxpelli: Make sure ctools_include can handle '' or NULL directory. -#919956 by traviss359: Correct example in wizard advanced help. -#942968: Fix taxonomy term access rule with tag term vocabs. -#840344: node add argument had crufty code causing notices. -#944462 by longhairedgit: Invalid character in regex causes rare notice. -#938778 by dereine: Fix profile content type for D7 updates. -Add detach event to modal close so that wysiwyg can detach the editor. -Variant titles showing up as blank if more than one variant on a page. -#940016: token support was not yet updated for D7. -#940446: Skip validation on back and cancel buttons in all wizards. -#954492: Redirect not always working in wizard.inc -#955348: Lack of redirect on "Update" button in Page Manager causing data loss sometimes. -#941778: Update and save button should not appear in the "Add variant" path. -#955070 by EclipseGc: Update ctools internal page tokens to work properly on content all content. -#956890 by EclipseGc: Update views_content to not use views dependency since that is gone. -#954728 by EclipseGc: Update node template page function name to not collide with new hook_node_view(). -#946534 by EclipseGc: Add support for field content on all entitities. -#952586 by EclipseGc: Fix node_author content type. -#959206: If a context is not set when rendering content, attempt to guess the context (fixes Views panes where "From context" was added but pane was never edited.) -#961654 by benshell: drupal_alter() only supports 4 arguments. -#911362 by alex_b: Facilitate plugin cache resets for tests. -#945360 by naxoc: node_tag_new() not updated to D7. -#953804 by EclipseGc: Fix node comment rendering. -#953542 by EclipseGc: Fix node rendering. -#953776 by EclipseGc: Fix node link rendering. -#954772 by EclipseGc: Fix node build mode selection in node content type. -#954762 by EclipseGc: Fix comment forbidden theme call. -#954894 by EclipseGc: Fix breadcrumb content type. -#955180 by EclipseGc: Fix page primary navigation type. -#957190 by EclipseGc: Fix page secondary navigation type. -#957194 by EclipseGc: Remove mission content type, since D7 no longer has a site mission. -#957348 by EclipseGc: Fix search form URL path. -#952586 by andypost: Use format_username for displaying unlinked usernames. -#963800 by benshell: Fix query to fetch custom block title. -#983496 by Amitaibu: Fix term argument to use proper load function. -#989484 by Amitaibu: Fix notice in views plugin. -#982496: Fix token context. -#995026: Fix export UI during enable/disable which would throw notices and not properly set/unset menu items. -#998870 by Amitaibu: Fix notice when content has no icon by using function already designed for that. -#983576 by Amitaibu: Node view fallback task showed white screen. -#1004644 by pillarsdotnet: Update a missed theme() call to D7. -#1006162 by aspilicious: .info file cleanup. -#998312 by dereine: Support the expanded/hidden options that Views did for dependent.js -#955030: Remove no longer supported footer message content type. -Fix broken query in term context config. -#992022 by pcambra: Fix node autocomplete. -#946302 by BerdArt and arywyr: Fix PHP 5.3 reference error. -#980528 by das-peter: Notice fix with entity settings. -#999302 by troky: ctools_jump_menu() needed updating to new form parameters. -#964174: stylizer plugin theme delegation was in the wrong place, causing errors. -#991658 by burlap: Fully load the "user" context for the logged in user because not all fields are in $user. -#1014866 by das-peter: Smarter title panes, notice fix on access plugin descriptions. -#1015662 by troky: plugin .info files were not using correct filepaths. -#941780 by EclipseGc: Restore the "No blocks" functionality. -#951048 by EclipseGc: Tighter entity integration so that new entities are automatic contexts and relationships. -#941800 by me and aspilicious: Use Drupal 7 #machine_name automation on page manager pages and all export_ui defaults. -Disabled exportables and pages not properly greyed out. -#969208 by me and benshell: Get user_view and user profile working. -#941796: Recategorize blocks - -ctools 7.x-1.x-alpha1 -===================== - -Changelog reset for 7.x -Basic conversion done during sprint. diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info b/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info index dd9f3e91..59bbd18a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info @@ -5,10 +5,9 @@ dependencies[] = ctools package = Chaos tool suite version = CTOOLS_MODULE_VERSION - -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.x-1.9" +; Information added by Drupal.org packaging script on 2018-02-04 +version = "7.x-1.13" core = "7.x" project = "ctools" -datestamp = "1440020680" +datestamp = "1517704095" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.module b/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.module index afb15b9e..1050caa6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.module +++ b/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.module @@ -137,7 +137,7 @@ function bulk_export_export($cli = FALSE, $options = array()) { // Add hook_ctools_plugin_api at the top of the module code, if there is any. if ($api_code) { foreach ($api_code as $api_hook => $text) { - $api = "\n/**\n"; + $api = "\n/**\n"; $api .= " * Implements hook_$api_hook().\n"; $api .= " */\n"; $api .= "function {$module_name}_$api_hook(\$module, \$api) {\n"; @@ -148,7 +148,7 @@ function bulk_export_export($cli = FALSE, $options = array()) { } if ($module_code) { - $module = "conf['css_id'] = 'my-id'; +} + /** * @} End of "addtogroup hooks". */ diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info index 02c86f52..7c8d5f41 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info @@ -2,16 +2,32 @@ name = Chaos tools description = A library of helpful tools by Merlin of Chaos. core = 7.x package = Chaos tool suite -version = CTOOLS_MODULE_VERSION files[] = includes/context.inc files[] = includes/css-cache.inc files[] = includes/math-expr.inc files[] = includes/stylizer.inc + +; Tests. +files[] = tests/context.test +files[] = tests/css.test files[] = tests/css_cache.test +files[] = tests/ctools.plugins.test +files[] = tests/ctools.test +files[] = tests/math_expression.test +files[] = tests/math_expression_stack.test +files[] = tests/object_cache.test +files[] = tests/object_cache_unit.test +files[] = tests/page_tokens.test +files[] = tests/uuid_with_uuid.test +files[] = tests/uuid_without_uuid.test + +; Dependencies that are only used for the tests. +; @see tests/uuid_with_uuid.test +test_dependencies[] = uuid -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.x-1.9" +; Information added by Drupal.org packaging script on 2018-02-04 +version = "7.x-1.13" core = "7.x" project = "ctools" -datestamp = "1440020680" +datestamp = "1517704095" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.install b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.install index e96c7432..47f01fb7 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.install +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.install @@ -40,7 +40,19 @@ function ctools_requirements($phase) { * Implements hook_schema(). */ function ctools_schema() { - return ctools_schema_3(); + return ctools_schema_4(); +} + +/** + * Version 4 of the CTools schema. + */ +function ctools_schema_4() { + $schema = ctools_schema_3(); + + // Update the 'name' field to be 255 bytes long: + $schema['ctools_object_cache']['fields']['name']['length'] = 255; + + return $schema; } /** @@ -49,7 +61,7 @@ function ctools_schema() { function ctools_schema_3() { $schema = ctools_schema_2(); - // update the 'obj' field to be 128 bytes long: + // Update the 'obj' field to be 128 bytes long: $schema['ctools_object_cache']['fields']['obj']['length'] = 128; return $schema; @@ -61,7 +73,7 @@ function ctools_schema_3() { function ctools_schema_2() { $schema = ctools_schema_1(); - // update the 'name' field to be 128 bytes long: + // Update the 'name' field to be 128 bytes long: $schema['ctools_object_cache']['fields']['name']['length'] = 128; // Update the 'data' field to be type 'blob'. @@ -95,10 +107,10 @@ function ctools_schema_2() { 'serialize' => TRUE, ), 'filter' => array( - 'type' => 'int', - 'size' => 'tiny', - 'description' => 'Whether or not this CSS needs to be filtered.', - ), + 'type' => 'int', + 'size' => 'tiny', + 'description' => 'Whether or not this CSS needs to be filtered.', + ), ), 'primary key' => array('cid'), ); @@ -197,7 +209,7 @@ function ctools_update_6003() { if ($result) { db_delete('system')->condition('name', 'panels_views')->execute(); module_enable(array('views_content'), TRUE); - } + } } /** @@ -216,7 +228,7 @@ function ctools_update_6005() { } /** - * ctools_custom_content table was originally here, but is now moved to + * The ctools_custom_content table was originally here, but is now moved to * its own module. */ function ctools_update_6007() { @@ -230,18 +242,18 @@ function ctools_update_6007() { } /** - * ctools_object_cache needs to be defined as a blob. + * The ctools_object_cache needs to be defined as a blob. */ function ctools_update_6008() { db_delete('ctools_object_cache') ->execute(); db_change_field('ctools_object_cache', 'data', 'data', array( - 'type' => 'blob', - 'size' => 'big', - 'description' => 'Serialized data being stored.', - 'serialize' => TRUE, - ) + 'type' => 'blob', + 'size' => 'big', + 'description' => 'Serialized data being stored.', + 'serialize' => TRUE, + ) ); } @@ -263,3 +275,14 @@ function ctools_update_7001() { 'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.', )); } + +/** + * Increase the length of the ctools_object_cache.name column to 255. + */ +function ctools_update_7002() { + db_change_field('ctools_object_cache', 'name', 'name', array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + )); +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module index 008214e2..3a805808 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module @@ -9,7 +9,7 @@ * must be implemented in the module file. */ -define('CTOOLS_API_VERSION', '2.0.8'); +define('CTOOLS_API_VERSION', '2.0.9'); /** * The current working ctools version. @@ -23,7 +23,7 @@ define('CTOOLS_API_VERSION', '2.0.8'); * ; Requires CTools v7.x-1.4 or newer. * dependencies[] = ctools (>=1.4) */ -define('CTOOLS_MODULE_VERSION', '7.x-1.9'); +define('CTOOLS_MODULE_VERSION', '7.x-1.13'); /** * Test the CTools API version. @@ -75,6 +75,9 @@ define('CTOOLS_MODULE_VERSION', '7.x-1.9'); * The minimum version of CTools necessary for your software to run with it. * @param $maximum * The maximum version of CTools allowed for your software to run with it. + * + * @return bool + * TRUE if the running ctools is usable, FALSE otherwise. */ function ctools_api_version($minimum, $maximum = NULL) { if (version_compare(CTOOLS_API_VERSION, $minimum, '<')) { @@ -89,8 +92,7 @@ function ctools_api_version($minimum, $maximum = NULL) { } // ----------------------------------------------------------------------- -// General utility functions - +// General utility functions. /** * Include .inc files as necessary. * @@ -118,6 +120,7 @@ function ctools_api_version($minimum, $maximum = NULL) { * @param $dir * Optional subdirectory containing the include file. */ + function ctools_include($file, $module = 'ctools', $dir = 'includes') { static $used = array(); @@ -147,8 +150,8 @@ function ctools_form_include(&$form_state, $file, $module = 'ctools', $dir = 'in /** * Add an arbitrary path to the $form_state so it can work with form cache. * - * module_load_include uses an unfortunately annoying syntax to work, making it - * difficult to translate the more simple $path + $file syntax. + * The module_load_include() function uses an unfortunately annoying syntax to + * work, making it difficult to translate the more simple $path + $file syntax. */ function ctools_form_include_file(&$form_state, $filename) { if (!isset($form_state['build_info']['args'])) { @@ -172,6 +175,9 @@ function ctools_form_include_file(&$form_state, $filename) { * Optional module containing the include. * @param $dir * Optional subdirectory containing the include file. + * + * @return string + * A string containing the appropriate path from drupal root. */ function ctools_image_path($image, $module = 'ctools', $dir = 'images') { return drupal_get_path('module', $module) . "/$dir/" . $image; @@ -211,6 +217,9 @@ function ctools_add_css($file, $module = 'ctools', $dir = 'css') { * Optional module containing the include. * @param $dir * Optional subdirectory containing the include file. + * + * @return string + * A string containing the appropriate path from drupal root. */ function ctools_attach_css($file, $module = 'ctools', $dir = 'css') { return drupal_get_path('module', $module) . "/$dir/$file.css"; @@ -249,6 +258,9 @@ function ctools_add_js($file, $module = 'ctools', $dir = 'js') { * Optional module containing the include. * @param $dir * Optional subdirectory containing the include file. + * + * @return string + * A string containing the appropriate path from drupal root. */ function ctools_attach_js($file, $module = 'ctools', $dir = 'js') { return drupal_get_path('module', $module) . "/$dir/$file.js"; @@ -267,16 +279,29 @@ function ctools_get_roles() { return user_roles(); } -/* - * Break x,y,z and x+y+z into an array. Numeric only. +/** + * Parse integer sequences of the form "x,y,z" or "x+y+z" into separate values. + * + * A string with integers separated by comma (,) is reported as an 'and' set; + * separation by a plus sign (+) or a space ( ) is an 'or' set. The meaning + * of this is up to the caller. Negative or fractional numbers are not + * recognised. + * + * Additional space characters within or around the sequence are not allowed. * * @param $str * The string to parse. * - * @return $object - * An object containing - * - operator: Either 'and' or 'or' - * - value: An array of numeric values. + * @return object + * An object containing the properties: + * + * - operator: Either 'and' or 'or' when there are multiple matched values. + * Absent when invalid_input is TRUE or there is only one value. + * - value: An array of integers (never strings) from $str. An empty array is + * returned if the input is empty. A single integer input is returned + * as a single value, but no 'operator' is defined. + * - invalid_input: TRUE if input could not be parsed and the values array + * will contain just -1. This property is otherwise absent. */ function ctools_break_phrase($str) { $object = new stdClass(); @@ -286,7 +311,7 @@ function ctools_break_phrase($str) { $object->operator = 'or'; $object->value = preg_split('/[+ ]/', $str); } - else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) { + elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) { $object->operator = 'and'; $object->value = explode(',', $str); } @@ -304,7 +329,7 @@ function ctools_break_phrase($str) { // Doubly ensure that all values are numeric only. foreach ($object->value as $id => $value) { - $object->value[$id] = intval($value); + $object->value[$id] = (int) $value; } return $object; @@ -314,23 +339,29 @@ function ctools_break_phrase($str) { * Set a token/value pair to be replaced later in the request, specifically in * ctools_page_token_processing(). * - * @param $token + * @param string $token * The token to be replaced later, during page rendering. This should - * ideally be a string inside of an HTML comment, so that if there is - * no replacement, the token will not render on the page. - * @param $type + * ideally be a string inside of an HTML comment, so that if there is + * no replacement, the token will not render on the page. + * If $token is NULL, the token set is not changed, but is still + * returned. + * @param string $type * The type of the token. Can be either 'variable', which will pull data - * directly from the page variables - * @param $argument - * If $type == 'variable' then argument should be the key to fetch from - * the $variables. If $type == 'callback' then it should either be the - * callback, or an array that will be sent to call_user_func_array(). - * - * @return + * directly from the page variables, or 'callback', which causes a function + * to be called to calculate the value. No other values are supported. + * @param string|array $argument + * For $type of: + * - 'variable': argument should be the key to fetch from the $variables. + * - 'callback': then it should either be the callback function name as a + * string, or an array that will be sent to call_user_func_array(). Argument + * arrays must not use array keys (i.e. $a[0] is the first and $a[1] the + * second element, etc.) + * + * @return array * A array of token/variable names to be replaced. */ function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) { - static $tokens = array(); + $tokens = &drupal_static('ctools_set_page_token', array()); if (isset($token)) { $tokens[$token] = array($type, $argument); @@ -339,13 +370,32 @@ function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) { } /** - * Easily set a token from the page variables. + * Reset the defined page tokens within this request. + * + * Introduced for simpletest purposes. Normally not needed. + */ +function ctools_reset_page_tokens() { + drupal_static_reset('ctools_set_page_token'); +} + +/** + * Set a replacement token from the containing element's children during #post_render. * * This function can be used like this: - * $token = ctools_set_variable_token('tabs'); + * $token = ctools_set_variable_token('tabs'); + * + * The token "" would then be replaced by the value of + * this element's (sibling) render array key 'tabs' during post-render (or be + * deleted if there was no such key by that point). + * + * @param string $token + * The token string for the page callback, e.g. 'title'. + * + * @return string + * The constructed token. * - * $token will then be a simple replacement for the 'tabs' about of the - * variables available in the page template. + * @see ctools_set_callback_token() + * @see ctools_page_token_processing() */ function ctools_set_variable_token($token) { $string = ''; @@ -354,10 +404,45 @@ function ctools_set_variable_token($token) { } /** - * Easily set a token from the page variables. + * Set a replacement token from the value of a function during #post_render. * * This function can be used like this: - * $token = ctools_set_variable_token('id', 'mymodule_myfunction'); + * $token = ctools_set_callback_token('id', 'mymodule_myfunction'); + * + * Or this (from its use in ctools_page_title_content_type_render): + * $token = ctools_set_callback_token('title', array( + * 'ctools_page_title_content_type_token', $conf['markup'], $conf['id'], $conf['class'] + * ) + * ); + * + * The token (e.g: "") + * would then be replaced during post-render by the return value of: + * + * ctools_page_title_content_type_token($value_markup, $value_id, $value_class); + * + * @param string $token + * The token string for the page callback, e.g. 'title'. + * + * @param string|array $callback + * For callback functions that require no args, the name of the function as a + * string; otherwise an array of two or more elements: the function name + * followed by one or more function arguments. + * + * NB: the value of $callback must be a procedural (non-class) function that + * passes the php function_exists() check. + * + * The callback function itself will be called with args dependent + * on $callback. If: + * - $callback is a string, the function is called with a reference to the + * render array; + * - $callback is an array, the function is called with $callback merged + * with an array containing a reference to the render array. + * + * @return string + * The constructed token. + * + * @see ctools_set_variable_token() + * @see ctools_page_token_processing() */ function ctools_set_callback_token($token, $callback) { // If the callback uses arguments they are considered in the token. @@ -384,9 +469,9 @@ function ctools_set_no_blocks($blocks = FALSE) { /** * Wrapper function to create UUIDs via ctools, falls back on UUID module * if it is enabled. This code is a copy of uuid.inc from the uuid module. + * * @see http://php.net/uniqid#65879 */ - function ctools_uuid_generate() { if (!module_exists('uuid')) { ctools_include('uuid'); @@ -413,6 +498,7 @@ function ctools_uuid_generate() { /** * Check that a string appears to be in the format of a UUID. + * * @see http://drupal.org/project/uuid * * @param $uuid @@ -468,6 +554,8 @@ function ctools_class_add($classes, $hook = 'html') { */ function ctools_class_remove($classes, $hook = 'html') { if (!is_array($classes)) { + // @todo Consider using explode(' ', $classes); + // @todo Consider checking that $classes is a string before adding. $classes = array($classes); } @@ -480,12 +568,35 @@ function ctools_class_remove($classes, $hook = 'html') { } } -// ----------------------------------------------------------------------- -// Drupal core hooks +/** + * Reset the storage used for ctools_class_add and ctools_class_remove. + * + * @see ctools_class_add() + * @see ctools_class_remove() + */ +function ctools_class_reset() { + drupal_static_reset('ctools_process_classes'); +} + +/** + * Return the classes for the body (added by ctools_class_add). + * + * @return array + * A copy of the array of classes to add to the body tag. If none have been + * added, this will be an empty array. + * + * @see ctools_class_add() + */ +function ctools_get_classes() { + return drupal_static('ctools_process_classes', array()); +} +// ----------------------------------------------------------------------- +// Drupal core hooks. /** * Implement hook_init to keep our global CSS at the ready. */ + function ctools_init() { ctools_add_css('ctools'); // If we are sure that CTools' AJAX is in use, change the error handling. @@ -504,7 +615,7 @@ function ctools_init() { * Shutdown handler used during ajax operations to help catch fatal errors. */ function ctools_shutdown_handler() { - if (function_exists('error_get_last') AND ($error = error_get_last())) { + if (function_exists('error_get_last') && ($error = error_get_last())) { switch ($error['type']) { case E_ERROR: case E_CORE_ERROR: @@ -583,7 +694,6 @@ function ctools_flush_caches() { /** * Implements hook_element_info_alter(). - * */ function ctools_element_info_alter(&$type) { ctools_include('dependent'); @@ -619,10 +729,10 @@ function ctools_registry_files_alter(&$files, $indexed_modules) { // ----------------------------------------------------------------------- // FAPI hooks that must be in the .module file. - /** * Alter the comment form to get a little more control over it. */ + function ctools_form_comment_form_alter(&$form, &$form_state) { if (!empty($form_state['ctools comment alter'])) { // Force the form to post back to wherever we are. @@ -640,11 +750,11 @@ function ctools_node_comment_form_submit(&$form, &$form_state) { // ----------------------------------------------------------------------- // CTools hook implementations. - /** * Implementation of hook_ctools_plugin_directory() to let the system know * where all our own plugins are. */ + function ctools_ctools_plugin_directory($owner, $plugin_type) { if ($owner == 'ctools') { return 'plugins/' . $plugin_type; @@ -665,11 +775,11 @@ function ctools_ctools_plugin_type() { // ----------------------------------------------------------------------- // Drupal theme preprocess hooks that must be in the .module file. - /** * A theme preprocess function to automatically allow panels-based node * templates based upon input when the panel was configured. */ + function ctools_preprocess_node(&$vars) { // The 'ctools_template_identifier' attribute of the node is added when the pane is // rendered. @@ -679,14 +789,13 @@ function ctools_preprocess_node(&$vars) { } } - /** * Implements hook_page_alter(). * * Last ditch attempt to remove sidebar regions if the "no blocks" * functionality has been activated. * - * @see ctools_block_list_alter(). + * @see ctools_block_list_alter() */ function ctools_page_alter(&$page) { $check = drupal_static('ctools_set_no_blocks', TRUE); @@ -716,6 +825,7 @@ function ctools_page_token_processing($children, $elements) { case 'variable': $tokens[$token] = isset($elements[$argument]) ? $elements[$argument] : ''; break; + case 'callback': if (is_string($argument) && function_exists($argument)) { $tokens[$token] = $argument($elements); @@ -744,7 +854,7 @@ function ctools_process(&$variables, $hook) { return; } - $classes = drupal_static('ctools_process_classes', array()); + $classes = ctools_get_classes(); // Process the classses to add. if (!empty($classes[$hook]['add'])) { @@ -765,7 +875,6 @@ function ctools_process(&$variables, $hook) { // ----------------------------------------------------------------------- // Menu callbacks that must be in the .module file. - /** * Determine if the current user has access via a plugin. * @@ -789,6 +898,7 @@ function ctools_process(&$variables, $hook) { * @return * TRUE if access is granted, false if otherwise. */ + function ctools_access_menu($access) { // Short circuit everything if there are no access tests. if (empty($access['plugins'])) { @@ -817,7 +927,7 @@ function ctools_access_menu($access) { * An indexed array of zero or more permission strings to be checked by * user_access(). * - * @return + * @return bool * Iff all checks pass will this function return TRUE. If an invalid argument * is passed (e.g., not a string), this function errs on the safe said and * returns FALSE. @@ -879,7 +989,6 @@ function ctools_export_ui_load($item_name, $plugin_name) { // ----------------------------------------------------------------------- // Caching callbacks on behalf of export-ui. - /** * Menu access callback for various tasks of export-ui. */ @@ -923,7 +1032,7 @@ function ctools_export_ui_ctools_access_get($argument) { } /** - * Callback for access control ajax form on behalf of export ui + * Callback for access control ajax form on behalf of export ui. * * Returns the cached access config and contexts used. * Note that this is assuming that access will be in $item->access -- if it @@ -956,8 +1065,9 @@ function ctools_menu_local_tasks_alter(&$data, $router_item, $root_path) { } /** - * Implement hook_block_list_alter() to potentially remove blocks. + * Implements hook_block_list_alter(). * + * Used to potentially remove blocks. * This exists in order to replicate Drupal 6's "no blocks" functionality. */ function ctools_block_list_alter(&$blocks) { @@ -1020,6 +1130,7 @@ function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { case 'entity_id:taxonomy_term': $plugin['no ui'] = TRUE; break; + case 'entity:user': $plugin = ctools_get_context('user'); unset($plugin['no ui']); @@ -1053,18 +1164,21 @@ function ctools_field_create_field($field) { function ctools_field_create_instance($instance) { ctools_flush_field_caches(); } + /** * Implements hook_field_delete_field(). */ function ctools_field_delete_field($field) { ctools_flush_field_caches(); } + /** * Implements hook_field_delete_instance(). */ function ctools_field_delete_instance($instance) { ctools_flush_field_caches(); } + /** * Implements hook_field_update_field(). */ diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info index 66ca12c0..e208eab7 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info @@ -5,9 +5,9 @@ package = Chaos tool suite version = CTOOLS_MODULE_VERSION dependencies[] = ctools -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.x-1.9" +; Information added by Drupal.org packaging script on 2018-02-04 +version = "7.x-1.13" core = "7.x" project = "ctools" -datestamp = "1440020680" +datestamp = "1517704095" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.install b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.install index 3f008772..70afb3c6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.install +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.install @@ -1,5 +1,9 @@ t('Custom rulesets are combinations of access plugins you can use for access control, selection criteria and pane visibility.'), ); - // Load all mini panels and their displays. + // Load all mini panels and their displays. ctools_include('export'); $items = ctools_export_crud_load_all('ctools_access_ruleset'); $count = 0; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc index f8abea6d..95f32c37 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc @@ -35,7 +35,7 @@ function ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item) { $plugin['required context'] = array(); foreach ($item->requiredcontexts as $context) { $info = ctools_get_context($context['name']); - // TODO: allow an optional setting + // TODO: allow an optional setting. $plugin['required context'][] = new ctools_context_required($context['identifier'], $info['context name']); } } @@ -72,7 +72,7 @@ function ctools_ruleset_ctools_access_get_children($plugin, $parent) { } /** - * Settings form for the 'by ruleset' access plugin + * Settings form for the 'by ruleset' access plugin. */ function ctools_ruleset_ctools_access_settings(&$form, &$form_state, $conf) { if (!empty($form_state['plugin']['ruleset']->admin_description)) { @@ -106,4 +106,3 @@ function ctools_ruleset_ctools_access_summary($conf, $context, $plugin) { return check_plain($plugin['ruleset']->admin_title); } } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc index d2a1c605..2589ac38 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc @@ -1,5 +1,9 @@ 'ctools_access_ruleset', 'access' => 'administer ctools access ruleset', @@ -26,4 +30,3 @@ $plugin = array( ), ), ); - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php index b1814645..c9f8c20f 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php @@ -2,7 +2,7 @@ class ctools_access_ruleset_ui extends ctools_export_ui { - function edit_form_context(&$form, &$form_state) { + public function edit_form_context(&$form, &$form_state) { ctools_include('context-admin'); ctools_context_admin_includes(); ctools_add_css('ruleset'); @@ -27,7 +27,7 @@ function edit_form_context(&$form, &$form_state) { ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $form_state['item'], $name); } - function edit_form_rules(&$form, &$form_state) { + public function edit_form_rules(&$form, &$form_state) { // The 'access' UI passes everything via $form_state, unlike the 'context' UI. // The main difference is that one is about 3 years newer than the other. ctools_include('context'); @@ -43,11 +43,12 @@ function edit_form_rules(&$form, &$form_state) { $form = ctools_access_admin_form($form, $form_state); } - function edit_form_rules_submit(&$form, &$form_state) { + public function edit_form_rules_submit(&$form, &$form_state) { $form_state['item']->access['logic'] = $form_state['values']['logic']; } - function edit_form_submit(&$form, &$form_state) { + public function edit_form_submit(&$form, &$form_state) { parent::edit_form_submit($form, $form_state); } + } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css index 8df17de5..c312e991 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css @@ -1,52 +1,52 @@ div.ctools-sample-modal-content { - background:none; - border:0; - color:#000000; - margin:0; - padding:0; - text-align:left; + background: none; + border: 0; + color: #000; + margin: 0; + padding: 0; + text-align: left; } -div.ctools-sample-modal-content .modal-scroll{ - overflow:hidden; - overflow-y:auto; +div.ctools-sample-modal-content .modal-scroll { + overflow: hidden; + overflow-y: auto; } div.ctools-sample-modal-content #popups-overlay { - background-color:transparent; + background-color: transparent; } div.ctools-sample-modal-content #popups-loading { - width:248px; - position:absolute; - display:none; - opacity:1; + width: 248px; + position: absolute; + display: none; + opacity: 1; -moz-border-radius: 8px; -webkit-border-radius: 8px; - z-index:99; + z-index: 99; } div.ctools-sample-modal-content #popups-loading span.popups-loading-message { - background:#FFF url(../images/loading-large.gif) no-repeat 8px center; - display:block; - color:#444444; - font-family:Arial; - font-size:22px; - font-weight:bold; - height:36px; - line-height:36px; - padding:0 40px; + background: #fff url(../images/loading-large.gif) no-repeat 8px center; + display: block; + color: #444; + font-family: Arial, serif; + font-size: 22px; + font-weight: bold; + height: 36px; + line-height: 36px; + padding: 0 40px; } div.ctools-sample-modal-content #popups-loading table, div.ctools-sample-modal-content .popups-box table { - margin:0px; + margin: 0; } div.ctools-sample-modal-content #popups-loading tbody, div.ctools-sample-modal-content .popups-box tbody { - border:none; + border: none; } div.ctools-sample-modal-content .popups-box tr { - background-color:transparent; + background-color: transparent; } div.ctools-sample-modal-content td.popups-border { background: url(../images/popups-border.png); - background-color:transparent; + background-color: transparent; border: none; } div.ctools-sample-modal-content td.popups-tl, @@ -54,79 +54,94 @@ div.ctools-sample-modal-content td.popups-tr, div.ctools-sample-modal-content td.popups-bl, div.ctools-sample-modal-content td.popups-br { background-repeat: no-repeat; - height:10px; - padding:0px; + height: 10px; + padding: 0; +} +div.ctools-sample-modal-content td.popups-tl { + background-position: 0 0; } -div.ctools-sample-modal-content td.popups-tl { background-position: 0px 0px; } div.ctools-sample-modal-content td.popups-t, div.ctools-sample-modal-content td.popups-b { - background-position: 0px -40px; + background-position: 0 -40px; background-repeat: repeat-x; } -div.ctools-sample-modal-content td.popups-tr { background-position: 0px -10px; width: 10px; } +div.ctools-sample-modal-content td.popups-tr { + background-position: 0 -10px; + width: 10px; +} div.ctools-sample-modal-content td.popups-cl, div.ctools-sample-modal-content td.popups-cr { background-position: -10px 0; background-repeat: repeat-y; - width:10px; + width: 10px; } div.ctools-sample-modal-content td.popups-cl, div.ctools-sample-modal-content td.popups-cr, -div.ctools-sample-modal-content td.popups-c { padding:0; border: none; } -div.ctools-sample-modal-content td.popups-c { background:#fff; } -div.ctools-sample-modal-content td.popups-bl { background-position: 0px -20px; } -div.ctools-sample-modal-content td.popups-br { background-position: 0px -30px; width: 10px; } +div.ctools-sample-modal-content td.popups-c { + padding: 0; + border: none; +} +div.ctools-sample-modal-content td.popups-c { + background: #fff; +} +div.ctools-sample-modal-content td.popups-bl { + background-position: 0 -20px; +} +div.ctools-sample-modal-content td.popups-br { + background-position: 0 -30px; + width: 10px; +} div.ctools-sample-modal-content .popups-box, div.ctools-sample-modal-content #popups-loading { - border: 0px solid #454545; - opacity:1; - overflow:hidden; - padding:0; - background-color:transparent; + border: 0 solid #454545; + opacity: 1; + overflow: hidden; + padding: 0; + background-color: transparent; } div.ctools-sample-modal-content .popups-container { - overflow:hidden; - height:100%; - background-color:#fff; + overflow: hidden; + height: 100%; + background-color: #fff; } div.ctools-sample-modal-content div.popups-title { - -moz-border-radius-topleft: 0px; - -webkit-border-radius-topleft: 0px; - margin-bottom:0px; - background-color:#ff7200; - border:1px solid #ce5c00; - padding:4px 10px 5px; - color:white; - font-size:1em; - font-weight:bold; + -moz-border-radius-topleft: 0; + -webkit-border-radius-topleft: 0; + margin-bottom: 0; + background-color: #ff7200; + border: 1px solid #ce5c00; + padding: 4px 10px 5px; + color: white; + font-size: 1em; + font-weight: bold; } div.ctools-sample-modal-content .popups-body { - background-color:#fff; - padding:8px; + background-color: #fff; + padding: 8px; } div.ctools-sample-modal-content .popups-box .popups-buttons, div.ctools-sample-modal-content .popups-box .popups-footer { - background-color:#fff; + background-color: #fff; } div.ctools-sample-modal-content .popups-title a.close { color: #fff; - text-decoration:none; + text-decoration: none; } div.ctools-sample-modal-content .popups-close { - font-size:120%; - float:right; - text-align:right; + font-size: 120%; + float: right; + text-align: right; } div.ctools-sample-modal-content .modal-loading-wrapper { - width:220px; - height:19px; - margin:0 auto; - margin-top:2%; + width: 220px; + height: 19px; + margin: 0 auto; + margin-top: 2%; } -div.ctools-sample-modal-content tbody{ - border:none; +div.ctools-sample-modal-content tbody { + border: none; } div.ctools-sample-modal-content .modal-content .modal-throbber-wrapper img { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info index f5d1e745..89fc1af3 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info @@ -5,9 +5,9 @@ version = CTOOLS_MODULE_VERSION dependencies[] = ctools core = 7.x -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.x-1.9" +; Information added by Drupal.org packaging script on 2018-02-04 +version = "7.x-1.13" core = "7.x" project = "ctools" -datestamp = "1440020680" +datestamp = "1517704095" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.install b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.install index 04325dbf..e0fdfc6f 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.install +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.install @@ -1,18 +1,18 @@ 'Chaos Tools AJAX Demo', - 'page callback' => 'ctools_ajax_sample_page', - 'access callback' => TRUE, - 'type' => MENU_NORMAL_ITEM, + 'title' => 'Chaos Tools AJAX Demo', + 'page callback' => 'ctools_ajax_sample_page', + 'access callback' => TRUE, + 'type' => MENU_NORMAL_ITEM, ); $items['ctools_ajax_sample/simple_form'] = array( 'title' => 'Simple Form', @@ -26,39 +26,39 @@ function ctools_ajax_sample_menu() { 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/hello'] = array( - 'title' => 'Hello World', - 'page callback' => 'ctools_ajax_sample_hello', - 'page arguments' => array(1), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Hello World', + 'page callback' => 'ctools_ajax_sample_hello', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/tablenix/%'] = array( - 'title' => 'Hello World', - 'page callback' => 'ctools_ajax_sample_tablenix', - 'page arguments' => array(1, 3), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Hello World', + 'page callback' => 'ctools_ajax_sample_tablenix', + 'page arguments' => array(1, 3), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/login'] = array( - 'title' => 'Login', - 'page callback' => 'ctools_ajax_sample_login', - 'page arguments' => array(1), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Login', + 'page callback' => 'ctools_ajax_sample_login', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/animal'] = array( - 'title' => 'Animal', - 'page callback' => 'ctools_ajax_sample_animal', - 'page arguments' => array(1), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Animal', + 'page callback' => 'ctools_ajax_sample_animal', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/login/%'] = array( - 'title' => 'Post-Login Action', - 'page callback' => 'ctools_ajax_sample_login_success', - 'page arguments' => array(1, 3), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Post-Login Action', + 'page callback' => 'ctools_ajax_sample_login_success', + 'page arguments' => array(1, 3), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/jumped'] = array( 'title' => 'Successful Jumping', @@ -104,11 +104,11 @@ function ctools_ajax_sample_theme() { } // --------------------------------------------------------------------------- -// Page callbacks - +// Page callbacks. /** * Page callback to display links and render a container for AJAX stuff. */ + function ctools_ajax_sample_page() { global $user; @@ -156,14 +156,14 @@ function ctools_ajax_sample_page() { // The extra class points to the info in ctools-sample-style which we added // to the settings, prefixed with 'ctools-modal'. - $links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style'); + $links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style'); } // Four ways to do our animal picking wizard. $button_form = ctools_ajax_sample_ajax_button_form(); $links[] = l(t('Wizard (no modal)'), 'ctools_ajax_sample/nojs/animal'); $links[] = ctools_modal_text_button(t('Wizard (default modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal')); - $links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style'); + $links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style'); $links[] = drupal_render($button_form); $links[] = ctools_ajax_text_button(t('Hello world!'), "ctools_ajax_sample/nojs/hello", t('Replace text with "hello world"')); @@ -176,9 +176,9 @@ function ctools_ajax_sample_page() { // Create a table that we can have data removed from via AJAX. $header = array(t('Row'), t('Content'), t('Actions')); $rows = array(); - for($i = 1; $i < 11; $i++) { + for ($i = 1; $i < 11; $i++) { $rows[] = array( - 'class' => array('ajax-sample-row-'. $i), + 'class' => array('ajax-sample-row-' . $i), 'data' => array( $i, md5($i), @@ -189,10 +189,10 @@ function ctools_ajax_sample_page() { $output .= theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('ajax-sample-table')))); - // Show examples of ctools javascript widgets - $output .= '

'. t('CTools Javascript Widgets') .'

'; + // Show examples of ctools javascript widgets. + $output .= '

' . t('CTools Javascript Widgets') . '

'; - // Create a drop down menu + // Create a drop down menu. $links = array(); $links[] = array('title' => t('Link 1'), 'href' => $_GET['q']); $links[] = array('title' => t('Link 2'), 'href' => $_GET['q']); @@ -201,16 +201,16 @@ function ctools_ajax_sample_page() { $output .= '

' . t('Drop Down Menu') . '

'; $output .= theme('ctools_dropdown', array('title' => t('Click to Drop Down'), 'links' => $links)); - // Create a collapsible div + // Create a collapsible div. $handle = t('Click to Collapse'); $content = 'Nulla ligula ante, aliquam at adipiscing egestas, varius vel arcu. Etiam laoreet elementum mi vel consequat. Etiam scelerisque lorem vel neque consequat quis bibendum libero congue. Nulla facilisi. Mauris a elit a leo feugiat porta. Phasellus placerat cursus est vitae elementum.'; - $output .= '

'. t('Collapsible Div') .'

'; + $output .= '

' . t('Collapsible Div') . '

'; $output .= theme('ctools_collapsible', array('handle' => $handle, 'content' => $content, 'collapsed' => FALSE)); - // Create a jump menu + // Create a jump menu. ctools_include('jump-menu'); $form = drupal_get_form('ctools_ajax_sample_jump_menu_form'); - $output .= '

'. t('Jump Menu') .'

'; + $output .= '

' . t('Jump Menu') . '

'; $output .= drupal_render($form); return array('markup' => array('#markup' => $output)); @@ -225,7 +225,8 @@ function ctools_ajax_sample_hello($js = NULL) { ctools_include('ajax'); $commands = array(); $commands[] = ajax_command_html('#ctools-sample', $output); - print ajax_render($commands); // this function exits. + // This function exits. + print ajax_render($commands); exit; } else { @@ -234,7 +235,7 @@ function ctools_ajax_sample_hello($js = NULL) { } /** - * Nix a row from a table and restripe. + * Nix a row from a table and restripe. */ function ctools_ajax_sample_tablenix($js, $row) { if (!$js) { @@ -272,7 +273,7 @@ function ctools_ajax_sample_login($js = NULL) { $output = array(); $inplace = ctools_ajax_text_button(t('remain here'), 'ctools_ajax_sample/nojs/login/inplace', t('Go to your account')); $account = ctools_ajax_text_button(t('your account'), 'ctools_ajax_sample/nojs/login/user', t('Go to your account')); - $output[] = ctools_modal_command_display(t('Login Success'), ''); + $output[] = ctools_modal_command_display(t('Login Success'), ''); } print ajax_render($output); exit; @@ -283,7 +284,7 @@ function ctools_ajax_sample_login($js = NULL) { */ function ctools_ajax_sample_login_success($js, $action) { if (!$js) { - // we should never be here out of ajax context + // We should never be here out of ajax context. return MENU_NOT_FOUND; } @@ -291,11 +292,11 @@ function ctools_ajax_sample_login_success($js, $action) { ctools_add_js('ajax-responder'); $commands = array(); if ($action == 'inplace') { - // stay here + // Stay here. $commands[] = ctools_ajax_command_reload(); } else { - // bounce bounce + // Bounce bounce. $commands[] = ctools_ajax_command_redirect('user'); } print ajax_render($commands); @@ -318,18 +319,18 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { 'show back' => TRUE, 'show cancel' => TRUE, 'show return' => FALSE, - 'next callback' => 'ctools_ajax_sample_wizard_next', + 'next callback' => 'ctools_ajax_sample_wizard_next', 'finish callback' => 'ctools_ajax_sample_wizard_finish', 'cancel callback' => 'ctools_ajax_sample_wizard_cancel', - // this controls order, as well as form labels + // This controls order, as well as form labels. 'order' => array( 'start' => t('Choose animal'), ), - // here we map a step to a form id. + // Here we map a step to a form id. 'forms' => array( - // e.g. this for the step at wombat/create + // e.g. this for the step at wombat/create. 'start' => array( - 'form id' => 'ctools_ajax_sample_start' + 'form id' => 'ctools_ajax_sample_start', ), ), ); @@ -341,7 +342,6 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { // in creation. // // We skip all this here by just using an id of 1. - $object_id = 1; if (empty($step)) { @@ -406,7 +406,7 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { $commands[] = ajax_command_html('#ctools-sample', $animal); $commands[] = ctools_modal_command_dismiss(); } - else if (!empty($form_state['cancel'])) { + elseif (!empty($form_state['cancel'])) { // If cancelling, return to the activity. $commands[] = ctools_modal_command_dismiss(); } @@ -420,7 +420,7 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { if ($output === FALSE || !empty($form_state['complete'])) { return $animal; } - else if (!empty($form_state['cancel'])) { + elseif (!empty($form_state['cancel'])) { drupal_goto('ctools_ajax_sample'); } else { @@ -430,11 +430,11 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { } // --------------------------------------------------------------------------- -// Themes - +// Themes. /** * Theme function for main rendered output. */ + function theme_ctools_ajax_sample_container($vars) { $output = '
'; $output .= $vars['content']; @@ -445,7 +445,6 @@ function theme_ctools_ajax_sample_container($vars) { // --------------------------------------------------------------------------- // Stuff needed for our little wizard. - /** * Get a list of our animals and associated forms. * @@ -453,6 +452,7 @@ function theme_ctools_ajax_sample_container($vars) { * which is often how it will work in the real world. If using CTools, what * you would probably really have, here, is a set of plugins for each animal. */ + function ctools_ajax_sample_animals() { return array( 'sheep' => array( @@ -478,10 +478,10 @@ function ctools_ajax_sample_animals() { // --------------------------------------------------------------------------- // Wizard caching helpers. - /** * Store our little cache so that we can retain data from form to form. */ + function ctools_ajax_sample_cache_set($id, $object) { ctools_include('object-cache'); ctools_object_cache_set('ctools_ajax_sample', $id, $object); @@ -495,7 +495,7 @@ function ctools_ajax_sample_cache_get($id) { $object = ctools_object_cache_get('ctools_ajax_sample', $id); if (!$object) { // Create a default object. - $object = new stdClass; + $object = new stdClass(); $object->type = 'unknown'; $object->name = ''; } @@ -513,12 +513,12 @@ function ctools_ajax_sample_cache_clear($id) { // --------------------------------------------------------------------------- // Wizard in-between helpers; what to do between or after forms. - /** * Handle the 'next' click on the add/edit pane form wizard. * * All we need to do is store the updated pane in the cache. */ + function ctools_ajax_sample_wizard_next(&$form_state) { ctools_ajax_sample_cache_set($form_state['object_id'], $form_state['object']); } @@ -542,10 +542,10 @@ function ctools_ajax_sample_wizard_cancel(&$form_state) { // --------------------------------------------------------------------------- // Wizard forms for our simple info collection wizard. - /** * Wizard start form. Choose an animal. */ + function ctools_ajax_sample_start($form, &$form_state) { $form_state['title'] = t('Choose animal'); @@ -713,7 +713,7 @@ function ctools_ajax_sample_show_raptor($object) { } /** - * Helper function to provide a sample jump menu form + * Helper function to provide a sample jump menu form. */ function ctools_ajax_sample_jump_menu_form() { $url = url('ctools_ajax_sample/jumped'); @@ -723,7 +723,7 @@ function ctools_ajax_sample_jump_menu_form() { } /** - * Provide a message to the user that the jump menu worked + * Provide a message to the user that the jump menu worked. */ function ctools_ajax_sample_jump_menu_page() { $return_link = l(t('Return to the examples page.'), 'ctools_ajax_sample'); @@ -732,7 +732,7 @@ function ctools_ajax_sample_jump_menu_page() { } /** - * Provide a form for an example ajax modal button + * Provide a form for an example ajax modal button. */ function ctools_ajax_sample_ajax_button_form() { $form = array(); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info index 185d8b64..094d2788 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info @@ -5,9 +5,9 @@ package = Chaos tool suite version = CTOOLS_MODULE_VERSION dependencies[] = ctools -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.x-1.9" +; Information added by Drupal.org packaging script on 2018-02-04 +version = "7.x-1.13" core = "7.x" project = "ctools" -datestamp = "1440020680" +datestamp = "1517704095" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.install b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.install index b4512f2a..dcf87e73 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.install +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.install @@ -1,5 +1,9 @@ type == 'custom') { - if(!isset($pane->configuration['name'])) { + if (!isset($pane->configuration['name'])) { $name_of_pane = $pane->subtype; } else { @@ -80,7 +80,7 @@ function ctools_custom_content_panels_dashboard_blocks(&$vars) { 'description' => t('Custom content panes are basic HTML you enter that can be reused in all of your panels.'), ); - // Load all mini panels and their displays. + // Load all mini panels and their displays. ctools_include('export'); $items = ctools_export_crud_load_all('ctools_custom_content'); $count = 0; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc index 467dc580..c7933bcb 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc @@ -1,5 +1,9 @@ 'ctools_custom_content', 'access' => 'administer custom content', @@ -17,4 +21,3 @@ $plugin = array( 'handler' => 'ctools_custom_content_ui', ); - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php index 56fe4b21..e56f7a7e 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php @@ -2,7 +2,7 @@ class ctools_custom_content_ui extends ctools_export_ui { - function edit_form(&$form, &$form_state) { + public function edit_form(&$form, &$form_state) { // Correct for an error that came in because filter format changed. if (is_array($form_state['item']->settings['body'])) { $form_state['item']->settings['format'] = $form_state['item']->settings['body']['format']; @@ -23,6 +23,22 @@ function edit_form(&$form, &$form_state) { '#title' => t('Title'), ); + $form['title_heading'] = array( + '#title' => t('Title heading'), + '#type' => 'select', + '#default_value' => isset($form_state['item']->settings['title_heading']) ? $form_state['item']->settings['title_heading'] : 'h2', + '#options' => array( + 'h1' => t('h1'), + 'h2' => t('h2'), + 'h3' => t('h3'), + 'h4' => t('h4'), + 'h5' => t('h5'), + 'h6' => t('h6'), + 'div' => t('div'), + 'span' => t('span'), + ), + ); + $form['body'] = array( '#type' => 'text_format', '#title' => t('Body'), @@ -38,17 +54,18 @@ function edit_form(&$form, &$form_state) { ); } - function edit_form_submit(&$form, &$form_state) { + public function edit_form_submit(&$form, &$form_state) { parent::edit_form_submit($form, $form_state); // Since items in our settings are not in the schema, we have to do these manually: $form_state['item']->settings['title'] = $form_state['values']['title']; + $form_state['item']->settings['title_heading'] = $form_state['values']['title_heading']; $form_state['item']->settings['body'] = $form_state['values']['body']['value']; $form_state['item']->settings['format'] = $form_state['values']['body']['format']; $form_state['item']->settings['substitute'] = $form_state['values']['substitute']; } - function list_form(&$form, &$form_state) { + public function list_form(&$form, &$form_state) { parent::list_form($form, $form_state); $options = array('all' => t('- All -')); @@ -65,7 +82,7 @@ function list_form(&$form, &$form_state) { ); } - function list_filter($form_state, $item) { + public function list_filter($form_state, $item) { if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) { return TRUE; } @@ -73,7 +90,7 @@ function list_filter($form_state, $item) { return parent::list_filter($form_state, $item); } - function list_sort_options() { + public function list_sort_options() { return array( 'disabled' => t('Enabled, title'), 'title' => t('Title'), @@ -83,21 +100,25 @@ function list_sort_options() { ); } - function list_build_row($item, &$form_state, $operations) { - // Set up sorting + public function list_build_row($item, &$form_state, $operations) { + // Set up sorting. switch ($form_state['values']['order']) { case 'disabled': $this->sorts[$item->name] = empty($item->disabled) . $item->admin_title; break; + case 'title': $this->sorts[$item->name] = $item->admin_title; break; + case 'name': $this->sorts[$item->name] = $item->name; break; + case 'category': $this->sorts[$item->name] = $item->category; break; + case 'storage': $this->sorts[$item->name] = $item->type . $item->admin_title; break; @@ -117,7 +138,7 @@ function list_build_row($item, &$form_state, $operations) { ); } - function list_table_header() { + public function list_table_header() { return array( array('data' => t('Name'), 'class' => array('ctools-export-ui-name')), array('data' => t('Title'), 'class' => array('ctools-export-ui-title')), diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/README.txt b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/README.txt index 42edcdc9..2f9b0fff 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/README.txt @@ -11,4 +11,4 @@ There are a number of ways to profit from this: 2. There is a sample panel. You can access it at /ctools_plugin_example/xxxx to see how it works. -3. There is Advanced Help at admin/advanced_help/ctools_plugin_example. \ No newline at end of file +3. There is Advanced Help at admin/advanced_help/ctools_plugin_example. diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info index d378641e..77c311f7 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -8,9 +8,9 @@ dependencies[] = page_manager dependencies[] = advanced_help core = 7.x -; Information added by Drupal.org packaging script on 2015-08-19 -version = "7.x-1.9" +; Information added by Drupal.org packaging script on 2018-02-04 +version = "7.x-1.13" core = "7.x" project = "ctools" -datestamp = "1440020680" +datestamp = "1517704095" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module index 01d53382..a9a6080b 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module @@ -1,9 +1,8 @@ .pages_default.inc + * .pages_default.inc. */ function ctools_plugin_example_ctools_plugin_api($module, $api) { // @todo -- this example should explain how to put it in a different file. @@ -71,7 +70,8 @@ function ctools_plugin_example_ctools_plugin_api($module, $api) { } /** - * Just provide an explanation page for the admin section + * Just provide an explanation page for the admin section. + * * @return unknown_type */ function ctools_plugin_example_explanation_page() { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc index 10a76193..bbabf227 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc @@ -6,7 +6,7 @@ */ /** - * Default panels pages for CTools Plugin Example + * Default panels pages for CTools Plugin Example. * * To pick up this file, your module needs to implement * hook_ctools_plugin_api() - See ctools_plugin_example_ctools_plugin_api() in @@ -23,12 +23,10 @@ * @return * Array of pages, normally exported from Panels. */ - function ctools_plugin_example_default_page_manager_pages() { - // begin exported panel. - - $page = new stdClass; + // Begin exported panel. + $page = new stdClass(); $page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */ $page->api_version = 1; $page->name = 'ctools_plugin_example'; @@ -37,31 +35,31 @@ function ctools_plugin_example_default_page_manager_pages() { $page->admin_description = 'This panel provides no functionality to a working Drupal system. It\'s intended to display the various sample plugins provided by the CTools Plugin Example module. '; $page->path = 'ctools_plugin_example/%sc'; $page->access = array( - 'logic' => 'and', + 'logic' => 'and', ); $page->menu = array( - 'type' => 'normal', - 'title' => 'CTools plugin example', - 'name' => 'navigation', - 'weight' => '0', - 'parent' => array( - 'type' => 'none', - 'title' => '', + 'type' => 'normal', + 'title' => 'CTools plugin example', 'name' => 'navigation', 'weight' => '0', - ), + 'parent' => array( + 'type' => 'none', + 'title' => '', + 'name' => 'navigation', + 'weight' => '0', + ), ); $page->arguments = array( - 'sc' => array( - 'id' => 2, - 'identifier' => 'simplecontext-arg', - 'name' => 'simplecontext_arg', - 'settings' => array(), - ), + 'sc' => array( + 'id' => 2, + 'identifier' => 'simplecontext-arg', + 'name' => 'simplecontext_arg', + 'settings' => array(), + ), ); $page->conf = array(); $page->default_handlers = array(); - $handler = new stdClass; + $handler = new stdClass(); $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */ $handler->api_version = 1; $handler->name = 'page_ctools_panel_context'; @@ -70,44 +68,44 @@ function ctools_plugin_example_default_page_manager_pages() { $handler->handler = 'panel_context'; $handler->weight = 0; $handler->conf = array( - 'title' => 'Panel', - 'no_blocks' => FALSE, - 'css_id' => '', - 'css' => '', - 'contexts' => array( - '0' => array( - 'name' => 'simplecontext', - 'id' => 1, - 'identifier' => 'Configured simplecontext (not from argument)', - 'keyword' => 'configured_simplecontext', - 'context_settings' => array( - 'sample_simplecontext_setting' => 'default simplecontext setting', - ), - ), - ), - 'relationships' => array( - '0' => array( - 'context' => 'argument_simplecontext_arg_2', - 'name' => 'relcontext_from_simplecontext', - 'id' => 1, - 'identifier' => 'Relcontext from simplecontext (from relationship)', - 'keyword' => 'relcontext', - ), - ), - 'access' => array( - 'logic' => 'and', - ), + 'title' => 'Panel', + 'no_blocks' => FALSE, + 'css_id' => '', + 'css' => '', + 'contexts' => array( + '0' => array( + 'name' => 'simplecontext', + 'id' => 1, + 'identifier' => 'Configured simplecontext (not from argument)', + 'keyword' => 'configured_simplecontext', + 'context_settings' => array( + 'sample_simplecontext_setting' => 'default simplecontext setting', + ), + ), + ), + 'relationships' => array( + '0' => array( + 'context' => 'argument_simplecontext_arg_2', + 'name' => 'relcontext_from_simplecontext', + 'id' => 1, + 'identifier' => 'Relcontext from simplecontext (from relationship)', + 'keyword' => 'relcontext', + ), + ), + 'access' => array( + 'logic' => 'and', + ), ); - $display = new panels_display; + $display = new panels_display(); $display->layout = 'threecol_33_34_33_stacked'; $display->layout_settings = array(); $display->panel_settings = array( - 'style' => 'rounded_corners', - 'style_settings' => array( - 'default' => array( - 'corner_location' => 'pane', - ), - ), + 'style' => 'rounded_corners', + 'style_settings' => array( + 'default' => array( + 'corner_location' => 'pane', + ), + ), ); $display->cache = array(); $display->title = 'CTools plugin example panel'; @@ -115,7 +113,7 @@ function ctools_plugin_example_default_page_manager_pages() { $display->title_pane = 1; $display->content = array(); $display->panels = array(); - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-1'; $pane->panel = 'left'; $pane->type = 'no_context_content_type'; @@ -135,7 +133,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 0; $display->content['new-1'] = $pane; $display->panels['left'][0] = 'new-1'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-2'; $pane->panel = 'left'; $pane->type = 'custom'; @@ -148,10 +146,10 @@ function ctools_plugin_example_default_page_manager_pages() { 'settings' => array( 'greater_than' => '1', 'arg_length' => '4', - ), + ), 'context' => 'argument_simplecontext_arg_2', - ), - ), + ), + ), ); $pane->configuration = array( 'title' => 'Long Arg Visibility Block', @@ -166,7 +164,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 1; $display->content['new-2'] = $pane; $display->panels['left'][1] = 'new-2'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-3'; $pane->panel = 'left'; $pane->type = 'custom'; @@ -179,10 +177,10 @@ function ctools_plugin_example_default_page_manager_pages() { 'settings' => array( 'greater_than' => '0', 'arg_length' => '4', - ), + ), 'context' => 'argument_simplecontext_arg_2', - ), - ), + ), + ), ); $pane->configuration = array( 'title' => 'Short Arg Visibility', @@ -197,7 +195,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 2; $display->content['new-3'] = $pane; $display->panels['left'][2] = 'new-3'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-4'; $pane->panel = 'middle'; $pane->type = 'simplecontext_content_type'; @@ -241,7 +239,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 0; $display->content['new-4'] = $pane; $display->panels['middle'][0] = 'new-4'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-5'; $pane->panel = 'middle'; $pane->type = 'simplecontext_content_type'; @@ -285,7 +283,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 1; $display->content['new-5'] = $pane; $display->panels['middle'][1] = 'new-5'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-6'; $pane->panel = 'middle'; $pane->type = 'custom'; @@ -309,7 +307,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 2; $display->content['new-6'] = $pane; $display->panels['middle'][2] = 'new-6'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-7'; $pane->panel = 'right'; $pane->type = 'relcontext_content_type'; @@ -353,7 +351,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 0; $display->content['new-7'] = $pane; $display->panels['right'][0] = 'new-7'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-8'; $pane->panel = 'top'; $pane->type = 'custom'; @@ -378,12 +376,11 @@ function ctools_plugin_example_default_page_manager_pages() { $handler->conf['display'] = $display; $page->default_handlers[$handler->name] = $handler; - // end of exported panel. + // End of exported panel. $pages['ctools_plugin_example_demo_page'] = $page; - // begin exported panel - - $page = new stdClass; + // Begin exported panel. + $page = new stdClass(); $page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */ $page->api_version = 1; $page->name = 'ctools_plugin_example_base'; @@ -396,7 +393,7 @@ function ctools_plugin_example_default_page_manager_pages() { $page->arguments = array(); $page->conf = array(); $page->default_handlers = array(); - $handler = new stdClass; + $handler = new stdClass(); $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */ $handler->api_version = 1; $handler->name = 'page_ctools_plugin_example_base_panel_context'; @@ -405,14 +402,14 @@ function ctools_plugin_example_default_page_manager_pages() { $handler->handler = 'panel_context'; $handler->weight = 0; $handler->conf = array( - 'title' => 'Panel', - 'no_blocks' => FALSE, - 'css_id' => '', - 'css' => '', - 'contexts' => array(), - 'relationships' => array(), + 'title' => 'Panel', + 'no_blocks' => FALSE, + 'css_id' => '', + 'css' => '', + 'contexts' => array(), + 'relationships' => array(), ); - $display = new panels_display; + $display = new panels_display(); $display->layout = 'onecol'; $display->layout_settings = array(); $display->panel_settings = array(); @@ -421,7 +418,7 @@ function ctools_plugin_example_default_page_manager_pages() { $display->hide_title = FALSE; $display->content = array(); $display->panels = array(); - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-1'; $pane->panel = 'middle'; $pane->type = 'custom'; @@ -443,9 +440,8 @@ function ctools_plugin_example_default_page_manager_pages() { $display->panels['middle'][0] = 'new-1'; $handler->conf['display'] = $display; $page->default_handlers[$handler->name] = $handler; - // end exported panel. - + // End exported panel. $pages['base_page'] = $page; return $pages; -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/arg_length.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/arg_length.inc index 2a09eea1..3b5d8636 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/arg_length.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/arg_length.inc @@ -59,7 +59,8 @@ function ctools_plugin_example_arg_length_ctools_access_check($conf, $context) { */ function ctools_plugin_example_arg_length_ctools_access_summary($conf, $context) { return t('Simpletext argument must be !comp @length characters', - array('!comp' => $conf['greater_than'] ? 'greater than' : 'less than or equal to', - '@length' => $conf['arg_length'])); + array( + '!comp' => $conf['greater_than'] ? 'greater than' : 'less than or equal to', + '@length' => $conf['arg_length'], + )); } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/example_role.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/example_role.inc index bbe364c1..75721e8b 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/example_role.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/access/example_role.inc @@ -4,7 +4,7 @@ * @file * Plugin to provide access control based upon role membership. * This is directly from the ctools module, but serves as a good - * example of an access plugin + * example of an access plugin. */ /** @@ -73,4 +73,3 @@ function ctools_plugin_example_example_role_ctools_access_summary($conf, $contex } return format_plural(count($names), '@identifier must have role "@roles"', '@identifier can be one of "@roles"', array('@roles' => implode(', ', $names), '@identifier' => $context->identifier)); } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc index 51c7c601..7fb732c6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc @@ -2,7 +2,6 @@ /** * @file - * * Sample plugin to provide an argument handler for a simplecontext. * * Given any argument to the page, simplecontext will get it @@ -18,14 +17,12 @@ */ $plugin = array( 'title' => t("Simplecontext arg"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'simplecontext', 'description' => t('Creates a "simplecontext" from the arg.'), 'context' => 'simplecontext_arg_context', - // 'settings form' => 'simplecontext_arg_settings_form', - // placeholder_form is used in panels preview, for example, so we can - // preview without getting the arg from a URL + // preview without getting the arg from a URL. 'placeholder form' => array( '#type' => 'textfield', '#description' => t('Enter the simplecontext arg'), diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc index 3c02ab84..48ce0f5c 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc @@ -5,7 +5,6 @@ * "No context" sample content type. It operates with no context at all. It would * be basically the same as a 'custom content' block, but it's not even that * sophisticated. - * */ /** @@ -33,7 +32,7 @@ $plugin = array( 'icon' => 'icon_example.png', 'category' => array(t('CTools Examples'), -9), - // this example does not provide 'admin info', which would populate the + // This example does not provide 'admin info', which would populate the // panels builder page preview. ); @@ -56,7 +55,7 @@ function no_context_content_type_render($subtype, $conf, $args, $context) { $ctools_help = theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')); $ctools_plugin_example_help = theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')); - // The title actually used in rendering + // The title actually used in rendering. $block->title = check_plain("No-context content type"); $block->content = t("
Welcome to the CTools Plugin Example demonstration content type. @@ -84,7 +83,6 @@ function no_context_content_type_render($subtype, $conf, $args, $context) { * Note that if we had not provided an entry for this in hook_content_types, * this could have had the default name * ctools_plugin_example_no_context_content_type_edit_form. - * */ function no_context_content_type_edit_form($form, &$form_state) { $conf = $form_state['conf']; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc index bf54dce6..ced64117 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc @@ -1,6 +1,5 @@ array(t('CTools Examples'), -9), 'edit form' => 'relcontext_edit_form', - // this example does not provide 'admin info', which would populate the + // This example does not provide 'admin info', which would populate the // panels builder page preview. - ); /** @@ -60,7 +58,7 @@ function relcontext_content_type_render($subtype, $conf, $args, $context) { In our case, the configuration form (\$conf) has just one field, 'config_item_1; and it's configured with: "); - if (!empty($conf)) { + if (!empty($conf)) { $block->content .= '
' . var_export($conf['config_item_1'], TRUE) . '
'; } if (!empty($context)) { @@ -77,7 +75,6 @@ function relcontext_content_type_render($subtype, $conf, $args, $context) { /** * 'Edit' callback for the content type. * This example just returns a form. - * */ function relcontext_edit_form($form, &$form_state) { $conf = $form_state['conf']; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc index a308683c..e34fa18e 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc @@ -1,6 +1,5 @@ 'simplecontext_content_type_edit_form', 'admin title' => 'ctools_plugin_example_simplecontext_content_type_admin_title', - // presents a block which is used in the preview of the data. + // Presents a block which is used in the preview of the data. // Pn Panels this is the preview pane shown on the panels building page. 'admin info' => 'ctools_plugin_example_simplecontext_content_type_admin_info', 'category' => array(t('CTools Examples'), -9), @@ -103,7 +101,6 @@ function simplecontext_content_type_render($subtype, $conf, $args, $context) { /** * 'Edit' callback for the content type. * This example just returns a form. - * */ function simplecontext_content_type_edit_form($form, &$form_state) { $conf = $form_state['conf']; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc index 0c7ef113..61e25db0 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc @@ -80,4 +80,3 @@ function relcontext_settings_form($conf, $external = FALSE) { ); return $form; } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc index e19a8422..0ee4658d 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc @@ -1,10 +1,8 @@ t("Simplecontext"), 'description' => t('A single "simplecontext" context, or data element.'), - 'context' => 'ctools_plugin_example_context_create_simplecontext', // func to create context +// Func to create context. + 'context' => 'ctools_plugin_example_context_create_simplecontext', 'context name' => 'simplecontext', 'settings form' => 'simplecontext_settings_form', 'keyword' => 'simplecontext', @@ -69,7 +68,7 @@ function ctools_plugin_example_context_create_simplecontext($empty, $data = NULL // This is used for keyword. $context->title = $data; $context->argument = $data; - // Make up a bogus context + // Make up a bogus context. $context->data = new stdClass(); $context->data->item1 = t("Item1"); $context->data->item2 = t("Item2"); @@ -102,8 +101,6 @@ function simplecontext_settings_form($conf, $external = FALSE) { return $form; } - - /** * Provide a list of sub-keywords. * @@ -125,10 +122,11 @@ function simplecontext_convert($context, $type) { switch ($type) { case 'item1': return $context->data->item1; + case 'item2': return $context->data->item2; + case 'description': return $context->data->description; } } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc index d3022af7..25422cfa 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc @@ -19,24 +19,24 @@ function ctools_plugin_example_default_panel_pages() { $page->load_flags = 1; $page->css_id = ''; $page->arguments = array( - 0 => + 0 => array( - 'name' => 'simplecontext_arg', - 'id' => 1, - 'default' => '404', - 'title' => '', - 'identifier' => 'Simplecontext arg', - 'keyword' => 'simplecontext', + 'name' => 'simplecontext_arg', + 'id' => 1, + 'default' => '404', + 'title' => '', + 'identifier' => 'Simplecontext arg', + 'keyword' => 'simplecontext', ), ); $page->relationships = array( - 0 => + 0 => array( - 'context' => 'argument_simplecontext_arg_1', - 'name' => 'relcontext_from_simplecontext', - 'id' => 1, - 'identifier' => 'Relcontext from Simplecontext', - 'keyword' => 'relcontext', + 'context' => 'argument_simplecontext_arg_1', + 'name' => 'relcontext_from_simplecontext', + 'id' => 1, + 'identifier' => 'Relcontext from Simplecontext', + 'keyword' => 'relcontext', ), ); $page->no_blocks = '0'; @@ -58,14 +58,14 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => '"No Context Item"', - 'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.', - 'format' => '1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => '"No Context Item"', + 'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.', + 'format' => '1', ); $pane->cache = array(); $display->content['new-1'] = $pane; @@ -78,14 +78,14 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'description'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'item1' => 'one', - 'item2' => 'two', - 'item3' => 'three', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'item1' => 'one', + 'item2' => 'two', + 'item3' => 'three', ); $pane->cache = array(); $display->content['new-2'] = $pane; @@ -98,16 +98,16 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => 'Simplecontext', - 'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it. + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => 'Simplecontext', + 'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it. Simplecontext includes configuration, so it can get info from the config. It can also get its information to run from a simplecontext context, generated either from an arg to the panels page or via explicitly adding a context to the page.', - 'format' => '1', + 'format' => '1', ); $pane->cache = array(); $display->content['new-3'] = $pane; @@ -119,17 +119,17 @@ function ctools_plugin_example_default_panel_pages() { $pane->shown = '1'; $pane->subtype = 'description'; $pane->access = array( - 0 => '2', - 1 => '4', + 0 => '2', + 1 => '4', ); $pane->configuration = array( - 'context' => 'argument_simplecontext_arg_1', - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'config_item_1' => 'simplecontext called from arg', + 'context' => 'argument_simplecontext_arg_1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'config_item_1' => 'simplecontext called from arg', ); $pane->cache = array(); $display->content['new-4'] = $pane; @@ -142,14 +142,14 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => 'Relcontext', - 'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.', - 'format' => '1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => 'Relcontext', + 'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.', + 'format' => '1', ); $pane->cache = array(); $display->content['new-5'] = $pane; @@ -162,13 +162,13 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'description'; $pane->access = array(); $pane->configuration = array( - 'context' => 'relationship_relcontext_from_simplecontext_1', - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'config_item_1' => 'default1', + 'context' => 'relationship_relcontext_from_simplecontext_1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'config_item_1' => 'default1', ); $pane->cache = array(); $display->content['new-6'] = $pane; @@ -181,13 +181,13 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom_php'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => '', - 'body' => '$arg = arg(1); + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => '', + 'body' => '$arg = arg(1); $arg0 = arg(0); if (!$arg) { $block->content = <<displays = array(); $pages['ctools_plugin_example'] = $page; - return $pages; } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc index 62246210..087d5ecf 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc @@ -1,9 +1,7 @@ 'Space separated list of exportables you want to view.', ), 'options' => array( - 'indent' => 'The string to use for indentation when dispalying the exportable export code. Defaults to \'\'.', + 'indent' => 'The string to use for indentation when displaying the exportable export code. Defaults to \'\'.', 'no-colour' => 'Remove any colour formatting from export string output. Ideal if you are sending the output of this command to a file.', 'module' => $module_text, 'all' => $all_text, @@ -140,13 +140,14 @@ function ctools_drush_help($section) { switch ($section) { case 'meta:ctools:title': return dt('CTools commands'); + case 'meta:entity:summary': return dt('CTools drush commands.'); } } /** - * Drush callback: export + * Drush callback: export. */ function ctools_drush_export($module = 'foo') { $error = FALSE; @@ -338,7 +339,7 @@ function _ctools_drush_selection_screen(array $tables = array()) { $build[$table_choice]['count'] = count($multi_select); $selections[$table_choice] = array(); foreach ($multi_select as $key) { - $selections[$table_choice][$key] = $key; + $selections[$table_choice][$key] = $key; } } } @@ -445,6 +446,7 @@ function ctools_drush_export_info() { } } } + /** * Drush callback: Acts as the hub for all op commands to keep * all arg handling etc in one place. @@ -457,17 +459,20 @@ function ctools_drush_export_op_command() { switch ($command['command']) { case 'ctools-export-view': $op = 'view'; - break; + break; + case 'ctools-export-revert': // Revert is same as deleting. As any objects in the db are deleted. $op = 'delete'; - break; + break; + case 'ctools-export-enable': $op = 'enable'; - break; + break; + case 'ctools-export-disable': $op = 'disable'; - break; + break; } if (!$op) { @@ -475,7 +480,7 @@ function ctools_drush_export_op_command() { } if (drush_get_option('all', FALSE)) { - $info = _ctools_drush_export_info('', TRUE); + $info = _ctools_drush_export_info(array(), TRUE); $exportable_info = $info['exportables']; $all = drush_confirm(dt('Are you sure you would like to !op all exportables on the system?', @@ -512,7 +517,6 @@ function ctools_drush_export_op_command() { * @param $op * @param $table_name * @param $exportables - * */ function ctools_drush_export_op($op = '', $table_name = '', $exportables = NULL) { $objects = ctools_export_crud_load_multiple($table_name, array_keys($exportables)); @@ -538,7 +542,7 @@ function ctools_drush_export_op($op = '', $table_name = '', $exportables = NULL) * @param $object_names * * @return - * Array of exportable objects (filtered if necessary, by name etc..) or FALSE if not. + * Array of exportable objects (filtered if necessary, by name etc..) or FALSE if not. */ function _ctools_drush_export_op_command_logic($op = '', $table_name = NULL, array $object_names = array()) { if (!$table_name) { @@ -644,7 +648,7 @@ function _ctools_drush_export_info(array $table_names = array(), $load = FALSE) return array('exportables' => $exportables, 'schemas' => $schemas); } -/* +/** * View a single object. * * @param $table_name @@ -662,7 +666,7 @@ function _ctools_drush_export_view($table_name, $object) { } } -/* +/** * Revert a single object. * * @param $table_name @@ -681,7 +685,7 @@ function _ctools_drush_export_delete($table_name, $object) { } } -/* +/** * Enable a single object. * * @param $table_name @@ -701,7 +705,7 @@ function _ctools_drush_export_enable($table_name, $object) { } } -/* +/** * Disable a single object. * * @param $table_name @@ -723,9 +727,9 @@ function _ctools_drush_export_disable($table_name, $object) { /** * Filter a nested array of exportables by export module. * - * @param $exportables array + * @param array $exportables * Passed by reference. A nested array of exportables, keyed by table name. - * @param $export_module string + * @param string $export_module * The name of the export module providing the exportable. */ function _ctools_drush_export_module_filter($exportables, $export_module) { @@ -778,7 +782,7 @@ function _ctools_drush_object_is_disabled($object) { /** * Determine if an object is enabled. * - * @see _ctools_drush_object_is_disabled. + * @see _ctools_drush_object_is_disabled() */ function _ctools_drush_object_is_enabled($object) { return (empty($object->disabled)) ? TRUE : FALSE; @@ -835,7 +839,7 @@ function _ctools_drush_object_is_not_code_only($object) { * Array of exportables to count. * * @return - * Array of count data containing the following: + * Array of count data containing the following: * 'total' - A total count of all exportables. * 'exportables' - An array of exportable counts per table. */ @@ -867,26 +871,32 @@ function _ctools_drush_filter_exportables($exportables, $filter) { // Show enabled exportables only. case 'enabled': $eval = '_ctools_drush_object_is_disabled'; - break; + break; + // Show disabled exportables only. case 'disabled': $eval = '_ctools_drush_object_is_enabled'; - break; + break; + // Show overridden exportables only. case 'overridden': $eval = '_ctools_drush_object_is_not_overridden'; - break; + break; + // Show database only exportables. case 'database': $eval = '_ctools_drush_object_is_not_db_only'; - break; + break; + // Show code only exportables. case 'code': $eval = '_ctools_drush_object_is_not_code_only'; - break; + break; + // Do nothing. case 'all': break; + default: drush_log(dt('Invalid filter option. Available options are: enabled, disabled, overridden, database, and code.'), 'error'); return; @@ -983,33 +993,42 @@ class shellColours { 'light_gray' => '47', ); + /** + * + */ private function __construct() {} - // Returns coloured string + /** + * Returns coloured string. + */ public static function getColouredOutput($string, $foreground_colour = NULL, $background_colour = NULL) { $coloured_string = ""; - // Check if given foreground colour found + // Check if given foreground colour found. if ($foreground_colour) { $coloured_string .= "\033[" . self::$foreground_colours[$foreground_colour] . "m"; } - // Check if given background colour found + // Check if given background colour found. if ($background_colour) { $coloured_string .= "\033[" . self::$background_colours[$background_colour] . "m"; } - // Add string and end colouring - $coloured_string .= $string . "\033[0m"; + // Add string and end colouring. + $coloured_string .= $string . "\033[0m"; return $coloured_string; } - // Returns all foreground colour names + /** + * Returns all foreground colour names. + */ public static function getForegroundColours() { return array_keys(self::$foreground_colours); } - // Returns all background colour names + /** + * Returns all background colour names. + */ public static function getBackgroundColours() { return array_keys(self::$background_colours); } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/help/export.html b/profiles/commerce_kickstart/modules/contrib/ctools/help/export.html index ce24cad9..573c98a6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/help/export.html +++ b/profiles/commerce_kickstart/modules/contrib/ctools/help/export.html @@ -154,7 +154,7 @@

The export section of the schema file

Bulk export callback to provide a list of exportable objects to be chosen for bulk exporting. Defaults to $module . '_' . $table . '_list' if the function exists. If it is not, a default listing function will be provided that will make a best effort to list the titles. See ctools_export_default_list().
to hook code callback
-
Function used to generate an export for the bulk export process. This is only necessary if the export is more complicated than simply listing the fields. Defaults to $module . '_' . $table . '_to_hook_code'. +
Function used to generate an export for the bulk export process. This is only necessary if the export is more complicated than simply listing the fields. Defaults to $module . '_' . $table . '_to_hook_code'.
boolean
Explicitly indicate if a table field contains a boolean or not. The Schema API does not model the diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/help/modal.html b/profiles/commerce_kickstart/modules/contrib/ctools/help/modal.html index ea823a0d..761fd2e3 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/help/modal.html +++ b/profiles/commerce_kickstart/modules/contrib/ctools/help/modal.html @@ -82,7 +82,7 @@

Customizing the modal

  • modalSize: an array of data to control the sizing of the modal. It can contain:
    • type: Either fixed or scale. If fixed, the modal will always be a fixed size. If scale the modal will scale to a percentage of the browser window. Default: scale. -
    • width: If fixed the width in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
    • +
    • width: If fixed the width in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
    • height: If fixed the height in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
    • addWidth: Any additional width to add to the modal in pixels. Only useful if the type is scale. Default: 0
    • addHeight: Any additional height to add to the modal in pixels. Only useful if the type is scale. Default: 0
    • @@ -168,7 +168,7 @@

      Rendering within the modal

       function ctools_ajax_hello_world($js) {
         $title = t('Greetings');
      -  $output = '<p>' . t('Hello world') . ''</p>';
      +  $output = '<p>' . t('Hello world') . '</p>';
         if ($js) {
           ctools_modal_render($title, $output);
         }
      diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/help/plugins-creating.html b/profiles/commerce_kickstart/modules/contrib/ctools/help/plugins-creating.html
      index 23237058..6d5b35f6 100644
      --- a/profiles/commerce_kickstart/modules/contrib/ctools/help/plugins-creating.html
      +++ b/profiles/commerce_kickstart/modules/contrib/ctools/help/plugins-creating.html
      @@ -26,7 +26,7 @@ 

      Defining a plugin

      If 'cache' is TRUE, then this value specifies the cache table where the cached plugin information will be stored.
      classes
      Defaults to: array()
      -
      An array of class identifiers(i.e. plugin array keys) which a plugin of this type uses to provide classes to the CTools autoloader. For example, if classes is set to array('class'), then CTools will search each $plugin['class'] for a class to autoload. Depending of the plugin structure, a class identifier may be either:
      +
      An array of class identifiers(i.e. plugin array keys) which a plugin of this type uses to provide classes to the CTools autoloader. For example, if classes is set to array('class'), then CTools will search each $plugin['class'] for a class to autoload. Depending of the plugin structure, a class identifier may be either:
      - a file name:
      the file which holds the class with the name structure as: [filename].[class].php
      @@ -35,6 +35,7 @@

      Defining a plugin

      if the class is in the same file as the $plugin
      the plugin .inc file can have a different name than the class identifier
      +
      defaults
      Defaults to: array()
      An array of defaults that should be added to each plugin; this can be used to ensure that every plugin has the basic data necessary. These defaults will not ovewrite data supplied by the plugin. This could also be a function name, in which case the callback will be used to provide defaults. NOTE, however, that the callback-based approach is deprecated as it is redundant with the 'process' callback, and as such will be removed in later versions. Consequently, you should only use the array form for maximum cross-version compatibility.
      diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/action-links.theme.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/action-links.theme.inc index 3a2398a1..f53f59c8 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/action-links.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/action-links.theme.inc @@ -1,4 +1,5 @@ ' . $links . '
    '; -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/ajax.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/ajax.inc index 96f5068f..4d72d0c6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/ajax.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/ajax.inc @@ -1,6 +1,10 @@ 'attr', - 'selector' => $selector, - 'name' => $name, - 'value' => $value, - ); - } + 'command' => 'attr', + 'selector' => $selector, + 'name' => $name, + 'value' => $value, + ); +} /** * Force a client-side redirect. @@ -154,4 +158,3 @@ function ctools_ajax_render_error($error = '') { print ajax_render($commands); exit; } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/cache.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/cache.inc index 3918683b..67d97fa3 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/cache.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/cache.inc @@ -2,7 +2,6 @@ /** * @file - * * Plugins to handle cache-indirection. * * Simple plugin management to allow clients to more tightly control where @@ -150,7 +149,7 @@ function ctools_cache_operation($mechanism, $key, $op, $object = NULL) { */ function ctools_cache_find_plugin($mechanism) { if (strpos($mechanism, '::') !== FALSE) { - // use explode(2) to ensure that the data can contain double + // Use explode(2) to ensure that the data can contain double // colons, just in case. list($name, $data) = explode('::', $mechanism, 2); } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/cleanstring.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/cleanstring.inc index 56b3e36f..11ffceff 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/cleanstring.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/cleanstring.inc @@ -1,5 +1,4 @@ $length) { - $string = drupal_substr($string, 0, $length + 1); // leave one more character - if ($last_break = strrpos($string, $separator)) { // space exists AND is not on position 0 + // Leave one more character. + $string = drupal_substr($string, 0, $length + 1); + // Space exists AND is not on position 0. + if ($last_break = strrpos($string, $separator)) { $string = substr($string, 0, $last_break); } else { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/collapsible.theme.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/collapsible.theme.inc index f7bbbb37..d19efd8a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/collapsible.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/collapsible.theme.inc @@ -76,4 +76,3 @@ function theme_ctools_collapsible_remembered($vars) { return $output; } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.inc index ae1c6073..49c35650 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.inc @@ -86,7 +86,7 @@ function ctools_content_process(&$plugin, $info) { /** * Fetch metadata on a specific content_type plugin. * - * @param $content type + * @param mixed $content * Name of a panel content type. * * @return @@ -146,7 +146,7 @@ function ctools_content_get_subtypes($type) { if (is_array($function)) { $subtypes = $function; } - else if (function_exists($function)) { + elseif (function_exists($function)) { // Cast to array to prevent errors from non-array returns. $subtypes = (array) $function($plugin); } @@ -155,6 +155,11 @@ function ctools_content_get_subtypes($type) { // Walk through the subtypes and ensure minimal settings are // retained. foreach ($subtypes as $id => $subtype) { + // Ensure that the 'subtype_id' value exists. + if (!isset($subtype['subtype_id'])) { + $subtypes[$id]['subtype_id'] = $id; + } + // Use exact name since this is a modify by reference. ctools_content_prepare_subtype($subtypes[$id], $plugin); } @@ -202,6 +207,13 @@ function ctools_content_get_subtype($type, $subtype_id) { } if ($subtype) { + // Ensure that the 'subtype_id' value exists. This is also done in + // ctools_content_get_subtypes(), but it wouldn't be called if the plugin + // provides the subtype through its own function. + if (!isset($subtype['subtype_id'])) { + $subtype['subtype_id'] = $subtype_id; + } + ctools_content_prepare_subtype($subtype, $plugin); } return $subtype; @@ -217,6 +229,7 @@ function ctools_content_prepare_subtype(&$subtype, $plugin) { } } + // Trigger hook_ctools_content_subtype_alter(). drupal_alter('ctools_content_subtype', $subtype, $plugin); } @@ -241,8 +254,8 @@ function ctools_content_prepare_subtype(&$subtype, $plugin) { * Any incoming content, if this display is a wrapper. * * @return - * The content as rendered by the plugin. This content should be an array - * with the following possible keys: + * The content as rendered by the plugin, or NULL. + * This content should be an object with the following possible properties: * - title: The safe to render title of the content. * - title_heading: The title heading. * - content: The safe to render HTML content. @@ -294,7 +307,7 @@ function ctools_content_render($type, $subtype, $conf, $keywords = array(), $arg $content->subtype = $subtype; } - // Override the title if configured to + // Override the title if configured to. if (!empty($conf['override_title'])) { // Give previous title as an available substitution here. $keywords['%title'] = empty($content->title) ? '' : $content->title; @@ -304,12 +317,12 @@ function ctools_content_render($type, $subtype, $conf, $keywords = array(), $arg } if (!empty($content->title)) { - // Perform substitutions + // Perform substitutions. if (!empty($keywords) || !empty($context)) { $content->title = ctools_context_keyword_substitute($content->title, $keywords, $context); } - // Sterilize the title + // Sterilize the title. $content->title = filter_xss_admin($content->title); // If a link is specified, populate. @@ -320,7 +333,7 @@ function ctools_content_render($type, $subtype, $conf, $keywords = array(), $arg else { $url = $content->title_link; } - // set defaults so we don't bring up notices + // Set defaults so we don't bring up notices. $url += array('href' => '', 'attributes' => array(), 'query' => array(), 'fragment' => '', 'absolute' => NULL, 'html' => TRUE); $content->title = l($content->title, $url['href'], $url); } @@ -342,7 +355,7 @@ function ctools_content_editable($type, $subtype, $conf) { } $function = FALSE; - + if (!empty($subtype['check editable'])) { $function = ctools_plugin_get_function($subtype, 'check editable'); } @@ -373,7 +386,7 @@ function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) { if (is_array($type)) { $plugin = $type; } - else if (is_string($type)) { + elseif (is_string($type)) { $plugin = ctools_get_content_type($type); } else { @@ -391,10 +404,10 @@ function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) { return $function($subtype, $conf, $pane_context); } - else if (isset($plugin['admin title'])) { + elseif (isset($plugin['admin title'])) { return $plugin['admin title']; } - else if (isset($plugin['title'])) { + elseif (isset($plugin['title'])) { return $plugin['title']; } } @@ -429,7 +442,7 @@ function ctools_content_get_defaults($plugin, $subtype) { if (isset($plugin['defaults'])) { $defaults = $plugin['defaults']; } - else if (isset($subtype['defaults'])) { + elseif (isset($subtype['defaults'])) { $defaults = $subtype['defaults']; } if (isset($defaults)) { @@ -438,7 +451,7 @@ function ctools_content_get_defaults($plugin, $subtype) { return $return; } } - else if (is_array($defaults)) { + elseif (is_array($defaults)) { return $defaults; } } @@ -472,7 +485,7 @@ function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) { if (empty($output) || !is_object($output)) { $output = new stdClass(); - // replace the _ with " " for a better output + // Replace the _ with " " for a better output. $subtype = check_plain(str_replace("_", " ", $subtype)); $output->title = $subtype; $output->content = t('No info available.'); @@ -481,7 +494,7 @@ function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) { } /** - * Add the default FAPI elements to the content type configuration form + * Add the default FAPI elements to the content type configuration form. */ function ctools_content_configure_form_defaults($form, &$form_state) { $plugin = $form_state['plugin']; @@ -598,7 +611,7 @@ function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_na if (!empty($subtype['add form'])) { _ctools_content_create_form_info($form_info, $subtype['add form'], $subtype, $subtype, $op); } - else if (!empty($plugin['add form'])) { + elseif (!empty($plugin['add form'])) { _ctools_content_create_form_info($form_info, $plugin['add form'], $plugin, $subtype, $op); } } @@ -608,7 +621,7 @@ function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_na if (!empty($subtype['edit form'])) { _ctools_content_create_form_info($form_info, $subtype['edit form'], $subtype, $subtype, $op); } - else if (!empty($plugin['edit form'])) { + elseif (!empty($plugin['edit form'])) { _ctools_content_create_form_info($form_info, $plugin['edit form'], $plugin, $subtype, $op); } } @@ -627,7 +640,7 @@ function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, if (empty($subtype['title'])) { $title = t('Configure'); } - else if ($op == 'add') { + elseif ($op == 'add') { $title = t('Configure new !subtype_title', array('!subtype_title' => $subtype['title'])); } else { @@ -642,7 +655,7 @@ function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, ), ); } - else if (is_array($info)) { + elseif (is_array($info)) { $form_info['order'] = array(); $form_info['forms'] = array(); $count = 0; @@ -693,7 +706,7 @@ function ctools_content_get_available_types($contexts = NULL, $has_content = FAL foreach ($plugins as $id => $plugin) { foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) { - // exclude items that require content if we're saying we don't + // Exclude items that require content if we're saying we don't // provide it. if (!empty($subtype['requires content']) && !$has_content) { continue; @@ -733,7 +746,6 @@ function ctools_content_get_available_types($contexts = NULL, $has_content = FAL * Get an array of all content types that can be fed into the * display editor for the add content list, regardless of * availability. - * */ function ctools_content_get_all_types() { $plugins = ctools_get_content_types(); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.menu.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.menu.inc index f7f93406..4e1c2f08 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.menu.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.menu.inc @@ -62,9 +62,8 @@ function ctools_content_autocomplete_entity($entity_type, $string = '') { } } -/* - * Use well known/tested entity reference code to build our search query - * From EntityReference_SelectionHandler_Generic class +/** + * Use EntityReference_SelectionHandler_Generic class to build our search query. */ function _ctools_buildQuery($entity_type, $entity_info, $match = NULL, $match_operator = 'CONTAINS') { $base_table = $entity_info['base table']; @@ -125,7 +124,7 @@ function _ctools_getReferencableEntities($entity_type, $entity_info, $match = NU global $user; $account = $user; $options = array(); - // We're an entity ID, return the id + // We're an entity ID, return the id. if (is_numeric($match) && $match_operator == '=') { if ($entity = array_shift(entity_load($entity_type, array($match)))) { if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) { @@ -143,10 +142,10 @@ function _ctools_getReferencableEntities($entity_type, $entity_info, $match = NU // If you don't have access, or an access callback or a valid entity, just // Return back the Entity ID. return array( - $match => array( + $match => array( 'label' => $match, 'bundle' => NULL, - ), + ), ); } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.plugin-type.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.plugin-type.inc index a0debc3e..0364a927 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.plugin-type.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/content.plugin-type.inc @@ -14,4 +14,4 @@ function ctools_content_plugin_type(&$items) { 'path' => drupal_get_path('module', 'ctools') . '/includes', ), ); -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-access-admin.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-access-admin.inc index 76643cf6..d8c4f3b1 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-access-admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-access-admin.inc @@ -245,10 +245,10 @@ function ctools_access_admin_form_submit($form, &$form_state) { // -------------------------------------------------------------------------- // AJAX menu entry points. - /** * AJAX callback to add a new access test to the list. */ + function ctools_access_ajax_add($fragment = NULL, $name = NULL) { ctools_include('ajax'); ctools_include('modal'); @@ -263,7 +263,7 @@ function ctools_access_ajax_add($fragment = NULL, $name = NULL) { ctools_ajax_render_error(); } - // Separate the fragment into 'module' and 'argument' + // Separate the fragment into 'module' and 'argument'. if (strpos($fragment, '-') === FALSE) { $module = $fragment; $argument = NULL; @@ -279,7 +279,7 @@ function ctools_access_ajax_add($fragment = NULL, $name = NULL) { list($access, $contexts) = $function($argument); - // Make sure we have the logged in user context + // Make sure we have the logged in user context. if (!isset($contexts['logged-in-user'])) { $contexts['logged-in-user'] = ctools_access_get_loggedin_context(); } @@ -306,6 +306,9 @@ function ctools_access_ajax_add($fragment = NULL, $name = NULL) { ); $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state); + $access = $form_state['access']; + $access['plugins'][$id] = $form_state['test']; + if (!isset($output[0])) { $function = $module . '_ctools_access_set'; if (function_exists($function)) { @@ -333,7 +336,7 @@ function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { ctools_ajax_render_error(); } - // Separate the fragment into 'module' and 'argument' + // Separate the fragment into 'module' and 'argument'. if (strpos($fragment, '-') === FALSE) { $module = $fragment; $argument = NULL; @@ -353,7 +356,7 @@ function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { ctools_ajax_render_error(); } - // Make sure we have the logged in user context + // Make sure we have the logged in user context. if (!isset($contexts['logged-in-user'])) { $contexts['logged-in-user'] = ctools_access_get_loggedin_context(); } @@ -367,12 +370,14 @@ function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { 'contexts' => $contexts, 'title' => t('Edit criteria'), 'ajax' => TRUE, - 'ajax' => TRUE, 'modal' => TRUE, 'modal return' => TRUE, ); $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state); + $access = $form_state['access']; + $access['plugins'][$id] = $form_state['test']; + if (!isset($output[0])) { $function = $module . '_ctools_access_set'; if (function_exists($function)) { @@ -452,7 +457,7 @@ function ctools_access_ajax_delete($fragment = NULL, $id = NULL) { ajax_render_error(); } - // Separate the fragment into 'module' and 'argument' + // Separate the fragment into 'module' and 'argument'. if (strpos($fragment, '-') === FALSE) { $module = $fragment; $argument = NULL; @@ -472,7 +477,7 @@ function ctools_access_ajax_delete($fragment = NULL, $id = NULL) { unset($access['plugins'][$id]); } - // re-cache + // re-cache. $function = $module . '_ctools_access_set'; if (function_exists($function)) { $function($argument, $access); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-task-handler.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-task-handler.inc index 21ceea5d..b0f9474d 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-task-handler.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context-task-handler.inc @@ -31,6 +31,7 @@ * If TRUE then this renderer owns the page and can use theme('page') * for no blocks; if false, output is returned regardless of any no * blocks settings. + * * @return * Either the output or NULL if there was output, FALSE if no handler * accepted the task. If $page is FALSE then the $info block is returned instead. @@ -84,7 +85,7 @@ function ctools_context_handler_get_render_handler($task, $subtask, $handlers, $ */ function ctools_context_handler_default_test($handler, $base_contexts, $args) { ctools_include('context'); - // Add my contexts + // Add my contexts. $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler); // Test. @@ -128,7 +129,7 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte 'contexts' => $contexts, 'task' => $task, 'subtask' => $subtask, - 'handler' => $handler + 'handler' => $handler, ); drupal_alter('ctools_render', $info, $page, $context); @@ -141,12 +142,15 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte switch ($info['response code']) { case 403: return MENU_ACCESS_DENIED; + case 404: return MENU_NOT_FOUND; + case 410: drupal_add_http_header('Status', '410 Gone'); drupal_exit(); break; + case 301: case 302: case 303: @@ -162,7 +166,7 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte 'fragment' => $info['fragment'], ); drupal_goto($info['destination'], $options, $info['response code']); - // @todo -- should other response codes be supported here? + // @todo -- should other response codes be supported here? } } @@ -241,7 +245,7 @@ function ctools_task_handler_default_contextual_link($handler, $plugin, $context if (is_array($plugin['tab operation'])) { $trail = $plugin['tab operation']; } - else if (function_exists($plugin['tab operation'])) { + elseif (function_exists($plugin['tab operation'])) { $trail = $plugin['tab operation']($handler, $contexts, $args); } } @@ -251,7 +255,8 @@ function ctools_task_handler_default_contextual_link($handler, $plugin, $context 'href' => $path, 'title' => $title, 'query' => drupal_get_destination(), - )); + ), + ); return $links; } @@ -259,7 +264,12 @@ function ctools_task_handler_default_contextual_link($handler, $plugin, $context /** * Called to execute actions that should happen before a handler is rendered. */ -function ctools_context_handler_pre_render($handler, $contexts, $args) { } +function ctools_context_handler_pre_render($handler, $contexts, $args) { + foreach (module_implements('ctools_context_handler_pre_render') as $module) { + $function = $module . '_ctools_context_handler_pre_render'; + $function($handler, $contexts, $args); + } +} /** * Compare arguments to contexts for selection purposes. @@ -320,7 +330,6 @@ function ctools_context_handler_summary($task, $subtask, $handler) { // the task handler, for example) but sometimes we need them separately // (when a task has contexts loaded and is trying out the task handlers, // for example). Therefore there are two paths we can take to getting contexts. - /** * Load the contexts for a task, using arguments. * @@ -368,7 +377,7 @@ function ctools_context_handler_get_all_contexts($task, $subtask, $handler) { * expects things in a certain, kind of clunky format. */ function ctools_context_handler_get_handler_object($handler) { - $object = new stdClass; + $object = new stdClass(); $object->name = $handler->name; $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array(); $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array(); @@ -382,7 +391,7 @@ function ctools_context_handler_get_handler_object($handler) { * arguments from the task. */ function ctools_context_handler_get_task_object($task, $subtask, $handler) { - $object = new stdClass; + $object = new stdClass(); $object->name = !empty($handler->name) ? $handler->name : 'temp'; $object->base_contexts = ctools_context_handler_get_base_contexts($task, $subtask, TRUE); $object->arguments = ctools_context_handler_get_task_arguments($task, $subtask); @@ -456,7 +465,7 @@ function ctools_context_handler_edit_criteria($form, &$form_state) { ctools_modal_add_plugin_js(ctools_get_access_plugins()); ctools_include('context-access-admin'); $form_state['module'] = (isset($form_state['module'])) ? $form_state['module'] : 'page_manager_task_handler'; - // Encode a bunch of info into the argument so we can get our cache later + // Encode a bunch of info into the argument so we can get our cache later. $form_state['callback argument'] = $form_state['task_name'] . '*' . $form_state['handler']->name; $form_state['access'] = $form_state['handler']->conf['access']; $form_state['no buttons'] = TRUE; @@ -472,7 +481,7 @@ function ctools_context_handler_edit_criteria($form, &$form_state) { } /** - * Submit handler for rules selection + * Submit handler for rules selection. */ function ctools_context_handler_edit_criteria_submit(&$form, &$form_state) { $form_state['handler']->conf['access']['logic'] = $form_state['values']['logic']; @@ -537,4 +546,3 @@ function ctools_context_handler_edit_context_submit(&$form, &$form_state) { unset($form_state['page']->context_cache[$cache_name]); } } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc index 1f9c1e45..0fc62080 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc @@ -2,7 +2,6 @@ /** * @file - * * Contains code related to the ctools system of 'context'. * * Context, originally from Panels, is a method of packaging objects into @@ -28,28 +27,104 @@ * of the context is important. */ class ctools_context { - var $type = NULL; - var $data = NULL; - // The title of this object. - var $title = ''; - // The title of the page if this object exists - var $page_title = ''; - // The identifier (in the UI) of this object - var $identifier = ''; - var $argument = NULL; - var $keyword = ''; - var $original_argument = NULL; - var $restrictions = array(); - var $empty = FALSE; - - function ctools_context($type = 'none', $data = NULL) { - $this->type = $type; - $this->data = $data; + /** + * @var string|array + * A string naming this specific context type. The values 'any' and 'none' + * are special: + * - 'any': used in is_type() to match any other type. + * - 'none': used to signal the type is not defined. + */ + public $type; + + /** + * @var mixed + * The data payload for this context object. + */ + public $data; + + /** + * @var string + * The title of this object. + */ + public $title; + + /** + * @var string + * The title of the page if this object exists + */ + public $page_title; + + /** + * @var string + * The identifier (in the UI) of this object. + */ + public $identifier; + + /** + * @var + */ + public $argument; + + /** + * @var string + */ + public $keyword; + + /** + * @var + */ + public $original_argument; + + /** + * @var array + */ + public $restrictions; + + /** + * @var bool + */ + public $empty; + + /** + * The ctools_context constructor. + * + * @param string $type + * The type name of this context. Should be unique. Use the machine_name + * conventions: lowercase, short, underscores and no spaces. + * @param mixed $data + * The data payload, if required for this context. + */ + public function __construct($type = 'none', $data = NULL) { + $this->type = $type; + $this->data = $data; $this->title = t('Unknown context'); + $this->page_title = ''; + $this->identifier = ''; + $this->keyword = ''; + $this->restrictions = array(); + $this->empty = FALSE; + // Other vars are NULL. } - function is_type($type) { - if ($type == 'any' || $this->type == 'any') { + /** + * Determine whether this object is of type @var $type . + * + * Both the internal value ($this->type) and the supplied value ($type) can + * be a string or an array of strings, and if one or both are arrays the match + * succeeds if at least one common element is found. + * + * Type names + * + * @param string|array $type + * 'type' can be: + * - 'any' to match all types (this is true of the internal value too). + * - an array of type name strings, when more than one type is acceptable. + * + * @return bool + * True if the type matches, False otherwise. + */ + public function is_type($type) { + if ($type === 'any' || $this->type === 'any') { return TRUE; } @@ -58,32 +133,71 @@ class ctools_context { return (bool) array_intersect($a, $b); } - function get_argument() { + /** + * Return the argument. + * + * @return mixed + * The value of $argument. + */ + public function get_argument() { return $this->argument; } - function get_original_argument() { + /** + * Return the value of argument (or arg) variable as it was passed in. + * + * For example see ctools_plugin_load_function() and ctools_terms_context(). + * + * @return mixed + * The original arg value. + */ + public function get_original_argument() { if (!is_null($this->original_argument)) { return $this->original_argument; } return $this->argument; } - function get_keyword() { + /** + * Return the keyword. + * + * @return mixed + * The value of $keyword. + */ + public function get_keyword() { return $this->keyword; } - function get_identifier() { + /** + * Return the identifier. + * + * @return mixed + * The value of $identifier. + */ + public function get_identifier() { return $this->identifier; } - function get_title() { + /** + * Return the title. + * + * @return mixed + * The value of $title. + */ + public function get_title() { return $this->title; } - function get_page_title() { + /** + * Return the page title. + * + * @return mixed + * The value of $page_title. + */ + public function get_page_title() { return $this->page_title; } + } /** @@ -91,39 +205,57 @@ class ctools_context { * match a required context type. */ class ctools_context_required { - var $keywords = ''; + /** + * @var array + * Keyword strings associated with the context. + */ + public $keywords; /** * If set, the title will be used in the selector to identify * the context. This is very useful when multiple contexts * are required to inform the user will be used for what. */ - var $title = NULL; + public $title; /** * Test to see if this context is required. */ - var $required = TRUE; + public $required = TRUE; /** * If TRUE, skip the check in ctools_context_required::select() * for contexts whose names may have changed. */ - var $skip_name_check = FALSE; + public $skip_name_check = FALSE; /** + * The ctools_context_required constructor. + * + * Note: Constructor accepts a variable number of arguments, with optional + * type-dependent args at the end of the list and one required argument, + * the title. Note in particular that skip_name_check MUST be passed in as + * a boolean (and not, for example, as an integer). * - * @param $title - * The first parameter should be the 'title' of the context for use - * in UYI selectors when multiple contexts qualify. - * @param ... + * @param string $title + * The title of the context for use in UI selectors when multiple contexts + * qualify. + * @param string $keywords * One or more keywords to use for matching which contexts are allowed. + * @param array $restrictions + * Array of context restrictions. + * @param bool $skip_name_check + * If True, skip the check in select() for contexts whose names may have + * changed. */ - function ctools_context_required($title) { + public function __construct($title) { + // If it was possible, using variadic syntax this should be: + // __construct($title, string ...$keywords, array $restrictions = NULL, bool $skip = NULL) + // but that form isn't allowed. $args = func_get_args(); $this->title = array_shift($args); - // If we have a boolean value at the end for $skip_name_check, store it + // If we have a boolean value at the end for $skip_name_check, store it. if (is_bool(end($args))) { $this->skip_name_check = array_pop($args); } @@ -133,20 +265,44 @@ class ctools_context_required { $this->restrictions = array_pop($args); } - if (count($args) == 1) { + if (count($args) === 1) { $args = array_shift($args); } $this->keywords = $args; } - function filter($contexts) { + /** + * Filter the contexts to determine which apply in the current environment. + * + * A context passes the filter if: + * - the context matches 'type' of the required keywords (uses + * ctools_context::is_type(), so includes 'any' matches, etc). + * - AND if restrictions are present, there are some common elements between + * the requirement and the context. + * + * @param array $contexts + * An array of ctools_context objects (or something which will cast to an + * array of them). The contexts to apply the filter on. + * + * @return array + * An array of context objects, keyed with the same keys used for $contexts, + * which pass the filter. + * + * @see ctools_context::is_type() + */ + public function filter($contexts) { $result = array(); - // See which of these contexts are valid + /** + * See which of these contexts are valid. + * @var ctools_context $context + */ foreach ((array) $contexts as $cid => $context) { if ($context->is_type($this->keywords)) { + // Compare to see if our contexts were met. if (!empty($this->restrictions) && !empty($context->restrictions)) { + foreach ($this->restrictions as $key => $values) { // If we have a restriction, the context must either not have that // restriction listed, which means we simply don't know what it is, @@ -155,11 +311,16 @@ class ctools_context_required { if (!is_array($values)) { $values = array($values); } - if (!empty($context->restrictions[$key]) && !array_intersect($values, $context->restrictions[$key])) { + + if (!empty($context->restrictions[$key]) + && !array_intersect($values, $context->restrictions[$key]) + ) { + // Break out to check next context; this one fails the filter. continue 2; } } } + // This context passes the filter. $result[$cid] = $context; } } @@ -167,7 +328,28 @@ class ctools_context_required { return $result; } - function select($contexts, $context) { + /** + * Select one context from the list of contexts, accounting for changed IDs. + * + * Fundamentally, this returns $contexts[$context] or FALSE if that does not + * exist. Additional logic accounts for changes in context names and dealing + * with a $contexts parameter that is not an array. + * + * If we had requested a $context but that $context doesn't exist in our + * context list, there is a good chance that what happened is the context + * IDs changed. Look for another context that satisfies our requirements, + * unless $skip_name_check is set. + * + * @param ctools_context|array $contexts + * A context, or an array of ctools_context. + * @param string $context + * A context ID. + * + * @return bool|ctools_context + * The matching ctools_context, or False if no such context was found. + */ + public function select($contexts, $context) { + // Easier to deal with a standalone object as a 1-element array of objects. if (!is_array($contexts)) { if (is_object($contexts) && $contexts instanceof ctools_context) { $contexts = array($contexts->id => $contexts); @@ -177,11 +359,12 @@ class ctools_context_required { } } - // If we had requested a $context but that $context doesn't exist - // in our context list, there is a good chance that what happened - // is our context IDs changed. See if there's another context - // that satisfies our requirements. - if (!$this->skip_name_check && !empty($context) && !isset($contexts[$context])) { + // If we had requested a $context but that $context doesn't exist in our + // context list, there is a good chance that what happened is the context + // IDs changed. Check for another context that satisfies our requirements. + if (!$this->skip_name_check + && !empty($context) && !isset($contexts[$context]) + ) { $choices = $this->filter($contexts); // If we got a hit, take the first one that matches. @@ -196,6 +379,7 @@ class ctools_context_required { } return $contexts[$context]; } + } /** @@ -203,28 +387,73 @@ class ctools_context_required { * can produce empty contexts to use as placeholders. */ class ctools_context_optional extends ctools_context_required { - var $required = FALSE; - function ctools_context_optional() { - $args = func_get_args(); - call_user_func_array(array($this, 'ctools_context_required'), $args); - } /** - * Add the 'empty' context which is possible for optional + * {@inheritdoc} */ - function add_empty(&$contexts) { + public $required = FALSE; + + /** + * Add the 'empty' context to the existing set. + * + * @param array &$contexts + * An array of ctools_context objects. + */ + public function add_empty(&$contexts) { $context = new ctools_context('any'); - $context->title = t('No context'); + $context->title = t('No context'); $context->identifier = t('No context'); $contexts['empty'] = $context; } - function filter($contexts) { + /** + * Filter the contexts to determine which apply in the current environment. + * + * As for ctools_context_required, but we add the empty context to those + * passed in so the check is optional (i.e. if nothing else matches, the + * empty context will, and so there will always be at least one matched). + * + * @param array $contexts + * An array of ctools_context objects (or something which will cast to an + * array of them). The contexts to apply the filter on. + * + * @return array + * An array of context objects, keyed with the same keys used for $contexts, + * which pass the filter. + * + * @see ctools_context::is_type() + */ + public function filter($contexts) { + /** + * @todo We are assuming here that $contexts is actually an array, whereas + * ctools_context_required::filter only requires $contexts is convertible + * to an array. + */ $this->add_empty($contexts); return parent::filter($contexts); } - function select($contexts, $context) { + /** + * Select and return one context from the list of applicable contexts. + * + * Fundamentally, this returns $contexts[$context] or the empty context if + * that does not exist. + * + * @param array $contexts + * The applicable contexts to check. + * @param string $context + * The context id to check for. + * + * @return bool|ctools_context + * The matching ctools_context, or False if no such context was found. + * + * @see ctools_context_required::select() + */ + public function select($contexts, $context) { + /** + * @todo We are assuming here that $contexts is actually an array, whereas + * ctools_context_required::select permits ctools_context objects as well. + */ $this->add_empty($contexts); if (empty($context)) { return $contexts['empty']; @@ -234,11 +463,12 @@ class ctools_context_optional extends ctools_context_required { // Don't flip out if it can't find the context; this is optional, put // in an empty. - if ($result == FALSE) { + if ($result === FALSE) { $result = $contexts['empty']; } return $result; } + } /** @@ -253,20 +483,20 @@ class ctools_context_optional extends ctools_context_required { * Since multiple contexts can be required, this function will accept either * an array of all required contexts, or just a single required context object. * - * @param $contexts + * @param array $contexts * A keyed array of all available contexts. - * @param $required - * A ctools_context_required or ctools_context_optional object, or an array - * of such objects. + * @param array|ctools_context_required|ctools_context_optional $required + * A *_required or *_optional object, or an array of such objects, which + * define the selection condition. * - * @return + * @return array * A keyed array of contexts that match the filter. */ function ctools_context_filter($contexts, $required) { if (is_array($required)) { $result = array(); - foreach ($required as $r) { - $result = array_merge($result, _ctools_context_filter($contexts, $r)); + foreach ($required as $item) { + $result = array_merge($result, _ctools_context_filter($contexts, $item)); } return $result; } @@ -274,6 +504,21 @@ function ctools_context_filter($contexts, $required) { return _ctools_context_filter($contexts, $required); } +/** + * Helper function for ctools_context_filter(). + * + * Used to transform the required context during the merge into the final array. + * + * @internal This function DOES NOT form part of the CTools API. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context_required|ctools_context_optional $required + * A ctools_context_required or ctools_context_optional object, although if + * given something else will return an empty array. + * + * @return array + */ function _ctools_context_filter($contexts, $required) { $result = array(); @@ -293,12 +538,17 @@ function _ctools_context_filter($contexts, $required) { * If an array of required contexts is provided, one selector will be * provided for each context. * - * @param $contexts + * @param array $contexts * A keyed array of all available contexts. - * @param $required + * @param array|ctools_context_required|ctools_context_optional $required * The required context object or array of objects. + * @param array|string $default + * The default value for the select object, suitable for a #default_value + * render key. Where $required is an array, this is an array keyed by the + * same key values as $required for all keys where an empty string is not a + * suitable default. Otherwise it is just the default value. * - * @return + * @return array * A form element, or NULL if there are no contexts that satisfy the * requirements. */ @@ -306,8 +556,10 @@ function ctools_context_selector($contexts, $required, $default) { if (is_array($required)) { $result = array('#tree' => TRUE); $count = 1; - foreach ($required as $id => $r) { - $result[] = _ctools_context_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++); + foreach ($required as $id => $item) { + $result[] = _ctools_context_selector( + $contexts, $item, isset($default[$id]) ? $default[$id] : '', $count++ + ); } return $result; } @@ -315,6 +567,27 @@ function ctools_context_selector($contexts, $required, $default) { return _ctools_context_selector($contexts, $required, $default); } +/** + * Helper function for ctools_context_selector(). + * + * @internal This function DOES NOT form part of the CTools API. Use the API + * function ctools_context_selector() instead. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context_required|ctools_context_optional $required + * The required context object. + * @param $default + * The default value for the select object, suitable for a #default_value + * render key. + * @param int $num + * If supplied and non-zero, the title of the select form element will be + * "Context $num", otherwise it will be "Context". + * + * @return array + * A form element, or NULL if there are no contexts that satisfy the + * requirements. + */ function _ctools_context_selector($contexts, $required, $default, $num = 0) { $filtered = ctools_context_filter($contexts, $required); $count = count($filtered); @@ -358,8 +631,8 @@ function _ctools_context_selector($contexts, $required, $default, $num = 0) { * @param $required * The required context object or array of objects. * - * @return - * TRUE if there are enough contexts, FALSE if there are not. + * @return bool + * True if there are enough contexts, otherwise False. */ function ctools_context_match_requirements($contexts, $required) { if (!is_array($required)) { @@ -392,8 +665,13 @@ function ctools_context_match_requirements($contexts, $required) { * A keyed array of all available contexts. * @param $required * The required context object or array of objects. + * @param array|string $default + * The default value for the select object, suitable for a #default_value + * render key. Where $required is an array, this is an array keyed by the + * same key values as $required for all keys where an empty string is not a + * suitable default. Otherwise it is just the default value. * - * @return + * @return array * A form element, or NULL if there are no contexts that satisfy the * requirements. */ @@ -401,8 +679,11 @@ function ctools_context_converter_selector($contexts, $required, $default) { if (is_array($required)) { $result = array('#tree' => TRUE); $count = 1; - foreach ($required as $id => $r) { - $result[] = _ctools_context_converter_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++); + foreach ($required as $id => $dependency) { + $default_id = isset($default[$id]) ? $default[$id] : ''; + $result[] = _ctools_context_converter_selector( + $contexts, $dependency, $default_id, $count++ + ); } return $result; } @@ -410,17 +691,36 @@ function ctools_context_converter_selector($contexts, $required, $default) { return _ctools_context_converter_selector($contexts, $required, $default); } +/** + * Helper function for ctools_context_converter_selector(). + * + * @internal This function DOES NOT form part of the CTools API. Use the API + * function ctools_context_converter_selector() instead. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context $required + * The required context object. + * @param $default + * The default value for the select object, suitable for a #default_value + * render key. + * @param int $num + * If supplied and non-zero, the title of the select form element will be + * "Context $num", otherwise it will be "Context". + * + * @return array|null + * A form element, or NULL if there are no contexts that satisfy the + * requirements. + */ function _ctools_context_converter_selector($contexts, $required, $default, $num = 0) { $filtered = ctools_context_filter($contexts, $required); $count = count($filtered); - $form = array(); - if ($count > 1) { // If there's more than one to choose from, create a select widget. $options = array(); foreach ($filtered as $cid => $context) { - if ($context->type == 'any') { + if ($context->type === 'any') { $options[''] = t('No context'); continue; } @@ -450,10 +750,22 @@ function _ctools_context_converter_selector($contexts, $required, $default, $num '#default_value' => $default, ); } + else { + // Not enough choices to need a selector, so don't make one. + return NULL; + } } /** * Get a list of converters available for a given context. + * + * @param string $cid + * A context ID. + * @param ctools_context $context + * The context for which converters are needed. + * + * @return array + * A list of context converters. */ function ctools_context_get_converters($cid, $context) { if (empty($context->plugin)) { @@ -465,6 +777,17 @@ function ctools_context_get_converters($cid, $context) { /** * Get a list of converters available for a given context. + * + * @internal This function DOES NOT form part of the CTools API. Use the API + * function ctools_context_get_converters() instead. + * + * @param string $id + * A context ID. + * @param string $plugin_name + * The name of the context plugin. + * + * @return array + * A list of context converters. */ function _ctools_context_get_converters($id, $plugin_name) { $plugin = ctools_get_context($plugin_name); @@ -476,7 +799,7 @@ function _ctools_context_get_converters($id, $plugin_name) { if (is_array($plugin['convert list'])) { $converters = $plugin['convert list']; } - else if ($function = ctools_plugin_get_function($plugin, 'convert list')) { + elseif ($function = ctools_plugin_get_function($plugin, 'convert list')) { $converters = (array) $function($plugin); } @@ -496,7 +819,13 @@ function _ctools_context_get_converters($id, $plugin_name) { } /** - * Get a list of all contexts + converters available. + * Get a list of all contexts converters available. + * + * For all contexts returned by ctools_get_contexts(), return the converter + * for all contexts that have one. + * + * @return array + * A list of context converters, keyed by the title of the converter. */ function ctools_context_get_all_converters() { $contexts = ctools_get_contexts(); @@ -516,21 +845,24 @@ function ctools_context_get_all_converters() { /** * Let the context convert an argument based upon the converter that was given. * - * @param $context - * The context object - * @param $converter - * The converter to use, which should be a string provided by the converter list. - * @param $converter_options - * A n array of options to pass on to the generation function. For contexts + * @param ctools_context $context + * The context object. + * @param string $converter + * The type of converter to use, which should be a string provided by the + * converter list function. + * @param array $converter_options + * An array of options to pass on to the generation function. For contexts * that use token module, of particular use is 'sanitize' => FALSE which can * get raw tokens. This should ONLY be used in values that will later be * treated as unsafe user input since these values are by themselves unsafe. * It is particularly useful to get raw values from Field API. + * + * @return string|null */ function ctools_context_convert_context($context, $converter, $converter_options = array()) { // Contexts without plugins might be optional placeholders. if (empty($context->plugin)) { - return; + return NULL; } $value = $context->argument; @@ -551,22 +883,35 @@ function ctools_context_convert_context($context, $converter, $converter_options * Choose a context or contexts based upon the selection made via * ctools_context_filter. * - * @param $contexts - * A keyed array of all available contexts - * @param $required - * The required context object provided by the plugin + * @param array $contexts + * A keyed array of all available contexts. + * @param array|ctools_context_required $required + * The required context object(s) provided by the plugin. * @param $context - * The selection made using ctools_context_selector + * The selection made using ctools_context_selector(). + * + * @return ctools_context|array|false + * Returns FALSE if $required is not an object, or array of objects, or + * the value of $required->select() for the context, or an array of those (if + * passed an array in $required). */ function ctools_context_select($contexts, $required, $context) { if (is_array($required)) { + + /** + * @var array $required + * Array of required context objects. + * @var ctools_context_required $item + * A required context object. + */ $result = array(); - foreach ($required as $id => $r) { + foreach ($required as $id => $item) { + // @todo What's the difference between the following and "empty($item)" ? if (empty($required[$id])) { continue; } - if (($result[] = _ctools_context_select($contexts, $r, $context[$id])) === FALSE) { + if (($result[] = _ctools_context_select($contexts, $item, $context[$id])) === FALSE) { return FALSE; } } @@ -576,6 +921,22 @@ function ctools_context_select($contexts, $required, $context) { return _ctools_context_select($contexts, $required, $context); } +/** + * Helper function for calling the required context object's selection function. + * + * This function DOES NOT form part of the CTools API. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context_required $required + * The required context object provided by the plugin. + * @param $context + * The selection made using ctools_context_selector(). + * + * @return ctools_context|bool + * FALSE if the $required is not an object. A ctools_context object if one + * matched. + */ function _ctools_context_select($contexts, $required, $context) { if (!is_object($required)) { return FALSE; @@ -587,16 +948,14 @@ function _ctools_context_select($contexts, $required, $context) { /** * Create a new context object. * - * @param $type + * @param string $type * The type of context to create; this loads a plugin. - * @param $data + * @param mixed $data * The data to put into the context. - * @param $empty - * Whether or not this context is specifically empty. * @param $conf * A configuration structure if this context was created via UI. * - * @return + * @return ctools_context * A $context or NULL if one could not be created. */ function ctools_context_create($type, $data = NULL, $conf = FALSE) { @@ -619,7 +978,7 @@ function ctools_context_create($type, $data = NULL, $conf = FALSE) { * @param $type * The type of context to create; this loads a plugin. * - * @return + * @return ctools_context * A $context or NULL if one could not be created. */ function ctools_context_create_empty($type) { @@ -636,8 +995,21 @@ function ctools_context_create_empty($type) { /** * Perform keyword and context substitutions. + * + * @param string $string + * The string in which to replace keywords. + * @param array $keywords + * Array of keyword-replacement pairs. + * @param array $contexts + * + * @param array $converter_options + * Options to pass on to ctools_context_convert_context(), defaults to an + * empty array. + * + * @return string + * The returned string, with substitutions performed. */ -function ctools_context_keyword_substitute($string, $keywords, $contexts, $converter_options = array()) { +function ctools_context_keyword_substitute($string, $keywords, $contexts, array $converter_options = array()) { // Ensure a default keyword exists: $keywords['%%'] = '%'; @@ -660,7 +1032,7 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve // If the keyword is already set by something passed in, don't try to // overwrite it. - if (!empty($keywords['%' . $keyword])) { + if (array_key_exists('%' . $keyword, $keywords)) { continue; } @@ -680,14 +1052,21 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve } } - if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) { - $keywords['%' . $keyword] = ''; - } - else if (!empty($converter)) { - $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options); + if (!isset($context_keywords[$context])) { + $keywords['%' . $keyword] = '%' . $keyword; } else { - $keywords['%' . $keyword] = $context_keywords[$keyword]->title; + if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) { + $keywords['%' . $keyword] = ''; + } + else { + if (!empty($converter)) { + $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options); + } + else { + $keywords['%' . $keyword] = $context_keywords[$keyword]->title; + } + } } } } @@ -695,18 +1074,22 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve } /** - * Determine a unique context ID for a context + * Determine a unique context ID for a context. * * Often contexts of many different types will be placed into a list. This * ensures that even though contexts of multiple types may share IDs, they * are unique in the final list. */ function ctools_context_id($context, $type = 'context') { - if (!$context['id']) { + // If not set, FALSE or empty. + if (!isset($context['id']) || !$context['id']) { $context['id'] = 1; } - return $type . '_' . $context['name'] . '_' . $context['id']; + // @todo is '' the appropriate default value? + $name = isset($context['name']) ? $context['name'] : ''; + + return $type . '_' . $name . '_' . $context['id']; } /** @@ -714,42 +1097,46 @@ function ctools_context_id($context, $type = 'context') { * * This finds the next id available for the named object. * - * @param $objects - * A list of context descriptor objects, i.e, arguments, relationships, contexts, etc. - * @param $name + * @param array $objects + * A list of context descriptor objects, i.e, arguments, relationships, + * contexts, etc. + * @param string $name * The name being used. + * + * @return int + * The next integer id available. */ function ctools_context_next_id($objects, $name) { $id = 0; - // Figure out which instance of this argument we're creating + // Figure out which instance of this argument we're creating. if (!$objects) { return $id + 1; } foreach ($objects as $object) { - if (isset($object['name']) && $object['name'] == $name) { - if ($object['id'] > $id) { + if (isset($object['name']) && $object['name'] === $name) { + if (isset($object['id']) && $object['id'] > $id) { $id = $object['id']; } + // @todo If obj has no 'id', should we increment local id? $id = $id + 1; } } return $id + 1; } - // --------------------------------------------------------------------------- // Functions related to contexts from arguments. - /** - * Fetch metadata on a specific argument plugin. + * Fetch metadata for a specific argument plugin. * * @param $argument * Name of an argument plugin. * - * @return + * @return array * An array with information about the requested argument plugin. */ + function ctools_get_argument($argument) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'arguments', $argument); @@ -758,7 +1145,7 @@ function ctools_get_argument($argument) { /** * Fetch metadata for all argument plugins. * - * @return + * @return array * An array of arrays with information about all available argument plugins. */ function ctools_get_arguments() { @@ -778,22 +1165,23 @@ function ctools_get_arguments() { * - keyword: The keyword used for this argument for substitutions. * * @param $arg - * The actual argument received. This is expected to be a string from a URL but - * this does not have to be the only source of arguments. + * The actual argument received. This is expected to be a string from a URL + * but this does not have to be the only source of arguments. * @param $empty * If true, the $arg will not be used to load the context. Instead, an empty * placeholder context will be loaded. * - * @return + * @return ctools_context * A context object if one can be loaded. */ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALSE) { ctools_include('plugins'); if (empty($argument['name'])) { - return; + return NULL; } - if ($function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context')) { + $function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context'); + if ($function) { // Backward compatibility: Merge old style settings into new style: if (!empty($argument['settings'])) { $argument += $argument['settings']; @@ -805,8 +1193,8 @@ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALS if (is_object($context)) { $context->identifier = $argument['identifier']; $context->page_title = isset($argument['title']) ? $argument['title'] : ''; - $context->keyword = $argument['keyword']; - $context->id = ctools_context_id($argument, 'argument'); + $context->keyword = $argument['keyword']; + $context->id = ctools_context_id($argument, 'argument'); $context->original_argument = $arg; if (!empty($context->empty)) { @@ -822,6 +1210,12 @@ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALS /** * Retrieve a list of empty contexts for all arguments. + * + * @param array $arguments + * + * @return array + * + * @see ctools_context_get_context_from_arguments() */ function ctools_context_get_placeholders_from_argument($arguments) { $contexts = array(); @@ -837,19 +1231,21 @@ function ctools_context_get_placeholders_from_argument($arguments) { /** * Load the contexts for a given list of arguments. * - * @param $arguments + * @param array $arguments * The array of argument definitions. - * @param &$contexts + * @param array &$contexts * The array of existing contexts. New contexts will be added to this array. - * @param $args + * @param array $args * The arguments to load. * - * @return - * FALSE if an argument wants to 404. + * @return bool + * TRUE if all is well, FALSE if an argument wants to 404. + * + * @see ctools_context_get_context_from_argument() */ function ctools_context_get_context_from_arguments($arguments, &$contexts, $args) { foreach ($arguments as $argument) { - // pull the argument off the list. + // Pull the argument off the list. $arg = array_shift($args); $id = ctools_context_id($argument, 'argument'); @@ -864,7 +1260,10 @@ function ctools_context_get_context_from_arguments($arguments, &$contexts, $args $context = $contexts[$id]; } - if ((empty($context) || empty($context->data)) && !empty($argument['default']) && $argument['default'] == '404') { + if ((empty($context) || empty($context->data)) + && !empty($argument['default']) + && $argument['default'] === '404' + ) { return FALSE; } } @@ -873,16 +1272,18 @@ function ctools_context_get_context_from_arguments($arguments, &$contexts, $args // --------------------------------------------------------------------------- // Functions related to contexts from relationships. - /** - * Fetch metadata on a specific relationship plugin. + * Fetch plugin metadata for a specific relationship plugin. * - * @param $content type + * @param $relationship * Name of a panel content type. * - * @return + * @return array * An array with information about the requested relationship. + * + * @see ctools_get_relationships() */ + function ctools_get_relationship($relationship) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'relationships', $relationship); @@ -891,8 +1292,10 @@ function ctools_get_relationship($relationship) { /** * Fetch metadata for all relationship plugins. * - * @return + * @return array * An array of arrays with information about all available relationships. + * + * @see ctools_get_relationship() */ function ctools_get_relationships() { ctools_include('plugins'); @@ -900,8 +1303,9 @@ function ctools_get_relationships() { } /** + * Return a context from a relationship. * - * @param $relationship + * @param array $relationship * The configuration of a relationship. It must contain the following data: * - name: The name of the relationship plugin being used. * - relationship_settings: The configuration based upon the plugin forms. @@ -909,18 +1313,21 @@ function ctools_get_relationships() { * defined by the UI. * - keyword: The keyword used for this relationship for substitutions. * - * @param $source_context + * @param ctools_context $source_context * The context this relationship is based upon. - * - * @param $placeholders + * @param bool $placeholders * If TRUE, placeholders are acceptable. * - * @return - * A context object if one can be loaded. + * @return ctools_context|null + * A context object if one can be loaded, otherwise NULL. + * + * @see ctools_context_get_relevant_relationships() + * @see ctools_context_get_context_from_relationships() */ function ctools_context_get_context_from_relationship($relationship, $source_context, $placeholders = FALSE) { ctools_include('plugins'); - if ($function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context')) { + $function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context'); + if ($function) { // Backward compatibility: Merge old style settings into new style: if (!empty($relationship['relationship_settings'])) { $relationship += $relationship['relationship_settings']; @@ -931,7 +1338,7 @@ function ctools_context_get_context_from_relationship($relationship, $source_con if ($context) { $context->identifier = $relationship['identifier']; $context->page_title = isset($relationship['title']) ? $relationship['title'] : ''; - $context->keyword = $relationship['keyword']; + $context->keyword = $relationship['keyword']; if (!empty($context->empty)) { $context->placeholder = array( 'type' => 'relationship', @@ -941,6 +1348,7 @@ function ctools_context_get_context_from_relationship($relationship, $source_con return $context; } } + return NULL; } /** @@ -954,18 +1362,24 @@ function ctools_context_get_context_from_relationship($relationship, $source_con * @param $contexts * An array of contexts used to figure out which relationships are relevant. * - * @return + * @return array * An array of relationship keys that are relevant for the given set of * contexts. + * + * @see ctools_context_filter() + * @see ctools_context_get_context_from_relationship() + * @see ctools_context_get_context_from_relationships() */ function ctools_context_get_relevant_relationships($contexts) { $relevant = array(); $relationships = ctools_get_relationships(); - // Go through each relationship + // Go through each relationship. foreach ($relationships as $rid => $relationship) { // For each relationship, see if there is a context that satisfies it. - if (empty($relationship['no ui']) && ctools_context_filter($contexts, $relationship['required context'])) { + if (empty($relationship['no ui']) + && ctools_context_filter($contexts, $relationship['required context']) + ) { $relevant[$rid] = $relationship['title']; } } @@ -974,7 +1388,7 @@ function ctools_context_get_relevant_relationships($contexts) { } /** - * Fetch all active relationships + * Fetch all active relationships. * * @param $relationships * An keyed array of relationship data including: @@ -989,10 +1403,11 @@ function ctools_context_get_relevant_relationships($contexts) { * * @param $placeholders * If TRUE, placeholders are acceptable. + * + * @see ctools_context_get_context_from_relationship() + * @see ctools_context_get_relevant_relationships() */ function ctools_context_get_context_from_relationships($relationships, &$contexts, $placeholders = FALSE) { - $return = array(); - foreach ($relationships as $rdata) { if (!isset($rdata['context'])) { continue; @@ -1023,16 +1438,16 @@ function ctools_context_get_context_from_relationships($relationships, &$context // --------------------------------------------------------------------------- // Functions related to loading contexts from simple context definitions. - /** * Fetch metadata on a specific context plugin. * - * @param $context + * @param string $context * Name of a context. * - * @return + * @return array * An array with information about the requested panel context. */ + function ctools_get_context($context) { static $gate = array(); ctools_include('plugins'); @@ -1056,7 +1471,7 @@ function ctools_get_context($context) { /** * Fetch metadata for all context plugins. * - * @return + * @return array * An array of arrays with information about all available panel contexts. */ function ctools_get_contexts() { @@ -1065,27 +1480,38 @@ function ctools_get_contexts() { } /** + * Return a context object from a context definition array. * - * @param $context + * The input $context contains the information needed to identify and invoke + * the context plugin and create the plugin context from that. + * + * @param array $context * The configuration of a context. It must contain the following data: * - name: The name of the context plugin being used. * - context_settings: The configuration based upon the plugin forms. * - identifier: The human readable identifier for this context, usually * defined by the UI. * - keyword: The keyword used for this context for substitutions. - * @param $type + * @param string $type * This is either 'context' which indicates the context will be loaded - * from data in the settings, or 'required_context' which means the + * from data in the settings, or 'requiredcontext' which means the * context must be acquired from an external source. This is the method * used to pass pure contexts from one system to another. + * @param mixed $argument + * Optional information passed to the plugin context via the arg defined in + * the plugin's "placeholder name" field. * - * @return + * @return ctools_context|null * A context object if one can be loaded. + * + * @see ctools_get_context() + * @see ctools_plugin_get_function() */ function ctools_context_get_context_from_context($context, $type = 'context', $argument = NULL) { ctools_include('plugins'); $plugin = ctools_get_context($context['name']); - if ($function = ctools_plugin_get_function($plugin, 'context')) { + $function = ctools_plugin_get_function($plugin, 'context'); + if ($function) { // Backward compatibility: Merge old style settings into new style: if (!empty($context['context_settings'])) { $context += $context['context_settings']; @@ -1100,7 +1526,7 @@ function ctools_context_get_context_from_context($context, $type = 'context', $a if ($return) { $return->identifier = $context['identifier']; $return->page_title = isset($context['title']) ? $context['title'] : ''; - $return->keyword = $context['keyword']; + $return->keyword = $context['keyword']; if (!empty($context->empty)) { $context->placeholder = array( @@ -1112,6 +1538,8 @@ function ctools_context_get_context_from_context($context, $type = 'context', $a return $return; } } + + return NULL; } /** @@ -1125,7 +1553,10 @@ function ctools_context_get_context_from_context($context, $type = 'context', $a * Either 'context' or 'requiredcontext', which indicates whether the contexts * are loaded from internal data or copied from an external source. * @param $placeholders - * If true, placeholders are acceptable. + * If True, placeholders are acceptable. + * + * @return array + * Array of contexts, keyed by context ID. */ function ctools_context_get_context_from_contexts($contexts, $type = 'context', $placeholders = FALSE) { $return = array(); @@ -1144,17 +1575,20 @@ function ctools_context_get_context_from_contexts($contexts, $type = 'context', /** * Match up external contexts to our required contexts. * - * This function is used to create a list of contexts with proper - * IDs based upon a list of required contexts. + * This function is used to create a list of contexts with proper IDs based + * upon a list of required contexts. * - * These contexts passed in should match the numeric positions of the - * required contexts. The caller must ensure this has already happened - * correctly as this function will not detect errors here. + * These contexts passed in should match the numeric positions of the required + * contexts. The caller must ensure this has already happened correctly as this + * function will not detect errors here. * * @param $required * A list of required contexts as defined by the UI. * @param $contexts * A list of matching contexts as passed in from the calling system. + * + * @return array + * Array of contexts, keyed by context ID. */ function ctools_context_match_required_contexts($required, $contexts) { $return = array(); @@ -1163,10 +1597,10 @@ function ctools_context_match_required_contexts($required, $contexts) { } foreach ($required as $r) { - $context = clone(array_shift($contexts)); + $context = clone array_shift($contexts); $context->identifier = $r['identifier']; $context->page_title = isset($r['title']) ? $r['title'] : ''; - $context->keyword = $r['keyword']; + $context->keyword = $r['keyword']; $return[ctools_context_id($r, 'requiredcontext')] = $context; } @@ -1178,31 +1612,35 @@ function ctools_context_match_required_contexts($required, $contexts) { * * Not all of the types need to be supported by this object. * - * This function is not used to load contexts from external data, but may - * be used to load internal contexts and relationships. Otherwise it can also - * be used to generate a full set of placeholders for UI purposes. + * This function is not used to load contexts from external data, but may be + * used to load internal contexts and relationships. Otherwise it can also be + * used to generate a full set of placeholders for UI purposes. * - * @param $object + * @param object $object * An object that contains some or all of the following variables: * - * - requiredcontexts: A list of UI configured contexts that are required - * from an external source. Since these require external data, they will - * only be added if $placeholders is set to TRUE, and empty contexts will - * be created. - * - arguments: A list of UI configured arguments that will create contexts. - * Since these require external data, they will only be added if $placeholders - * is set to TRUE. - * - contexts: A list of UI configured contexts that have no external source, - * and are essentially hardcoded. For example, these might configure a - * particular node or a particular taxonomy term. - * - relationships: A list of UI configured contexts to be derived from other - * contexts that already exist from other sources. For example, these might - * be used to get a user object from a node via the node author relationship. - * @param $placeholders - * If TRUE, this will generate placeholder objects for types this function + * - requiredcontexts: A list of UI configured contexts that are required + * from an external source. Since these require external data, they will + * only be added if $placeholders is set to TRUE, and empty contexts will + * be created. + * - arguments: A list of UI configured arguments that will create contexts. + * As these require external data, they will only be added if $placeholders + * is set to TRUE. + * - contexts: A list of UI configured contexts that have no external source, + * and are essentially hardcoded. For example, these might configure a + * particular node or a particular taxonomy term. + * - relationships: A list of UI configured contexts to be derived from other + * contexts that already exist from other sources. For example, these might + * be used to get a user object from a node via the node author + * relationship. + * @param bool $placeholders + * If True, this will generate placeholder objects for any types this function * cannot load. - * @param $contexts + * @param array $contexts * An array of pre-existing contexts that will be part of the return value. + * + * @return array + * Merged output of all results of ctools_context_get_context_from_contexts(). */ function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = array()) { if (!empty($object->base_contexts)) { @@ -1226,7 +1664,7 @@ function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = $contexts += ctools_context_get_context_from_contexts($object->contexts, 'context', $placeholders); } - // add contexts from relationships + // Add contexts from relationships. if (!empty($object->relationships) && is_array($object->relationships)) { ctools_context_get_context_from_relationships($object->relationships, $contexts, $placeholders); } @@ -1245,7 +1683,7 @@ function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = function ctools_context_get_form($contexts) { if (!empty($contexts)) { foreach ($contexts as $id => $context) { - // if a form shows its id as being a 'required context' that means the + // If a form shows its id as being a 'required context' that means the // the context is external to this display and does not count. if (!empty($context->form_id) && substr($id, 0, 15) != 'requiredcontext') { return $context; @@ -1264,7 +1702,7 @@ function ctools_context_get_form($contexts) { * The arguments. These will be acquired from $form_state['values'] and the * keys must match the context IDs. * - * @return + * @return array * A new $contexts array containing the replaced contexts. Not all contexts * may be replaced if, for example, an argument was unable to be converted * into a context. @@ -1283,12 +1721,14 @@ function ctools_context_replace_placeholders($contexts, $arguments) { $new_context = ctools_context_get_context_from_relationship($relationship, $contexts[$relationship['context']]); } break; + case 'argument': if (isset($arguments[$cid]) && $arguments[$cid] !== '') { $argument = $context->placeholder['conf']; $new_context = ctools_context_get_context_from_argument($argument, $arguments[$cid]); } break; + case 'context': if (!empty($arguments[$cid])) { $context_info = $context->placeholder['conf']; @@ -1337,32 +1777,37 @@ function ctools_context_replace_form(&$form, $contexts) { if (is_array($plugin['placeholder form'])) { $form[$cid] = $plugin['placeholder form']; } - else if (function_exists($plugin['placeholder form'])) { - $widget = $plugin['placeholder form']($info); - if ($widget) { - $form[$cid] = $widget; + else { + if (function_exists($plugin['placeholder form'])) { + $widget = $plugin['placeholder form']($info); + if ($widget) { + $form[$cid] = $widget; + } } } if (!empty($form[$cid])) { - $form[$cid]['#title'] = t('@identifier (@keyword)', array('@keyword' => '%' . $context->keyword, '@identifier' => $context->identifier)); + $form[$cid]['#title'] = t('@identifier (@keyword)', array( + '@keyword' => '%' . $context->keyword, + '@identifier' => $context->identifier, + )); } } } } // --------------------------------------------------------------------------- -// Functions related to loading access control plugins - +// Functions related to loading access control plugins. /** * Fetch metadata on a specific access control plugin. * * @param $name * Name of a plugin. * - * @return + * @return array * An array with information about the requested access control plugin. */ + function ctools_get_access_plugin($name) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'access', $name); @@ -1371,7 +1816,7 @@ function ctools_get_access_plugin($name) { /** * Fetch metadata for all access control plugins. * - * @return + * @return array * An array of arrays with information about all available access control plugins. */ function ctools_get_access_plugins() { @@ -1383,8 +1828,14 @@ function ctools_get_access_plugins() { * Fetch a list of access plugins that are available for a given list of * contexts. * - * if 'logged-in-user' is not in the list of contexts, it will be added as + * If 'logged-in-user' is not in the list of contexts, it will be added as * this is required. + * + * @param array $contexts + * Array of ctools_context objects with which to select access plugins. + * + * @return array + * Array of applicable access plugins. Can be empty. */ function ctools_get_relevant_access_plugins($contexts) { if (!isset($contexts['logged-in-user'])) { @@ -1394,7 +1845,9 @@ function ctools_get_relevant_access_plugins($contexts) { $all_plugins = ctools_get_access_plugins(); $plugins = array(); foreach ($all_plugins as $id => $plugin) { - if (!empty($plugin['required context']) && !ctools_context_match_requirements($contexts, $plugin['required context'])) { + if (!empty($plugin['required context']) + && !ctools_context_match_requirements($contexts, $plugin['required context']) + ) { continue; } $plugins[$id] = $plugin; @@ -1409,14 +1862,17 @@ function ctools_get_relevant_access_plugins($contexts) { function ctools_access_get_loggedin_context() { $context = ctools_context_create('entity:user', array('type' => 'current'), TRUE); $context->identifier = t('Logged in user'); - $context->keyword = 'viewer'; - $context->id = 0; + $context->keyword = 'viewer'; + $context->id = 0; return $context; } /** * Get a summary of an access plugin's settings. + * + * @return string + * The summary text. */ function ctools_access_summary($plugin, $contexts, $test) { if (!isset($contexts['logged-in-user'])) { @@ -1426,8 +1882,9 @@ function ctools_access_summary($plugin, $contexts, $test) { $description = ''; if ($function = ctools_plugin_get_function($plugin, 'summary')) { $required_context = isset($plugin['required context']) ? $plugin['required context'] : array(); - $context = isset($test['context']) ? $test['context'] : array(); - $description = $function($test['settings'], ctools_context_select($contexts, $required_context, $context), $plugin); + $context = isset($test['context']) ? $test['context'] : array(); + $selected_context = ctools_context_select($contexts, $required_context, $context); + $description = $function($test['settings'], $selected_context, $plugin); } if (!empty($test['not'])) { @@ -1439,10 +1896,23 @@ function ctools_access_summary($plugin, $contexts, $test) { /** * Get a summary of a group of access plugin's settings. + * + * @param $access + * An array of settings theoretically set by the user, including the array + * of plugins to check: + * - 'plugins': the array of plugin metadata info to check + * - 'logic': (optional) either 'and' or 'or', indicating how to combine + * restrictions. Defaults to 'or'. + * @param array $contexts + * An array of zero or more contexts that may be used to determine if + * the user has access. + * + * @return string + * The summary text. Can be NULL if there are no plugins defined. */ function ctools_access_group_summary($access, $contexts) { - if (empty($access['plugins'])) { - return; + if (empty($access['plugins']) || !is_array($access['plugins'])) { + return NULL; } $descriptions = array(); @@ -1451,21 +1921,29 @@ function ctools_access_group_summary($access, $contexts) { $descriptions[] = ctools_access_summary($plugin, $contexts, $test); } - $separator = (isset($access['logic']) && $access['logic'] == 'and') ? t(', and ') : t(', or '); + $separator = + (isset($access['logic']) && $access['logic'] === 'and') + ? t(', and ') : t(', or '); return implode($separator, $descriptions); } /** - * Determine if the current user has access via plugin. + * Determine if the current user has access via a plugin. * - * @param $settings - * An array of settings theoretically set by the user. - * @param $contexts + * @param array $settings + * An array of settings theoretically set by the user, including the array + * of plugins to check: + * - 'plugins': the array of plugin metadata info to check + * - 'logic': (optional) either 'and' or 'or', indicating how to combine + * restrictions. The 'or' case is not fully implemented and returns the + * input contexts unchanged. + * + * @param array $contexts * An array of zero or more contexts that may be used to determine if * the user has access. * - * @return - * TRUE if access is granted, false if otherwise. + * @return bool + * TRUE if access is granted, FALSE if otherwise. */ function ctools_access($settings, $contexts = array()) { if (empty($settings['plugins'])) { @@ -1504,7 +1982,7 @@ function ctools_access($settings, $contexts = array()) { // Pass if 'or' and this rule passed. return TRUE; } - else if (!$pass && $settings['logic'] == 'and') { + elseif (!$pass && $settings['logic'] == 'and') { // Fail if 'and' and this rule failed. return FALSE; } @@ -1512,7 +1990,7 @@ function ctools_access($settings, $contexts = array()) { // Return TRUE if logic was and, meaning all rules passed. // Return FALSE if logic was or, meaning no rule passed. - return $settings['logic'] == 'and'; + return ($settings['logic'] === 'and'); } /** @@ -1521,7 +1999,7 @@ function ctools_access($settings, $contexts = array()) { * @param $plugin * The access plugin being used. * - * @return + * @return array * A default configured test that should be placed in $access['plugins']; */ function ctools_access_new_test($plugin) { @@ -1543,7 +2021,6 @@ function ctools_access_new_test($plugin) { } } - $default = NULL; if (isset($plugin['default'])) { $default = $plugin['default']; @@ -1557,7 +2034,7 @@ function ctools_access_new_test($plugin) { if (is_array($default)) { $test['settings'] = $default; } - else if (function_exists($default)) { + elseif (function_exists($default)) { $test['settings'] = $default(); } else { @@ -1571,11 +2048,23 @@ function ctools_access_new_test($plugin) { /** * Apply restrictions to contexts based upon the access control configured. * - * These restrictions allow the UI to not show content that may not - * be relevant to all types of a particular context. + * These restrictions allow the UI to not show content that may not be relevant + * to all types of a particular context. + * + * @param array $settings + * Array of keys specifying the settings: + * - 'plugins': the array of plugin metadata info to check. If not set, or + * not an array, the function returns with no action. + * - 'logic': (optional) either 'and' or 'or', indicating how to combine + * restrictions. Defaults to 'and'. + * The 'or' case is not fully implemented and returns with no action if + * there is more than one plugin. + * + * @param array $contexts + * Array of available contexts. */ function ctools_access_add_restrictions($settings, $contexts) { - if (empty($settings['plugins'])) { + if (empty($settings['plugins']) || !is_array($settings['plugins'])) { return; } @@ -1584,16 +2073,20 @@ function ctools_access_add_restrictions($settings, $contexts) { } // We're not going to try to figure out restrictions on the or. - if ($settings['logic'] == 'or' && count($settings['plugins']) > 1) { + if ($settings['logic'] === 'or' && count($settings['plugins']) > 1) { return; } foreach ($settings['plugins'] as $test) { $plugin = ctools_get_access_plugin($test['name']); - if ($plugin && $function = ctools_plugin_get_function($plugin, 'restrictions')) { + // $plugin is 'array()' on error. + if ($plugin + && $function = ctools_plugin_get_function($plugin, 'restrictions') + ) { $required_context = isset($plugin['required context']) ? $plugin['required context'] : array(); $context = isset($test['context']) ? $test['context'] : array(); $contexts = ctools_context_select($contexts, $required_context, $context); + if ($contexts !== FALSE) { $function($test['settings'], $contexts); } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.menu.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.menu.inc index ee227cb7..798d167c 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.menu.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.menu.inc @@ -25,7 +25,7 @@ function ctools_context_menu(&$items) { 'page callback' => 'ctools_context_ajax_item_delete', ) + $base; - // For the access system + // For the access system. $base['file'] = 'includes/context-access-admin.inc'; $items['ctools/context/ajax/access/add'] = array( 'page callback' => 'ctools_access_ajax_add', diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.theme.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.theme.inc index 8f660b8c..d0d866f7 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.theme.inc @@ -19,7 +19,6 @@ function ctools_context_theme(&$theme) { ); $theme['ctools_context_item_form'] = array( 'render element' => 'form', -// 'variables' => array('form' => NULL), 'file' => 'includes/context.theme.inc', ); $theme['ctools_context_item_row'] = array( @@ -27,7 +26,7 @@ function ctools_context_theme(&$theme) { 'file' => 'includes/context.theme.inc', ); - // For the access plugin + // For the access plugin. $theme['ctools_access_admin_add'] = array( 'render element' => 'form', 'file' => 'includes/context-access-admin.inc', @@ -62,10 +61,10 @@ function theme_ctools_context_item_row($vars) { function theme_ctools_context_item_form($vars) { $form = $vars['form']; - $output = ''; - $type = $form['#ctools_context_type']; - $module = $form['#ctools_context_module']; - $cache_key = $form['#cache_key']; + $output = ''; + $type = $form['#ctools_context_type']; + $module = $form['#ctools_context_module']; + $cache_key = $form['#cache_key']; $type_info = ctools_context_info($type); @@ -104,17 +103,17 @@ function theme_ctools_context_item_form($vars) { if (!empty($form['buttons'])) { // Display the add context item. - $row = array(); - $row[] = array('data' => render($form['buttons'][$type]['item']), 'class' => array('title')); - $row[] = array('data' => render($form['buttons'][$type]['add']), 'class' => array('add'), 'width' => "60%"); - $output .= '
    '; - $output .= render($form['buttons'][$type]); - $theme_vars = array(); - $theme_vars['header'] = array(); - $theme_vars['rows'] = array($row); + $row = array(); + $row[] = array('data' => render($form['buttons'][$type]['item']), 'class' => array('title')); + $row[] = array('data' => render($form['buttons'][$type]['add']), 'class' => array('add'), 'width' => "60%"); + $output .= '
    '; + $output .= render($form['buttons'][$type]); + $theme_vars = array(); + $theme_vars['header'] = array(); + $theme_vars['rows'] = array($row); $theme_vars['attributes'] = array('id' => $type . '-add-table'); - $output .= theme('table', $theme_vars); - $output .= '
    '; + $output .= theme('table', $theme_vars); + $output .= '
    '; } if (!empty($form['description'])) { $output .= render($form['description']); @@ -139,7 +138,7 @@ function theme_ctools_context_list($vars) { $description = (!empty($vars['description'])) ? $vars['description'] : NULL; $titles = array(); $output = ''; - $count = 1; + $count = 1; $contexts = ctools_context_load_contexts($object); @@ -209,7 +208,7 @@ function theme_ctools_context_list($vars) { } } - // And relationships + // And relationships. if (!empty($object->relationships)) { foreach ($object->relationships as $relationship) { $output .= ''; @@ -253,15 +252,15 @@ function theme_ctools_context_list($vars) { } /** - * ctools_context_list() but not in a table format because tabledrag - * won't let us have tables within tables and still drag. + * The ctools_context_list() function but not in a table format because + * tabledrag won't let us have tables within tables and still drag. */ function theme_ctools_context_list_no_table($vars) { $object = $vars['object']; ctools_add_css('context'); $titles = array(); $output = ''; - $count = 1; + $count = 1; // Describe 'built in' contexts. if (!empty($object->base_contexts)) { foreach ($object->base_contexts as $id => $context) { @@ -312,7 +311,7 @@ function theme_ctools_context_list_no_table($vars) { $count++; } } - // And relationships + // And relationships. if (!empty($object->relationships)) { foreach ($object->relationships as $relationship) { $output .= '
    '; @@ -341,4 +340,3 @@ function theme_ctools_context_list_no_table($vars) { return $output; } - diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/css.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/css.inc index 8cf5ed40..83fe1c34 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/css.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/css.inc @@ -1,6 +1,6 @@ $declaration) { foreach ($declaration as $property => $value) { if (!in_array($property, $allowed_properties)) { - // $filtered['properties'][$selector_str][$property] = $value; + // $filtered['properties'][$selector_str][$property] = $value;. unset($css[$selector_str][$property]); continue; } $value = str_replace('!important', '', $value); if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) { - // $filtered['values'][$selector_str][$property] = $value; + // $filtered['values'][$selector_str][$property] = $value;. unset($css[$selector_str][$property]); continue; } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/dependent.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/dependent.inc index 74de9197..20c25ded 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/dependent.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/dependent.inc @@ -44,7 +44,7 @@ * * A fuller example, that hides the menu title when no menu is selected: * @code - *function ctools_dependent_example() { + * function ctools_dependent_example() { * $form = array(); * $form['menu'] = array( * '#type' => 'fieldset', @@ -72,12 +72,12 @@ * ); * * return system_settings_form($form); - *} + * } * @endcode * * An example for hiding checkboxes using #prefix and #suffix: * @code - *function ctools_dependent_example_checkbox() { + * function ctools_dependent_example_checkbox() { * $form = array(); * $form['object'] = array( * '#type' => 'fieldset', @@ -111,7 +111,7 @@ * ); * * return system_settings_form($form); - *} + * } * @endcode * * Deprecated: @@ -125,7 +125,6 @@ /** * Process callback to add dependency to form items. - * */ function ctools_dependent_process($element, &$form_state, &$form) { return $element; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropbutton.theme.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropbutton.theme.inc index fcdd5a37..8ce99f07 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropbutton.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropbutton.theme.inc @@ -75,7 +75,7 @@ function theme_links__ctools_dropbutton($vars) { if (!empty($vars['links'])) { $is_drop_button = (count($vars['links']) > 1); - // Add needed files + // Add needed files. if ($is_drop_button) { ctools_add_js('dropbutton'); ctools_add_css('dropbutton'); @@ -86,7 +86,7 @@ function theme_links__ctools_dropbutton($vars) { static $id = 0; $id++; - // Wrapping div + // Wrapping div. $class = 'ctools-no-js'; $class .= ($is_drop_button) ? ' ctools-dropbutton' : ''; $class .= ' ctools-button'; @@ -98,7 +98,7 @@ function theme_links__ctools_dropbutton($vars) { $output .= '
    '; - // Add a twisty if this is a dropbutton + // Add a twisty if this is a dropbutton. if ($is_drop_button) { $vars['title'] = ($vars['title'] ? check_plain($vars['title']) : t('open')); @@ -109,10 +109,11 @@ function theme_links__ctools_dropbutton($vars) { else { $output .= '' . $vars['title'] . ''; } - $output .= '
    '; // ctools-link + // ctools-link. + $output .= '
    '; } - // The button content + // The button content. $output .= '
    '; // Check for attributes. theme_links expects an array(). diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropdown.theme.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropdown.theme.inc index 7e748f5e..2437b89e 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropdown.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/dropdown.theme.inc @@ -57,7 +57,7 @@ function ctools_dropdown_theme(&$items) { * to style a single dropdown however you like without interfering with * other dropdowns. */ -function theme_ctools_dropdown($vars) { +function theme_ctools_dropdown($vars) { // Provide a unique identifier for every dropdown on the page. static $id = 0; $id++; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/entity-access.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/entity-access.inc index 972cf13b..69f2c335 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/entity-access.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/entity-access.inc @@ -24,19 +24,23 @@ function _ctools_entity_access(&$entity_info, $entity_type) { // Sad panda, we don't use Entity API, lets manually add access callbacks. $entity_info['access callback'] = 'ctools_metadata_no_hook_node_access'; break; + case 'user': $entity_info['access callback'] = 'ctools_metadata_user_access'; break; + case 'comment': if (module_exists('comment')) { $entity_info['access callback'] = 'ctools_metadata_comment_access'; } break; + case 'taxonomy_term': if (module_exists('taxonomy')) { $entity_info['access callback'] = 'ctools_metadata_taxonomy_access'; } break; + case 'taxonomy_vocabulary': if (module_exists('taxonomy')) { $entity_info['access callback'] = 'ctools_metadata_taxonomy_access'; @@ -64,7 +68,7 @@ function _ctools_entity_access(&$entity_info, $entity_type) { * * @throws EntityMalformedException * - * @return boolean + * @return bool * TRUE if access is allowed, FALSE otherwise. */ function ctools_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc index 16e57d6e..1eb87ae7 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc @@ -81,7 +81,6 @@ function ctools_export_ui_process(&$plugin, $info) { // Add some default fields that appear often in exports // If these use different keys they can easily be specified in the // $plugin. - if (empty($plugin['export']['admin_title']) && !empty($schema['fields']['admin_title'])) { $plugin['export']['admin_title'] = 'admin_title'; } @@ -297,7 +296,6 @@ function ctools_export_ui_process(&$plugin, $info) { ); // Define strings. - // For all strings, %title may be filled in at a later time via str_replace // since we do not know the title now. $plugin['strings'] += array( @@ -414,6 +412,7 @@ function ctools_export_ui_plugin_base_path($plugin) { * The id in the menu items from the plugin. * @param $export_key * The export key of the item being edited, if it exists. + * * @return * The menu path to the plugin's list. */ @@ -429,12 +428,11 @@ function ctools_export_ui_plugin_menu_path($plugin, $item_id, $export_key = NULL * Helper function to include CTools plugins and get an export-ui exportable. * * @param $plugin_name - * The plugin that should be laoded. + * The plugin that should be loaded. */ function ctools_get_export_ui($plugin_name) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'export_ui', $plugin_name); - } /** @@ -456,14 +454,14 @@ function ctools_export_ui_switcher_page($plugin_name, $op) { $args = func_get_args(); $js = !empty($_REQUEST['js']); - // Load the $plugin information + // Load the $plugin information. $plugin = ctools_get_export_ui($plugin_name); $handler = ctools_export_ui_get_handler($plugin); if ($handler) { $method = $op . '_page'; if (method_exists($handler, $method)) { - // replace the first two arguments: + // Replace the first two arguments: $args[0] = $js; $args[1] = $_POST; return call_user_func_array(array($handler, $method), $args); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.menu.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.menu.inc index d27bf157..0fda8c25 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.menu.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.menu.inc @@ -1,5 +1,9 @@ array('handler'), ); -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc index 0b85c2ef..d2fff2fd 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc @@ -4,7 +4,7 @@ * @file * Contains code to make it easier to have exportable objects. * - * Documentation for exportable objects is contained in help/export.html + * Documentation for exportable objects is contained in help/export.html. */ /** @@ -126,6 +126,7 @@ function ctools_export_crud_load_multiple($table, array $names) { * If TRUE, the static cache of all objects will be flushed prior to * loading all. This can be important on listing pages where items * might have changed on the page load. + * * @return * An array of all loaded objects, keyed by the unique IDs of the export key. */ @@ -328,7 +329,6 @@ function ctools_export_crud_set_status($table, $object, $status) { } - /** * Enable a certain object. * @@ -426,7 +426,7 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { } } - // Build the query + // Build the query. $query = db_select($table, 't__0')->fields('t__0'); $alias_count = 1; if (!empty($schema['join'])) { @@ -451,7 +451,7 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { if ($type == 'names') { $query->condition($export['key'], $args, 'IN'); } - else if ($type == 'conditions') { + elseif ($type == 'conditions') { foreach ($args as $key => $value) { if (isset($schema['fields'][$key])) { $query->condition($key, $value); @@ -499,7 +499,7 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { if ($defaults) { foreach ($defaults as $object) { if ($type == 'conditions') { - // if this does not match all of our conditions, skip it. + // If this does not match all of our conditions, skip it. foreach ($args as $key => $value) { if (!isset($object->$key)) { continue 2; @@ -509,12 +509,12 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { continue 2; } } - else if ($object->$key != $value) { + elseif ($object->$key != $value) { continue 2; } } } - else if ($type == 'names') { + elseif ($type == 'names') { if (!in_array($object->{$export['key']}, $args)) { continue; } @@ -561,7 +561,6 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { } } - // For conditions, return $return; } @@ -807,10 +806,10 @@ function _ctools_export_get_some_defaults($table, $export, $names) { function _ctools_export_unpack_object($schema, $data, $object = 'stdClass') { if (is_string($object)) { if (class_exists($object)) { - $object = new $object; + $object = new $object(); } else { - $object = new stdClass; + $object = new stdClass(); } } @@ -873,14 +872,14 @@ function ctools_var_export($var, $prefix = '') { $output .= $prefix . ')'; } } - else if (is_object($var) && get_class($var) === 'stdClass') { + elseif (is_object($var) && get_class($var) === 'stdClass') { // var_export() will export stdClass objects using an undefined // magic method __set_state() leaving the export broken. This // workaround avoids this by casting the object as an array for // export and casting it back to an object when evaluated. $output = '(object) ' . ctools_var_export((array) $var, $prefix); } - else if (is_bool($var)) { + elseif (is_bool($var)) { $output = $var ? 'TRUE' : 'FALSE'; } else { @@ -902,7 +901,8 @@ function ctools_export_object($table, $object, $indent = '', $identifier = NULL, $output = $indent . '$' . $identifier . ' = new ' . get_class($object) . "();\n"; if ($schema['export']['can disable']) { - $output .= $indent . '$' . $identifier . '->disabled = FALSE; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n"; + $disabled = !isset($object->disabled) || $object->disabled != TRUE ? 'FALSE' : 'TRUE'; + $output .= $indent . '$' . $identifier . '->disabled = ' . $disabled . '; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n"; } if (!empty($schema['export']['api']['current_version'])) { $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n"; @@ -958,7 +958,7 @@ function ctools_export_object($table, $object, $indent = '', $identifier = NULL, } } - // And bottom additions here + // And bottom additions here. foreach ($additions2 as $field => $value) { $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; } @@ -986,7 +986,7 @@ function ctools_export_get_schema($table) { // simply hasn't been cached. If we've been asked, let's force the // issue. if (!$schema || empty($schema['export'])) { - // force a schema reset: + // Force a schema reset: $schema = drupal_get_schema($table, TRUE); } @@ -998,7 +998,7 @@ function ctools_export_get_schema($table) { return array(); } - // Add some defaults + // Add some defaults. $schema['export'] += array( 'key' => 'name', 'key name' => 'Name', @@ -1106,7 +1106,7 @@ function ctools_export_set_object_status($object, $new_status = TRUE) { $export = $schema['export']; $status = variable_get($export['status'], array()); - // Compare + // Compare. if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) { unset($status[$object->{$export['key']}]); } @@ -1149,12 +1149,12 @@ function ctools_export_new_object($table, $set_defaults = TRUE) { $schema = ctools_export_get_schema($table); $export = $schema['export']; - $object = new $export['object']; + $object = new $export['object'](); foreach ($schema['fields'] as $field => $info) { if (isset($info['object default'])) { $object->$field = $info['object default']; } - else if (isset($info['default'])) { + elseif (isset($info['default'])) { $object->$field = $info['default']; } else { @@ -1179,11 +1179,11 @@ function ctools_export_new_object($table, $set_defaults = TRUE) { function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') { $schema = ctools_export_get_schema($table); $export = $schema['export']; - // Use the schema-specified function for generating hook code, if one exists + // Use the schema-specified function for generating hook code, if one exists. if (function_exists($export['to hook code callback'])) { $output = $export['to hook code callback']($names, $name); } - // Otherwise, the following code generates basic hook code + // Otherwise, the following code generates basic hook code. else { $output = ctools_export_default_to_hook_code($schema, $table, $names, $name); } @@ -1227,7 +1227,7 @@ function ctools_export_default_to_hook_code($schema, $table, $names, $name) { $output .= " \${$export['identifier']}s = array();\n\n"; foreach ($objects as $object) { $output .= ctools_export_crud_export($table, $object, ' '); - $output .= " \${$export['identifier']}s['" . check_plain($object->$export['key']) . "'] = \${$export['identifier']};\n\n"; + $output .= " \${$export['identifier']}s['" . check_plain($object->{$export['key']}) . "'] = \${$export['identifier']};\n\n"; } $output .= " return \${$export['identifier']}s;\n"; $output .= "}\n"; @@ -1235,6 +1235,7 @@ function ctools_export_default_to_hook_code($schema, $table, $names, $name) { return $output; } + /** * Default function for listing bulk exportable objects. */ diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc index f379f5e9..0d3256f2 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc @@ -5,7 +5,6 @@ * Extend core fields with some helper functions to reduce code complexity within views and ctools plugins. */ - /** * Fake an instance of a field. * @@ -75,27 +74,30 @@ function ctools_fields_get_field_formatter_settings_form($field, $formatter_type $conf['formatter_settings'] += $formatter['settings']; } $function = $formatter['module'] . '_field_formatter_settings_form'; + + $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']); if (function_exists($function)) { - $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']); $settings_form = $function($field, $instance, $view_mode, $form, $form_state); - if ($settings_form) { - // Allow other modules to alter the formatter settings form. - $context = array( - 'module' => $formatter['module'], - 'formatter' => $formatter, - 'field' => $field, - 'instance' => $instance, - 'view_mode' => $view_mode, - 'form' => $form, - 'form_state' => $form_state, - ); - drupal_alter('field_formatter_settings_form', $settings_form, $context); - - $settings_form['#tree'] = TRUE; - $form['ctools_field_list']['#value'][] = $field; - $form += $settings_form; - } } + if (empty($settings_form)) { + $settings_form = array(); + } + + // Allow other modules to alter the formatter settings form. + $context = array( + 'module' => $formatter['module'], + 'formatter' => $formatter, + 'field' => $field, + 'instance' => $instance, + 'view_mode' => $view_mode, + 'form' => $form, + 'form_state' => $form_state, + ); + drupal_alter('field_formatter_settings_form', $settings_form, $context); + + $settings_form['#tree'] = TRUE; + $form['ctools_field_list']['#value'][] = $field; + $form += $settings_form; if (isset($field['cardinality']) && $field['cardinality'] != 1) { list($prefix, $suffix) = explode('@count', t('Skip the first @count item(s)')); @@ -137,7 +139,7 @@ function ctools_fields_get_field_formatter_settings_form($field, $formatter_type */ function ctools_fields_get_field_formatter_info($fields) { $info = array(); - $field_info = module_invoke_all('field_formatter_info'); + $field_info = field_info_formatter_types(); foreach ($fields as $field) { foreach ($field_info as $format_name => $formatter_info) { if (in_array($field['type'], $formatter_info['field types'])) { @@ -145,7 +147,6 @@ function ctools_fields_get_field_formatter_info($fields) { } } } - drupal_alter('field_formatter_info', $info); return $info; } @@ -209,15 +210,15 @@ function ctools_field_label($field_name) { * - Otherwise NULL. * @param $options * An associative array of additional options, with the following keys: - * - 'field_name': The name of the field whose operation should be + * - 'field_name': The name of the field whose operation should be * invoked. By default, the operation is invoked on all the fields * in the entity's bundle. NOTE: This option is not compatible with * the 'deleted' option; the 'field_id' option should be used * instead. - * - 'field_id': The id of the field whose operation should be + * - 'field_id': The id of the field whose operation should be * invoked. By default, the operation is invoked on all the fields * in the entity's' bundles. - * - 'default': A boolean value, specifying which implementation of + * - 'default': A boolean value, specifying which implementation of * the operation should be invoked. * - if FALSE (default), the field types implementation of the operation * will be invoked (hook_field_[op]) @@ -225,10 +226,10 @@ function ctools_field_label($field_name) { * will be invoked (field_default_[op]) * Internal use only. Do not explicitely set to TRUE, but use * _field_invoke_default() instead. - * - 'deleted': If TRUE, the function will operate on deleted fields + * - 'deleted': If TRUE, the function will operate on deleted fields * as well as non-deleted fields. If unset or FALSE, only * non-deleted fields are operated on. - * - 'language': A language code or an array of language codes keyed by field + * - 'language': A language code or an array of language codes keyed by field * name. It will be used to narrow down to a single value the available * languages to act on. * @@ -341,7 +342,7 @@ function ctools_field_foreign_keys($field_name) { $foreign_keys[$field_name] = $field['foreign keys']; } else { - // try to fetch foreign keys from schema, as not everything + // Try to fetch foreign keys from schema, as not everything // stores foreign keys properly in the field info. $module = $field['module']; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/jump-menu.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/jump-menu.inc index 51f45982..e2ae1cfc 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/jump-menu.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/jump-menu.inc @@ -8,7 +8,6 @@ * if javascript is in use. Each item is keyed to the href that the button * should go to. With javascript, the page is immediately redirected. Without * javascript, the form is submitted and a drupal_goto() is given. - * */ /** @@ -127,7 +126,7 @@ function ctools_jump_menu_submit($form, &$form_state) { // This allows duplicate paths to be used in jump menus for multiple options. $redirect_array = explode("::", $form_state['values']['jump']); - if(isset($redirect_array[1]) && !empty($redirect_array[1])){ + if (isset($redirect_array[1]) && !empty($redirect_array[1])) { $redirect = $redirect_array[1]; } else { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/language.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/language.inc index 9a7850b7..a850670e 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/language.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/language.inc @@ -1,5 +1,9 @@ t("Current user's language"), @@ -41,4 +44,4 @@ function ctools_language_list_all() { ); $languages = array_merge($languages, ctools_language_list()); return $languages; -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc index eeb184d8..3105ec52 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc @@ -99,7 +99,7 @@ class ctools_math_expr { 'sqrt','abs','ln','log', 'time', 'ceil', 'floor', 'min', 'max', 'round'); - function ctools_math_expr() { + function __construct() { // make the variables a little more accurate $this->v['pi'] = pi(); $this->v['e'] = exp(1); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc index fc990159..18b216fc 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc @@ -55,9 +55,9 @@ function ctools_modal_add_js() { 'alt' => t('Close window'), )), 'throbber' => theme('image', array( - 'path' => ctools_image_path('throbber.gif'), - 'title' => t('Loading...'), - 'alt' => t('Loading'), + 'path' => ctools_image_path('throbber.gif'), + 'title' => t('Loading...'), + 'alt' => t('Loading'), )), ), ); @@ -85,7 +85,7 @@ function ctools_modal_add_plugin_js($plugins) { if (file_exists($file)) { $js[$file] = TRUE; } - else if (file(exists($subtype['path'] . '/' . $file))) { + elseif (file(exists($subtype['path'] . '/' . $file))) { $js[$subtype['path'] . '/' . $file] = TRUE; } } @@ -95,7 +95,7 @@ function ctools_modal_add_plugin_js($plugins) { if (file_exists($file)) { $css[$file] = TRUE; } - else if (file(exists($subtype['path'] . '/' . $file))) { + elseif (file(exists($subtype['path'] . '/' . $file))) { $css[$subtype['path'] . '/' . $file] = TRUE; } } @@ -140,7 +140,7 @@ function ctools_modal_command_dismiss() { } /** - * Display loading screen in the modal + * Display loading screen in the modal. */ function ctools_modal_command_loading() { return array( @@ -189,7 +189,7 @@ function ctools_modal_text_button($text, $dest, $alt, $class = '') { * Wrap a form so that we can use it properly with AJAX. Essentially if the * form wishes to render, it automatically does that, otherwise it returns * the render array so we can see submission results. - + * * @param array $form * An associative array containing the structure of the form. * @param array $form_state @@ -222,7 +222,7 @@ function ctools_modal_form_wrapper($form_id, &$form_state) { $output = drupal_build_form($form_id, $form_state); if (!empty($form_state['ajax']) && (!$form_state['executed'] || $form_state['rebuild'])) { - return ctools_modal_form_render($form_state, $output); + return ctools_modal_form_render($form_state, $output); } return $output; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc index 29225b05..614c56f0 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc @@ -27,6 +27,7 @@ * defaults to session_id(). * * @deprecated $skip_cache is deprecated in favor of drupal_static* + * * @return * The data that was cached. */ @@ -91,7 +92,7 @@ function ctools_object_cache_set($obj, $name, $cache, $sid = NULL) { } /** - * Remove an object from the non-volatile ctools cache + * Remove an object from the non-volatile ctools cache. * * @param $obj * A 128 character or less string to define what kind of object is being @@ -117,7 +118,6 @@ function ctools_object_cache_clear($obj, $name, $sid = NULL) { drupal_static_reset('ctools_object_cache_get'); } - /** * Determine if another user has a given object cached. * @@ -197,7 +197,8 @@ function ctools_object_cache_clear_all($obj, $name) { */ function ctools_object_cache_clean($age = NULL) { if (empty($age)) { - $age = 86400 * 7; // 7 days + // 7 days. + $age = 86400 * 7; } db_delete('ctools_object_cache') ->condition('updated', REQUEST_TIME - $age, '<') diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc index a211361b..9690aae0 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc @@ -1,5 +1,9 @@ plugin = $plugin; if ($function = ctools_plugin_get_function($plugin, 'default cache')) { $function($cache); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins-admin.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins-admin.inc index d4ead0a4..c11fcfd6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins-admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins-admin.inc @@ -10,6 +10,7 @@ * * Implementing this */ + /** * Get a plugin configuration form. * @@ -100,7 +101,7 @@ function _ctools_plugin_configure_create_form_info(&$form_info, $plugin_definiti if (empty($plugin_definition['title'])) { $title = t('Configure'); } - else if ($op == 'add') { + elseif ($op == 'add') { $title = t('Configure new !plugin_title', array('!plugin_title' => $plugin_definition['title'])); } else { @@ -134,10 +135,10 @@ function _ctools_plugin_configure_create_form_info(&$form_info, $plugin_definiti $form_info['forms']['form']['wrapper'] = 'ctools_plugins_default_form_wrapper'; } } - else if (is_array($info)) { - if (empty($form_info['order'])) { - $form_info['order'] = array(); - } + elseif (is_array($info)) { + if (empty($form_info['order'])) { + $form_info['order'] = array(); + } if (empty($form_info['forms'])) { $form_info['forms'] = array(); } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins.inc index 79a6087f..a4abb537 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/plugins.inc @@ -2,7 +2,6 @@ /** * @file - * * Contains routines to organize and load plugins. It allows a special * variation of the hook system so that plugins can be kept in separate * .inc files, and can be either loaded all at once or loaded only when @@ -70,7 +69,7 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version if (isset($info['version'])) { $version = $info['version']; } - else if (isset($info['api'])) { + elseif (isset($info['api'])) { $version = $info['api']; } @@ -99,7 +98,7 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version } // Only process if version is between minimum and current, inclusive. - if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) { + if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) { if (!isset($info['path'])) { $info['path'] = ''; } @@ -151,7 +150,7 @@ function ctools_plugin_api_include($owner, $api, $minimum_version, $current_vers if (isset($plugin_info["$api file"])) { $file = $plugin_info["$api file"]; } - else if (isset($plugin_info['file'])) { + elseif (isset($plugin_info['file'])) { $file = $plugin_info['file']; } else { @@ -161,7 +160,7 @@ function ctools_plugin_api_include($owner, $api, $minimum_version, $current_vers if (file_exists(DRUPAL_ROOT . "/$plugin_info[path]/$file")) { require_once DRUPAL_ROOT . "/$plugin_info[path]/$file"; } - else if (file_exists(DRUPAL_ROOT . "/$file")) { + elseif (file_exists(DRUPAL_ROOT . "/$file")) { require_once DRUPAL_ROOT . "/$file"; } $already_done[$owner][$api][$module] = TRUE; @@ -184,7 +183,7 @@ function ctools_plugin_api_get_hook($owner, $api) { if (function_exists($function = $owner . '_' . $api . '_hook_name')) { $hook = $function(); } - else if (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) { + elseif (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) { $hook = $function(); } @@ -200,18 +199,18 @@ function ctools_plugin_api_get_hook($owner, $api) { /** * Fetch a group of plugins by name. * - * @param $module - * The name of the module that utilizes this plugin system. It will be - * used to call hook_ctools_plugin_$plugin() to get more data about the plugin. - * @param $type + * @param string $module + * The name of the module that utilizes this plugin system. It will be used to + * get more data about the plugin as defined on hook_ctools_plugin_type(). + * @param string $type * The type identifier of the plugin. - * @param $id + * @param string $id * If specified, return only information about plugin with this identifier. * The system will do its utmost to load only plugins with this id. * - * @return - * An array of information arrays about the plugins received. The contents - * of the array are specific to the plugin. + * @return array + * An array of information arrays about the plugins received. The contents of + * the array are specific to the plugin. */ function ctools_get_plugins($module, $type, $id = NULL) { // Store local caches of plugins and plugin info so we don't have to do full @@ -224,10 +223,14 @@ function ctools_get_plugins($module, $type, $id = NULL) { $info = ctools_plugin_get_plugin_type_info(); - // Bail out noisily if an invalid module/type combination is requested. if (!isset($info[$module][$type])) { - watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR); - return array(); + // If we don't find the plugin we attempt a cache rebuild before bailing out. + $info = ctools_plugin_get_plugin_type_info(TRUE); + // Bail out noisily if an invalid module/type combination is requested. + if (!isset($info[$module][$type])) { + watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR); + return array(); + } } // Make sure our plugins array is populated. @@ -235,8 +238,8 @@ function ctools_get_plugins($module, $type, $id = NULL) { $plugins[$module][$type] = array(); } - // Attempt to shortcut this whole piece of code if we already have - // the requested plugin: + // Attempt to shortcut this whole piece of code if we already have the + // requested plugin: if ($id && array_key_exists($id, $plugins[$module][$type])) { return $plugins[$module][$type][$id]; } @@ -254,9 +257,9 @@ function ctools_get_plugins($module, $type, $id = NULL) { if (!empty($cache->data)) { // Cache load succeeded so use the cached plugin list. - $plugins[$module][$type] = $cache->data; + $plugins[$module][$type] = $cache->data; // Set $setup to true so we know things where loaded. - $setup[$module][$type] = TRUE; + $setup[$module][$type] = TRUE; } else { // Cache load failed so store that we need to build and write the cache. @@ -271,8 +274,8 @@ function ctools_get_plugins($module, $type, $id = NULL) { $plugins[$module][$type] = ctools_plugin_load_hooks($info[$module][$type]); } - // Then see if we should load all files. We only do this if we - // want a list of all plugins or there was a cache miss. + // Then see if we should load all files. We only do this if we want a list of + // all plugins or there was a cache miss. if (empty($setup[$module][$type]) && ($build_cache || !$id)) { $setup[$module][$type] = TRUE; $plugins[$module][$type] = array_merge($plugins[$module][$type], ctools_plugin_load_includes($info[$module][$type])); @@ -295,9 +298,8 @@ function ctools_get_plugins($module, $type, $id = NULL) { } } - - // If we were told earlier that this is cacheable and the cache was - // empty, give something back. + // If we were told earlier that this is cacheable and the cache was empty, + // give something back. if ($build_cache) { cache_set("plugins:$module:$type", $plugins[$module][$type], $info[$module][$type]['cache table']); } @@ -309,7 +311,7 @@ function ctools_get_plugins($module, $type, $id = NULL) { return array_filter($plugins[$module][$type]); } - // Check to see if we need to look for the file + // Check to see if we need to look for the file. if (!array_key_exists($id, $plugins[$module][$type])) { // If we can have child plugins, check to see if the plugin name is in the // format of parent:child and break it up if it is. @@ -415,19 +417,20 @@ function ctools_get_plugins_reset() { /** * Load plugins from a directory. * - * @param $info + * @param array $info * The plugin info as returned by ctools_plugin_get_info() - * @param $file + * @param string $filename * The file to load if we're looking for just one particular plugin. * - * @return - * An array of information created for this plugin. + * @return array + * A (possibly empty) array of information created for this plugin. */ function ctools_plugin_load_includes($info, $filename = NULL) { // Keep a static array so we don't hit file_scan_directory more than necessary. $all_files = &drupal_static(__FUNCTION__, array()); - // store static of plugin arrays for reference because they can't be reincluded. + // Store static of plugin arrays for reference because they can't be + // reincluded, so there is no point in using drupal_static(). static $plugin_arrays = array(); if (!isset($all_files[$info['module']][$info['type']])) { @@ -462,24 +465,23 @@ function ctools_plugin_load_includes($info, $filename = NULL) { } foreach ($files as $file) { if (!empty($info['info file'])) { - // Parse a .info file + // Parse a .info file. $result = ctools_plugin_process_info($info, $module, $file); } else { // Parse a hook. - $plugin = NULL; // ensure that we don't have something leftover from earlier. + // Ensure that we don't have something leftover from earlier. + $plugin = NULL; if (isset($plugin_arrays[$file->uri])) { $identifier = $plugin_arrays[$file->uri]; } else { - - require_once DRUPAL_ROOT . '/' . $file->uri; + include_once DRUPAL_ROOT . '/' . $file->uri; // .inc files have a special format for the hook identifier. // For example, 'foo.inc' in the module 'mogul' using the plugin - // whose hook is named 'borg_type' should have a function named (deep breath) - // mogul_foo_borg_type() - + // whose hook is named 'borg_type' should have a function named + // (deep breath) mogul_foo_borg_type(). // If, however, the .inc file set the quasi-global $plugin array, we // can use that and not even call a function. Set the $identifier // appropriately and ctools_plugin_process() will handle it. @@ -492,7 +494,8 @@ function ctools_plugin_load_includes($info, $filename = NULL) { } } - $result = ctools_plugin_process($info, $module, $identifier, dirname($file->uri), basename($file->uri), $file->name); + $result = ctools_plugin_process($info, $module, $identifier, + dirname($file->uri), basename($file->uri), $file->name); } if (is_array($result)) { $plugins = array_merge($plugins, $result); @@ -512,7 +515,7 @@ function ctools_plugin_load_includes($info, $filename = NULL) { * @param $info * The $info array for the plugin as returned by ctools_plugin_get_info(). * - * @return array $directories + * @return array * An array of directories to search. */ function ctools_plugin_get_directories($info) { @@ -538,21 +541,21 @@ function ctools_plugin_get_directories($info) { } /** - * Helper function to build a ctools-friendly list of themes capable of - * providing plugins. + * Helper to build a ctools-friendly list of themes capable of providing plugins. * - * @return array $themes + * @return array * A list of themes that can act as plugin providers, sorted parent-first with * the active theme placed last. */ function _ctools_list_themes() { + // @TODO: Use drupal_static() here? static $themes; if (is_null($themes)) { $current = variable_get('theme_default', FALSE); $themes = $active = array(); $all_themes = list_themes(); foreach ($all_themes as $name => $theme) { - // Only search from active themes + // Only search from active themes. if (empty($theme->status) && $theme->name != $current) { continue; } @@ -563,19 +566,19 @@ function _ctools_list_themes() { } } - // Construct a parent-first list of all themes + // Construct a parent-first list of all themes. foreach ($active as $name => $theme) { $base_themes = isset($theme->base_themes) ? $theme->base_themes : array(); $themes = array_merge($themes, $base_themes, array($name => $theme->info['name'])); } - // Put the actual theme info objects into the array + // Put the actual theme info objects into the array. foreach (array_keys($themes) as $name) { if (isset($all_themes[$name])) { $themes[$name] = $all_themes[$name]; } } - // Make sure the current default theme always gets the last word + // Make sure the current default theme always gets the last word. if ($current_key = array_search($current, array_keys($themes))) { $themes += array_splice($themes, $current_key, 1); } @@ -583,7 +586,6 @@ function _ctools_list_themes() { return $themes; } - /** * Find all the base themes for the specified theme. * @@ -599,9 +601,10 @@ function _ctools_list_themes() { * The name of the theme whose base we are looking for. * @param $used_keys * A recursion parameter preventing endless loops. - * @return + * + * @return array * Returns an array of all of the theme's ancestors; the first element's value - * will be NULL if an error occurred. + * will be NULL if an error occurred. (Note: this is NOT $arr[0]). */ function ctools_find_base_themes($themes, $key, $used_keys = array()) { $base_key = $themes[$key]->info['base theme']; @@ -629,7 +632,6 @@ function ctools_find_base_themes($themes, $key, $used_keys = array()) { return $current_base_theme; } - /** * Load plugin info for the provided hook; this is handled separately from * plugins from files. @@ -654,22 +656,28 @@ function ctools_plugin_load_hooks($info) { /** * Process a single hook implementation of a ctools plugin. * - * @param $info + * @param array $info * The $info array about the plugin as returned by ctools_plugin_get_info() - * @param $module + * @param string $module * The module that implements the plugin being processed. - * @param $identifier - * The plugin identifier, which is used to create the name of the hook - * function being called. - * @param $path + * @param string|array $identifier + * Used to create the base setting of return value. If: + * - $identifier is a string, a hook name is created from this and the 'hook' + * key of the $info array, and the return value of that hook function is + * used. The hook is called like this: $identifier_$hook($info); + * - $identifier is an array, this array is used directly. + * @param string $path * The path where files utilized by this plugin will be found. - * @param $file + * @param string $file * The file that was loaded for this plugin, if it exists. - * @param $base + * @param string $base * The base plugin name to use. If a file was loaded for the plugin, this * is the plugin to assume must be present. This is used to automatically * translate the array to make the syntax more friendly to plugin * implementors. + * + * @return null|array + * NULL on failure, otherwise an array containing the results keyed by name. */ function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, $base = NULL) { if (is_array($identifier)) { @@ -737,9 +745,19 @@ function _ctools_process_data($result, $plugin_type_info, $module, $path, $file) return $result; } - /** * Process an info file for plugin information, rather than a hook. + * + * @param array $info + * The $info array about the plugin as returned by ctools_plugin_get_info() + * @param string $module + * The module that implements the plugin being processed. + * @param object $file + * An object containing 'uri' and 'name' properties. 'uri' is the name of the + * 'info' file to process. 'name' is the plugin key-name. + * + * @return null|array + * NULL on failure, otherwise an array containing the results keyed by name. */ function ctools_plugin_process_info($info, $module, $file) { $result = drupal_parse_info_file($file->uri); @@ -766,7 +784,7 @@ function ctools_plugin_get_info($module, $type) { * @param $function_name * The identifier of the function. For example, 'settings form'. * - * @return + * @return string * The actual name of the function to call, or NULL if the function * does not exist. */ @@ -783,7 +801,7 @@ function ctools_plugin_get_function($plugin_definition, $function_name) { } if (!isset($plugin_definition[$function_name])) { - return; + return NULL; } if (is_array($plugin_definition[$function_name]) && isset($plugin_definition[$function_name]['function'])) { @@ -818,7 +836,7 @@ function ctools_plugin_get_function($plugin_definition, $function_name) { * @param $function_name * The identifier of the function. For example, 'settings form'. * - * @return + * @return string * The actual name of the function to call, or NULL if the function * does not exist. */ @@ -836,7 +854,7 @@ function ctools_plugin_load_function($module, $type, $id, $function_name) { * @param $class_name * The identifier of the class. For example, 'handler'. * - * @return + * @return string * The actual name of the class to call, or NULL if the class does not exist. */ function ctools_plugin_get_class($plugin_definition, $class_name) { @@ -855,11 +873,11 @@ function ctools_plugin_get_class($plugin_definition, $class_name) { if (!isset($plugin_definition[$class_name])) { return; } - else if (is_string($plugin_definition[$class_name])) { + elseif (is_string($plugin_definition[$class_name])) { // Plugin uses the string form shorthand. $return = $plugin_definition[$class_name]; } - else if (isset($plugin_definition[$class_name]['class'])) { + elseif (isset($plugin_definition[$class_name]['class'])) { // Plugin uses the verbose array form. $return = $plugin_definition[$class_name]['class']; } @@ -881,7 +899,7 @@ function ctools_plugin_get_class($plugin_definition, $class_name) { * @param $class_name * The identifier of the class. For example, 'handler'. * - * @return + * @return string * The actual name of the class to call, or NULL if the class does not exist. */ function ctools_plugin_load_class($module, $type, $id, $class_name) { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/registry.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/registry.inc index 9d4328e6..7e517bb9 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/registry.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/registry.inc @@ -2,7 +2,6 @@ /** * @file - * * Registry magic. In a separate file to minimize unnecessary code loading. */ diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/uuid.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/uuid.inc index 6e4c42c3..13897f1f 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/uuid.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/uuid.inc @@ -9,7 +9,9 @@ /** * Pattern for detecting a valid UUID. */ -define('UUID_PATTERN', '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'); +if (!defined('UUID_PATTERN')) { + define('UUID_PATTERN', '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'); +} /** * Generates a UUID using the Windows internal GUID generator. diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/wizard.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/wizard.inc index 1a821a58..e92e0e30 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/wizard.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/wizard.inc @@ -469,10 +469,8 @@ function ctools_wizard_get_path($form_info, $step) { if (!isset($path[1]) || !is_array($path[1])) { $path[1] = array(); } - // Ensure that the query part of options is an array. - $path[1] += array('query' => array()); // Add the destination parameter, if not set already. - $path[1]['query'] += drupal_get_destination(); + $path[1] += drupal_get_destination(); } return $path; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js index a3e9aa42..b658577a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js @@ -36,9 +36,11 @@ Drupal.behaviors.CToolsAutoSubmit = { attach: function(context) { // 'this' references the form element function triggerSubmit (e) { - var $this = $(this); - if (!$this.hasClass('ctools-ajaxing')) { - $this.find('.ctools-auto-submit-click').click(); + if ($.contains(document.body, this)) { + var $this = $(this); + if (!$this.hasClass('ctools-ajaxing')) { + $this.find('.ctools-auto-submit-click').click(); + } } } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js index f74ec81b..a60fc120 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js @@ -97,7 +97,13 @@ else { switch ($(trigger).attr('type')) { case 'checkbox': - var val = $(trigger).attr('checked') ? true : false; + // **This check determines if using a jQuery version 1.7 or newer which requires the use of the prop function instead of the attr function when not called on an attribute + if ($().prop) { + var val = $(trigger).prop('checked') ? true : false; + } + else { + var val = $(trigger).attr('checked') ? true : false; + } if (val) { $(trigger).siblings('label').removeClass('hidden-options').addClass('expanded-options'); @@ -148,34 +154,41 @@ len++; } - var object = $('#' + id + '-wrapper'); - if (!object.size()) { - // Some elements can't use the parent() method or they can - // damage things. They are guaranteed to have wrappers but - // only if dependent.inc provided them. This check prevents - // problems when multiple AJAX calls cause settings to build - // up. - var $original = $('#' + id); - if ($original.is('fieldset') || $original.is('textarea')) { - continue; - } - - object = $('#' + id).parent(); + var $original = $('#' + id); + if ($original.is('fieldset') || $original.is('textarea')) { + continue; } + var object = $original.parent(); + if (Drupal.settings.CTools.dependent[id].type == 'disable') { if (Drupal.settings.CTools.dependent[id].num <= len) { // Show if the element if criteria is matched - object.attr('disabled', false); - object.addClass('dependent-options'); - object.children().attr('disabled', false); + // **This check determines if using a jQuery version 1.7 or newer which requires the use of the prop function instead of the attr function when not called on an attribute + if (typeof $().prop == 'function') { + object.prop('disabled', false); + object.addClass('dependent-options'); + object.children().prop('disabled', false); + } + else { + object.attr('disabled', false); + object.addClass('dependent-options'); + object.children().attr('disabled', false); + } } else { // Otherwise hide. Use css rather than hide() because hide() // does not work if the item is already hidden, for example, // in a collapsed fieldset. - object.attr('disabled', true); - object.children().attr('disabled', true); + // **This check determines if using a jQuery version 1.7 or newer which requires the use of the prop function instead of the attr function when not called on an attribute + if (typeof $().prop == 'function') { + object.prop('disabled', true); + object.children().prop('disabled', true); + } + else { + object.attr('disabled', true); + object.children().attr('disabled', true); + } } } else { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js index c757ef27..e65f51af 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js @@ -121,18 +121,18 @@ */ Drupal.theme.prototype.CToolsModalDialog = function () { var html = '' - html += '
    ' - html += '
    ' // panels-modal-content - html += ' '; - html += ' '; + html += '
    ' + html += '
    ' // panels-modal-content + html += ' '; + html += ' '; html += '
    '; + html += '
    '; return html; } @@ -142,11 +142,11 @@ */ Drupal.theme.prototype.CToolsModalThrobber = function () { var html = ''; - html += ' '; + + return theme('form_element', $variables); +} + +/** + * Returns HTML for remaining message. + */ +function theme_date_display_remaining($variables) { + $remaining_days = $variables['remaining_days']; + $output = ''; + $show_remaining_text = t('The upcoming date less then 1 day.'); + if ($remaining_days) { + $show_remaining_text = format_plural($remaining_days, 'To event remaining 1 day', 'To event remaining @count days'); + } + + return '
    ' . $show_remaining_text . '
    '; +} + /** @} End of addtogroup themeable */ diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_admin.inc b/profiles/commerce_kickstart/modules/contrib/date/date_admin.inc index 0e32fc5a..b800201e 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_admin.inc @@ -14,15 +14,24 @@ function date_default_formatter_settings_form($field, $instance, $view_mode, $fo $formatter = $display['type']; $form = array(); + $date_formats = date_format_type_options(); $form['format_type'] = array( '#title' => t('Choose how users view dates and times:'), '#type' => 'select', - '#options' => date_format_type_options(), + '#options' => $date_formats + array('custom' => t('Custom')), '#default_value' => $settings['format_type'], '#description' => t('To add or edit options, visit Date and time settings.', array('@date-time-page' => url('admin/config/regional/date-time'))), '#weight' => 0, ); + $form['custom_date_format'] = array( + '#type' => 'textfield', + '#title' => t('Custom date format'), + '#description' => t('If "Custom", see the PHP manual for date formats. Otherwise, enter the number of different time units to display, which defaults to 2.', array('@url' => 'http://php.net/manual/function.date.php')), + '#default_value' => isset($settings['custom_date_format']) ? $settings['custom_date_format'] : '', + '#dependency' => array('edit-options-settings-format-type' => array('custom')), + ); + $form['fromto'] = array( '#title' => t('Display:'), '#type' => 'select', @@ -74,6 +83,12 @@ function date_default_formatter_settings_form($field, $instance, $view_mode, $fo '#description' => t('Identify specific start and/or end dates in the format YYYY-MM-DDTHH:MM:SS, or leave blank for all available dates.'), ); + $form['show_remaining_days'] = array( + '#title' => t('Show remaining days'), + '#type' => 'checkbox', + '#default_value' => $settings['show_remaining_days'], + '#weight' => 0, + ); return $form; } @@ -110,6 +125,14 @@ function date_interval_formatter_settings_form($field, $instance, $view_mode, $f '#default_value' => $settings['interval_display'], '#weight' => 0, ); + if (!empty($field['settings']['todate'])) { + $form['use_end_date'] = array( + '#title' => t('Use End date'), + '#description' => 'Use the End date, instead of the start date', + '#type' => 'checkbox', + '#default_value' => $settings['use_end_date'], + ); + } return $form; } @@ -127,9 +150,11 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) case 'date_plain': $format = t('Plain'); break; + case 'format_interval': $format = t('Interval'); break; + default: if (!empty($format_types[$settings['format_type']])) { $format = $format_types[$settings['format_type']]; @@ -148,7 +173,9 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) 'value' => t('Display Start date only'), 'value2' => t('Display End date only'), ); - $summary[] = $options[$settings['fromto']]; + if (isset($options[$settings['fromto']])) { + $summary[] = $options[$settings['fromto']]; + } } if (array_key_exists('multiple_number', $settings) && !empty($field['cardinality'])) { @@ -159,6 +186,10 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) )); } + if (array_key_exists('show_remaining_days', $settings)) { + $summary[] = t('Show remaining days: @value', array('@value' => ($settings['show_remaining_days'] ? 'yes' : 'no'))); + } + return $summary; } @@ -172,7 +203,9 @@ function date_interval_formatter_settings_summary($field, $instance, $view_mode) $display = $instance['display'][$view_mode]; $settings = $display['settings']; $formatter = $display['type']; - $summary[] = t('Display time ago, showing @interval units.', array('@interval' => $settings['interval'])); + $field = ($settings['use_end_date'] == 1) ? 'End' : 'Start'; + $summary[] = t('Display time ago, showing @interval units. Using @field Date', + array('@interval' => $settings['interval'], '@field' => $field)); return $summary; } @@ -191,7 +224,11 @@ function _date_field_instance_settings_form($field, $instance) { '#type' => 'select', '#title' => t('Default date'), '#default_value' => $settings['default_value'], - '#options' => array('blank' => t('No default value'), 'now' => t('Now'), 'strtotime' => t('Relative')), + '#options' => array( + 'blank' => t('No default value'), + 'now' => t('Now'), + 'strtotime' => t('Relative'), + ), '#weight' => 1, '#fieldset' => 'default_values', ); @@ -204,8 +241,11 @@ function _date_field_instance_settings_form($field, $instance) { '#default_value' => $settings['default_value_code'], '#states' => array( 'visible' => array( - ':input[name="instance[settings][default_value]"]' => array('value' => 'strtotime')), + ':input[name="instance[settings][default_value]"]' => array( + 'value' => 'strtotime', + ), ), + ), '#weight' => 1.1, '#fieldset' => 'default_values', ); @@ -213,7 +253,12 @@ function _date_field_instance_settings_form($field, $instance) { '#type' => !empty($field['settings']['todate']) ? 'select' : 'hidden', '#title' => t('Default end date'), '#default_value' => $settings['default_value2'], - '#options' => array('same' => t('Same as Default date'), 'blank' => t('No default value'), 'now' => t('Now'), 'strtotime' => t('Relative')), + '#options' => array( + 'same' => t('Same as Default date'), + 'blank' => t('No default value'), + 'now' => t('Now'), + 'strtotime' => t('Relative'), + ), '#weight' => 2, '#fieldset' => 'default_values', ); @@ -224,8 +269,11 @@ function _date_field_instance_settings_form($field, $instance) { '#default_value' => $settings['default_value_code2'], '#states' => array( 'visible' => array( - ':input[name="instance[settings][default_value2]"]' => array('value' => 'strtotime')), + ':input[name="instance[settings][default_value2]"]' => array( + 'value' => 'strtotime', + ), ), + ), '#weight' => 2.1, '#fieldset' => 'default_values', ); @@ -244,7 +292,7 @@ function _date_field_instance_settings_form($field, $instance) { /** * Form validation handler for _date_field_instance_settings_form(). */ -function date_field_instance_settings_form_validate(&$form, &$form_state) { +function _date_field_instance_settings_form_validate(&$form, &$form_state) { $settings = $form_state['values']['instance']['settings']; if ($settings['default_value'] == 'strtotime') { @@ -284,6 +332,7 @@ function _date_field_widget_settings_form($field, $instance) { $formats = drupal_map_assoc($formats); } $now = date_example_date(); + $options['site-wide'] = t('Short date format: @date', array('@date' => date_format_date($now, 'short'))); foreach ($formats as $f) { $options[$f] = date_format_date($now, 'custom', $f); } @@ -369,11 +418,18 @@ function _date_field_widget_settings_form($field, $instance) { '#weight' => 9, ); if (in_array($widget['type'], array('date_select'))) { - $options = array('above' => t('Above'), 'within' => t('Within'), 'none' => t('None')); + $options = array( + 'above' => t('Above'), + 'within' => t('Within'), + 'none' => t('None'), + ); $description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'Within' inserts the label as the first option in the select list and in blank textfields. 'None' doesn't visually label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text."); } else { - $options = array('above' => t('Above'), 'none' => t('None')); + $options = array( + 'above' => t('Above'), + 'none' => t('None'), + ); $description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'None' doesn't visually label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text."); } $form['advanced']['label_position'] = array( @@ -403,6 +459,13 @@ function _date_field_widget_settings_form($field, $instance) { } } + $form['advanced']['no_fieldset'] = array( + '#type' => 'checkbox', + '#title' => t('Render as a regular field'), + '#default_value' => !empty($settings['no_fieldset']), + '#description' => t('Whether to render this field as a regular field instead of a fieldset. The date field elements are wrapped in a fieldset by default, and may not display well without it.'), + ); + $context = array( 'field' => $field, 'instance' => $instance, @@ -415,7 +478,7 @@ function _date_field_widget_settings_form($field, $instance) { /** * Form validation handler for _date_field_widget_settings_form(). */ -function date_field_widget_settings_form_validate(&$form, &$form_state) { +function _date_field_widget_settings_form_validate(&$form, &$form_state) { // The widget settings are in the wrong place in the form because of #tree on // the top level. $settings = $form_state['values']['instance']['widget']['settings']; @@ -470,7 +533,9 @@ function _date_field_settings_form($field, $instance, $has_data) { '#title' => t('Date attributes to collect'), '#default_value' => $granularity, '#options' => $options, - '#attributes' => array('class' => array('container-inline')), + '#attributes' => array( + 'class' => array('container-inline'), + ), '#description' => $description, 'year' => $checkbox_year, ); @@ -515,7 +580,7 @@ function _date_field_settings_form($field, $instance, $has_data) { $form['cache_enabled'] = array( '#type' => 'checkbox', '#title' => t('Cache dates'), - '#description' => t('Date objects can be created and cached as date fields are loaded rather than when they are displayed to improve performance.'), + '#description' => t('Date objects can be created and cached as date fields are loaded, rather than when they are displayed, to improve performance.'), '#default_value' => !empty($settings['cache_enabled']), '#weight' => 10, ); @@ -528,7 +593,9 @@ function _date_field_settings_form($field, $instance, $has_data) { '#weight' => 11, '#states' => array( 'visible' => array( - 'input[name="field[settings][cache_enabled]"]' => array('checked' => TRUE), + 'input[name="field[settings][cache_enabled]"]' => array( + 'checked' => TRUE, + ), ), ), ); @@ -546,7 +613,7 @@ function _date_field_settings_form($field, $instance, $has_data) { /** * Form validation handler for _date_field_settings_form(). */ -function date_field_settings_validate(&$form, &$form_state) { +function _date_field_settings_validate(&$form, &$form_state) { $field = &$form_state['values']['field']; if ($field['settings']['tz_handling'] == 'none') { @@ -600,7 +667,7 @@ function date_timezone_handling_options() { 'site' => t("Site's time zone"), 'date' => t("Date's time zone"), 'user' => t("User's time zone"), - 'utc' => 'UTC', + 'utc' => 'UTC', 'none' => t('No time zone conversion'), ); } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.info b/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.info index 8eeb7325..fcdc673a 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.info @@ -5,9 +5,9 @@ dependencies[] = date package = Date/Time core = 7.x -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.module b/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.module index 93fc61ba..e79bc944 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_all_day/date_all_day.module @@ -31,11 +31,11 @@ function date_all_day_theme() { 'format' => NULL, 'entity_type' => NULL, 'entity' => NULL, - 'view' => NULL - ) + 'view' => NULL, + ), ), 'date_all_day_label' => array( - 'variables' => array() + 'variables' => array(), ), ); @@ -91,14 +91,29 @@ function date_all_day_date_formatter_dates_alter(&$dates, $context) { /** * Adjust start/end date format to account for 'all day' . * - * @param array $field, the field definition for this date field. - * @param string $which, which value to return, 'date1' or 'date2' . - * @param object $date1, a date/time object for the 'start' date. - * @param object $date2, a date/time object for the 'end' date. - * @param string $format - * @param object $entity, the node this date comes from (may be incomplete, always contains nid). - * @param object $view, the view this node comes from, if applicable. - * @return formatted date. + * @params array $field + * The field definition for this date field. + * + * @params string $which + * Which value to return, 'date1' or 'date2'. + * + * @params object $date1 + * A date/time object for the 'start' date. + * + * @params object $date2 + * A date/time object for the 'end' date. + * + * @params string $format + * A date/time format + * + * @params object $entity + * The node this date comes from (may be incomplete, always contains nid). + * + * @params object $view + * The view this node comes from, if applicable. + * + * @return string + * Formatted date. */ function theme_date_all_day($vars) { $field = $vars['field']; @@ -135,23 +150,32 @@ function theme_date_all_day($vars) { } return trim(date_format_date($$which, 'custom', $format) . $suffix); - } /** * Theme the way an 'all day' label will look. */ function theme_date_all_day_label() { - return '(' . t('All day', array(), array('context' => 'datetime')) .')'; + return '(' . t('All day', array(), array('context' => 'datetime')) . ')'; } /** * Determine if a Start/End date combination qualify as 'All day'. * - * @param array $field, the field definition for this date field. - * @param object $date1, a date/time object for the 'Start' date. - * @param object $date2, a date/time object for the 'End' date. - * @return TRUE or FALSE. + * @param array $field + * The field definition for this date field. + * + * @param array $instance + * The field instance for this date field. + * + * @param object $date1 + * A date/time object for the 'Start' date. + * + * @param object $date2 + * A date/time object for the 'End' date. + * + * @return bool + * TRUE or FALSE. */ function date_all_day_field($field, $instance, $date1, $date2 = NULL) { if (empty($date1) || !is_object($date1)) { @@ -167,7 +191,6 @@ function date_all_day_field($field, $instance, $date1, $date2 = NULL) { $granularity = date_granularity_precision($field['settings']['granularity']); $increment = isset($instance['widget']['settings']['increment']) ? $instance['widget']['settings']['increment'] : 1; return date_is_all_day(date_format($date1, DATE_FORMAT_DATETIME), date_format($date2, DATE_FORMAT_DATETIME), $granularity, $increment); - } /** @@ -222,7 +245,8 @@ function date_all_day_date_combo_process_alter(&$element, &$form_state, $context function date_all_day_date_text_process_alter(&$element, &$form_state, $context) { $all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : ''; if ($all_day_id != '') { - // All Day handling on text dates works only if the user leaves the time out of the input value. + // All Day handling on text dates works only + // if the user leaves the time out of the input value. // There is no element to hide or show. } } @@ -234,10 +258,11 @@ function date_all_day_date_text_process_alter(&$element, &$form_state, $context) */ function date_all_day_date_select_process_alter(&$element, &$form_state, $context) { - // Hide or show this element in reaction to the all_day status for this element. + // Hide or show this element in reaction + // to the all_day status for this element. $all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : ''; if ($all_day_id != '') { - foreach(array('hour', 'minute', 'second', 'ampm') as $field) { + foreach (array('hour', 'minute', 'second', 'ampm') as $field) { if (array_key_exists($field, $element)) { $element[$field]['#states'] = array( 'visible' => array( @@ -255,7 +280,8 @@ function date_all_day_date_select_process_alter(&$element, &$form_state, $contex */ function date_all_day_date_popup_process_alter(&$element, &$form_state, $context) { - // Hide or show this element in reaction to the all_day status for this element. + // Hide or show this element in reaction to + // the all_day status for this element. $all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : ''; if ($all_day_id != '' && array_key_exists('time', $element)) { $element['time']['#states'] = array( @@ -272,7 +298,8 @@ function date_all_day_date_popup_process_alter(&$element, &$form_state, $context * of the date_select validation gets fired. */ function date_all_day_date_text_pre_validate_alter(&$element, &$form_state, &$input) { - // Let Date module massage the format for all day values so they will pass validation. + // Let Date module massage the format for all day + // values so they will pass validation. // The All day flag, if used, actually exists on the parent element. date_all_day_value($element, $form_state); } @@ -284,7 +311,8 @@ function date_all_day_date_text_pre_validate_alter(&$element, &$form_state, &$in * of the date_select validation gets fired. */ function date_all_day_date_select_pre_validate_alter(&$element, &$form_state, &$input) { - // Let Date module massage the format for all day values so they will pass validation. + // Let Date module massage the format for all + // day values so they will pass validation. // The All day flag, if used, actually exists on the parent element. date_all_day_value($element, $form_state); } @@ -296,13 +324,16 @@ function date_all_day_date_select_pre_validate_alter(&$element, &$form_state, &$ * of the date_popup validation gets fired. */ function date_all_day_date_popup_pre_validate_alter(&$element, &$form_state, &$input) { - // Let Date module massage the format for all day values so they will pass validation. + // Let Date module massage the format for all + // day values so they will pass validation. // The All day flag, if used, actually exists on the parent element. date_all_day_value($element, $form_state); } /** - * A helper function to check if the all day flag is set on the parent of an + * A helper function date_all_day_value(). + * + * To check if the all day flag is set on the parent of an * element, and adjust the date_format accordingly so the missing time will * not cause validation errors. */ @@ -332,7 +363,8 @@ function date_all_day_date_combo_pre_validate_alter(&$element, &$form_state, $co $field = $context['field']; // If we have an all day flag on this date and the time is empty, - // change the format to match the input value so we don't get validation errors. + // change the format to match the input value + // so we don't get validation errors. $element['#date_is_all_day'] = TRUE; $element['value']['#date_format'] = date_part_format('date', $element['value']['#date_format']); if (!empty($field['settings']['todate'])) { @@ -344,29 +376,29 @@ function date_all_day_date_combo_pre_validate_alter(&$element, &$form_state, $co /** * Implements hook_date_combo_validate_date_start_alter(). * - * This hook lets us alter the local date objects created by the date_combo validation - * before they are converted back to the database timezone and stored. + * This hook lets us alter the local date objects + * created by the date_combo validation before they are + * converted back to the database timezone and stored. */ function date_all_day_date_combo_validate_date_start_alter(&$date, &$form_state, $context) { - - // If this is an 'All day' value, set the time to midnight. - if (!empty($context['element']['#date_is_all_day'])) { - $date->setTime(0, 0, 0); - } + // If this is an 'All day' value, set the time to midnight. + if (!empty($context['element']['#date_is_all_day'])) { + $date->setTime(0, 0, 0); + } } /** * Implements hook_date_combo_validate_date_end_alter(). * - * This hook lets us alter the local date objects created by the date_combo validation - * before they are converted back to the database timezone and stored. + * This hook lets us alter the local date objects + * created by the date_combo validation before + * they are converted back to the database timezone and stored. */ function date_all_day_date_combo_validate_date_end_alter(&$date, &$form_state, $context) { - - // If this is an 'All day' value, set the time to midnight. - if (!empty($context['element']['#date_is_all_day'])) { - $date->setTime(0, 0, 0); - } + // If this is an 'All day' value, set the time to midnight. + if (!empty($context['element']['#date_is_all_day'])) { + $date->setTime(0, 0, 0); + } } /** diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date.css b/profiles/commerce_kickstart/modules/contrib/date/date_api/date.css index 9b72ecdb..e47a2ae2 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date.css +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date.css @@ -15,9 +15,11 @@ .container-inline-date > .form-item { display: inline-block; margin-right: 0.5em; /* LTR */ - margin-bottom: 10px; vertical-align: top; } +fieldset.date-combo .container-inline-date > .form-item { + margin-bottom: 10px; +} .container-inline-date .form-item .form-item { float: left; /* LTR */ } @@ -52,9 +54,11 @@ /* The exposed Views form doesn't need some of these styles */ .container-inline-date .date-padding { - padding: 10px; float: left; } +fieldset.date-combo .container-inline-date .date-padding { + padding: 10px; +} .views-exposed-form .container-inline-date .date-padding { padding: 0; } @@ -116,7 +120,7 @@ span.date-display-end { } /* Add space between the date and time portions of the date_select widget. */ -.form-type-date-select .form-type-select[class$=hour] { +.form-type-date-select .form-type-select[class*=hour] { margin-left: .75em; /* LTR */ } @@ -173,6 +177,10 @@ div.date-calendar-day span.year { padding: 2px; } +.date-form-element-content-multiline { + padding: 10px; + border: 1px solid #CCC; +} /* Admin styling */ .form-item.form-item-instance-widget-settings-input-format-custom, .form-item.form-item-field-settings-enddate-required { diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.admin.inc b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.admin.inc index 966b1a42..3c706aeb 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.admin.inc @@ -10,112 +10,112 @@ */ function _date_timezone_replacement($old) { $replace = array( - 'Brazil/Acre' => 'America/Rio_Branco', - 'Brazil/DeNoronha' => 'America/Noronha', - 'Brazil/East' => 'America/Recife', - 'Brazil/West' => 'America/Manaus', - 'Canada/Atlantic' => 'America/Halifax', - 'Canada/Central' => 'America/Winnipeg', - 'Canada/East-Saskatchewan' => 'America/Regina', - 'Canada/Eastern' => 'America/Toronto', - 'Canada/Mountain' => 'America/Edmonton', - 'Canada/Newfoundland' => 'America/St_Johns', - 'Canada/Pacific' => 'America/Vancouver', - 'Canada/Saskatchewan' => 'America/Regina', - 'Canada/Yukon' => 'America/Whitehorse', - 'CET' => 'Europe/Berlin', - 'Chile/Continental' => 'America/Santiago', - 'Chile/EasterIsland' => 'Pacific/Easter', - 'CST6CDT' => 'America/Chicago', - 'Cuba' => 'America/Havana', - 'EET' => 'Europe/Bucharest', - 'Egypt' => 'Africa/Cairo', - 'Eire' => 'Europe/Belfast', - 'EST' => 'America/New_York', - 'EST5EDT' => 'America/New_York', - 'GB' => 'Europe/London', - 'GB-Eire' => 'Europe/Belfast', - 'Etc/GMT' => 'UTC', - 'Etc/GMT+0' => 'UTC', - 'Etc/GMT+1' => 'UTC', - 'Etc/GMT+10' => 'UTC', - 'Etc/GMT+11' => 'UTC', - 'Etc/GMT+12' => 'UTC', - 'Etc/GMT+2' => 'UTC', - 'Etc/GMT+3' => 'UTC', - 'Etc/GMT+4' => 'UTC', - 'Etc/GMT+5' => 'UTC', - 'Etc/GMT+6' => 'UTC', - 'Etc/GMT+7' => 'UTC', - 'Etc/GMT+8' => 'UTC', - 'Etc/GMT+9' => 'UTC', - 'Etc/GMT-0' => 'UTC', - 'Etc/GMT-1' => 'UTC', - 'Etc/GMT-10' => 'UTC', - 'Etc/GMT-11' => 'UTC', - 'Etc/GMT-12' => 'UTC', - 'Etc/GMT-13' => 'UTC', - 'Etc/GMT-14' => 'UTC', - 'Etc/GMT-2' => 'UTC', - 'Etc/GMT-3' => 'UTC', - 'Etc/GMT-4' => 'UTC', - 'Etc/GMT-5' => 'UTC', - 'Etc/GMT-6' => 'UTC', - 'Etc/GMT-7' => 'UTC', - 'Etc/GMT-8' => 'UTC', - 'Etc/GMT-9' => 'UTC', - 'Etc/GMT0' => 'UTC', - 'Etc/Greenwich' => 'UTC', - 'Etc/UCT' => 'UTC', - 'Etc/Universal' => 'UTC', - 'Etc/UTC' => 'UTC', - 'Etc/Zulu' => 'UTC', - 'Factory' => 'UTC', - 'GMT' => 'UTC', - 'GMT+0' => 'UTC', - 'GMT-0' => 'UTC', - 'GMT0' => 'UTC', - 'Hongkong' => 'Asia/Hong_Kong', - 'HST' => 'Pacific/Honolulu', - 'Iceland' => 'Atlantic/Reykjavik', - 'Iran' => 'Asia/Tehran', - 'Israel' => 'Asia/Tel_Aviv', - 'Jamaica' => 'America/Jamaica', - 'Japan' => 'Asia/Tokyo', - 'Kwajalein' => 'Pacific/Kwajalein', - 'Libya' => 'Africa/Tunis', - 'MET' => 'Europe/Budapest', - 'Mexico/BajaNorte' => 'America/Tijuana', - 'Mexico/BajaSur' => 'America/Mazatlan', - 'Mexico/General' => 'America/Mexico_City', - 'MST' => 'America/Boise', - 'MST7MDT' => 'America/Boise', - 'Navajo' => 'America/Phoenix', - 'NZ' => 'Pacific/Auckland', - 'NZ-CHAT' => 'Pacific/Chatham', - 'Poland' => 'Europe/Warsaw', - 'Portugal' => 'Europe/Lisbon', - 'PRC' => 'Asia/Chongqing', - 'PST8PDT' => 'America/Los_Angeles', - 'ROC' => 'Asia/Taipei', - 'ROK' => 'Asia/Seoul', - 'Singapore' => 'Asia/Singapore', - 'Turkey' => 'Europe/Istanbul', - 'US/Alaska' => 'America/Anchorage', - 'US/Aleutian' => 'America/Adak', - 'US/Arizona' => 'America/Phoenix', - 'US/Central' => 'America/Chicago', - 'US/East-Indiana' => 'America/Indianapolis', - 'US/Eastern' => 'America/New_York', - 'US/Hawaii' => 'Pacific/Honolulu', - 'US/Indiana-Starke' => 'America/Indiana/Knox', - 'US/Michigan' => 'America/Detroit', - 'US/Mountain' => 'America/Boise', - 'US/Pacific' => 'America/Los_Angeles', - 'US/Pacific-New' => 'America/Los_Angeles', - 'US/Samoa' => 'Pacific/Samoa', - 'W-SU' => 'Europe/Moscow', - 'WET' => 'Europe/Paris', + 'Brazil/Acre' => 'America/Rio_Branco', + 'Brazil/DeNoronha' => 'America/Noronha', + 'Brazil/East' => 'America/Recife', + 'Brazil/West' => 'America/Manaus', + 'Canada/Atlantic' => 'America/Halifax', + 'Canada/Central' => 'America/Winnipeg', + 'Canada/East-Saskatchewan' => 'America/Regina', + 'Canada/Eastern' => 'America/Toronto', + 'Canada/Mountain' => 'America/Edmonton', + 'Canada/Newfoundland' => 'America/St_Johns', + 'Canada/Pacific' => 'America/Vancouver', + 'Canada/Saskatchewan' => 'America/Regina', + 'Canada/Yukon' => 'America/Whitehorse', + 'CET' => 'Europe/Berlin', + 'Chile/Continental' => 'America/Santiago', + 'Chile/EasterIsland' => 'Pacific/Easter', + 'CST6CDT' => 'America/Chicago', + 'Cuba' => 'America/Havana', + 'EET' => 'Europe/Bucharest', + 'Egypt' => 'Africa/Cairo', + 'Eire' => 'Europe/Belfast', + 'EST' => 'America/New_York', + 'EST5EDT' => 'America/New_York', + 'GB' => 'Europe/London', + 'GB-Eire' => 'Europe/Belfast', + 'Etc/GMT' => 'UTC', + 'Etc/GMT+0' => 'UTC', + 'Etc/GMT+1' => 'UTC', + 'Etc/GMT+10' => 'UTC', + 'Etc/GMT+11' => 'UTC', + 'Etc/GMT+12' => 'UTC', + 'Etc/GMT+2' => 'UTC', + 'Etc/GMT+3' => 'UTC', + 'Etc/GMT+4' => 'UTC', + 'Etc/GMT+5' => 'UTC', + 'Etc/GMT+6' => 'UTC', + 'Etc/GMT+7' => 'UTC', + 'Etc/GMT+8' => 'UTC', + 'Etc/GMT+9' => 'UTC', + 'Etc/GMT-0' => 'UTC', + 'Etc/GMT-1' => 'UTC', + 'Etc/GMT-10' => 'UTC', + 'Etc/GMT-11' => 'UTC', + 'Etc/GMT-12' => 'UTC', + 'Etc/GMT-13' => 'UTC', + 'Etc/GMT-14' => 'UTC', + 'Etc/GMT-2' => 'UTC', + 'Etc/GMT-3' => 'UTC', + 'Etc/GMT-4' => 'UTC', + 'Etc/GMT-5' => 'UTC', + 'Etc/GMT-6' => 'UTC', + 'Etc/GMT-7' => 'UTC', + 'Etc/GMT-8' => 'UTC', + 'Etc/GMT-9' => 'UTC', + 'Etc/GMT0' => 'UTC', + 'Etc/Greenwich' => 'UTC', + 'Etc/UCT' => 'UTC', + 'Etc/Universal' => 'UTC', + 'Etc/UTC' => 'UTC', + 'Etc/Zulu' => 'UTC', + 'Factory' => 'UTC', + 'GMT' => 'UTC', + 'GMT+0' => 'UTC', + 'GMT-0' => 'UTC', + 'GMT0' => 'UTC', + 'Hongkong' => 'Asia/Hong_Kong', + 'HST' => 'Pacific/Honolulu', + 'Iceland' => 'Atlantic/Reykjavik', + 'Iran' => 'Asia/Tehran', + 'Israel' => 'Asia/Tel_Aviv', + 'Jamaica' => 'America/Jamaica', + 'Japan' => 'Asia/Tokyo', + 'Kwajalein' => 'Pacific/Kwajalein', + 'Libya' => 'Africa/Tunis', + 'MET' => 'Europe/Budapest', + 'Mexico/BajaNorte' => 'America/Tijuana', + 'Mexico/BajaSur' => 'America/Mazatlan', + 'Mexico/General' => 'America/Mexico_City', + 'MST' => 'America/Boise', + 'MST7MDT' => 'America/Boise', + 'Navajo' => 'America/Phoenix', + 'NZ' => 'Pacific/Auckland', + 'NZ-CHAT' => 'Pacific/Chatham', + 'Poland' => 'Europe/Warsaw', + 'Portugal' => 'Europe/Lisbon', + 'PRC' => 'Asia/Chongqing', + 'PST8PDT' => 'America/Los_Angeles', + 'ROC' => 'Asia/Taipei', + 'ROK' => 'Asia/Seoul', + 'Singapore' => 'Asia/Singapore', + 'Turkey' => 'Europe/Istanbul', + 'US/Alaska' => 'America/Anchorage', + 'US/Aleutian' => 'America/Adak', + 'US/Arizona' => 'America/Phoenix', + 'US/Central' => 'America/Chicago', + 'US/East-Indiana' => 'America/Indianapolis', + 'US/Eastern' => 'America/New_York', + 'US/Hawaii' => 'Pacific/Honolulu', + 'US/Indiana-Starke' => 'America/Indiana/Knox', + 'US/Michigan' => 'America/Detroit', + 'US/Mountain' => 'America/Boise', + 'US/Pacific' => 'America/Los_Angeles', + 'US/Pacific-New' => 'America/Los_Angeles', + 'US/Samoa' => 'Pacific/Samoa', + 'W-SU' => 'Europe/Moscow', + 'WET' => 'Europe/Paris', ); if (array_key_exists($old, $replace)) { return $replace[$old]; diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.info b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.info index 3ca86e65..c537ca04 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.info @@ -9,9 +9,9 @@ stylesheets[all][] = date.css files[] = date_api.module files[] = date_api_sql.inc -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.install b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.install index ce5b746d..d5a68bbd 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.install +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.install @@ -96,7 +96,7 @@ function date_api_uninstall() { 'date_php_min_year', 'date_db_tz_support', 'date_api_use_iso8601', - ); + ); foreach ($variables as $variable) { variable_del($variable); } @@ -118,8 +118,9 @@ function date_api_update_last_removed() { } /** - * Move old date format data to new date format tables, and delete the old - * tables. Insert only values that don't already exist in the new tables, in + * Move old date format to new date format tables,and delete the old tables. + * + * Insert only values that don't already exist in the new tables, in * case new version of those custom values have already been created. */ function date_api_update_7000() { diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.module b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.module index b1be6e5f..160bc9fb 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api.module @@ -59,16 +59,16 @@ function date_help($path, $arg) { } if (module_exists('date_tools')) { - $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and with a date field. ', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); + $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and with a date field.', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); } else { - $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. If you enable the Date Tools module, it provides a Date Wizard that makes it easy to create a simple date content type with a date field. '); + $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. If you enable the Date Tools module, it provides a Date Wizard that makes it easy to create a simple date content type with a date field.'); } $output .= '

    More Information

    ' . t('Complete documentation for the Date and Date API modules is available at http://drupal.org/node/92460.', array('@link' => 'http://drupal.org/node/262062')) . '

    '; return $output; - break; + } } @@ -101,7 +101,7 @@ function date_api_status() { $value = variable_get('date_format_medium'); if (isset($value)) { $now = date_now(); - $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at Date and time settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time'))); + $success_messages[] = $t('The medium date format type has been set to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at Date and time settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time'))); } else { $error_messages[] = $t('The Date API requires that you set up the system date formats to function correctly.', array('@regional_date_time' => url('admin/config/regional/date-time'))); @@ -143,7 +143,15 @@ function date_api_menu() { class DateObject extends DateTime { public $granularity = array(); public $errors = array(); - protected static $allgranularity = array('year', 'month', 'day', 'hour', 'minute', 'second', 'timezone'); + protected static $allgranularity = array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + 'timezone' + ); private $serializedTime; private $serializedTimezone; @@ -402,7 +410,7 @@ class DateObject extends DateTime { * A single date part. */ public function removeGranularity($g) { - if ($key = array_search($g, $this->granularity)) { + if (($key = array_search($g, $this->granularity)) !== FALSE) { unset($this->granularity[$key]); } } @@ -458,23 +466,35 @@ class DateObject extends DateTime { $true = $this->hasGranularity() && (!$granularity || $flexible || $this->hasGranularity($granularity)); if (!$true && $granularity) { foreach ((array) $granularity as $part) { - if (!$this->hasGranularity($part) && in_array($part, array('second', 'minute', 'hour', 'day', 'month', 'year'))) { + if (!$this->hasGranularity($part) && in_array($part, array( + 'second', + 'minute', + 'hour', + 'day', + 'month', + 'year') + )) { switch ($part) { case 'second': $this->errors[$part] = t('The second is missing.'); break; + case 'minute': $this->errors[$part] = t('The minute is missing.'); break; + case 'hour': $this->errors[$part] = t('The hour is missing.'); break; + case 'day': $this->errors[$part] = t('The day is missing.'); break; + case 'month': $this->errors[$part] = t('The month is missing.'); break; + case 'year': $this->errors[$part] = t('The year is missing.'); break; @@ -537,7 +557,14 @@ class DateObject extends DateTime { $temp = date_parse($time); // Special case for 'now'. if ($time == 'now') { - $this->granularity = array('year', 'month', 'day', 'hour', 'minute', 'second'); + $this->granularity = array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ); } else { // This PHP date_parse() method currently doesn't have resolution down to @@ -600,7 +627,14 @@ class DateObject extends DateTime { return FALSE; } $this->granularity = array(); - $final_date = array('hour' => 0, 'minute' => 0, 'second' => 0, 'month' => 1, 'day' => 1, 'year' => 0); + $final_date = array( + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + 'month' => 1, + 'day' => 1, + 'year' => 0, + ); foreach ($letters as $i => $letter) { $value = $values[$i]; switch ($letter) { @@ -609,21 +643,25 @@ class DateObject extends DateTime { $final_date['day'] = intval($value); $this->addGranularity('day'); break; + case 'n': case 'm': $final_date['month'] = intval($value); $this->addGranularity('month'); break; + case 'F': $array_month_long = array_flip(date_month_names()); $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1; $this->addGranularity('month'); break; + case 'M': $array_month = array_flip(date_month_names_abbr()); $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1; $this->addGranularity('month'); break; + case 'Y': $final_date['year'] = $value; $this->addGranularity('year'); @@ -631,16 +669,19 @@ class DateObject extends DateTime { $this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.'); } break; + case 'y': $year = $value; // If no century, we add the current one ("06" => "2006"). $final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT); $this->addGranularity('year'); break; + case 'a': case 'A': $ampm = strtolower($value); break; + case 'g': case 'h': case 'G': @@ -648,14 +689,17 @@ class DateObject extends DateTime { $final_date['hour'] = intval($value); $this->addGranularity('hour'); break; + case 'i': $final_date['minute'] = intval($value); $this->addGranularity('minute'); break; + case 's': $final_date['second'] = intval($value); $this->addGranularity('second'); break; + case 'U': parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC")); $this->addGranularity('year'); @@ -665,7 +709,7 @@ class DateObject extends DateTime { $this->addGranularity('minute'); $this->addGranularity('second'); return $this; - break; + } } if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) { @@ -758,10 +802,24 @@ class DateObject extends DateTime { // date or we will get date slippage, i.e. a value of 2011-00-00 will get // interpreted as November of 2010 by PHP. if ($full) { - $arr += array('year' => 0, 'month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0); + $arr += array( + 'year' => 0, + 'month' => 1, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + ); } else { - $arr += array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => ''); + $arr += array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); } $datetime = ''; if ($arr['year'] !== '') { @@ -839,28 +897,27 @@ class DateObject extends DateTime { case 'year': $fallback = $now->format('Y'); return !is_int($value) || empty($value) || $value < variable_get('date_min_year', 1) || $value > variable_get('date_max_year', 4000) ? $fallback : $value; - break; + case 'month': $fallback = $default == 'first' ? 1 : $now->format('n'); return !is_int($value) || empty($value) || $value <= 0 || $value > 12 ? $fallback : $value; - break; + case 'day': $fallback = $default == 'first' ? 1 : $now->format('j'); $max_day = isset($year) && isset($month) ? date_days_in_month($year, $month) : 31; return !is_int($value) || empty($value) || $value <= 0 || $value > $max_day ? $fallback : $value; - break; + case 'hour': $fallback = $default == 'first' ? 0 : $now->format('G'); return !is_int($value) || $value < 0 || $value > 23 ? $fallback : $value; - break; + case 'minute': $fallback = $default == 'first' ? 0 : $now->format('i'); return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value; - break; + case 'second': $fallback = $default == 'first' ? 0 : $now->format('s'); return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value; - break; } } @@ -898,18 +955,23 @@ class DateObject extends DateTime { case 'year': $errors['year'] = t('The year is invalid.'); break; + case 'month': $errors['month'] = t('The month is invalid.'); break; + case 'day': $errors['day'] = t('The day is invalid.'); break; + case 'hour': $errors['hour'] = t('The hour is invalid.'); break; + case 'minute': $errors['minute'] = t('The minute is invalid.'); break; + case 'second': $errors['second'] = t('The second is invalid.'); break; @@ -929,7 +991,7 @@ class DateObject extends DateTime { * The stop date. * @param string $measure * (optional) A granularity date part. Defaults to 'seconds'. - * @param boolean $absolute + * @param bool $absolute * (optional) Indicate whether the absolute value of the difference should * be returned or if the sign should be retained. Defaults to TRUE. */ @@ -955,10 +1017,13 @@ class DateObject extends DateTime { // The easy cases first. case 'seconds': return $diff; + case 'minutes': return $diff / 60; + case 'hours': return $diff / 3600; + case 'years': return $year_diff; @@ -1013,7 +1078,7 @@ class DateObject extends DateTime { $sign = ($year_diff < 0) ? -1 : 1; for ($i = 1; $i <= abs($year_diff); $i++) { - date_modify($date1, (($sign > 0) ? '+': '-').'1 year'); + date_modify($date1, (($sign > 0) ? '+' : '-') . '1 year'); $week_diff += (date_iso_weeks_in_year($date1) * $sign); } return $week_diff; @@ -1060,10 +1125,13 @@ function date_type_format($type) { switch ($type) { case DATE_ISO: return DATE_FORMAT_ISO; + case DATE_UNIX: return DATE_FORMAT_UNIX; + case DATE_DATETIME: return DATE_FORMAT_DATETIME; + case DATE_ICAL: return DATE_FORMAT_ICAL; } @@ -1119,7 +1187,7 @@ function date_month_names($required = FALSE) { } /** - * Constructs a translated array of month name abbreviations + * Constructs a translated array of month name abbreviations. * * @param bool $required * (optional) If FALSE, the returned array will include a blank value. @@ -1211,9 +1279,11 @@ function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) { case 1: $context = 'day_abbr1'; break; + case 2: $context = 'day_abbr2'; break; + default: $context = ''; break; @@ -1248,10 +1318,10 @@ function date_week_days_ordered($weekdays) { /** * Constructs an array of years. * - * @param int $min - * The minimum year in the array. - * @param int $max - * The maximum year in the array. + * @param int $start + * The start year in the array. + * @param int $end + * The end year in the array. * @param bool $required * (optional) If FALSE, the returned array will include a blank value. * Defaults to FALSE. @@ -1259,16 +1329,16 @@ function date_week_days_ordered($weekdays) { * @return array * An array of years in the selected range. */ -function date_years($min = 0, $max = 0, $required = FALSE) { +function date_years($start = 0, $end = 0, $required = FALSE) { // Ensure $min and $max are valid values. - if (empty($min)) { - $min = intval(date('Y', REQUEST_TIME) - 3); + if (empty($start)) { + $start = intval(date('Y', REQUEST_TIME) - 3); } - if (empty($max)) { - $max = intval(date('Y', REQUEST_TIME) + 3); + if (empty($end)) { + $end = intval(date('Y', REQUEST_TIME) + 3); } $none = array(0 => ''); - return !$required ? $none + drupal_map_assoc(range($min, $max)) : drupal_map_assoc(range($min, $max)); + return !$required ? $none + drupal_map_assoc(range($start, $end)) : drupal_map_assoc(range($start, $end)); } /** @@ -1474,7 +1544,14 @@ function date_granularity_names() { * An array of date parts. */ function date_granularity_sorted($granularity) { - return array_intersect(array('year', 'month', 'day', 'hour', 'minute', 'second'), $granularity); + return array_intersect(array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ), $granularity); } /** @@ -1492,14 +1569,19 @@ function date_granularity_array_from_precision($precision) { switch ($precision) { case 'year': return array_slice($granularity_array, -6, 1); + case 'month': return array_slice($granularity_array, -6, 2); + case 'day': return array_slice($granularity_array, -6, 3); + case 'hour': return array_slice($granularity_array, -6, 4); + case 'minute': return array_slice($granularity_array, -6, 5); + default: return $granularity_array; } @@ -1533,14 +1615,19 @@ function date_granularity_format($granularity) { switch ($granularity) { case 'year': return substr($format, 0, 1); + case 'month': return substr($format, 0, 3); + case 'day': return substr($format, 0, 5); + case 'hour'; return substr($format, 0, 7); + case 'minute': return substr($format, 0, 9); + default: return $format; } @@ -1657,40 +1744,51 @@ function date_format_date($date, $type = 'medium', $format = '', $langcode = NUL case 'l': $datestring .= t($date->format('l'), array(), array('context' => '', 'langcode' => $langcode)); break; + case 'D': $datestring .= t($date->format('D'), array(), array('context' => '', 'langcode' => $langcode)); break; + case 'F': $datestring .= t($date->format('F'), array(), array('context' => 'Long month name', 'langcode' => $langcode)); break; + case 'M': $datestring .= t($date->format('M'), array(), array('langcode' => $langcode)); break; + case 'A': case 'a': $datestring .= t($date->format($c), array(), array('context' => 'ampm', 'langcode' => $langcode)); break; + // The timezone name translations can use t(). case 'e': case 'T': $datestring .= t($date->format($c)); break; + // Remaining date parts need no translation. case 'O': $datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60); break; + case 'P': $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60); break; + case 'Z': $datestring .= date_offset_get($date); break; + case '\\': $datestring .= $format[++$i]; break; + case 'r': - $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode); + $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', 'en'); break; + default: if (strpos('BdcgGhHiIjLmnNosStTuUwWYyz', $c) !== FALSE) { $datestring .= $date->format($c); @@ -1735,22 +1833,28 @@ function date_format_interval($date, $granularity = 2, $display_ago = TRUE) { /** * A date object for the current time. * - * @param object $timezone - * (optional) Optionally force time to a specific timezone, defaults to user - * timezone, if set, otherwise site timezone. Defaults to NULL. + * @param object|string|null $timezone + * (optional) PHP DateTimeZone object, string or NULL allowed. Optionally + * force time to a specific timezone, defaults to user timezone, if set, + * otherwise site timezone. Defaults to NULL. * - * @param boolean $reset [optional] - * Static cache reset + * @param bool $reset + * (optional) Static cache reset. * * @return object * The current time as a date object. */ function date_now($timezone = NULL, $reset = FALSE) { + $static_var = __FUNCTION__ . $timezone; + if ($timezone instanceof DateTimeZone) { + $static_var = __FUNCTION__ . $timezone->getName(); + } + if ($reset) { - drupal_static_reset(__FUNCTION__ . $timezone); + drupal_static_reset($static_var); } - $now = &drupal_static(__FUNCTION__ . $timezone); + $now = &drupal_static($static_var); if (!isset($now)) { $now = new DateObject('now', $timezone); @@ -1822,7 +1926,12 @@ function date_days_in_month($year, $month) { // Pick a day in the middle of the month to avoid timezone shifts. $datetime = date_pad($year, 4) . '-' . date_pad($month) . '-15 00:00:00'; $date = new DateObject($datetime); - return $date->format('t'); + if ($date->errors) { + return FALSE; + } + else { + return $date->format('t'); + } } /** @@ -1831,7 +1940,7 @@ function date_days_in_month($year, $month) { * @param mixed $date * (optional) The current date object, or a date string. Defaults to NULL. * - * @return integer + * @return int * The number of days in the year. */ function date_days_in_year($date = NULL) { @@ -1860,7 +1969,7 @@ function date_days_in_year($date = NULL) { * @param mixed $date * (optional) The current date object, or a date string. Defaults to NULL. * - * @return integer + * @return int * The number of ISO weeks in a year. */ function date_iso_weeks_in_year($date = NULL) { @@ -1952,7 +2061,7 @@ function date_week_range($week, $year) { // Move forwards to the last day of the week. $max_date = clone($min_date); - date_modify($max_date, '+7 days'); + date_modify($max_date, '+6 days'); if (date_format($min_date, 'Y') != $year) { $min_date = new DateObject($year . '-01-01 00:00:00'); @@ -1977,6 +2086,9 @@ function date_iso_week_range($week, $year) { date_timezone_set($min_date, date_default_timezone_object()); // Find the first day of the first ISO week in the year. + // If it's already a Monday, date_modify won't add a Monday, + // it will remain the same day. So add a Sunday first, then a Monday. + date_modify($min_date, '+1 Sunday'); date_modify($min_date, '+1 Monday'); // Jump ahead to the desired week for the beginning of the week range. @@ -1986,7 +2098,7 @@ function date_iso_week_range($week, $year) { // Move forwards to the last day of the week. $max_date = clone($min_date); - date_modify($max_date, '+7 days'); + date_modify($max_date, '+6 days'); return array($min_date, $max_date); } @@ -2094,7 +2206,8 @@ function date_has_time($granularity) { if (!is_array($granularity)) { $granularity = array(); } - return (bool) count(array_intersect($granularity, array('hour', 'minute', 'second'))); + $options = array('hour', 'minute', 'second'); + return (bool) count(array_intersect($granularity, $options)); } /** @@ -2110,7 +2223,8 @@ function date_has_date($granularity) { if (!is_array($granularity)) { $granularity = array(); } - return (bool) count(array_intersect($granularity, array('year', 'month', 'day'))); + $options = array('year', 'month', 'day'); + return (bool) count(array_intersect($granularity, $options)); } /** @@ -2128,8 +2242,10 @@ function date_part_format($part, $format) { switch ($part) { case 'date': return date_limit_format($format, array('year', 'month', 'day')); + case 'time': return date_limit_format($format, array('hour', 'minute', 'second')); + default: return date_limit_format($format, array($part)); } @@ -2157,7 +2273,7 @@ function date_limit_format($format, $granularity) { $drupal_static_fast['formats'] = &drupal_static(__FUNCTION__); } $formats = &$drupal_static_fast['formats']; - $format_granularity_cid = $format .'|'. implode(',', $granularity); + $format_granularity_cid = $format . '|' . implode(',', $granularity); if (isset($formats[$format_granularity_cid])) { return $formats[$format_granularity_cid]; } @@ -2191,21 +2307,27 @@ function date_limit_format($format, $granularity) { case 'year': $regex[] = '([\-/\.,:]?\s?(? "$path/theme", ); return array( - 'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)), + 'date_nav_title' => $base + array( + 'variables' => array( + 'granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL + ), + ), 'date_timezone' => $base + array('render element' => 'element'), 'date_select' => $base + array('render element' => 'element'), 'date_text' => $base + array('render element' => 'element'), @@ -2355,7 +2495,11 @@ function date_api_theme($existing, $type, $theme, $path) { 'date_part_label_time' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)), 'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'), 'date_calendar_day' => $base + array('variables' => array('date' => NULL)), - 'date_time_ago' => $base + array('variables' => array('start_date' => NULL, 'end_date' => NULL, 'interval' => NULL)), + 'date_time_ago' => $base + array( + 'variables' => array( + 'start_date' => NULL, 'end_date' => NULL, 'interval' => NULL + ), + ), ); } @@ -2375,9 +2519,11 @@ function date_get_timezone($handling, $timezone = '') { case 'date': $timezone = !empty($timezone) ? $timezone : date_default_timezone(); break; + case 'utc': $timezone = 'UTC'; break; + default: $timezone = date_default_timezone(); } @@ -2404,6 +2550,7 @@ function date_get_timezone_db($handling, $timezone = NULL) { // These handling modes all convert to UTC before storing in the DB. $timezone = 'UTC'; break; + case ('date'): if ($timezone == NULL) { // This shouldn't happen, since it's meaning is undefined. But we need @@ -2411,6 +2558,7 @@ function date_get_timezone_db($handling, $timezone = NULL) { $timezone = date_default_timezone(); } break; + case ('none'): default: $timezone = date_default_timezone(); @@ -2465,12 +2613,12 @@ function date_order() { * TRUE if the date range is valid, FALSE otherwise. */ function date_range_valid($string) { - $matches = preg_match('@^(\-[0-9]+|[0-9]{4}):([\+|\-][0-9]+|[0-9]{4})$@', $string); + $matches = preg_match('@^([\+\-][0-9]+|[0-9]{4}):([\+\-][0-9]+|[0-9]{4})$@', $string); return $matches < 1 ? FALSE : TRUE; } /** - * Splits a string like -3:+3 or 2001:2010 into an array of min and max years. + * Splits a string like -3:+3 or 2001:2010 into an array of start and end years. * * Center the range around the current year, if any, but expand it far * enough so it will pick up the year value in the field in case @@ -2482,45 +2630,44 @@ function date_range_valid($string) { * (optional) A date object. Defaults to NULL. * * @return array - * A numerically indexed array, containing a minimum and maximum year. + * A numerically indexed array, containing a start and end year. */ function date_range_years($string, $date = NULL) { $this_year = date_format(date_now(), 'Y'); - list($min_year, $max_year) = explode(':', $string); + list($start_year, $end_year) = explode(':', $string); // Valid patterns would be -5:+5, 0:+1, 2008:2010. - $plus_pattern = '@[\+|\-][0-9]{1,4}@'; + $plus_pattern = '@[\+\-][0-9]{1,4}@'; $year_pattern = '@^[0-9]{4}@'; - if (!preg_match($year_pattern, $min_year, $matches)) { - if (preg_match($plus_pattern, $min_year, $matches)) { - $min_year = $this_year + $matches[0]; + if (!preg_match($year_pattern, $start_year, $matches)) { + if (preg_match($plus_pattern, $start_year, $matches)) { + $start_year = $this_year + $matches[0]; } else { - $min_year = $this_year; + $start_year = $this_year; } } - if (!preg_match($year_pattern, $max_year, $matches)) { - if (preg_match($plus_pattern, $max_year, $matches)) { - $max_year = $this_year + $matches[0]; + if (!preg_match($year_pattern, $end_year, $matches)) { + if (preg_match($plus_pattern, $end_year, $matches)) { + $end_year = $this_year + $matches[0]; } else { - $max_year = $this_year; + $end_year = $this_year; } } - // We expect the $min year to be less than the $max year. - // Some custom values for -99:+99 might not obey that. - if ($min_year > $max_year) { - $temp = $max_year; - $max_year = $min_year; - $min_year = $temp; - } // If there is a current value, stretch the range to include it. $value_year = is_object($date) ? $date->format('Y') : ''; if (!empty($value_year)) { - $min_year = min($value_year, $min_year); - $max_year = max($value_year, $max_year); + if ($start_year <= $end_year) { + $start_year = min($value_year, $start_year); + $end_year = max($value_year, $end_year); + } + else { + $start_year = max($value_year, $start_year); + $end_year = min($value_year, $end_year); + } } - return array($min_year, $max_year); + return array($start_year, $end_year); } /** @@ -2680,6 +2827,7 @@ function date_is_all_day($string1, $string2, $granularity = 'second', $increment || ($hour2 == 23 && in_array($min2, array($max_minutes, 59)) && in_array($sec2, array($max_seconds, 59))) || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0 && $sec1 == 0 && $sec2 == 0); break; + case 'minute': $min_match = $time1 == '00:00:00' || ($hour1 == 0 && $min1 == 0); @@ -2687,6 +2835,7 @@ function date_is_all_day($string1, $string2, $granularity = 'second', $increment || ($hour2 == 23 && in_array($min2, array($max_minutes, 59))) || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0); break; + case 'hour': $min_match = $time1 == '00:00:00' || ($hour1 == 0); @@ -2694,6 +2843,7 @@ function date_is_all_day($string1, $string2, $granularity = 'second', $increment || ($hour2 == 23) || ($hour1 == 0 && $hour2 == 0); break; + default: $min_match = TRUE; $max_match = FALSE; @@ -2754,15 +2904,21 @@ function date_is_date($date) { } /** - * This function will replace ISO values that have the pattern 9999-00-00T00:00:00 - * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO - * dates and ensure that date objects created from this value contain a valid month - * and day. Without this fix, the ISO date '2020-00-00T00:00:00' would be created as + * Replace specific ISO values using patterns. + * + * Function will replace ISO values that have the pattern 9999-00-00T00:00:00 + * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO dates + * and ensure that date objects created from this value contain a valid month + * and day. + * Without this fix, the ISO date '2020-00-00T00:00:00' would be created as * November 30, 2019 (the previous day in the previous month). * * @param string $iso_string * An ISO string that needs to be made into a complete, valid date. * + * @return mixed|string + * replaced value, or incoming value. + * * @TODO Expand on this to work with all sorts of partial ISO dates. */ function date_make_iso_valid($iso_string) { diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_elements.inc b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_elements.inc index 57e41615..638fcda5 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_elements.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_elements.inc @@ -111,20 +111,24 @@ function date_default_date($element) { $format = DATE_FORMAT_DATETIME; // The text and popup widgets might return less than a full datetime string. - if (strlen($element['#default_value']) < 19) { + if (is_string($element['#default_value']) && strlen($element['#default_value']) < 19) { switch (strlen($element['#default_value'])) { case 16: $format = 'Y-m-d H:i'; break; + case 13: $format = 'Y-m-d H'; break; + case 10: $format = 'Y-m-d'; break; + case 7: $format = 'Y-m'; break; + case 4: $format = 'Y'; break; @@ -170,7 +174,7 @@ function date_year_range_element_process($element, &$form_state, $form) { $element['#attached']['js'][] = drupal_get_path('module', 'date_api') . '/date_year_range.js'; $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_year_range_process', $element, $form_state, $context); @@ -256,7 +260,7 @@ function date_timezone_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_timezone_process', $element, $form_state, $context); @@ -264,7 +268,7 @@ function date_timezone_element_process($element, &$form_state, $form) { } /** - * Validation for timezone input + * Validation for timezone input. * * Move the timezone value from the nested field back to the original field. */ @@ -307,7 +311,6 @@ function date_text_element_value_callback($element, $input = FALSE, &$form_state * * The exact parts displayed in the field are those in #date_granularity. * The display of each part comes from #date_format. - * */ function date_text_element_process($element, &$form_state, $form) { if (date_hidden_element($element)) { @@ -316,16 +319,25 @@ function date_text_element_process($element, &$form_state, $form) { $element['#tree'] = TRUE; $element['#theme_wrappers'] = array('date_text'); - $element['date']['#value'] = $element['#value']['date']; + $element['date']['#value'] = isset($element['#value']['date']) ? $element['#value']['date'] : ''; $element['date']['#type'] = 'textfield'; $element['date']['#weight'] = !empty($element['date']['#weight']) ? $element['date']['#weight'] : $element['#weight']; $element['date']['#attributes'] = array('class' => isset($element['#attributes']['class']) ? $element['#attributes']['class'] += array('date-date') : array('date-date')); $now = date_example_date(); $element['date']['#title'] = t('Date'); $element['date']['#title_display'] = 'invisible'; - $element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_format_date(date_example_date(), 'custom', $element['#date_format']))); + $element['date']['#description'] = ' ' . t('Format: @date', array( + '@date' => date_format_date(date_example_date(), 'custom', $element['#date_format'] + ))); $element['date']['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; + // Make changes if instance is set to be rendered as a regular field. + if (!empty($element['#instance']['widget']['settings']['no_fieldset']) && $element['#field']['cardinality'] == 1) { + $element['date']['#title'] = check_plain($element['#instance']['label']); + $element['date']['#title_display'] = $element['#title_display']; + $element['date']['#required'] = $element['#required']; + } + // Keep the system from creating an error message for the sub-element. // We'll set our own message on the parent element. // $element['date']['#required'] = $element['#required']; @@ -341,7 +353,7 @@ function date_text_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_text_process', $element, $form_state, $context); @@ -349,12 +361,11 @@ function date_text_element_process($element, &$form_state, $form) { } /** - * Validation for text input. + * Validation for text input. * * When used as a Views widget, the validation step always gets triggered, * even with no form submission. Before form submission $element['#value'] * contains a string, after submission it contains an array. - * */ function date_text_validate($element, &$form_state) { if (date_hidden_element($element)) { @@ -367,6 +378,11 @@ function date_text_validate($element, &$form_state) { $input_exists = NULL; $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists); + // Trim extra spacing off user input of text fields. + if (isset($input['date'])) { + $input['date'] = trim($input['date']); + } + drupal_alter('date_text_pre_validate', $element, $form_state, $input); $label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : ''); @@ -421,7 +437,14 @@ function date_text_input_date($element, $input) { * Element value callback for date_select element. */ function date_select_element_value_callback($element, $input = FALSE, &$form_state = array()) { - $return = array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => ''); + $return = array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); $date = NULL; if ($input !== FALSE) { $return = $input; @@ -431,7 +454,14 @@ function date_select_element_value_callback($element, $input = FALSE, &$form_sta $date = date_default_date($element); } $granularity = date_format_order($element['#date_format']); - $formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's'); + $formats = array( + 'year' => 'Y', + 'month' => 'n', + 'day' => 'j', + 'hour' => 'H', + 'minute' => 'i', + 'second' => 's', + ); foreach ($granularity as $field) { if ($field != 'timezone') { $return[$field] = date_is_date($date) ? $date->format($formats[$field]) : ''; @@ -449,7 +479,6 @@ function date_select_element_value_callback($element, $input = FALSE, &$form_sta * * The exact parts displayed in the field are those in #date_granularity. * The display of each part comes from ['#date_settings']['format']. - * */ function date_select_element_process($element, &$form_state, $form) { if (date_hidden_element($element)) { @@ -473,7 +502,14 @@ function date_select_element_process($element, &$form_state, $form) { // Store a hidden value for all date parts not in the current display. $granularity = date_format_order($element['#date_format']); - $formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's'); + $formats = array( + 'year' => 'Y', + 'month' => 'n', + 'day' => 'j', + 'hour' => 'H', + 'minute' => 'i', + 'second' => 's', + ); foreach (date_nongranularity($granularity) as $field) { if ($field != 'timezone') { $element[$field] = array( @@ -490,7 +526,7 @@ function date_select_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_select_process', $element, $form_state, $context); @@ -521,7 +557,7 @@ function date_parts_element($element, $date, $format) { $sub_element = array('#granularity' => $granularity); $order = array_flip($granularity); - $hours_format = strpos(strtolower($element['#date_format']), 'a') ? 'g': 'G'; + $hours_format = strpos(strtolower($element['#date_format']), 'a') ? 'g' : 'G'; $month_function = strpos($element['#date_format'], 'F') !== FALSE ? 'date_month_names' : 'date_month_names_abbr'; $count = 0; $increment = min(intval($element['#date_increment']), 1); @@ -539,26 +575,29 @@ function date_parts_element($element, $date, $format) { switch ($field) { case 'year': $range = date_range_years($element['#date_year_range'], $date); - $min_year = $range[0]; - $max_year = $range[1]; + $start_year = $range[0]; + $end_year = $range[1]; $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('Y') : ''; if ($part_type == 'select') { - $sub_element[$field]['#options'] = drupal_map_assoc(date_years($min_year, $max_year, $part_required)); + $sub_element[$field]['#options'] = drupal_map_assoc(date_years($start_year, $end_year, $part_required)); } break; + case 'month': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('n') : ''; if ($part_type == 'select') { $sub_element[$field]['#options'] = $month_function($part_required); } break; + case 'day': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('j') : ''; if ($part_type == 'select') { $sub_element[$field]['#options'] = drupal_map_assoc(date_days($part_required)); } break; + case 'hour': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format($hours_format) : ''; if ($part_type == 'select') { @@ -566,6 +605,7 @@ function date_parts_element($element, $date, $format) { } $sub_element[$field]['#prefix'] = theme('date_part_hour_prefix', $element); break; + case 'minute': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('i') : ''; if ($part_type == 'select') { @@ -573,6 +613,7 @@ function date_parts_element($element, $date, $format) { } $sub_element[$field]['#prefix'] = theme('date_part_minsec_prefix', $element); break; + case 'second': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('s') : ''; if ($part_type == 'select') { diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_ical.inc b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_ical.inc index 2ca484e7..4911298c 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_ical.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_ical.inc @@ -181,6 +181,7 @@ function date_ical_parse($icaldatafolded = array()) { $parent[array_pop($parents)][] = array_pop($subgroups); } break; + // Add the timezones in with their index their TZID. case 'VTIMEZONE': $subgroup = end($subgroups); @@ -196,6 +197,7 @@ function date_ical_parse($icaldatafolded = array()) { array_pop($subgroups); array_pop($parents); break; + // Do some fun stuff with durations and all_day events and then append // to parent. case 'VEVENT': @@ -222,9 +224,9 @@ function date_ical_parse($icaldatafolded = array()) { // assumes the end date is inclusive. if (!empty($subgroup['DTEND']) && (!empty($subgroup['DTEND']['all_day']))) { // Make the end date one day earlier. - $date = new DateObject ($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']); + $date = new DateObject($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']); date_modify($date, '-1 day'); - $subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d'); + $subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d'); } // If a start datetime is defined AND there is no definition for // the end datetime THEN make the end datetime equal the start @@ -239,7 +241,7 @@ function date_ical_parse($icaldatafolded = array()) { if (!empty($subgroup['DTSTART']['all_day'])) { $subgroup['all_day'] = TRUE; } - // Add this element to the parent as an array under the + // Add this element to the parent as an array under the. prev($subgroups); $parent = &$subgroups[key($subgroups)]; @@ -264,12 +266,13 @@ function date_ical_parse($icaldatafolded = array()) { $field = !empty($matches[2]) ? $matches[2] : ''; $data = !empty($matches[3]) ? $matches[3] : ''; $parse_result = ''; + switch ($name) { // Keep blank lines out of the results. case '': break; - // Lots of properties have date values that must be parsed out. + // Lots of properties have date values that must be parsed out. case 'CREATED': case 'LAST-MODIFIED': case 'DTSTART': @@ -317,9 +320,9 @@ function date_ical_parse($icaldatafolded = array()) { $parse_result = date_ical_parse_location($field, $data); break; - // For all other properties, just store the property and the value. - // This can be expanded on in the future if other properties should - // be given special treatment. + // For all other properties, just store the property and the value. + // This can be expanded on in the future if other properties should + // be given special treatment. default: $parse_result = $data; break; @@ -360,7 +363,7 @@ function date_ical_parse($icaldatafolded = array()) { * has no timezone; the ical specs say no timezone * conversion should be done if no timezone info is * supplied - * @todo + * @todo * Another option for dates is the format PROPERTY;VALUE=PERIOD:XXXX. The * period may include a duration, or a date and a duration, or two dates, so * would have to be split into parts and run through date_ical_parse_date() @@ -401,6 +404,7 @@ function date_ical_parse_date($field, $data) { // Date. $datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]); break; + case 'DATE-TIME': preg_match(DATE_REGEX_ICAL_DATETIME, $data, $regs); // Date. @@ -519,12 +523,12 @@ function date_ical_parse_duration(&$subgroup, $field = 'DURATION') { $data = $items['DATA']; preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $data, $duration); $items['year'] = isset($duration[1]) ? str_replace('Y', '', $duration[1]) : ''; - $items['month'] = isset($duration[2]) ?str_replace('M', '', $duration[2]) : ''; - $items['week'] = isset($duration[3]) ?str_replace('W', '', $duration[3]) : ''; - $items['day'] = isset($duration[4]) ?str_replace('D', '', $duration[4]) : ''; - $items['hour'] = isset($duration[6]) ?str_replace('H', '', $duration[6]) : ''; - $items['minute'] = isset($duration[7]) ?str_replace('M', '', $duration[7]) : ''; - $items['second'] = isset($duration[8]) ?str_replace('S', '', $duration[8]) : ''; + $items['month'] = isset($duration[2]) ? str_replace('M', '', $duration[2]) : ''; + $items['week'] = isset($duration[3]) ? str_replace('W', '', $duration[3]) : ''; + $items['day'] = isset($duration[4]) ? str_replace('D', '', $duration[4]) : ''; + $items['hour'] = isset($duration[6]) ? str_replace('H', '', $duration[6]) : ''; + $items['minute'] = isset($duration[7]) ? str_replace('M', '', $duration[7]) : ''; + $items['second'] = isset($duration[8]) ? str_replace('S', '', $duration[8]) : ''; $start_date = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['datetime'] : date_format(date_now(), DATE_FORMAT_ISO); $timezone = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['tz'] : variable_get('date_default_timezone'); if (empty($timezone)) { @@ -542,7 +546,7 @@ function date_ical_parse_duration(&$subgroup, $field = 'DURATION') { 'datetime' => date_format($date2, DATE_FORMAT_DATETIME), 'all_day' => isset($subgroup['DTSTART']['all_day']) ? $subgroup['DTSTART']['all_day'] : 0, 'tz' => $timezone, - ); + ); $duration = date_format($date2, 'U') - date_format($date, 'U'); $subgroup['DURATION'] = array('DATA' => $data, 'DURATION' => $duration); } @@ -631,7 +635,6 @@ function date_ical_date($ical_date, $to_tz = FALSE) { * * @return string * Escaped text - * */ function date_ical_escape_text($text) { $text = drupal_html_to_text($text); @@ -693,14 +696,14 @@ function date_ical_escape_text($text) { * ) */ function date_api_ical_build_rrule($form_values) { - $RRULE = ''; + $rrule = ''; if (empty($form_values) || !is_array($form_values)) { - return $RRULE; + return $rrule; } // Grab the RRULE data and put them into iCal RRULE format. - $RRULE .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']); - $RRULE .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']); + $rrule .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']); + $rrule .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']); // Unset the empty 'All' values. if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) { @@ -713,14 +716,14 @@ function date_api_ical_build_rrule($form_values) { unset($form_values['BYMONTHDAY']['']); } - if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY']) && $BYDAY = implode(",", $form_values['BYDAY'])) { - $RRULE .= ';BYDAY=' . $BYDAY; + if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY']) && $byday = implode(",", $form_values['BYDAY'])) { + $rrule .= ';BYDAY=' . $byday; } - if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH']) && $BYMONTH = implode(",", $form_values['BYMONTH'])) { - $RRULE .= ';BYMONTH=' . $BYMONTH; + if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH']) && $bymonth = implode(",", $form_values['BYMONTH'])) { + $rrule .= ';BYMONTH=' . $bymonth; } - if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY']) && $BYMONTHDAY = implode(",", $form_values['BYMONTHDAY'])) { - $RRULE .= ';BYMONTHDAY=' . $BYMONTHDAY; + if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY']) && $bymonthday = implode(",", $form_values['BYMONTHDAY'])) { + $rrule .= ';BYMONTHDAY=' . $bymonthday; } // The UNTIL date is supposed to always be expressed in UTC. // The input date values may already have been converted to a date object on a @@ -731,8 +734,17 @@ function date_api_ical_build_rrule($form_values) { if (!is_object($form_values['UNTIL']['datetime'])) { // If this is a date without time, give it time. if (strlen($form_values['UNTIL']['datetime']) < 11) { + $granularity_options = drupal_map_assoc(array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + )); + $form_values['UNTIL']['datetime'] .= ' 23:59:59'; - $form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); + $form_values['UNTIL']['granularity'] = serialize($granularity_options); $form_values['UNTIL']['all_day'] = FALSE; } $until = date_ical_date($form_values['UNTIL'], 'UTC'); @@ -740,21 +752,21 @@ function date_api_ical_build_rrule($form_values) { else { $until = $form_values['UNTIL']['datetime']; } - $RRULE .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z'; + $rrule .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z'; } // Our form doesn't allow a value for COUNT, but it may be needed by // modules using the API, so add it to the rule. if (array_key_exists('COUNT', $form_values)) { - $RRULE .= ';COUNT=' . $form_values['COUNT']; + $rrule .= ';COUNT=' . $form_values['COUNT']; } // iCal rules presume the week starts on Monday unless otherwise specified, // so we'll specify it. if (array_key_exists('WKST', $form_values)) { - $RRULE .= ';WKST=' . $form_values['WKST']; + $rrule .= ';WKST=' . $form_values['WKST']; } else { - $RRULE .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 0)); + $rrule .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 0)); } // Exceptions dates go last, on their own line. @@ -765,7 +777,7 @@ function date_api_ical_build_rrule($form_values) { foreach ($form_values['EXDATE'] as $value) { if (!empty($value['datetime'])) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z' : ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } @@ -773,11 +785,11 @@ function date_api_ical_build_rrule($form_values) { } if (!empty($ex_dates)) { sort($ex_dates); - $RRULE .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates); + $rrule .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates); } } elseif (!empty($form_values['EXDATE'])) { - $RRULE .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE']; + $rrule .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE']; } // Exceptions dates go last, on their own line. @@ -785,19 +797,19 @@ function date_api_ical_build_rrule($form_values) { $ex_dates = array(); foreach ($form_values['RDATE'] as $value) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z' : ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } } if (!empty($ex_dates)) { sort($ex_dates); - $RRULE .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates); + $rrule .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates); } } elseif (!empty($form_values['RDATE'])) { - $RRULE .= chr(13) . chr(10) . 'RDATE:' . $form_values['RDATE']; + $rrule .= chr(13) . chr(10) . 'RDATE:' . $form_values['RDATE']; } - return $RRULE; + return $rrule; } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_sql.inc b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_sql.inc index e02f0507..a95ca4ef 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_sql.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/date_api_sql.inc @@ -23,13 +23,14 @@ function date_sql_concat($array) { switch (Database::getConnection()->databaseType()) { case 'mysql': return "CONCAT(" . implode(",", $array) . ")"; + case 'pgsql': return implode(" || ", $array); } } /** - * Helper function to do cross-database NULL replacements + * Helper function to do cross-database NULL replacements. * * @param array $array * An array of values to test for NULL values. @@ -61,6 +62,7 @@ function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') { switch ($side) { case 'r': return "RPAD($str, $size, '$pad')"; + default: return "LPAD($str, $size, '$pad')"; } @@ -69,6 +71,7 @@ function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') { /** * A class to manipulate date SQL. */ +// @codingStandardsIgnoreStart class date_sql_handler { var $db_type = NULL; var $date_type = DATE_DATETIME; @@ -86,7 +89,7 @@ class date_sql_handler { /** * The object constuctor. */ - function __construct($date_type = DATE_DATETIME, $local_timezone = NULL, $offset = '+00:00') { + public function __construct($date_type = DATE_DATETIME, $local_timezone = NULL, $offset = '+00:00') { $this->db_type = Database::getConnection()->databaseType(); $this->date_type = $date_type; $this->db_timezone = 'UTC'; @@ -97,7 +100,7 @@ class date_sql_handler { /** * See if the db has timezone name support. */ - function db_tz_support($reset = FALSE) { + public function db_tz_support($reset = FALSE) { $has_support = variable_get('date_db_tz_support', -1); if ($has_support == -1 || $reset) { $has_support = FALSE; @@ -108,6 +111,7 @@ class date_sql_handler { $has_support = TRUE; } break; + case 'pgsql': $test = db_query("SELECT '2008-02-15 12:00:00 UTC' AT TIME ZONE 'US/Central'")->fetchField(); if ($test == '2008-02-15 06:00:00') { @@ -136,7 +140,7 @@ class date_sql_handler { * set a fixed offset, not a timezone, so any value other than * '+00:00' should be used with caution. */ - function set_db_timezone($offset = '+00:00') { + public function set_db_timezone($offset = '+00:00') { static $already_set = FALSE; $type = Database::getConnection()->databaseType(); if (!$already_set) { @@ -144,9 +148,11 @@ class date_sql_handler { case 'mysql': db_query("SET @@session.time_zone = '$offset'"); break; + case 'pgsql': db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE"); break; + case 'sqlsrv': // Issue #1201342, This is the wrong way to set the timezone, this // still needs to be fixed. In the meantime, commenting this out makes @@ -161,7 +167,7 @@ class date_sql_handler { /** * Return timezone offset for the date being processed. */ - function get_offset($comp_date = NULL) { + public function get_offset($comp_date = NULL) { if (!empty($this->db_timezone) && !empty($this->local_timezone)) { if ($this->db_timezone != $this->local_timezone) { if (empty($comp_date)) { @@ -199,47 +205,57 @@ class date_sql_handler { case DATE_UNIX: $field = "FROM_UNIXTIME($field)"; break; + case DATE_ISO: $field = "STR_TO_DATE($field, '%Y-%m-%dT%T')"; break; + case DATE_DATETIME: break; } break; + case 'pgsql': switch ($this->date_type) { case DATE_UNIX: $field = "$field::ABSTIME"; break; + case DATE_ISO: $field = "TO_DATE($field, 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS')"; break; + case DATE_DATETIME: break; } break; + case 'sqlite': switch ($this->date_type) { case DATE_UNIX: $field = "datetime($field, 'unixepoch')"; break; + case DATE_ISO: case DATE_DATETIME: $field = "datetime($field)"; break; } break; + case 'sqlsrv': switch ($this->date_type) { case DATE_UNIX: $field = "DATEADD(s, $field, '19700101 00:00:00:000')"; break; + case DATE_ISO: case DATE_DATETIME: $field = "CAST($field as smalldatetime)"; break; } break; + break; } // Adjust the resulting value to the right timezone/offset. @@ -254,10 +270,13 @@ class date_sql_handler { switch ($this->db_type) { case 'mysql': return "ADDTIME($field, SEC_TO_TIME($offset))"; + case 'pgsql': - return "($field + INTERVAL '$offset SECONDS')";; + return "($field + INTERVAL '$offset SECONDS')"; + case 'sqlite': return "datetime($field, '$offset seconds')"; + case 'sqlsrv': return "DATEADD(second, $offset, $field)"; } @@ -285,6 +304,7 @@ class date_sql_handler { switch ($direction) { case 'ADD': return "DATE_ADD($field, INTERVAL $count $granularity)"; + case 'SUB': return "DATE_SUB($field, INTERVAL $count $granularity)"; } @@ -294,6 +314,7 @@ class date_sql_handler { switch ($direction) { case 'ADD': return "($field + INTERVAL '$count $granularity')"; + case 'SUB': return "($field - INTERVAL '$count $granularity')"; } @@ -302,6 +323,7 @@ class date_sql_handler { switch ($direction) { case 'ADD': return "datetime($field, '+$count $granularity')"; + case 'SUB': return "datetime($field, '-$count $granularity')"; } @@ -352,6 +374,7 @@ class date_sql_handler { switch ($this->db_type) { case 'mysql': return "CONVERT_TZ($field, $db_zone, $localzone)"; + case 'pgsql': // WITH TIME ZONE assumes the date is using the system // timezone, which should have been set to UTC. @@ -395,6 +418,7 @@ class date_sql_handler { ); $format = strtr($format, $replace); return "DATE_FORMAT($field, '$format')"; + case 'pgsql': $replace = array( 'Y' => 'YYYY', @@ -421,6 +445,7 @@ class date_sql_handler { ); $format = strtr($format, $replace); return "TO_CHAR($field, '$format')"; + case 'sqlite': $replace = array( // 4 digit year number. @@ -460,6 +485,7 @@ class date_sql_handler { ); $format = strtr($format, $replace); return "strftime('$format', $field)"; + case 'sqlsrv': $replace = array( // 4 digit year number. @@ -528,18 +554,25 @@ class date_sql_handler { switch (strtoupper($extract_type)) { case 'DATE': return $field; + case 'YEAR': return "EXTRACT(YEAR FROM($field))"; + case 'MONTH': return "EXTRACT(MONTH FROM($field))"; + case 'DAY': return "EXTRACT(DAY FROM($field))"; + case 'HOUR': return "EXTRACT(HOUR FROM($field))"; + case 'MINUTE': return "EXTRACT(MINUTE FROM($field))"; + case 'SECOND': return "EXTRACT(SECOND FROM($field))"; + // ISO week number for date. case 'WEEK': switch ($this->db_type) { @@ -547,6 +580,7 @@ class date_sql_handler { // WEEK using arg 3 in MySQl should return the same value as // Postgres EXTRACT. return "WEEK($field, 3)"; + case 'pgsql': return "EXTRACT(WEEK FROM($field))"; } @@ -556,6 +590,7 @@ class date_sql_handler { // MySQL returns 1 for Sunday through 7 for Saturday, PHP date // functions and Postgres use 0 for Sunday and 6 for Saturday. return "INTEGER(DAYOFWEEK($field) - 1)"; + case 'pgsql': return "EXTRACT(DOW FROM($field))"; } @@ -563,6 +598,7 @@ class date_sql_handler { switch ($this->db_type) { case 'mysql': return "DAYOFYEAR($field)"; + case 'pgsql': return "EXTRACT(DOY FROM($field))"; } @@ -775,8 +811,7 @@ class date_sql_handler { } /** - * Create a complete datetime value out of an - * incomplete array of selected values. + * Create a complete date/time value out of an incomplete array of values. * * For example, array('year' => 2008, 'month' => 05) will fill * in the day, hour, minute and second with the earliest possible @@ -795,9 +830,11 @@ class date_sql_handler { case 'empty_min': case 'min': return date_format($dates[0], 'Y-m-d H:i:s'); + case 'empty_max': case 'max': return date_format($dates[1], 'Y-m-d H:i:s'); + default: return; } @@ -840,7 +877,7 @@ class date_sql_handler { } /** - * A function to test the validity of various date parts + * A function to test the validity of various date parts. */ function part_is_valid($value, $type) { if (!preg_match('/^[0-9]*$/', $value)) { @@ -856,16 +893,19 @@ class date_sql_handler { return FALSE; } break; + case 'month': if ($value < 0 || $value > 12) { return FALSE; } break; + case 'day': if ($value < 0 || $value > 31) { return FALSE; } break; + case 'week': if ($value < 0 || $value > 53) { return FALSE; @@ -890,26 +930,36 @@ class date_sql_handler { $formats['display'] = 'Y'; $formats['sql'] = 'Y'; break; + case 'month': $formats['display'] = date_limit_format($short, array('year', 'month')); $formats['sql'] = 'Y-m'; break; + case 'day': - $formats['display'] = date_limit_format($short, array('year', 'month', 'day')); + $args = array('year', 'month', 'day'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d'; break; + case 'hour': - $formats['display'] = date_limit_format($short, array('year', 'month', 'day', 'hour')); + $args = array('year', 'month', 'day', 'hour'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d\TH'; break; + case 'minute': - $formats['display'] = date_limit_format($short, array('year', 'month', 'day', 'hour', 'minute')); + $args = array('year', 'month', 'day', 'hour', 'minute'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d\TH:i'; break; + case 'second': - $formats['display'] = date_limit_format($short, array('year', 'month', 'day', 'hour', 'minute', 'second')); + $args = array('year', 'month', 'day', 'hour', 'minute', 'second'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d\TH:i:s'; break; + case 'week': $formats['display'] = 'F j Y (W)'; $formats['sql'] = 'Y-\WW'; @@ -927,7 +977,7 @@ class date_sql_handler { '#type' => 'radios', '#default_value' => $granularity, '#options' => $this->date_parts(), - ); + ); return $form; } @@ -1030,7 +1080,6 @@ class date_sql_handler { $direction = $results[1]; $count = $results[2]; $item = $results[3]; - $replace = array( 'now' => '@', '+' => 'P', @@ -1051,14 +1100,27 @@ class date_sql_handler { 'second' => 'S', ' ' => '', ' ' => '', - ); - $prefix = in_array($item, array('hours', 'hour', 'minutes', 'minute', 'seconds', 'second')) ? 'T' : ''; - return $prefix . strtr($direction, $replace) . $count . strtr($item, $replace); + ); + $args = array('hours', 'hour', 'minutes', 'minute', 'seconds', 'second'); + if (in_array($item, $args)) { + $prefix = 'T'; + } + else { + $prefix = ''; + } + $return = $prefix; + $return .= strtr($direction, $replace); + $return .= $count; + $return .= strtr($item, $replace); + + return $return; } /** - * Use the parsed values from the ISO argument to determine the - * granularity of this period. + * Granularity arguments handler. + * + * Use the parsed values from the ISO argument + * to determine the granularity of this period. */ function arg_granularity($arg) { $granularity = ''; @@ -1137,8 +1199,9 @@ class date_sql_handler { } return array($min_date, $max_date); } - // Intercept invalid info and fall back to the current date. + // Intercept invalid info and fall back to the current date. $now = date_now(); return array($now, $now); } } +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_api/theme/theme.inc b/profiles/commerce_kickstart/modules/contrib/date/date_api/theme/theme.inc index 032e3e93..0a57ecd0 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_api/theme/theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_api/theme/theme.inc @@ -194,6 +194,7 @@ function theme_date_calendar_day($variables) { function theme_date_time_ago($variables) { $start_date = $variables['start_date']; $end_date = $variables['end_date']; + $use_end_date = isset($variables['use_end_date']) ? $variables['use_end_date'] : false; $interval = !empty($variables['interval']) ? $variables['interval'] : 2; $display = isset($variables['interval_display']) ? $variables['interval_display'] : 'time ago'; @@ -202,28 +203,43 @@ function theme_date_time_ago($variables) { return; } + // We use the end date only when the option is checked. + if ($use_end_date){ + $date = date_format($end_date, DATE_FORMAT_UNIX); + } + else { + $date = date_format($start_date, DATE_FORMAT_UNIX); + } + // Time to compare dates to. + $now = date_format(date_now(), DATE_FORMAT_UNIX); - $start = date_format($start_date, DATE_FORMAT_UNIX); - // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence) - $time_diff = $now - $start; + // Will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence). + $time_diff = $now - $date; // Uses the same options used by Views format_interval. switch ($display) { case 'raw time ago': return format_interval($time_diff, $interval); + case 'time ago': return t('%time ago', array('%time' => format_interval($time_diff, $interval))); + case 'raw time hence': return format_interval(-$time_diff, $interval); + case 'time hence': return t('%time hence', array('%time' => format_interval(-$time_diff, $interval))); + case 'raw time span': return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), $interval); + case 'inverse time span': return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), $interval); + case 'time span': return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), $interval))); + } } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.info b/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.info index 1d8a551d..97cb8c9d 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.info @@ -8,9 +8,9 @@ dependencies[] = context files[] = date_context.module files[] = plugins/date_context_date_condition.inc -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.module b/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.module index 44e975ac..9b568e15 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_context/date_context.module @@ -1,5 +1,8 @@ execute() @@ -8,8 +11,6 @@ * Cache the date processing, perhaps cache the formatted, timezone-adjusted * date strings for each entity (would have to be cached differently for each * timezone, based on the tz_handling method for the date). - * - * Add an option to set/not set the context on forms vs views. */ /** @@ -22,7 +23,7 @@ function date_context_context_node_condition_alter($node, $op) { } /** - * Implements hook_context_plugins() + * Implements hook_context_plugins(). */ function date_context_context_plugins() { $plugins = array(); @@ -38,7 +39,7 @@ function date_context_context_plugins() { } /** - * Implements hook_context_registry() + * Implements hook_context_registry(). */ function date_context_context_registry() { return array( @@ -51,4 +52,3 @@ function date_context_context_registry() { ), ); } - diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_context/plugins/date_context_date_condition.inc b/profiles/commerce_kickstart/modules/contrib/date/date_context/plugins/date_context_date_condition.inc index 573b0354..97c18b47 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_context/plugins/date_context_date_condition.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_context/plugins/date_context_date_condition.inc @@ -1,10 +1,20 @@ $field) { @@ -15,10 +25,13 @@ class date_context_date_condition extends context_condition_node { return $values; } - function options_form($context) { + /** + * {@inheritdoc} + */ + public function options_form($context) { $defaults = $this->fetch_from_context($context, 'options'); $options = array( - '<' => t('Is less than'), + '<' => t('Is less than'), '<=' => t('Is less than or equal to'), '>=' => t('Is greater than or equal to'), '>' => t('Is greater than'), @@ -27,6 +40,8 @@ class date_context_date_condition extends context_condition_node { 'empty' => t('Is empty'), 'not empty' => t('Is not Empty'), ); + $dependency_options = array('<', '<=', '>', '>=', '=', '!='); + $form['operation'] = array( '#title' => t('Operation'), '#type' => 'select', @@ -41,12 +56,15 @@ class date_context_date_condition extends context_condition_node { '#description' => t("The value the field should contain to meet the condition. This can either be an absolute date in ISO format (YYYY-MM-DDTHH:MM:SS) or a relative string like '12AM today'. Examples: 2011-12-31T00:00:00, now, now +1 day, 12AM today, Monday next week. More examples of relative date formats in the PHP documentation.", array('@relative_format' => 'http://www.php.net/manual/en/datetime.formats.relative.php')), '#default_value' => isset($defaults['value']) ? $defaults['value'] : '', '#process' => array('ctools_dependent_process'), - '#dependency' => array('edit-conditions-plugins-date-context-date-condition-options-operation' => array('<', '<=', '>', '>=', '=', '!=')), + '#dependency' => array('edit-conditions-plugins-date-context-date-condition-options-operation' => $dependency_options), ); return $form; } - function execute($entity, $op) { + /** + * {@inheritdoc} + */ + public function execute($entity, $op) { if (in_array($op, array('view', 'form'))) { foreach ($this->get_contexts() as $context) { $options = $this->fetch_from_context($context, 'options'); @@ -91,32 +109,37 @@ class date_context_date_condition extends context_condition_node { str_replace('now', 'today', $options['value']); $date = date_create($options['value'], date_default_timezone_object()); $compdate = $date->format(DATE_FORMAT_DATETIME); - switch($options['operation']) { + switch ($options['operation']) { case '=': if ($date2 >= $compdate && $date1 <= $compdate) { $this->condition_met($context, $field_name); } break; + case '>': if ($date1 > $compdate) { $this->condition_met($context, $field_name); } break; + case '>=': if ($date1 >= $compdate) { $this->condition_met($context, $field_name); } break; + case '<': if ($date2 < $compdate) { $this->condition_met($context, $field_name); } break; + case '<=': if ($date2 <= $compdate) { $this->condition_met($context, $field_name); } break; + case '!=': if ($date1 < $compdate || $date2 > $compdate) { $this->condition_met($context, $field_name); @@ -130,3 +153,4 @@ class date_context_date_condition extends context_condition_node { } } } +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_elements.inc b/profiles/commerce_kickstart/modules/contrib/date/date_elements.inc index 6908d967..656d4998 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_elements.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_elements.inc @@ -40,7 +40,6 @@ * * - In the field's submission processing, the new date values, which are in * the local timezone, are converted back to their UTC values and stored. - * */ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $base) { @@ -75,7 +74,7 @@ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langco // The repeating values will be re-generated when the repeat widget form is validated. // At this point we can't tell if this form element is going to be hidden by #access, and we're going to // lose all but the first value by doing this, so store the original values in case we need to replace them later. - if (!empty($field['settings']['repeat'])) { + if (!empty($field['settings']['repeat']) && module_exists('date_repeat_field')) { if ($delta == 0) { $form['#after_build'][] = 'date_repeat_after_build'; $form_state['storage']['repeat_fields'][$field_name] = array_merge($form['#parents'], array($field_name)); @@ -87,7 +86,7 @@ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langco } module_load_include('inc', 'date_api', 'date_api_elements'); - $timezone = date_get_timezone($field['settings']['tz_handling'], isset($items[0]['timezone']) ? $items[0]['timezone'] : date_default_timezone()); + $timezone = date_get_timezone($field['settings']['tz_handling'], isset($items[$delta]['timezone']) ? $items[$delta]['timezone'] : date_default_timezone()); // TODO see if there's a way to keep the timezone element from ever being // nested as array('timezone' => 'timezone' => value)). After struggling @@ -122,7 +121,13 @@ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langco '#weight' => $instance['widget']['weight'] + 1, '#attributes' => array('class' => array('date-no-float')), '#date_label_position' => $instance['widget']['settings']['label_position'], - ); + ); + } + + // Make changes if instance is set to be rendered as a regular field. + if (!empty($instance['widget']['settings']['no_fieldset'])) { + $element['#title'] = check_plain($instance['label']); + $element['#theme_wrappers'] = ($field['cardinality'] == 1) ? array('date_form_element') : array(); } return $element; @@ -148,6 +153,7 @@ function date_local_date($item, $timezone, $field, $instance, $part = 'value') { // @TODO Figure out how to replace date_fuzzy_datetime() function. // Special case for ISO dates to create a valid date object for formatting. // Is this still needed? + // @codingStandardsIgnoreStart /* if ($field['type'] == DATE_ISO) { $value = date_fuzzy_datetime($value); @@ -157,6 +163,7 @@ function date_local_date($item, $timezone, $field, $instance, $part = 'value') { $value = date_convert($value, $field['type'], DATE_DATETIME, $db_timezone); } */ + // @codingStandardsIgnoreEnd $date = new DateObject($value, date_get_timezone_db($field['settings']['tz_handling'])); $date->limitGranularity($field['settings']['granularity']); @@ -193,8 +200,7 @@ function date_default_value($field, $instance, $langcode) { } /** - * Helper function for the date default value callback to set - * either 'value' or 'value2' to its default value. + * Helper function for the date default value callback to set either 'value' or 'value2' to its default value. */ function date_default_value_part($item, $field, $instance, $langcode, $part = 'value') { $timezone = date_get_timezone($field['settings']['tz_handling']); @@ -241,7 +247,6 @@ function date_default_value_part($item, $field, $instance, $langcode, $part = 'v * Process an individual date element. */ function date_combo_element_process($element, &$form_state, $form) { - if (date_hidden_element($element)) { // A hidden value for a new entity that had its end date set to blank // will not get processed later to populate the end date, so set it here. @@ -296,6 +301,7 @@ function date_combo_element_process($element, &$form_state, $form) { // Blank out the end date for optional end dates that match the start date, // except when this is a new node that has default values that should be honored. if (!$date_is_default && $field['settings']['todate'] != 'required' + && is_array($element['#default_value']) && !empty($element['#default_value'][$to_field]) && $element['#default_value'][$to_field] == $element['#default_value'][$from_field]) { unset($element['#default_value'][$to_field]); @@ -329,10 +335,13 @@ function date_combo_element_process($element, &$form_state, $form) { '#date_increment' => $instance['widget']['settings']['increment'], '#date_year_range' => $instance['widget']['settings']['year_range'], '#date_label_position' => $instance['widget']['settings']['label_position'], - ); + ); - $description = !empty($element['#description']) ? t($element['#description']) : ''; - unset($element['#description']); + // Date repeat is a multiple value field. So the description is removed from + // the single element earlier. Let's get it back. + if (isset($element['show_repeat_settings']) && !empty($element['value']['#instance']['description'])) { + $element['#description'] = $element['value']['#instance']['description']; + } // Give this element the right type, using a Date API // or a Date Popup element type. @@ -347,11 +356,13 @@ function date_combo_element_process($element, &$form_state, $form) { $element['#attached']['js'][] = drupal_get_path('module', 'date') . '/date.js'; $element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; break; + case 'date_popup': $element[$from_field]['#type'] = 'date_popup'; $element[$from_field]['#theme_wrappers'] = array('date_popup'); $element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; break; + default: $element[$from_field]['#type'] = 'date_text'; $element[$from_field]['#theme_wrappers'] = array('date_text'); @@ -375,18 +386,17 @@ function date_combo_element_process($element, &$form_state, $form) { $element[$to_field]['#prefix'] = ''; // Users with JS enabled will never see initially blank values for the end // date (see Drupal.date.EndDateHandler()), so hide the message for them. - $description .= ' ' . t("Empty 'End date' values will use the 'Start date' values.") . ''; - $element['#fieldset_description'] = $description; + $element['#description'] .= ' ' . t("Empty 'End date' values will use the 'Start date' values.") . ''; if ($field['settings']['todate'] == 'optional') { $element[$to_field]['#states'] = array( 'visible' => array( - 'input[name="' . $show_id . '"]' => array('checked' => TRUE), - )); + 'input[name="' . $show_id . '"]' => array( + 'checked' => TRUE, + ), + ), + ); } } - else { - $element[$from_field]['#description'] = $description; - } // Create label for error messages that make sense in multiple values // and when the title field is left blank. @@ -404,16 +414,27 @@ function date_combo_element_process($element, &$form_state, $form) { $element[$from_field]['#date_title'] = t('@field_name', array('@field_name' => $instance['label'])); } + // Make changes if instance is set to be rendered as a regular field. + if (!empty($instance['widget']['settings']['no_fieldset'])) { + unset($element[$from_field]['#description']); + if (!empty($field['settings']['todate']) && isset($element['#description'])) { + $element['#description'] .= ' ' . t("Empty 'End date' values will use the 'Start date' values.") . ''; + } + } + $context = array( - 'field' => $field, - 'instance' => $instance, - 'form' => $form, + 'field' => $field, + 'instance' => $instance, + 'form' => $form, ); drupal_alter('date_combo_process', $element, $form_state, $context); return $element; } +/** + * Empty a date element. + */ function date_element_empty($element, &$form_state) { $item = array(); $item['value'] = NULL; @@ -428,6 +449,7 @@ function date_element_empty($element, &$form_state) { /** * Validate and update a combo element. + * * Don't try this if there were errors before reaching this point. */ function date_combo_validate($element, &$form_state) { @@ -444,9 +466,19 @@ function date_combo_validate($element, &$form_state) { $delta = $element['#delta']; $langcode = $element['#language']; + // Related issue: https://drupal.org/node/2279831. + if (!is_array($element['#field_parents'])) { + $element['#field_parents'] = array(); + } $form_values = drupal_array_get_nested_value($form_state['values'], $element['#field_parents']); $form_input = drupal_array_get_nested_value($form_state['input'], $element['#field_parents']); + // Programmatically calling drupal_submit_form() does not always add the date + // combo to $form_state['input']. + if (empty($form_input[$field_name]) && !empty($form_values[$field_name])) { + form_set_value($element, $element['#date_items'], $form_state); + return; + } // If the whole field is empty and that's OK, stop now. if (empty($form_input[$field_name]) && !$element['#required']) { return; @@ -519,11 +551,7 @@ function date_combo_validate($element, &$form_state) { return; } } - // Don't look for further errors if errors are already flagged - // because otherwise we'll show errors on the nested elements - // more than once. - elseif (!form_get_errors()) { - + else { $timezone = !empty($item[$tz_field]) ? $item[$tz_field] : $element['#date_timezone']; $timezone_db = date_get_timezone_db($field['settings']['tz_handling']); $element[$from_field]['#date_timezone'] = $timezone; @@ -598,7 +626,10 @@ function date_combo_validate($element, &$form_state) { } } } - if (!empty($errors)) { + // Don't show further errors if errors are already flagged + // because otherwise we'll show errors on the nested elements + // more than once. + if (!form_get_errors() && !empty($errors)) { if ($field['cardinality']) { form_error($element, t('There are errors in @field_name value #@delta:', array('@field_name' => $instance['label'], '@delta' => $delta + 1)) . theme('item_list', array('items' => $errors))); } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate.info b/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate.info index 69012711..41e416a3 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate.info @@ -4,9 +4,9 @@ core = 7.x package = Date/Time hidden = TRUE -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info b/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info index 7223b763..ba07220d 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info @@ -20,9 +20,9 @@ package = "Features" project = "date_migrate_example" version = "7.x-2.0" -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc b/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc index e11dea66..216e1c54 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc @@ -47,8 +47,8 @@ class DateExampleMigration extends XMLMigration { $xml_folder = drupal_get_path('module', 'date_migrate_example'); $items_url = $xml_folder . '/date_migrate_example.xml'; $item_xpath = '/source_data/item'; - $item_ID_xpath = 'id'; - $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath); + $item_id_xpath = 'id'; + $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_id_xpath); $this->source = new MigrateSourceMultiItems($items_class, $fields); $this->destination = new MigrateDestinationNode('date_migrate_example'); @@ -78,7 +78,7 @@ class DateExampleMigration extends XMLMigration { $this->addFieldMapping('field_datestamp_range:to', 'datestamp_range_to'); // You can specify a timezone to be applied to all values going into the - // field (Tokyo is UTC+9, no DST) + // field (Tokyo is UTC+9, no DST). $this->addFieldMapping('field_datetime', 'datetime') ->xpath('datetime'); $this->addFieldMapping('field_datetime:timezone') @@ -107,25 +107,25 @@ class DateExampleMigration extends XMLMigration { // The date range field can have multiple values. $current_row->date_range_from = array(); foreach ($current_row->xml->date_range as $range) { - $current_row->date_range_from[] = (string)$range->from[0]; - $current_row->date_range_to[] = (string)$range->to[0]; + $current_row->date_range_from[] = (string) $range->from[0]; + $current_row->date_range_to[] = (string) $range->to[0]; } - $current_row->datestamp_range_from = - (string) $current_row->xml->datestamp_range->from[0]; - $current_row->datestamp_range_to = - (string) $current_row->xml->datestamp_range->to[0]; - - $current_row->datetime_range_from = - (string) $current_row->xml->datetime_range->from[0]; - $current_row->datetime_range_to = - (string) $current_row->xml->datetime_range->to[0]; - $current_row->datetime_range_timezone = - (string) $current_row->xml->datetime_range->timezone[0]; - - $current_row->date_repeat = - (string) $current_row->xml->date_repeat->date[0]; - $current_row->date_repeat_rrule = - (string) $current_row->xml->date_repeat->rule[0]; + $current_row->datestamp_range_from + = (string) $current_row->xml->datestamp_range->from[0]; + $current_row->datestamp_range_to + = (string) $current_row->xml->datestamp_range->to[0]; + + $current_row->datetime_range_from + = (string) $current_row->xml->datetime_range->from[0]; + $current_row->datetime_range_to + = (string) $current_row->xml->datetime_range->to[0]; + $current_row->datetime_range_timezone + = (string) $current_row->xml->datetime_range->timezone[0]; + + $current_row->date_repeat + = (string) $current_row->xml->date_repeat->date[0]; + $current_row->date_repeat_rrule + = (string) $current_row->xml->date_repeat->rule[0]; } } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.info b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.info index 271e1c68..41945a63 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.info @@ -7,9 +7,9 @@ configure = admin/config/date/date_popup stylesheets[all][] = themes/datepicker.1.7.css -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.install b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.install index 790a514a..e13a4f5a 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.install +++ b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.install @@ -5,6 +5,7 @@ * Install, update and uninstall functions for the Date Popup module. */ +// @codingStandardsIgnoreStart /** * Implements hook_install(). */ @@ -17,6 +18,7 @@ function date_popup_install() { function date_popup_uninstall() { } +// @codingStandardsIgnoreEnd /** * Implements hook_enable(). diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.js b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.js index 1847f84f..f38dcb59 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.js +++ b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.js @@ -1,62 +1,73 @@ /** * Attaches the calendar behavior to all required fields */ -(function ($) { -Drupal.behaviors.date_popup = { - attach: function (context) { - for (var id in Drupal.settings.datePopup) { - $('#'+ id).bind('focus', Drupal.settings.datePopup[id], function(e) { - if (!$(this).hasClass('date-popup-init')) { - var datePopup = e.data; - // Explicitely filter the methods we accept. - switch (datePopup.func) { - case 'datepicker': - $(this) - .datepicker(datePopup.settings) - .addClass('date-popup-init') - $(this).click(function(){ - $(this).focus(); +(function($) { + function makeFocusHandler(e) { + if (!$(this).hasClass('date-popup-init')) { + var datePopup = e.data; + // Explicitely filter the methods we accept. + switch (datePopup.func) { + case 'datepicker': + $(this) + .datepicker(datePopup.settings) + .addClass('date-popup-init'); + $(this).click(function(){ + $(this).focus(); + }); + if (datePopup.settings.syncEndDate) { + $('.start-date-wrapper').each(function(){ + var start_date_wrapper = this; + $(this).find('input:eq(0)').change(function(){ + $(start_date_wrapper).next('.end-date-wrapper').find('input:eq(0)').val($(this).val()); + }); }); - break; + } + break; - case 'timeEntry': - $(this) - .timeEntry(datePopup.settings) - .addClass('date-popup-init') - $(this).click(function(){ - $(this).focus(); - }); - break; - case 'timepicker': - // Translate the PHP date format into the style the timepicker uses. - datePopup.settings.timeFormat = datePopup.settings.timeFormat - // 12-hour, leading zero, - .replace('h', 'hh') - // 12-hour, no leading zero. - .replace('g', 'h') - // 24-hour, leading zero. - .replace('H', 'HH') - // 24-hour, no leading zero. - .replace('G', 'H') - // AM/PM. - .replace('A', 'p') - // Minutes with leading zero. - .replace('i', 'mm') - // Seconds with leading zero. - .replace('s', 'ss'); + case 'timeEntry': + $(this) + .timeEntry(datePopup.settings) + .addClass('date-popup-init'); + $(this).click(function(){ + $(this).focus(); + }); + break; - datePopup.settings.startTime = new Date(datePopup.settings.startTime); - $(this) - .timepicker(datePopup.settings) - .addClass('date-popup-init'); - $(this).click(function(){ - $(this).focus(); - }); - break; - } + case 'timepicker': + // Translate the PHP date format into the style the timepicker uses. + datePopup.settings.timeFormat = datePopup.settings.timeFormat + // 12-hour, leading zero, + .replace('h', 'hh') + // 12-hour, no leading zero. + .replace('g', 'h') + // 24-hour, leading zero. + .replace('H', 'HH') + // 24-hour, no leading zero. + .replace('G', 'H') + // AM/PM. + .replace('A', 'p') + // Minutes with leading zero. + .replace('i', 'mm') + // Seconds with leading zero. + .replace('s', 'ss'); + + datePopup.settings.startTime = new Date(datePopup.settings.startTime); + $(this) + .timepicker(datePopup.settings) + .addClass('date-popup-init'); + $(this).click(function(){ + $(this).focus(); + }); + break; } - }); + } } - } -}; + + Drupal.behaviors.date_popup = { + attach: function (context) { + for (var id in Drupal.settings.datePopup) { + $('#'+ id).bind('focus', Drupal.settings.datePopup[id], makeFocusHandler); + } + } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.module b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.module index a2d0ebb1..a03132e9 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_popup/date_popup.module @@ -16,7 +16,6 @@ * If no time elements are included in the format string, only the date * textfield will be created. If no date elements are included in the format * string, only the time textfield, will be created. - * */ /** @@ -44,7 +43,7 @@ function date_popup_add() { /** * Get the location of the Willington Vega timepicker library. * - * @return + * @return string * The location of the library, or FALSE if the library isn't installed. */ function date_popup_get_wvega_path() { @@ -94,9 +93,11 @@ function date_popup_library() { } /** - * Create a unique CSS id name and output a single inline JS block for - * each startup function to call and settings array to pass it. This - * used to create a unique CSS class for each unique combination of + * Create a unique CSS id name and output a single inline JS block. + * + * For each startup function to call and settings array to pass it. + * + * This used to create a unique CSS class for each unique combination of * function and settings, but using classes requires a DOM traversal * and is much slower than an id lookup. The new approach returns to * requiring a duplicate copy of the settings/code for every element @@ -104,17 +105,20 @@ function date_popup_library() { * putting the ids for each unique function/settings combo into * Drupal.settings and searching for each listed id. * - * @param $pfx + * @param string $id * The CSS class prefix to search the DOM for. * TODO : unused ? - * @param $func - * The jQuery function to invoke on each DOM element containing the - * returned CSS class. - * @param $settings + * + * @param string $func + * The jQuery function to invoke on each DOM element + * containing the returned CSS class. + * + * @param array $settings * The settings array to pass to the jQuery function. + * * @returns - * The CSS id to assign to the element that should have - * $func($settings) invoked on it. + * The CSS id to assign to the element that should have $func($settings) + * invoked on it. */ function date_popup_js_settings_id($id, $func, $settings) { static $js_added = FALSE; @@ -123,14 +127,15 @@ function date_popup_js_settings_id($id, $func, $settings) { // Make sure popup date selector grid is in correct year. if (!empty($settings['yearRange'])) { $parts = explode(':', $settings['yearRange']); - // Set the default date to 0 or the lowest bound if the date ranges do not include the current year - // Necessary for the datepicker to render and select dates correctly - $defaultDate = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0; - $settings += array('defaultDate' => (string) $defaultDate . 'y'); + // Set the default date to 0 or the lowest bound if + // the date ranges do not include the current year. + // Necessary for the datepicker to render and select dates correctly. + $default_date = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0; + $settings += array('defaultDate' => (string) $default_date . 'y'); } if (!$js_added) { - drupal_add_js(drupal_get_path('module', 'date_popup') .'/date_popup.js'); + drupal_add_js(drupal_get_path('module', 'date_popup') . '/date_popup.js'); $js_added = TRUE; } @@ -140,29 +145,35 @@ function date_popup_js_settings_id($id, $func, $settings) { $id_count[$id] = 0; } -// It looks like we need the additional id_count for this to -// work correctly when there are multiple values. -// $return_id = "$id-$func-popup"; - $return_id = "$id-$func-popup-". $id_count[$id]++; + // It looks like we need the additional id_count for this to + // work correctly when there are multiple values. + // $return_id = "$id-$func-popup"; + $return_id = "$id-$func-popup-" . $id_count[$id]++; $js_settings['datePopup'][$return_id] = array( 'func' => $func, - 'settings' => $settings + 'settings' => $settings, ); drupal_add_js($js_settings, 'setting'); return $return_id; } +/** + * Date popup theme handler. + */ function date_popup_theme() { return array( - 'date_popup' => array('render element' => 'element'), - ); + 'date_popup' => array( + 'render element' => 'element', + ), + ); } /** * Implements hook_element_info(). * * Set the #type to date_popup and fill the element #default_value with - * a date adjusted to the proper local timezone in datetime format (YYYY-MM-DD HH:MM:SS). + * a date adjusted to the proper local timezone in datetime format + * (YYYY-MM-DD HH:MM:SS). * * The element will create two textfields, one for the date and one for the * time. The date textfield will include a jQuery popup calendar date picker, @@ -218,20 +229,32 @@ function date_popup_element_info() { return $type; } +/** + * Date popup date granularity. + */ function date_popup_date_granularity($element) { $granularity = date_format_order($element['#date_format']); return array_intersect($granularity, array('month', 'day', 'year')); } +/** + * Date popup time granularity. + */ function date_popup_time_granularity($element) { $granularity = date_format_order($element['#date_format']); return array_intersect($granularity, array('hour', 'minute', 'second')); } +/** + * Date popup date format. + */ function date_popup_date_format($element) { return (date_limit_format($element['#date_format'], date_popup_date_granularity($element))); } +/** + * Date popup time format. + */ function date_popup_time_format($element) { return date_popup_format_to_popup_time(date_limit_format($element['#date_format'], date_popup_time_granularity($element)), $element['#timepicker']); } @@ -239,6 +262,7 @@ function date_popup_time_format($element) { /** * Element value callback for date_popup element. */ +// @codingStandardsIgnoreStart function date_popup_element_value_callback($element, $input = FALSE, &$form_state) { $granularity = date_format_order($element['#date_format']); $has_time = date_has_time($granularity); @@ -266,9 +290,11 @@ function date_popup_element_value_callback($element, $input = FALSE, &$form_stat return $return; } +// @codingStandardsIgnoreEnd /** * Javascript popup element processing. + * * Add popup attributes to $element. */ function date_popup_element_process($element, &$form_state, $form) { @@ -284,7 +310,9 @@ function date_popup_element_process($element, &$form_state, $form) { if (!empty($element['#ajax'])) { $element['#ajax'] += array( - 'trigger_as' => array('name' =>$element['#name']), + 'trigger_as' => array( + 'name' => $element['#name'], + ), 'event' => 'change', ); } @@ -292,6 +320,18 @@ function date_popup_element_process($element, &$form_state, $form) { $element['date'] = date_popup_process_date_part($element); $element['time'] = date_popup_process_time_part($element); + // Make changes if instance is set to be rendered as a regular field. + if (!empty($element['#instance']['widget']['settings']['no_fieldset']) && $element['#field']['cardinality'] == 1) { + if (!empty($element['date']) && empty($element['time'])) { + $element['date']['#title'] = check_plain($element['#instance']['label']); + $element['date']['#required'] = $element['#required']; + } + elseif (empty($element['date']) && !empty($element['time'])) { + $element['time']['#title'] = check_plain($element['#instance']['label']); + $element['time']['#required'] = $element['#required']; + } + } + if (isset($element['#element_validate'])) { array_push($element['#element_validate'], 'date_popup_validate'); } @@ -300,7 +340,7 @@ function date_popup_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_popup_process', $element, $form_state, $context); @@ -313,13 +353,22 @@ function date_popup_element_process($element, &$form_state, $form) { function date_popup_process_date_part(&$element) { $granularity = date_format_order($element['#date_format']); $date_granularity = date_popup_date_granularity($element); - if (empty($date_granularity)) return array(); + if (empty($date_granularity)) { + return array(); + } // The datepicker can't handle zero or negative values like 0:+1 // even though the Date API can handle them, so rework the value // we pass to the datepicker to use defaults it can accept (such as +0:+1) // date_range_string() adds the necessary +/- signs to the range string. $this_year = date_format(date_now(), 'Y'); + // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string. + // Fill the 'date' string in this case. + $mock = NULL; + $callback_values = date_popup_element_value_callback($element, FALSE, $mock); + if (!isset($element['#value']['date']) && isset($callback_values['date'])) { + $element['#value']['date'] = $callback_values['date']; + } $date = ''; if (!empty($element['#value']['date'])) { $date = new DateObject($element['#value']['date'], $element['#date_timezone'], date_popup_date_format($element)); @@ -336,37 +385,49 @@ function date_popup_process_date_part(&$element) { 'closeAtTop' => FALSE, 'speed' => 'immediate', 'firstDay' => intval(variable_get('date_first_day', 0)), - //'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png", - //'buttonImageOnly' => TRUE, + // 'buttonImage' => base_path() + // . drupal_get_path('module', 'date_api') ."/images/calendar.png", + // 'buttonImageOnly' => TRUE, 'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'), 'yearRange' => $year_range, // Custom setting, will be expanded in Drupal.behaviors.date_popup() 'fromTo' => isset($fromto), ); + if (!empty($element['#instance'])) { + $settings['syncEndDate'] = $element['#instance']['settings']['default_value2'] == 'sync'; + } + // Create a unique id for each set of custom settings. $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings); - // Manually build this element and set the value - this will prevent corrupting - // the parent value + // Manually build this element and set the value - + // this will prevent corrupting the parent value. $parents = array_merge($element['#parents'], array('date')); $sub_element = array( '#type' => 'textfield', '#title' => theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)), '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible', - '#default_value' => $element['#value']['date'], + '#default_value' => date_format_date($date, 'custom', date_popup_date_format($element)), '#id' => $id, '#input' => FALSE, '#size' => !empty($element['#size']) ? $element['#size'] : 20, '#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30, '#attributes' => $element['#attributes'], '#parents' => $parents, - '#name' => array_shift($parents) . '['. implode('][', $parents) .']', + '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']', '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE, ); $sub_element['#value'] = $sub_element['#default_value']; - // TODO, figure out exactly when we want this description. In many places it is not desired. - $sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_example_date(), 'custom', date_popup_date_format($element)))); + // TODO, figure out exactly when we want this description. + // In many places it is not desired. + $sub_element['#description'] = ' ' . t('E.g., @date', array( + '@date' => date_format_date( + date_example_date(), + 'custom', + date_popup_date_format($element) + ), + )); return $sub_element; } @@ -377,7 +438,17 @@ function date_popup_process_date_part(&$element) { function date_popup_process_time_part(&$element) { $granularity = date_format_order($element['#date_format']); $has_time = date_has_time($granularity); - if (empty($has_time)) return array(); + if (empty($has_time)) { + return array(); + } + + // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string. + // Fill the 'time' string in this case. + $mock = NULL; + $callback_values = date_popup_element_value_callback($element, FALSE, $mock); + if (!isset($element['#value']['time']) && isset($callback_values['time'])) { + $element['#value']['time'] = $callback_values['time']; + } switch ($element['#timepicker']) { case 'default': @@ -385,10 +456,14 @@ function date_popup_process_time_part(&$element) { $settings = array( 'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE, 'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE), - 'timeSteps' => array(1, intval($element['#date_increment']), (in_array('second', $granularity) ? $element['#date_increment'] : 0)), + 'timeSteps' => array( + 1, + intval($element['#date_increment']), + (in_array('second', $granularity) ? $element['#date_increment'] : 0), + ), 'spinnerImage' => '', 'fromTo' => isset($fromto), - ); + ); if (strpos($element['#date_format'], 'a') !== FALSE) { // Then we are using lowercase am/pm. $settings['ampmNames'] = array('am', 'pm'); @@ -397,14 +472,17 @@ function date_popup_process_time_part(&$element) { $settings['ampmPrefix'] = ' '; } break; + case 'wvega': $func = 'timepicker'; - $time_granularity = array_intersect($granularity, array('hour', 'minute', 'second')); + $grans = array('hour', 'minute', 'second'); + $time_granularity = array_intersect($granularity, $grans); $format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity), 'wvega'); + $default_value = isset($element['#default_value']) ? $element['#default_value'] : ''; // The first value in the dropdown list should be the same as the element // default_value, but it needs to be in JS format (i.e. milliseconds since // the epoch). - $start_time = new DateObject($element['#default_value'], $element['#date_timezone'], DATE_FORMAT_DATETIME); + $start_time = new DateObject($default_value, $element['#date_timezone'], DATE_FORMAT_DATETIME); date_increment_round($start_time, $element['#date_increment']); $start_time = $start_time->format(DATE_FORMAT_UNIX) * 1000; $settings = array( @@ -414,6 +492,7 @@ function date_popup_process_time_part(&$element) { 'scrollbar' => TRUE, ); break; + default: $func = ''; $settings = array(); @@ -423,8 +502,8 @@ function date_popup_process_time_part(&$element) { // Create a unique id for each set of custom settings. $id = date_popup_js_settings_id($element['#id'], $func, $settings); - // Manually build this element and set the value - this will prevent corrupting - // the parent value + // Manually build this element and set the value - + // this will prevent corrupting the parent value. $parents = array_merge($element['#parents'], array('time')); $sub_element = array( '#type' => 'textfield', @@ -436,16 +515,22 @@ function date_popup_process_time_part(&$element) { '#maxlength' => 10, '#attributes' => $element['#attributes'], '#parents' => $parents, - '#name' => array_shift($parents) . '['. implode('][', $parents) .']', + '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']', '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE, ); $sub_element['#value'] = $sub_element['#default_value']; - // TODO, figure out exactly when we want this description. In many places it is not desired. + // TODO, figure out exactly when we want this description. + // In many places it is not desired. $example_date = date_now(); date_increment_round($example_date, $element['#date_increment']); - $sub_element['#description'] = t('E.g., @date', array('@date' => date_format_date($example_date, 'custom', date_popup_time_format($element)))); + $sub_element['#description'] = t('E.g., @date', array( + '@date' => date_format_date( + $example_date, + 'custom', + date_popup_time_format($element) + ))); return ($sub_element); } @@ -456,7 +541,6 @@ function date_popup_process_time_part(&$element) { * When used as a Views widget, the validation step always gets triggered, * even with no form submission. Before form submission $element['#value'] * contains a string, after submission it contains an array. - * */ function date_popup_validate($element, &$form_state) { @@ -473,6 +557,11 @@ function date_popup_validate($element, &$form_state) { $input_exists = NULL; $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists); + // If the date is a string, it is not considered valid and can cause problems + // later on, so just exit out now. + if (is_string($input)) { + return; + } drupal_alter('date_popup_pre_validate', $element, $form_state, $input); @@ -481,16 +570,22 @@ function date_popup_validate($element, &$form_state) { $time_granularity = date_popup_time_granularity($element); $has_time = date_has_time($granularity); - $label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : ''); - $label = t($label); - + // @codingStandardsIgnoreStart + $label = ''; + if (!empty($element['#date_title'])) { + $label = t($element['#date_title']); + } + elseif (!empty($element['#title'])) { + $label = t($element['#title']); + } + // @codingStandardsIgnoreEnd $date = date_popup_input_date($element, $input); // If the date has errors, display them. // If something was input but there is no date, the date is invalid. // If the field is empty and required, set error message and return. $error_field = implode('][', $element['#parents']); - if (empty($date) || !empty($date->errors)) { + if ((empty($element['#value']['date']) && empty($element['#value']['time'])) || !empty($date->errors)) { if (is_object($date) && !empty($date->errors)) { $message = t('The value input for field %field is invalid:', array('%field' => $label)); $message .= '
    ' . implode('
    ', $date->errors); @@ -517,13 +612,15 @@ function date_popup_validate($element, &$form_state) { /** * Helper function for extracting a date value out of user input. * - * @param autocomplete + * @param bool $auto_complete * Should we add a time value to complete the date if there is no time? * Useful anytime the time value is optional. */ function date_popup_input_date($element, $input, $auto_complete = FALSE) { if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || empty($input['date'])) { - return NULL; + //check if there is no time associated in the input variable. This is the exception scenario where the user has entered only time and not date. + if(empty($input['time'])) + return NULL; } date_popup_add(); $granularity = date_format_order($element['#date_format']); @@ -532,9 +629,14 @@ function date_popup_input_date($element, $input, $auto_complete = FALSE) { $format = date_popup_date_format($element); $format .= $has_time ? ' ' . date_popup_time_format($element) : ''; - $datetime = $input['date']; - $datetime .= $has_time ? ' ' . $input['time'] : ''; + //check if date is empty, if yes, then leave it blank. + $datetime = !empty($input['date']) ? trim($input['date']) : ''; + $datetime .= $has_time ? ' ' . trim($input['time']) : ''; $date = new DateObject($datetime, $element['#date_timezone'], $format); + //if the variable is time only then set TimeOnly to TRUE + if(empty($input['date']) && !empty($input['time']) ){ + $date->timeOnly = 'TRUE'; + } if (is_object($date)) { $date->limitGranularity($granularity); if ($date->validGranularity($granularity, $flexible)) { @@ -552,7 +654,7 @@ function date_popup_time_formats($with_seconds = FALSE) { return array( 'H:i:s', 'h:i:sA', - ); + ); } /** @@ -561,8 +663,17 @@ function date_popup_time_formats($with_seconds = FALSE) { * TODO Remove any formats not supported by the widget, if any. */ function date_popup_formats() { - $formats = str_replace('i', 'i:s', array_keys(system_get_date_formats('short'))); + // Load short date formats. + $formats = system_get_date_formats('short'); + + // Load custom date formats. + if ($formats_custom = system_get_date_formats('custom')) { + $formats = array_merge($formats, $formats_custom); + } + + $formats = str_replace('i', 'i:s', array_keys($formats)); $formats = drupal_map_assoc($formats); + return $formats; } @@ -570,7 +681,8 @@ function date_popup_formats() { * Recreate a date format string so it has the values popup expects. * * @param string $format - * a normal date format string, like Y-m-d + * A normal date format string, like Y-m-d + * * @return string * A format string in popup format, like YMD-, for the * earlier 'calendar' version, or m/d/Y for the later 'datepicker' @@ -588,15 +700,34 @@ function date_popup_format_to_popup($format) { * Recreate a time format string so it has the values popup expects. * * @param string $format - * a normal time format string, like h:i (a) + * A normal time format string, like h:i (a) + * * @return string - * a format string that the popup can accept like h:i a + * A format string that the popup can accept like h:i a */ function date_popup_format_to_popup_time($format, $timepicker = NULL) { if (empty($format)) { $format = 'H:i'; } - $format = str_replace(array('/', '-', ' .', ',', 'F', 'M', 'l', 'z', 'w', 'W', 'd', 'j', 'm', 'n', 'y', 'Y'), '', $format); + $symbols = array( + '/', + '-', + ' .', + ',', + 'F', + 'M', + 'l', + 'z', + 'w', + 'W', + 'd', + 'j', + 'm', + 'n', + 'y', + 'Y', + ); + $format = str_replace($symbols, '', $format); $format = strtr($format, date_popup_timepicker_format_replacements($timepicker)); return $format; } @@ -605,9 +736,10 @@ function date_popup_format_to_popup_time($format, $timepicker = NULL) { * Reconstruct popup format string into normal format string. * * @param string $format - * a string in popup format, like YMD- + * A string in popup format, like YMD- + * * @return string - * a normal date format string, like Y-m-d + * A normal date format string, like Y-m-d */ function date_popup_popup_to_format($format) { $replace = array_flip(date_popup_datepicker_format_replacements()); @@ -621,22 +753,21 @@ function date_popup_popup_to_format($format) { * This function returns a map of format replacements required to change any * input format into one that the given timepicker can support. * - * @param $timepicker + * @param string $timepicker * The time entry plugin being used: either 'wvega' or 'default'. - * @return + * + * @return array * A map of replacements. */ function date_popup_timepicker_format_replacements($timepicker = 'default') { switch ($timepicker) { case 'wvega': - return array( - 'a' => 'A', // The wvega timepicker only supports uppercase AM/PM. - ); + // The wvega timepicker only supports uppercase AM/PM. + return array('a' => 'A'); + default: - return array( - 'G' => 'H', // The default timeEntry plugin requires leading zeros. - 'g' => 'h', - ); + // The default timeEntry plugin requires leading zeros. + return array('G' => 'H', 'g' => 'h'); } } @@ -645,16 +776,16 @@ function date_popup_timepicker_format_replacements($timepicker = 'default') { */ function date_popup_datepicker_format_replacements() { return array( - 'd' => 'dd', - 'j' => 'd', - 'l' => 'DD', - 'D' => 'D', - 'm' => 'mm', - 'n' => 'm', - 'F' => 'MM', - 'M' => 'M', - 'Y' => 'yy', - 'y' => 'y', + 'd' => 'dd', + 'j' => 'd', + 'l' => 'DD', + 'D' => 'D', + 'm' => 'mm', + 'n' => 'm', + 'F' => 'MM', + 'M' => 'M', + 'Y' => 'yy', + 'y' => 'y', ); } @@ -667,16 +798,26 @@ function theme_date_popup($vars) { $element = $vars['element']; $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array()); $attributes['class'][] = 'container-inline-date'; - // If there is no description, the floating date elements need some extra padding below them. + // If there is no description, the floating date + // elements need some extra padding below them. $wrapper_attributes = array('class' => array('date-padding')); if (empty($element['date']['#description'])) { $wrapper_attributes['class'][] = 'clearfix'; } - // Add an wrapper to mimic the way a single value field works, for ease in using #states. + // Add an wrapper to mimic the way a single value field works, + // for ease in using #states. if (isset($element['#children'])) { - $element['#children'] = '
    ' . $element['#children'] . '
    '; + $element['#children'] = '
    ' . $element['#children'] . '
    '; } - return '
    ' . theme('form_element', $element) . '
    '; + return '
    ' . theme('form_element', $element) . '
    '; +} + +/** + * Implements hook_date_field_instance_settings_form_alter(). + */ +function date_popup_date_field_instance_settings_form_alter(&$form, $context) { + // Add an extra option to sync the end date with the start date. + $form['default_value2']['#options']['sync'] = t('Sync with start date'); } /** @@ -707,8 +848,8 @@ function date_popup_settings() { '#type' => 'select', '#options' => array( 'default' => t('Use default jQuery timepicker'), - 'wvega' => t('Use dropdown timepicker'), - 'none' => t('Manual time entry, no jQuery timepicker') + 'wvega' => t('Use dropdown timepicker'), + 'none' => t('Manual time entry, no jQuery timepicker'), ), '#title' => t('Timepicker'), '#default_value' => variable_get('date_popup_timepicker', $preferred_timepicker), @@ -734,7 +875,7 @@ function date_popup_settings() { } EOM; - $form['#suffix'] = t('

    The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7, 8, and 9, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:

    ') .'
    ' . $css .'
    '; + $form['#suffix'] = t('

    The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7, 8, and 9, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:

    ') . '
    ' . $css . '
    '; return system_settings_form($form); } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_popup/jquery.timeentry.pack.js b/profiles/commerce_kickstart/modules/contrib/date/date_popup/jquery.timeentry.pack.js index e26c64bf..658e7e49 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_popup/jquery.timeentry.pack.js +++ b/profiles/commerce_kickstart/modules/contrib/date/date_popup/jquery.timeentry.pack.js @@ -1,7 +1,8 @@ /* http://keith-wood.name/timeEntry.html - Time entry for jQuery v1.4.8. + Time entry for jQuery v1.5.2. Written by Keith Wood (kbwood{at}iinet.com.au) June 2007. - Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and - MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. Please attribute the author if you use it. */ -eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(r($){r 1n(){p.Y=[];p.26=[];p.26[\'\']={1c:B,1d:\':\',1o:\'\',Z:[\'3J\',\'3K\'],2C:[\'3L\',\'3M 2D\',\'3N 2D\',\'3O\',\'3P\']};p.1B={2E:\'\',1C:B,27:[1,1,1],2F:0,2G:L,2H:v,2I:v,2J:v,2K:\'3Q.3R\',1p:[20,20,8],2L:\'\',1D:[40,40,16],2M:B,28:[3S,3T],2N:v,2O:v};$.1q(p.1B,p.26[\'\'])}7 m=\'o\';$.1q(1n.2P,{1e:\'3U\',3V:r(a){1E(p.1B,a||{});u p},2Q:r(b,c){7 d=$(b);q(d.2R(p.1e)){u}7 e={};e.1r=$.1q({},c);e.E=0;e.13=0;e.14=0;e.x=0;e.w=$(b);$.y(b,m,e);7 f=p.t(e,\'2K\');7 g=p.t(e,\'3W\');7 h=p.t(e,\'1p\');7 i=p.t(e,\'2E\');7 j=(!f?v:$(\'<15 1F="3X" 2S="3Y: 3Z-41; \'+\'29: 2T(\\\'\'+f+\'\\\') 0 0 2U-2V; \'+\'2a: \'+h[0]+\'O; 2W: \'+h[1]+\'O;\'+($.K.2X&&$.K.42<\'1.9\'?\' 2Y-U: \'+h[0]+\'O; 2Y-43: \'+(h[1]-18)+\'O;\':\'\')+\'">\'));d.44(\'<15 1F="45">\').2Z(i?\'<15 1F="47">\'+i+\'\':\'\').2Z(j||\'\');d.49(p.1e).17(\'2b.o\',p.2c).17(\'4a.o\',p.30).17(\'4b.o\',p.31).17(\'4c.o\',p.32).17(\'4d.o\',p.33);q($.K.2X){d.17(\'w.o\',r(a){$.o.1f(e)})}q($.K.2d){d.17(\'4e.o\',r(a){1G(r(){$.o.1f(e)},1)})}q(p.t(e,\'2G\')&&$.1H.2e){d.2e(p.34)}q(j){j.3a(p.2f).2g(p.1I).4f(p.3b).2h(p.1I).3c(p.2i)}},4g:r(a){p.2j(a,B)},4h:r(a){p.2j(a,L)},2j:r(b,c){7 d=$.y(b,m);q(!d){u}b.3d=c;q(b.2k&&b.2k.1J.1g()==\'15\'){$.o.1K(d,b.2k,(c?5:-1))}$.o.Y=$.3e($.o.Y,r(a){u(a==b?v:a)});q(c){$.o.Y.4i(b)}},1s:r(a){u $.3f(a,p.Y)>-1},4j:r(a,b,c){7 d=$.y(a,m);q(d){q(1h b==\'1L\'){7 e=b;b={};b[e]=c}7 f=p.1i(d);1E(d.1r,b||{});q(f){p.19(d,F J(0,0,0,f[0],f[1],f[2]))}}$.y(a,m,d)},4k:r(b){$w=$(b);q(!$w.2R(p.1e)){u}$w.4l(p.1e).4m(\'.o\');q($.1H.2e){$w.4n()}p.Y=$.3e(p.Y,r(a){u(a==b?v:a)});$w.4o().4p($w);$.4q(b,m)},4r:r(a,b){7 c=$.y(a,m);q(c){p.19(c,b?(1h b==\'4s\'?F J(b.2l()):b):v)}},3g:r(a){7 b=$.y(a,m);7 c=(b?p.1i(b):v);u(!c?v:F J(0,0,0,c[0],c[1],c[2]))},4t:r(a){7 b=$.y(a,m);7 c=(b?p.1i(b):v);u(!c?0:(c[0]*4u+c[1]*2m+c[2])*3h)},2c:r(a){7 b=(a.1J&&a.1J.1g()==\'w\'?a:p);q($.o.P==b||$.o.1s(b)){$.o.1M=B;u}7 c=$.y(b,m);$.o.1M=L;$.o.P=b;$.o.1j=v;7 d=$.o.t(c,\'2N\');1E(c.1r,(d?d.1N(b,[b]):{}));$.y(b,m,c);$.o.1f(c);1G(r(){$.o.1a(c)},10)},30:r(a){$.o.1j=$.o.P;$.o.P=v},31:r(b){7 c=b.1k;7 d=$.y(c,m);q(!$.o.1M){7 e=$.o.t(d,\'1d\').G+2;d.x=0;q(c.3i!=v){1O(7 f=0;f<=I.1l(1,d.V,d.D);f++){7 g=(f!=d.D?(f*e)+2:(d.D*e)+$.o.t(d,\'1o\').G+$.o.t(d,\'Z\')[0].G);d.x=f;q(c.3i=48){u L}7 b=$.y(a.1k,m);2r(a.2q){A 9:u(a.4B?$.o.W(b,-1,L):$.o.W(b,+1,L));A 35:q(a.3m){$.o.1R(b,\'\')}N{b.x=I.1l(1,b.V,b.D);$.o.R(b,0)}C;A 36:q(a.3m){$.o.19(b)}N{b.x=0;$.o.R(b,0)}C;A 37:$.o.W(b,-1,B);C;A 38:$.o.R(b,+1);C;A 39:$.o.W(b,+1,B);C;A 40:$.o.R(b,-1);C;A 46:$.o.1R(b,\'\');C}u B},33:r(a){7 b=4C.4D(a.3n==4E?a.2q:a.3n);q(b<\' \'){u L}7 c=$.y(a.1k,m);$.o.3o(c,b);u B},34:r(a,b){q($.o.1s(a.1k)){u}b=($.K.3p?-b/I.1S(b):($.K.2s?b/I.1S(b):b));7 c=$.y(a.1k,m);c.w.2b();q(!c.w.T()){$.o.1f(c)}$.o.R(c,b);a.4F()},3b:r(b){7 c=$.o.1b(b);7 d=$.y($.o.1m(c),m);7 e=$.o.t(d,\'2L\');q(e){d.1T=L;7 f=$(c).1Q();7 g=v;$(c).3q().2t(r(){7 a=$(p);q(a.1v(\'1w\')==\'4G\'||a.1v(\'1w\')==\'3r\'){g=a.1Q()}u!g});7 h=$.o.t(d,\'1p\');7 i=$.o.t(d,\'1D\');$(\'<3s 1F="4H" 2S="1w: 3r; U: \'+(f.U-(i[0]-h[0])/2-(g?g.U:0))+\'O; 1x: \'+(f.1x-(i[1]-h[1])/2-(g?g.1x:0))+\'O; 2a: \'+i[0]+\'O; 2W: \'+i[1]+\'O; 29: 4I 2T(\'+e+\') 2U-2V 2u 2u; z-4J: 10;">\').3a($.o.2f).2g($.o.1I).2h($.o.3t).3c($.o.2i).4K(c)}},1m:r(a){u $(a).4L(\'.\'+$.o.1e)[0]},2i:r(a){7 b=$.o.1b(a);7 c=$.y($.o.1m(b),m);b.4M=$.o.t(c,\'2C\')[$.o.2v(c,a)]},2f:r(a){7 b=$.o.1b(a);7 c=$.o.1m(b);q($.o.1s(c)){u}q(c==$.o.1j){$.o.P=c;$.o.1j=v}7 d=$.y(c,m);$.o.2c(c);7 e=$.o.2v(d,a);$.o.1K(d,b,e);$.o.2w(d,e);$.o.X=v;$.o.1U=L;7 f=$.o.t(d,\'28\');q(e>=3&&f[0]){$.o.X=1G(r(){$.o.2x(d,e)},f[0]);$(b).3u(\'2h\',$.o.2y).3u(\'2g\',$.o.2y)}},2w:r(a,b){q(!a.w.T()){$.o.1f(a)}2r(b){A 0:p.19(a);C;A 1:p.W(a,-1,B);C;A 2:p.W(a,+1,B);C;A 3:p.R(a,+1);C;A 4:p.R(a,-1);C}},2x:r(a,b){q(!$.o.X){u}$.o.P=$.o.1j;p.2w(a,b);p.X=1G(r(){$.o.2x(a,b)},p.t(a,\'28\')[1])},2y:r(a){4N($.o.X);$.o.X=v},3t:r(a){$.o.X=v;7 b=$.o.1b(a);7 c=$.o.1m(b);7 d=$.y(c,m);$(b).4O();d.1T=B},1I:r(a){$.o.X=v;7 b=$.o.1b(a);7 c=$.o.1m(b);7 d=$.y(c,m);q(!$.o.1s(c)){$.o.1K(d,b,-1)}q($.o.1U){$.o.P=$.o.1j}q($.o.P&&$.o.1U){$.o.1a(d)}$.o.1U=B},1b:r(a){u a.1k||a.3j},2v:r(a,b){7 c=p.1b(b);7 d=($.K.3p||$.K.2s?$.o.3v(c):$(c).1Q());7 e=($.K.2s?$.o.3w(c):[1t.2n.1u||1t.3x.1u,1t.2n.1V||1t.3x.1V]);7 f=p.t(a,\'2M\');7 g=(f?3y:b.3k+e[0]-d.U-($.K.2d?2:0));7 h=b.4P+e[1]-d.1x-($.K.2d?2:0);7 i=p.t(a,(a.1T?\'1D\':\'1p\'));7 j=(f?3y:i[0]-1-g);7 k=i[1]-1-h;q(i[2]>0&&I.1S(g-j)<=i[2]&&I.1S(h-k)<=i[2]){u 0}7 l=I.3z(g,h,j,k);u(l==g?1:(l==j?2:(l==h?3:4)))},1K:r(a,b,c){$(b).1v(\'29-1w\',\'-\'+((c+1)*p.t(a,(a.1T?\'1D\':\'1p\'))[0])+\'O 2u\')},3v:r(a){7 b=1W=0;q(a.3A){b=a.2o;1W=a.3B;2z(a=a.3A){7 c=b;b+=a.2o;q(b<0){b=c}1W+=a.3B}}u{U:b,1x:1W}},3w:r(a){7 b=B;$(a).3q().2t(r(){b|=$(p).1v(\'1w\')==\'4Q\'});q(b){u[0,0]}7 c=a.1u;7 d=a.1V;2z(a=a.4R){c+=a.1u||0;d+=a.1V||0}u[c,d]},t:r(a,b){u(a.1r[b]!=v?a.1r[b]:$.o.1B[b])},1f:r(a){7 b=p.1i(a);7 c=p.t(a,\'1C\');q(b){a.E=b[0];a.13=b[1];a.14=b[2]}N{7 d=p.1y(a);a.E=d[0];a.13=d[1];a.14=(c?d[2]:0)}a.V=(c?2:-1);a.D=(p.t(a,\'1c\')?-1:(c?3:2));a.1X=\'\';a.x=I.1l(0,I.3z(I.1l(1,a.V,a.D),p.t(a,\'2F\')));q(a.w.T()!=\'\'){p.2A(a)}},1i:r(a,b){b=b||a.w.T();7 c=p.t(a,\'1d\');7 d=b.4S(c);q(c==\'\'&&b!=\'\'){d[0]=b.1z(0,2);d[1]=b.1z(2,4);d[2]=b.1z(4,6)}7 e=p.t(a,\'Z\');7 f=p.t(a,\'1c\');q(d.G>=2){7 g=!f&&(b.3C(e[0])>-1);7 h=!f&&(b.3C(e[1])>-1);7 i=Q(d[0],10);i=(2B(i)?0:i);i=((g||h)&&i==12?0:i)+(h?12:0);7 j=Q(d[1],10);j=(2B(j)?0:j);7 k=(d.G>=3?Q(d[2],10):0);k=(2B(k)||!p.t(a,\'1C\')?0:k);u p.1y(a,[i,j,k])}u v},1y:r(a,b){7 c=(b!=v);q(!c){7 d=p.1A(a,p.t(a,\'2H\'))||F J();b=[d.1Y(),d.1Z(),d.21()]}7 e=B;7 f=p.t(a,\'27\');1O(7 i=0;i1){b[i]=I.4T(b[i]/f[i])*f[i];e=L}}u b},2A:r(a){7 b=p.t(a,\'1c\');7 c=p.t(a,\'1d\');7 d=(p.22(b?a.E:((a.E+11)%12)+1)+c+p.22(a.13)+(p.t(a,\'1C\')?c+p.22(a.14):\'\')+(b?\'\':p.t(a,\'1o\')+p.t(a,\'Z\')[(a.E<12?0:1)]));p.1R(a,d);p.1a(a)},1a:r(a){7 b=a.w[0];q(a.w.4U(\':4V\')||$.o.P!=b){u}7 c=p.t(a,\'1d\');7 d=c.G+2;7 e=(a.x!=a.D?(a.x*d):(a.D*d)-c.G+p.t(a,\'1o\').G);7 f=e+(a.x!=a.D?2:p.t(a,\'Z\')[0].G);q(b.3D){b.3D(e,f)}N q(b.1P){7 g=b.1P();g.4W(\'2p\',e);g.3l(\'2p\',f-a.w.T().G);g.4X()}q(!b.3d){b.2b()}},22:r(a){u(a<10?\'0\':\'\')+a},1R:r(a,b){q(b!=a.w.T()){a.w.T(b).4Y(\'4Z\')}},W:r(a,b,c){7 d=(a.w.T()==\'\'||a.x==(b==-1?0:I.1l(1,a.V,a.D)));q(!d){a.x+=b}p.1a(a);a.1X=\'\';$.y(a.w[0],m,a);u(d&&c)},R:r(a,b){q(a.w.T()==\'\'){b=0}7 c=p.t(a,\'27\');p.19(a,F J(0,0,0,a.E+(a.x==0?b*c[0]:0)+(a.x==a.D?b*12:0),a.13+(a.x==1?b*c[1]:0),a.14+(a.x==a.V?b*c[2]:0)))},19:r(a,b){b=p.1A(a,b);7 c=p.1y(a,b?[b.1Y(),b.1Z(),b.21()]:v);b=F J(0,0,0,c[0],c[1],c[2]);7 b=p.25(b);7 d=p.25(p.1A(a,p.t(a,\'2I\')));7 e=p.25(p.1A(a,p.t(a,\'2J\')));b=(d&&be?e:b));7 f=p.t(a,\'2O\');q(f){b=f.1N(a.w[0],[p.3g(a.w[0]),b,d,e])}a.E=b.1Y();a.13=b.1Z();a.14=b.21();p.2A(a);$.y(a.w[0],m,a)},25:r(a){q(!a){u v}a.50(51);a.52(0);a.53(0);u a},1A:r(i,j){7 k=r(a){7 b=F J();b.54(b.2l()+a*3h);u b};7 l=r(a){7 b=$.o.1i(i,a);7 c=F J();7 d=(b?b[0]:c.1Y());7 e=(b?b[1]:c.1Z());7 f=(b?b[2]:c.21());q(!b){7 g=/([+-]?[0-9]+)\\s*(s|S|m|M|h|H)?/g;7 h=g.3E(a);2z(h){2r(h[2]||\'s\'){A\'s\':A\'S\':f+=Q(h[1],10);C;A\'m\':A\'M\':e+=Q(h[1],10);C;A\'h\':A\'H\':d+=Q(h[1],10);C}h=g.3E(a)}}c=F J(0,0,10,d,e,f,0);q(/^!/.55(a)){q(c.3F()>10){c=F J(0,0,10,23,59,59)}N q(c.3F()<10){c=F J(0,0,10,0,0,0)}}u c};u(j?(1h j==\'1L\'?l(j):(1h j==\'56\'?k(j):j)):v)},3o:r(a,b){q(b==p.t(a,\'1d\')){p.W(a,+1,B)}N q(b>=\'0\'&&b<=\'9\'){7 c=Q(b,10);7 d=Q(a.1X+b,10);7 e=p.t(a,\'1c\');7 f=(a.x!=0?a.E:(e?(d<24?d:c):(d>=1&&d<=12?d:(c>0?c:a.E))%12+(a.E>=12?12:0)));7 g=(a.x!=1?a.13:(d<2m?d:c));7 h=(a.x!=a.V?a.14:(d<2m?d:c));7 i=p.1y(a,[f,g,h]);p.19(a,F J(0,0,0,i[0],i[1],i[2]));a.1X=b}N q(!p.t(a,\'1c\')){b=b.1g();7 j=p.t(a,\'Z\');q((b==j[0].1z(0,1).1g()&&a.E>=12)||(b==j[1].1z(0,1).1g()&&a.E<12)){7 k=a.x;a.x=a.D;p.R(a,+1);a.x=k;p.1a(a)}}}});r 1E(a,b){$.1q(a,b);1O(7 c 57 b){q(b[c]==v){a[c]=v}}u a}7 n=[\'58\',\'2l\',\'5a\'];$.1H.o=r(c){7 d=5b.2P.5c.5d(5e,1);q(1h c==\'1L\'&&$.3f(c,n)>-1){u $.o[\'3G\'+c+\'1n\'].1N($.o,[p[0]].3H(d))}u p.2t(r(){7 a=p.1J.1g();q(a==\'w\'){q(1h c==\'1L\'){$.o[\'3G\'+c+\'1n\'].1N($.o,[p].3H(d))}N{7 b=($.1H.3I?$(p).3I():{});$.o.2Q(p,$.1q(b,c))}}})};$.o=F 1n()})(5f);',62,326,'|||||||var|||||||||||||||||timeEntry|this|if|function||_get|return|null|input|_field|data||case|false|break|_ampmField|_selectedHour|new|length||Math|Date|browser|true||else|px|_lastInput|parseInt|_adjustField||val|left|_secondField|_changeField|_timer|_disabledInputs|ampmNames||||_selectedMinute|_selectedSecond|span||bind||_setTime|_showField|_getSpinnerTarget|show24Hours|separator|markerClassName|_parseTime|toLowerCase|typeof|_extractTime|_blurredInput|target|max|_getInput|TimeEntry|ampmPrefix|spinnerSize|extend|options|_isDisabledTimeEntry|document|scrollLeft|css|position|top|_constrainTime|substring|_determineTime|_defaults|showSeconds|spinnerBigSize|extendRemove|class|setTimeout|fn|_endSpinner|nodeName|_changeSpinner|string|_focussed|apply|for|createTextRange|offset|_setValue|abs|_expanded|_handlingSpinner|scrollTop|curTop|_lastChr|getHours|getMinutes||getSeconds|_formatNumber|||_normaliseTime|regional|timeSteps|spinnerRepeat|background|width|focus|_doFocus|msie|mousewheel|_handleSpinner|mouseup|mouseout|_describeSpinner|_enableDisable|nextSibling|getTime|60|documentElement|offsetLeft|character|keyCode|switch|safari|each|0px|_getSpinnerRegion|_actionSpinner|_repeatSpinner|_releaseSpinner|while|_showTime|isNaN|spinnerTexts|field|appendText|initialField|useMouseWheel|defaultTime|minTime|maxTime|spinnerImage|spinnerBigImage|spinnerIncDecOnly|beforeShow|beforeSetTime|prototype|_connectTimeEntry|hasClass|style|url|no|repeat|height|mozilla|padding|after|_doBlur|_doClick|_doKeyDown|_doKeyPress|_doMouseWheel||||||mousedown|_expandSpinner|mousemove|disabled|map|inArray|_getTimeTimeEntry|1000|selectionStart|srcElement|clientX|moveEnd|ctrlKey|charCode|_handleKeyPress|opera|parents|absolute|div|_endExpand|one|_findPos|_findScroll|body|99|min|offsetParent|offsetTop|indexOf|setSelectionRange|exec|getDate|_|concat|metadata|AM|PM|Now|Previous|Next|Increment|Decrement|spinnerDefault|png|500|250|hasTimeEntry|setDefaults|spinnerText|timeEntry_control|display|inline||block|version|bottom|wrap|timeEntry_wrap||timeEntry_append||addClass|blur|click|keydown|keypress|paste|mouseover|_enableTimeEntry|_disableTimeEntry|push|_changeTimeEntry|_destroyTimeEntry|removeClass|unbind|unmousewheel|parent|replaceWith|removeData|_setTimeTimeEntry|object|_getOffsetTimeEntry|3600|thin|medium|thick|border|collapse|boundingWidth|shiftKey|String|fromCharCode|undefined|preventDefault|relative|timeEntry_expand|transparent|index|insertAfter|siblings|title|clearTimeout|remove|clientY|fixed|parentNode|split|round|is|hidden|moveStart|select|trigger|change|setFullYear|1900|setMonth|setDate|setTime|test|number|in|getOffset||isDisabled|Array|slice|call|arguments|jQuery'.split('|'),0,{})) \ No newline at end of file +/* jshint ignore:start */ +(function($){function TimeEntry(){this._disabledInputs=[];this.regional=[];this.regional['']={show24Hours:false,separator:':',ampmPrefix:'',ampmNames:['AM','PM'],spinnerTexts:['Now','Previous field','Next field','Increment','Decrement']};this._defaults={appendText:'',showSeconds:false,timeSteps:[1,1,1],initialField:0,noSeparatorEntry:false,useMouseWheel:true,defaultTime:null,minTime:null,maxTime:null,spinnerImage:'spinnerDefault.png',spinnerSize:[20,20,8],spinnerBigImage:'',spinnerBigSize:[40,40,16],spinnerIncDecOnly:false,spinnerRepeat:[500,250],beforeShow:null,beforeSetTime:null};$.extend(this._defaults,this.regional[''])}$.extend(TimeEntry.prototype,{markerClassName:'hasTimeEntry',propertyName:'timeEntry',_appendClass:'timeEntry_append',_controlClass:'timeEntry_control',_expandClass:'timeEntry_expand',setDefaults:function(a){$.extend(this._defaults,a||{});return this},_attachPlugin:function(b,c){var d=$(b);if(d.hasClass(this.markerClassName)){return}var e={options:$.extend({},this._defaults,c),input:d,_field:0,_selectedHour:0,_selectedMinute:0,_selectedSecond:0};d.data(this.propertyName,e).addClass(this.markerClassName).bind('focus.'+this.propertyName,this._doFocus).bind('blur.'+this.propertyName,this._doBlur).bind('click.'+this.propertyName,this._doClick).bind('keydown.'+this.propertyName,this._doKeyDown).bind('keypress.'+this.propertyName,this._doKeyPress).bind('paste.'+this.propertyName,function(a){setTimeout(function(){n._parseTime(e)},1)});this._optionPlugin(b,c)},_optionPlugin:function(a,b,c){a=$(a);var d=a.data(this.propertyName);if(!b||(typeof b=='string'&&c==null)){var e=b;b=(d||{}).options;return(b&&e?b[e]:b)}if(!a.hasClass(this.markerClassName)){return}b=b||{};if(typeof b=='string'){var e=b;b={};b[e]=c}var f=this._extractTime(d);$.extend(d.options,b);d._field=0;if(f){this._setTime(d,new Date(0,0,0,f[0],f[1],f[2]))}a.next('span.'+this._appendClass).remove();a.parent().find('span.'+this._controlClass).remove();if($.fn.mousewheel){a.unmousewheel()}var g=(!d.options.spinnerImage?null:$(''));a.after(d.options.appendText?''+d.options.appendText+'':'').after(g||'');if(d.options.useMouseWheel&&$.fn.mousewheel){a.mousewheel(this._doMouseWheel)}if(g){g.mousedown(this._handleSpinner).mouseup(this._endSpinner).mouseover(this._expandSpinner).mouseout(this._endSpinner).mousemove(this._describeSpinner)}},_enablePlugin:function(a){this._enableDisable(a,false)},_disablePlugin:function(a){this._enableDisable(a,true)},_enableDisable:function(b,c){var d=$.data(b,this.propertyName);if(!d){return}b.disabled=c;if(b.nextSibling&&b.nextSibling.nodeName.toLowerCase()=='span'){n._changeSpinner(d,b.nextSibling,(c?5:-1))}n._disabledInputs=$.map(n._disabledInputs,function(a){return(a==b?null:a)});if(c){n._disabledInputs.push(b)}},_isDisabledPlugin:function(a){return $.inArray(a,this._disabledInputs)>-1},_destroyPlugin:function(b){b=$(b);if(!b.hasClass(this.markerClassName)){return}b.removeClass(this.markerClassName).removeData(this.propertyName).unbind('.'+this.propertyName);if($.fn.mousewheel){b.unmousewheel()}this._disabledInputs=$.map(this._disabledInputs,function(a){return(a==b[0]?null:a)});b.siblings('.'+this._appendClass+',.'+this._controlClass).remove()},_setTimePlugin:function(a,b){var c=$.data(a,this.propertyName);if(c){if(b===null||b===''){c.input.val('')}else{this._setTime(c,b?(typeof b=='object'?new Date(b.getTime()):b):null)}}},_getTimePlugin:function(a){var b=$.data(a,this.propertyName);var c=(b?this._extractTime(b):null);return(!c?null:new Date(0,0,0,c[0],c[1],c[2]))},_getOffsetPlugin:function(a){var b=$.data(a,this.propertyName);var c=(b?this._extractTime(b):null);return(!c?0:(c[0]*3600+c[1]*60+c[2])*1000)},_doFocus:function(a){var b=(a.nodeName&&a.nodeName.toLowerCase()=='input'?a:this);if(n._lastInput==b||n._isDisabledPlugin(b)){n._focussed=false;return}var c=$.data(b,n.propertyName);n._focussed=true;n._lastInput=b;n._blurredInput=null;$.extend(c.options,($.isFunction(c.options.beforeShow)?c.options.beforeShow.apply(b,[b]):{}));n._parseTime(c);setTimeout(function(){n._showField(c)},10)},_doBlur:function(a){n._blurredInput=n._lastInput;n._lastInput=null},_doClick:function(b){var c=b.target;var d=$.data(c,n.propertyName);var e=d._field;if(!n._focussed){var f=d.options.separator.length+2;d._field=0;if(c.selectionStart!=null){for(var g=0;g<=Math.max(1,d._secondField,d._ampmField);g++){var h=(g!=d._ampmField?(g*f)+2:(d._ampmField*f)+d.options.ampmPrefix.length+d.options.ampmNames[0].length);d._field=g;if(c.selectionStart=48){return true}var b=$.data(a.target,n.propertyName);switch(a.keyCode){case 9:return(a.shiftKey?n._changeField(b,-1,true):n._changeField(b,+1,true));case 35:if(a.ctrlKey){n._setValue(b,'')}else{b._field=Math.max(1,b._secondField,b._ampmField);n._adjustField(b,0)}break;case 36:if(a.ctrlKey){n._setTime(b)}else{b._field=0;n._adjustField(b,0)}break;case 37:n._changeField(b,-1,false);break;case 38:n._adjustField(b,+1);break;case 39:n._changeField(b,+1,false);break;case 40:n._adjustField(b,-1);break;case 46:n._setValue(b,'');break;default:return true}return false},_doKeyPress:function(a){var b=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);if(b<' '){return true}var c=$.data(a.target,n.propertyName);n._handleKeyPress(c,b);return false},_doMouseWheel:function(a,b){if(n._isDisabledPlugin(a.target)){return}var c=$.data(a.target,n.propertyName);c.input.focus();if(!c.input.val()){n._parseTime(c)}n._adjustField(c,b);a.preventDefault()},_expandSpinner:function(b){var c=n._getSpinnerTarget(b);var d=$.data(n._getInput(c),n.propertyName);if(n._isDisabledPlugin(d.input[0])){return}if(d.options.spinnerBigImage){d._expanded=true;var e=$(c).offset();var f=null;$(c).parents().each(function(){var a=$(this);if(a.css('position')=='relative'||a.css('position')=='absolute'){f=a.offset()}return!f});$('
    ').mousedown(n._handleSpinner).mouseup(n._endSpinner).mouseout(n._endExpand).mousemove(n._describeSpinner).insertAfter(c)}},_getInput:function(a){return $(a).siblings('.'+n.markerClassName)[0]},_describeSpinner:function(a){var b=n._getSpinnerTarget(a);var c=$.data(n._getInput(b),n.propertyName);b.title=c.options.spinnerTexts[n._getSpinnerRegion(c,a)]},_handleSpinner:function(a){var b=n._getSpinnerTarget(a);var c=n._getInput(b);if(n._isDisabledPlugin(c)){return}if(c==n._blurredInput){n._lastInput=c;n._blurredInput=null}var d=$.data(c,n.propertyName);n._doFocus(c);var e=n._getSpinnerRegion(d,a);n._changeSpinner(d,b,e);n._actionSpinner(d,e);n._timer=null;n._handlingSpinner=true;if(e>=3&&d.options.spinnerRepeat[0]){n._timer=setTimeout(function(){n._repeatSpinner(d,e)},d.options.spinnerRepeat[0]);$(b).one('mouseout',n._releaseSpinner).one('mouseup',n._releaseSpinner)}},_actionSpinner:function(a,b){if(!a.input.val()){n._parseTime(a)}switch(b){case 0:this._setTime(a);break;case 1:this._changeField(a,-1,false);break;case 2:this._changeField(a,+1,false);break;case 3:this._adjustField(a,+1);break;case 4:this._adjustField(a,-1);break}},_repeatSpinner:function(a,b){if(!n._timer){return}n._lastInput=n._blurredInput;this._actionSpinner(a,b);this._timer=setTimeout(function(){n._repeatSpinner(a,b)},a.options.spinnerRepeat[1])},_releaseSpinner:function(a){clearTimeout(n._timer);n._timer=null},_endExpand:function(a){n._timer=null;var b=n._getSpinnerTarget(a);var c=n._getInput(b);var d=$.data(c,n.propertyName);$(b).remove();d._expanded=false},_endSpinner:function(a){n._timer=null;var b=n._getSpinnerTarget(a);var c=n._getInput(b);var d=$.data(c,n.propertyName);if(!n._isDisabledPlugin(c)){n._changeSpinner(d,b,-1)}if(n._handlingSpinner){n._lastInput=n._blurredInput}if(n._lastInput&&n._handlingSpinner){n._showField(d)}n._handlingSpinner=false},_getSpinnerTarget:function(a){return a.target||a.srcElement},_getSpinnerRegion:function(a,b){var c=this._getSpinnerTarget(b);var d=$(c).offset();var e=[document.documentElement.scrollLeft||document.body.scrollLeft,document.documentElement.scrollTop||document.body.scrollTop];var f=(a.options.spinnerIncDecOnly?99:b.clientX+e[0]-d.left);var g=b.clientY+e[1]-d.top;var h=a.options[a._expanded?'spinnerBigSize':'spinnerSize'];var i=(a.options.spinnerIncDecOnly?99:h[0]-1-f);var j=h[1]-1-g;if(h[2]>0&&Math.abs(f-i)<=h[2]&&Math.abs(g-j)<=h[2]){return 0}var k=Math.min(f,g,i,j);return(k==f?1:(k==i?2:(k==g?3:4)))},_changeSpinner:function(a,b,c){$(b).css('background-position','-'+((c+1)*a.options[a._expanded?'spinnerBigSize':'spinnerSize'][0])+'px 0px')},_parseTime:function(a){var b=this._extractTime(a);if(b){a._selectedHour=b[0];a._selectedMinute=b[1];a._selectedSecond=b[2]}else{var c=this._constrainTime(a);a._selectedHour=c[0];a._selectedMinute=c[1];a._selectedSecond=(a.options.showSeconds?c[2]:0)}a._secondField=(a.options.showSeconds?2:-1);a._ampmField=(a.options.show24Hours?-1:(a.options.showSeconds?3:2));a._lastChr='';a._field=Math.max(0,Math.min(Math.max(1,a._secondField,a._ampmField),a.options.initialField));if(a.input.val()!=''){this._showTime(a)}},_extractTime:function(a,b){b=b||a.input.val();var c=b.split(a.options.separator);if(a.options.separator==''&&b!=''){c[0]=b.substring(0,2);c[1]=b.substring(2,4);c[2]=b.substring(4,6)}if(c.length>=2){var d=!a.options.show24Hours&&(b.indexOf(a.options.ampmNames[0])>-1);var e=!a.options.show24Hours&&(b.indexOf(a.options.ampmNames[1])>-1);var f=parseInt(c[0],10);f=(isNaN(f)?0:f);f=((d||e)&&f==12?0:f)+(e?12:0);var g=parseInt(c[1],10);g=(isNaN(g)?0:g);var h=(c.length>=3?parseInt(c[2],10):0);h=(isNaN(h)||!a.options.showSeconds?0:h);return this._constrainTime(a,[f,g,h])}return null},_constrainTime:function(a,b){var c=(b!=null);if(!c){var d=this._determineTime(a.options.defaultTime,a)||new Date();b=[d.getHours(),d.getMinutes(),d.getSeconds()]}var e=false;for(var i=0;i1){b[i]=Math.round(b[i]/a.options.timeSteps[i])*a.options.timeSteps[i];e=true}}return b},_showTime:function(a){var b=(this._formatNumber(a.options.show24Hours?a._selectedHour:((a._selectedHour+11)%12)+1)+a.options.separator+this._formatNumber(a._selectedMinute)+(a.options.showSeconds?a.options.separator+this._formatNumber(a._selectedSecond):'')+(a.options.show24Hours?'':a.options.ampmPrefix+a.options.ampmNames[(a._selectedHour<12?0:1)]));this._setValue(a,b);this._showField(a)},_showField:function(a){var b=a.input[0];if(a.input.is(':hidden')||n._lastInput!=b){return}var c=a.options.separator.length+2;var d=(a._field!=a._ampmField?(a._field*c):(a._ampmField*c)-a.options.separator.length+a.options.ampmPrefix.length);var e=d+(a._field!=a._ampmField?2:a.options.ampmNames[0].length);if(b.setSelectionRange){b.setSelectionRange(d,e)}else if(b.createTextRange){var f=b.createTextRange();f.moveStart('character',d);f.moveEnd('character',e-a.input.val().length);f.select()}if(!b.disabled){b.focus()}},_formatNumber:function(a){return(a<10?'0':'')+a},_setValue:function(a,b){if(b!=a.input.val()){a.input.val(b).trigger('change')}},_changeField:function(a,b,c){var d=(a.input.val()==''||a._field==(b==-1?0:Math.max(1,a._secondField,a._ampmField)));if(!d){a._field+=b}this._showField(a);a._lastChr='';return(d&&c)},_adjustField:function(a,b){if(a.input.val()==''){b=0}this._setTime(a,new Date(0,0,0,a._selectedHour+(a._field==0?b*a.options.timeSteps[0]:0)+(a._field==a._ampmField?b*12:0),a._selectedMinute+(a._field==1?b*a.options.timeSteps[1]:0),a._selectedSecond+(a._field==a._secondField?b*a.options.timeSteps[2]:0)))},_setTime:function(a,b){b=this._determineTime(b,a);var c=this._constrainTime(a,b?[b.getHours(),b.getMinutes(),b.getSeconds()]:null);b=new Date(0,0,0,c[0],c[1],c[2]);var b=this._normaliseTime(b);var d=this._normaliseTime(this._determineTime(a.options.minTime,a));var e=this._normaliseTime(this._determineTime(a.options.maxTime,a));if(d&&e&&d>e){if(be){b=(Math.abs(b-d)e?e:b))}if($.isFunction(a.options.beforeSetTime)){b=a.options.beforeSetTime.apply(a.input[0],[this._getTimePlugin(a.input[0]),b,d,e])}a._selectedHour=b.getHours();a._selectedMinute=b.getMinutes();a._selectedSecond=b.getSeconds();this._showTime(a)},_determineTime:function(i,j){var k=function(a){var b=new Date();b.setTime(b.getTime()+a*1000);return b};var l=function(a){var b=n._extractTime(j,a);var c=new Date();var d=(b?b[0]:c.getHours());var e=(b?b[1]:c.getMinutes());var f=(b?b[2]:c.getSeconds());if(!b){var g=/([+-]?[0-9]+)\s*(s|S|m|M|h|H)?/g;var h=g.exec(a);while(h){switch(h[2]||'s'){case's':case'S':f+=parseInt(h[1],10);break;case'm':case'M':e+=parseInt(h[1],10);break;case'h':case'H':d+=parseInt(h[1],10);break}h=g.exec(a)}}c=new Date(0,0,10,d,e,f,0);if(/^!/.test(a)){if(c.getDate()>10){c=new Date(0,0,10,23,59,59)}else if(c.getDate()<10){c=new Date(0,0,10,0,0,0)}}return c};return(i?(typeof i=='string'?l(i):(typeof i=='number'?k(i):i)):null)},_normaliseTime:function(a){if(!a){return null}a.setFullYear(1900);a.setMonth(0);a.setDate(0);return a},_handleKeyPress:function(a,b){if(b==a.options.separator){this._changeField(a,+1,false)}else if(b>='0'&&b<='9'){var c=parseInt(b,10);var d=parseInt(a._lastChr+b,10);var e=(a._field!=0?a._selectedHour:(a.options.show24Hours?(d<24?d:c):(d>=1&&d<=12?d:(c>0?c:a._selectedHour))%12+(a._selectedHour>=12?12:0)));var f=(a._field!=1?a._selectedMinute:(d<60?d:c));var g=(a._field!=a._secondField?a._selectedSecond:(d<60?d:c));var h=this._constrainTime(a,[e,f,g]);this._setTime(a,new Date(0,0,0,h[0],h[1],h[2]));if(a.options.noSeparatorEntry&&a._lastChr){this._changeField(a,+1,false)}else{a._lastChr=b}}else if(!a.options.show24Hours){b=b.toLowerCase();if((b==a.options.ampmNames[0].substring(0,1).toLowerCase()&&a._selectedHour>=12)||(b==a.options.ampmNames[1].substring(0,1).toLowerCase()&&a._selectedHour<12)){var i=a._field;a._field=a._ampmField;this._adjustField(a,+1);a._field=i;this._showField(a)}}}});var m=['getOffset','getTime','isDisabled'];function isNotChained(a,b){if(a=='option'&&(b.length==0||(b.length==1&&typeof b[0]=='string'))){return true}return $.inArray(a,m)>-1}$.fn.timeEntry=function(b){var c=Array.prototype.slice.call(arguments,1);if(isNotChained(b,c)){return n['_'+b+'Plugin'].apply(n,[this[0]].concat(c))}return this.each(function(){if(typeof b=='string'){if(!n['_'+b+'Plugin']){throw'Unknown command: '+b;}n['_'+b+'Plugin'].apply(n,[this].concat(c))}else{var a=($.fn.metadata?$(this).metadata():{});n._attachPlugin(this,$.extend({},a,b||{}))}})};var n=$.timeEntry=new TimeEntry()})(jQuery); +/* jshint ignore:end */ \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat.inc b/profiles/commerce_kickstart/modules/contrib/date/date_repeat.inc index 2df03dc8..046d8aee 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat.inc @@ -2,5 +2,6 @@ /** * @file * Empty file to avoid fatal error if it doesn't exist. + * * Formerly the Date Repeat field code. - */ \ No newline at end of file + */ diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.info b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.info index 695a2fd9..0a63175c 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.info @@ -7,9 +7,9 @@ php = 5.2 files[] = tests/date_repeat.test files[] = tests/date_repeat_form.test -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.install b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.install index 65c3adbb..52039630 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.install +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.install @@ -12,21 +12,3 @@ function date_repeat_install() { // Make sure this module loads after date_api. db_query("UPDATE {system} SET weight = 1 WHERE name = 'date_repeat'"); } - -/** - * Implements hook_uninstall(). - */ -function date_repeat_uninstall() { -} - -/** - * Implements hook_enable(). - */ -function date_repeat_enable() { -} - -/** - * Implements hook_disable(). - */ -function date_repeat_disable() { -} diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.module b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.module index d742dc88..61d971ba 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat.module @@ -1,7 +1,6 @@ array('render element' => 'element'), @@ -55,6 +57,9 @@ function date_repeat_freq_options() { ); } +/** + * Helper function for interval options. + */ function date_repeat_interval_options() { $options = range(0, 366); unset($options[0]); @@ -92,9 +97,11 @@ function date_repeat_dow_day_options_abbr($translated = TRUE, $length = 3) { case 1: $context = 'day_abbr1'; break; + case 2: $context = 'day_abbr2'; break; + default: $context = ''; break; @@ -105,16 +112,28 @@ function date_repeat_dow_day_options_abbr($translated = TRUE, $length = 3) { return $return; } +/** + * Helper function for weekdays translated. + */ function date_repeat_dow_day_untranslated() { static $date_repeat_weekdays; if (empty($date_repeat_weekdays)) { - $date_repeat_weekdays = array('SU' => 'Sunday', 'MO' => 'Monday', 'TU' => 'Tuesday', - 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', - 'SA' => 'Saturday'); + $date_repeat_weekdays = array( + 'SU' => 'Sunday', + 'MO' => 'Monday', + 'TU' => 'Tuesday', + 'WE' => 'Wednesday', + 'TH' => 'Thursday', + 'FR' => 'Friday', + 'SA' => 'Saturday' + ); } return $date_repeat_weekdays; } +/** + * Helper function for weekdays order. + */ function date_repeat_dow_day_options_ordered($weekdays) { $day_keys = array_keys($weekdays); $day_values = array_values($weekdays); @@ -164,8 +183,7 @@ function date_repeat_dow2day($dow) { } /** - * Shift the array of iCal day names into the right order - * for a specific week start day. + * Shift the array of iCal day names into the right order for a specific week start day. */ function date_repeat_days_ordered($week_start_day) { $days = array_flip(array_keys(date_repeat_dow_day_options(FALSE))); @@ -212,18 +230,21 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { '!except' => '', '!additional' => '', '!week_starts_on' => '', - ); + ); $interval = date_repeat_interval_options(); switch ($rrule['FREQ']) { case 'WEEKLY': $description['!interval'] = format_plural($rrule['INTERVAL'], 'every week', 'every @count weeks') . ' '; break; + case 'MONTHLY': $description['!interval'] = format_plural($rrule['INTERVAL'], 'every month', 'every @count months') . ' '; break; + case 'YEARLY': $description['!interval'] = format_plural($rrule['INTERVAL'], 'every year', 'every @count years') . ' '; break; + default: $description['!interval'] = format_plural($rrule['INTERVAL'], 'every day', 'every @count days') . ' '; break; @@ -240,26 +261,41 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { if (!empty($count)) { // See if there is a 'pretty' option for this count, i.e. +1 => First. $order = array_key_exists($count, $counts) ? strtolower($counts[$count]) : $count; - $results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', array('!repeats_every_interval ' => '', '!date_order' => $order, '!day_of_week' => $days[$day]))); + $results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', + array( + '!repeats_every_interval ' => '', + '!date_order' => $order, + '!day_of_week' => $days[$day] + ))); } else { - $results[] = trim(t('!repeats_every_interval every !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $days[$day]))); + $results[] = trim(t('!repeats_every_interval every !day_of_week', + array('!repeats_every_interval ' => '', '!day_of_week' => $days[$day]))); } } $description['!byday'] = implode(' ' . t('and') . ' ', $results); } if (!empty($rrule['BYMONTH'])) { - if (sizeof($rrule['BYMONTH']) < 12) { + if (count($rrule['BYMONTH']) < 12) { $results = array(); $months = date_month_names(); foreach ($rrule['BYMONTH'] as $month) { $results[] = $months[$month]; } if (!empty($rrule['BYMONTHDAY'])) { - $description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', array('!repeats_every_interval ' => '', '!month_days' => implode(', ', $rrule['BYMONTHDAY']), '!month_names' => implode(', ', $results)))); + $description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', + array( + '!repeats_every_interval ' => '', + '!month_days' => implode(', ', $rrule['BYMONTHDAY']), + '!month_names' => implode(', ', $results) + ))); } else { - $description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', array('!repeats_every_interval ' => '', '!month_names' => implode(', ', $results)))); + $description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', + array( + '!repeats_every_interval ' => '', + '!month_names' => implode(', ', $results) + ))); } } } @@ -267,12 +303,17 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { $rrule['INTERVAL'] = 1; } if (!empty($rrule['COUNT'])) { - $description['!count'] = trim(t('!repeats_every_interval !count times', array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT']))); + $description['!count'] = trim(t('!repeats_every_interval !count times', + array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT']))); } if (!empty($rrule['UNTIL'])) { $until = date_ical_date($rrule['UNTIL'], 'UTC'); date_timezone_set($until, date_default_timezone_object()); - $description['!until'] = trim(t('!repeats_every_interval until !until_date', array('!repeats_every_interval ' => '', '!until_date' => date_format_date($until, 'custom', $format)))); + $description['!until'] = trim(t('!repeats_every_interval until !until_date', + array( + '!repeats_every_interval ' => '', + '!until_date' => date_format_date($until, 'custom', $format) + ))); } if ($exceptions) { $values = array(); @@ -281,11 +322,16 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { date_timezone_set($except, date_default_timezone_object()); $values[] = date_format_date($except, 'custom', $format); } - $description['!except'] = trim(t('!repeats_every_interval except !except_dates', array('!repeats_every_interval ' => '', '!except_dates' => implode(', ', $values)))); + $description['!except'] = trim(t('!repeats_every_interval except !except_dates', + array( + '!repeats_every_interval ' => '', + '!except_dates' => implode(', ', $values) + ))); } if (!empty($rrule['WKST'])) { $day_names = date_repeat_dow_day_options(); - $description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])]))); + $description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', + array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])]))); } if ($additions) { $values = array(); @@ -294,9 +340,15 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { date_timezone_set($add, date_default_timezone_object()); $values[] = date_format_date($add, 'custom', $format); } - $description['!additional'] = trim(t('Also includes !additional_dates.', array('!additional_dates' => implode(', ', $values)))); + $description['!additional'] = trim(t('Also includes !additional_dates.', + array('!additional_dates' => implode(', ', $values)))); } - return t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description); + $output = t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description); + // Removes double whitespaces from Repeat tile. + $output = preg_replace('/\s+/', ' ', $output); + // Removes whitespace before full stop ".", at the end of the title. + $output = str_replace(' .', '.', $output); + return $output; } /** @@ -310,17 +362,17 @@ function date_repeat_split_rrule($rrule) { $additions = array(); foreach ($parts as $part) { if (strstr($part, 'RRULE')) { - $RRULE = str_replace('RRULE:', '', $part); - $rrule = (array) date_ical_parse_rrule('RRULE:', $RRULE); + $cleanded_part = str_replace('RRULE:', '', $part); + $rrule = (array) date_ical_parse_rrule('RRULE:', $cleanded_part); } elseif (strstr($part, 'EXDATE')) { - $EXDATE = str_replace('EXDATE:', '', $part); - $exceptions = (array) date_ical_parse_exceptions('EXDATE:', $EXDATE); + $exdate = str_replace('EXDATE:', '', $part); + $exceptions = (array) date_ical_parse_exceptions('EXDATE:', $exdate); unset($exceptions['DATA']); } elseif (strstr($part, 'RDATE')) { - $RDATE = str_replace('RDATE:', '', $part); - $additions = (array) date_ical_parse_exceptions('RDATE:', $RDATE); + $rdate = str_replace('RDATE:', '', $part); + $additions = (array) date_ical_parse_exceptions('RDATE:', $rdate); unset($additions['DATA']); } } @@ -372,7 +424,7 @@ function date_repeat_form_element_radios_process($element) { '#title_display' => 'invisible', '#return_value' => $key, '#default_value' => isset($element['#default_value']) ? - $element['#default_value'] : NULL, + $element['#default_value'] : NULL, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_calc.inc b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_calc.inc index 4ff3c4a5..d840df62 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_calc.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_calc.inc @@ -53,9 +53,11 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi // Create a date object for the start and end dates. $start_date = new DateObject($start, $timezone); - // Versions of PHP greater than PHP 5.3.5 require that we set an explicit time when - // using date_modify() or the time may not match the original value. Adding this - // modifier gives us the same results in both older and newer versions of PHP. + // Versions of PHP greater than PHP 5.3.5 require + // that we set an explicit time when using date_modify() + // or the time may not match the original value. + // Adding this modifier gives us the same results in both older + // and newer versions of PHP. $modify_time = ' ' . $start_date->format('g:ia'); // If the rule has an UNTIL, see if that is earlier than the end date. @@ -91,27 +93,32 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi } // Make sure DAILY frequency isn't used in places it won't work; - if (!empty($rrule['BYMONTHDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { + if (!empty($rrule['BYMONTHDAY']) && + !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { $rrule['FREQ'] = 'MONTHLY'; } - elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { + elseif (!empty($rrule['BYDAY']) + && !in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { $rrule['FREQ'] = 'WEEKLY'; - } + } // Find the time period to jump forward between dates. switch ($rrule['FREQ']) { - case 'DAILY': - $jump = $interval . ' days'; - break; - case 'WEEKLY': - $jump = $interval . ' weeks'; - break; - case 'MONTHLY': - $jump = $interval . ' months'; - break; - case 'YEARLY': - $jump = $interval . ' years'; - break; + case 'DAILY': + $jump = $interval . ' days'; + break; + + case 'WEEKLY': + $jump = $interval . ' weeks'; + break; + + case 'MONTHLY': + $jump = $interval . ' months'; + break; + + case 'YEARLY': + $jump = $interval . ' years'; + break; } $rrule = date_repeat_adjust_rrule($rrule, $start_date); @@ -135,7 +142,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi $direction_days[$day] = array( 'direction' => !empty($regs[1]) ? $regs[1] : '+', 'direction_count' => $regs[2], - ); + ); } } while (!$finished) { @@ -198,8 +205,9 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi else { - // More complex searches for day names and criteria like '-1SU' or '2TU,2TH', - // require that we interate through the whole time period checking each BYDAY. + // More complex searches for day names and criteria + // like '-1SU' or '2TU,2TH', require that we interate through + // the whole time period checking each BYDAY. // Create helper array to pull day names out of iCal day strings. $day_names = date_repeat_dow_day_options(FALSE); @@ -303,7 +311,8 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi // period, then jumping ahead to the next week, month, or year, // an INTERVAL at a time. - if (!empty($week_days) && in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { + if (!empty($week_days) && + in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { $finished = FALSE; $current_day = clone($start_date); $format = $rrule['FREQ'] == 'YEARLY' ? 'Y' : 'n'; @@ -322,8 +331,9 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi $moved = FALSE; foreach ($week_days as $delta => $day) { // Find the next occurence of each day in this week, only add it - // if we are still in the current month or year. The date_repeat_add_dates - // function is insufficient to test whether to include this date + // if we are still in the current month or year. + // The date_repeat_add_dates function is insufficient + // to test whether to include this date // if we are using a rule like 'every other month', so we must // explicitly test it here. @@ -370,10 +380,12 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi date_modify($current_day, '+1 ' . $week_start_day . $modify_time); date_modify($current_day, '-1 week' . $modify_time); break; + case 'MONTHLY': date_modify($current_day, '-' . (date_format($current_day, 'j') - 1) . ' days' . $modify_time); date_modify($current_day, '-1 month' . $modify_time); break; + case 'YEARLY': date_modify($current_day, '-' . date_format($current_day, 'z') . ' days' . $modify_time); date_modify($current_day, '-1 year' . $modify_time); @@ -387,7 +399,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi } } - // add additional dates + // Add additional dates. foreach ($additions as $addition) { $date = new dateObject($addition . ' ' . $start_date->format('H:i:s'), $timezone); $days[] = date_format($date, DATE_FORMAT_DATETIME); @@ -426,8 +438,8 @@ function date_repeat_adjust_rrule($rrule, $start_date) { // position rules make no sense in other periods and just add complexity. elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { - foreach ($rrule['BYDAY'] as $delta => $BYDAY) { - $rrule['BYDAY'][$delta] = substr($BYDAY, -2); + foreach ($rrule['BYDAY'] as $delta => $by_day) { + $rrule['BYDAY'][$delta] = substr($by_day, -2); } } @@ -442,7 +454,7 @@ function date_repeat_adjust_rrule($rrule, $start_date) { * and that it meets other criteria in the RRULE. */ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $exceptions, $rrule) { - if (isset($rrule['COUNT']) && sizeof($days) >= $rrule['COUNT']) { + if (isset($rrule['COUNT']) && count($days) >= $rrule['COUNT']) { return FALSE; } $formatted = date_format($current_day, DATE_FORMAT_DATETIME); @@ -456,13 +468,14 @@ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $ex return FALSE; } if (!empty($rrule['BYDAY'])) { - $BYDAYS = $rrule['BYDAY']; - foreach ($BYDAYS as $delta => $BYDAY) { - $BYDAYS[$delta] = substr($BYDAY, -2); + $by_days = $rrule['BYDAY']; + foreach ($by_days as $delta => $by_day) { + $by_days[$delta] = substr($by_day, -2); } - if (!in_array(date_repeat_dow2day(date_format($current_day, 'w')), $BYDAYS)) { + if (!in_array(date_repeat_dow2day(date_format($current_day, 'w')), $by_days)) { return FALSE; - }} + } + } if (!empty($rrule['BYYEAR']) && !in_array(date_format($current_day, 'Y'), $rrule['BYYEAR'])) { return FALSE; } @@ -472,17 +485,17 @@ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $ex if (!empty($rrule['BYMONTHDAY'])) { // Test month days, but only if there are no negative numbers. $test = TRUE; - $BYMONTHDAYS = array(); + $by_month_days = array(); foreach ($rrule['BYMONTHDAY'] as $day) { if ($day > 0) { - $BYMONTHDAYS[] = $day; + $by_month_days[] = $day; } else { $test = FALSE; break; } } - if ($test && !empty($BYMONTHDAYS) && !in_array(date_format($current_day, 'j'), $BYMONTHDAYS)) { + if ($test && !empty($by_month_days) && !in_array(date_format($current_day, 'j'), $by_month_days)) { return FALSE; } } @@ -499,7 +512,7 @@ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $ex * Stop when $current_day is greater than $end_date or $count is reached. */ function date_repeat_is_finished($current_day, $days, $count, $end_date) { - if (($count && sizeof($days) >= $count) + if (($count && count($days) >= $count) || (!empty($end_date) && date_format($current_day, 'U') > date_format($end_date, 'U'))) { return TRUE; } @@ -517,7 +530,7 @@ function date_repeat_is_finished($current_day, $days, $count, $end_date) { * If $day is empty, will set to the number of days from the * beginning or end of the month. */ -function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) { +function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time = '') { if (is_object($date_in)) { $current_month = date_format($date_in, 'n'); @@ -567,7 +580,7 @@ function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', * If $day is empty, will set to the number of days from the * beginning or end of the year. */ -function date_repeat_set_year_day($date_in, $month, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) { +function date_repeat_set_year_day($date_in, $month, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time = '') { if (is_object($date_in)) { $current_year = date_format($date_in, 'Y'); @@ -620,4 +633,4 @@ function date_repeat_set_year_day($date_in, $month, $day, $count = 1, $direction } } return $date_in; -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_form.inc b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_form.inc index db7a79bc..33525956 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_form.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/date_repeat_form.inc @@ -30,13 +30,16 @@ * BYSETPOS * Seldom used anywhere, so no reason to complicated the code. */ + /** * Generate the repeat setting form. */ function _date_repeat_rrule_process($element, &$form_state, $form) { - // If the RRULE field is not visible to the user, needs no processing or validation. - // The Date field module is not adding this element to forms if the field is hidden, + // If the RRULE field is not visible to the user, + // needs no processing or validation. + // The Date field module is not adding this element to forms + // if the field is hidden, // this test is just in case some other module attempts to do so. if (date_hidden_element($element)) { @@ -67,16 +70,16 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(); $merged_values = date_repeat_merge($rrule, $element); - $UNTIL = ''; + $until = ''; if (!empty($merged_values['UNTIL']['datetime'])) { $until_date = new DateObject($merged_values['UNTIL']['datetime'], $merged_values['UNTIL']['tz']); date_timezone_set($until_date, timezone_open($timezone)); - $UNTIL = date_format($until_date, DATE_FORMAT_DATETIME); + $until = date_format($until_date, DATE_FORMAT_DATETIME); } - $COUNT = ''; + $count = ''; if (!empty($merged_values['COUNT'])) { - $COUNT = $merged_values['COUNT']; + $count = $merged_values['COUNT']; } $element['FREQ'] = array( @@ -137,7 +140,7 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ); list($prefix, $suffix) = explode('@interval', t('Every @interval days', array(), array('context' => 'Date repeat'))); - $DAILY_INTERVAL = array( + $daily_interval = array( '#type' => 'textfield', '#title' => t('Repeats', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', @@ -210,32 +213,34 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#suffix' => '
    ', ); - $DAILY_radios_default = 'INTERVAL'; + $daily_radios_default = 'INTERVAL'; if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'DAILY' && !empty($rrule['BYDAY'])) { switch (count($rrule['BYDAY'])) { case 2: - $DAILY_radios_default = 'every_tu_th'; + $daily_radios_default = 'every_tu_th'; break; + case 3: - $DAILY_radios_default = 'every_mo_we_fr'; + $daily_radios_default = 'every_mo_we_fr'; break; + case 5: - $DAILY_radios_default = 'every_weekday'; + $daily_radios_default = 'every_weekday'; break; } } - $DAILY_every_weekday = array( + $daily_every_weekday = array( '#type' => 'item', '#markup' => '
    ' . t('Every weekday', array(), array('context' => 'Date repeat')) . '
    ', ); - $DAILY_mo_we_fr = array( + $daily_mo_we_fr = array( '#type' => 'item', '#markup' => '
    ' . t('Every Mon, Wed, Fri', array(), array('context' => 'Date repeat')) . '
    ', ); - $DAILY_tu_th = array( + $daily_tu_th = array( '#type' => 'item', '#markup' => '
    ' . t('Every Tue, Thu', array(), array('context' => 'Date repeat')) . '
    ', ); @@ -251,17 +256,17 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'DAILY'), ), ), - '#default_value' => $DAILY_radios_default, + '#default_value' => $daily_radios_default, '#options' => array( 'INTERVAL' => t('interval'), 'every_weekday' => t('every weekday'), 'every_mo_we_fr' => t('monday wednesday friday'), 'every_tu_th' => t('tuesday thursday'), ), - 'INTERVAL_child' => $DAILY_INTERVAL, - 'every_weekday_child' => $DAILY_every_weekday, - 'mo_we_fr_child' => $DAILY_mo_we_fr, - 'tu_th_child' => $DAILY_tu_th, + 'INTERVAL_child' => $daily_interval, + 'every_weekday_child' => $daily_every_weekday, + 'mo_we_fr_child' => $daily_mo_we_fr, + 'tu_th_child' => $daily_tu_th, '#div_classes' => array( 'container-inline interval', 'container-inline weekday', @@ -270,18 +275,18 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ); - $MONTHLY_day_month_default = 'BYMONTHDAY_BYMONTH'; + $monthly_day_month_default = 'BYMONTHDAY_BYMONTH'; if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'MONTHLY' && !empty($rrule['BYDAY'])) { - $MONTHLY_day_month_default = 'BYDAY_BYMONTH'; + $monthly_day_month_default = 'BYDAY_BYMONTH'; } - $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH = array( + $monthly_on_day_bymonthday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); list($bymonthday_title, $bymonthday_suffix) = explode('@bymonthday', t('On day @bymonthday of', array(), array('context' => 'Date repeat'))); - $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTHDAY'] = array( + $monthly_on_day_bymonthday_of_bymonth['BYMONTHDAY'] = array( '#type' => 'select', '#title' => $bymonthday_title, '#default_value' => !empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] === 'MONTHLY' ? $rrule['BYMONTHDAY'] : '', @@ -292,11 +297,11 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#field_suffix' => $bymonthday_suffix, ); - $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTH'] = array( + $monthly_on_day_bymonthday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $MONTHLY_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $monthly_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -304,45 +309,45 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#suffix' => '
    ', ); - $MONTHLY_on_the_BYDAY_of_BYMONTH = array( + $monthly_on_the_byday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); - $MONTHLY_BYDAY_COUNT = ''; - $MONTHLY_BYDAY_DAY = ''; + $monthly_byday_count = ''; + $monthly_byday_day = ''; if (isset($rrule['BYDAY']) && !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'MONTHLY') { - $MONTHLY_BYDAY_COUNT = substr($rrule['BYDAY'][0], 0, -2); - $MONTHLY_BYDAY_DAY = substr($rrule['BYDAY'][0], -2);; + $monthly_byday_count = substr($rrule['BYDAY'][0], 0, -2); + $monthly_byday_day = substr($rrule['BYDAY'][0], -2);; } list($byday_count_title, $byday_day_title) = explode('@byday', t('On the @byday of', array(), array('context' => 'Date repeat'))); - $MONTHLY_on_the_BYDAY_of_BYMONTH['BYDAY_COUNT'] = array( + $monthly_on_the_byday_of_bymonth['BYDAY_COUNT'] = array( '#type' => 'select', '#title' => $byday_count_title, - '#default_value' => !empty($MONTHLY_BYDAY_COUNT) ? $MONTHLY_BYDAY_COUNT : '', + '#default_value' => !empty($monthly_byday_count) ? $monthly_byday_count : '', '#options' => date_order_translated(), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $MONTHLY_on_the_BYDAY_of_BYMONTH['BYDAY_DAY'] = array( + $monthly_on_the_byday_of_bymonth['BYDAY_DAY'] = array( '#type' => 'select', '#title' => $byday_day_title, '#title_display' => 'after', - '#default_value' => !empty($MONTHLY_BYDAY_DAY) ? $MONTHLY_BYDAY_DAY : '', + '#default_value' => !empty($monthly_byday_day) ? $monthly_byday_day : '', '#options' => date_repeat_dow_day_options(TRUE), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $MONTHLY_on_the_BYDAY_of_BYMONTH['BYMONTH'] = array( + $monthly_on_the_byday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $MONTHLY_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $monthly_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -361,31 +366,31 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), '#attributes' => array('class' => array('date-repeat-radios clearfix')), - '#default_value' => $MONTHLY_day_month_default, + '#default_value' => $monthly_day_month_default, '#options' => array( 'BYMONTHDAY_BYMONTH' => t('On day ... of ...'), 'BYDAY_BYMONTH' => t('On the ... of ...'), ), - 'BYMONTHDAY_BYMONTH_child' => $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH, - 'BYDAY_BYMONTH_child' => $MONTHLY_on_the_BYDAY_of_BYMONTH, + 'BYMONTHDAY_BYMONTH_child' => $monthly_on_day_bymonthday_of_bymonth, + 'BYDAY_BYMONTH_child' => $monthly_on_the_byday_of_bymonth, '#div_classes' => array( 'date-repeat-radios-item date-clear clearfix bymonthday-bymonth', 'date-repeat-radios-item date-clear clearfix byday-bymonth', ), ); - $YEARLY_day_month_default = 'BYMONTHDAY_BYMONTH'; + $yearly_day_month_default = 'BYMONTHDAY_BYMONTH'; if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'YEARLY' && !empty($rrule['BYDAY'])) { - $YEARLY_day_month_default = 'BYDAY_BYMONTH'; + $yearly_day_month_default = 'BYDAY_BYMONTH'; } - $YEARLY_on_day_BYMONTHDAY_of_BYMONTH = array( + $yearly_on_day_bymonthday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); list($bymonthday_title, $bymonthday_suffix) = explode('@bymonthday', t('On day @bymonthday of', array(), array('context' => 'Date repeat'))); - $YEARLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTHDAY'] = array( + $yearly_on_day_bymonthday_of_bymonth['BYMONTHDAY'] = array( '#type' => 'select', '#title' => $bymonthday_title, '#default_value' => !empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] === 'YEARLY' ? $rrule['BYMONTHDAY'] : '', @@ -396,11 +401,11 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#field_suffix' => $bymonthday_suffix, ); - $YEARLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTH'] = array( + $yearly_on_day_bymonthday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $YEARLY_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $yearly_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -408,45 +413,45 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#suffix' => '
    ', ); - $YEARLY_on_the_BYDAY_of_BYMONTH = array( + $yearly_on_the_byday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); - $YEARLY_BYDAY_COUNT = ''; - $YEARLY_BYDAY_DAY = ''; + $yearly_byday_count = ''; + $yearly_byday_day = ''; if (isset($rrule['BYDAY']) && !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'YEARLY') { - $YEARLY_BYDAY_COUNT = substr($rrule['BYDAY'][0], 0, -2); - $YEARLY_BYDAY_DAY = substr($rrule['BYDAY'][0], -2);; + $yearly_byday_count = substr($rrule['BYDAY'][0], 0, -2); + $yearly_byday_day = substr($rrule['BYDAY'][0], -2);; } list($byday_count_title, $byday_day_title) = explode('@byday', t('On the @byday of', array(), array('context' => 'Date repeat'))); - $YEARLY_on_the_BYDAY_of_BYMONTH['BYDAY_COUNT'] = array( + $yearly_on_the_byday_of_bymonth['BYDAY_COUNT'] = array( '#type' => 'select', '#title' => $byday_count_title, - '#default_value' => !empty($YEARLY_BYDAY_COUNT) ? $YEARLY_BYDAY_COUNT : '', + '#default_value' => !empty($yearly_byday_count) ? $yearly_byday_count : '', '#options' => date_order_translated(), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $YEARLY_on_the_BYDAY_of_BYMONTH['BYDAY_DAY'] = array( + $yearly_on_the_byday_of_bymonth['BYDAY_DAY'] = array( '#type' => 'select', '#title' => $byday_day_title, '#title_display' => 'after', - '#default_value' => !empty($YEARLY_BYDAY_DAY) ? $YEARLY_BYDAY_DAY : '', + '#default_value' => !empty($yearly_byday_day) ? $yearly_byday_day : '', '#options' => date_repeat_dow_day_options(TRUE), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $YEARLY_on_the_BYDAY_of_BYMONTH['BYMONTH'] = array( + $yearly_on_the_byday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $YEARLY_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $yearly_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -465,13 +470,13 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), '#attributes' => array('class' => array('date-repeat-radios clearfix')), - '#default_value' => $YEARLY_day_month_default, + '#default_value' => $yearly_day_month_default, '#options' => array( 'BYMONTHDAY_BYMONTH' => t('On day ... of ...'), 'BYDAY_BYMONTH' => t('On the ... of ...'), ), - 'BYMONTHDAY_BYMONTH_child' => $YEARLY_on_day_BYMONTHDAY_of_BYMONTH, - 'BYDAY_BYMONTH_child' => $YEARLY_on_the_BYDAY_of_BYMONTH, + 'BYMONTHDAY_BYMONTH_child' => $yearly_on_day_bymonthday_of_bymonth, + 'BYDAY_BYMONTH_child' => $yearly_on_the_byday_of_bymonth, '#div_classes' => array( 'date-repeat-radios-item date-clear clearfix bymonthday-bymonth', 'date-repeat-radios-item date-clear clearfix byday-bymonth', @@ -482,7 +487,7 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { $count_form_element = array( '#type' => 'textfield', '#title' => t('Count', array(), array('context' => 'Date repeat')), - '#default_value' => $COUNT, + '#default_value' => $count, '#element_validate' => array('element_validate_integer_positive'), '#attributes' => array('placeholder' => array('#')), '#prefix' => $prefix, @@ -499,21 +504,26 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#type' => $element['#date_repeat_widget'], '#title' => t('Until', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => $UNTIL, - '#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', + '#default_value' => $until, + '#date_format' => !empty($element['#date_format']) ? + date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', '#date_timezone' => $timezone, '#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(), '#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3', - '#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within', + '#date_label_position' => !empty($element['#date_label_position']) ? + $element['#date_label_position'] : 'within', '#date_flexible' => 0, ), 'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']), 'all_day' => array('#type' => 'hidden', '#value' => 1), - 'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))), + 'granularity' => array( + '#type' => 'hidden', + '#value' => serialize(array('year', 'month', 'day')), + ), ); $range_of_repeat_default = 'COUNT'; - if (!empty($UNTIL)) { + if (!empty($until)) { $range_of_repeat_default = 'UNTIL'; } $element['range_of_repeat'] = array( @@ -528,7 +538,7 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'NONE'), ), ), - '#default_value' => $range_of_repeat_default, + '#default_value' => $range_of_repeat_default, '#options' => array( 'COUNT' => t('Count'), 'UNTIL' => t('Until'), @@ -544,7 +554,8 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { $parents = $element['#array_parents']; $instance = implode('-', $parents); - // Make sure this will work right either in the normal form or in an ajax callback from the 'Add more' button. + // Make sure this will work right either in the normal + // form or in an ajax callback from the 'Add more' button. if (empty($form_state['num_exceptions'][$instance])) { $form_state['num_exceptions'][$instance] = count($exceptions); } @@ -576,33 +587,48 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), ); - for ($i = 0; $i < max($form_state['num_exceptions'][$instance], 1) ; $i++) { - $EXCEPT = ''; + for ($i = 0; $i < max($form_state['num_exceptions'][$instance], 1); $i++) { + $except = ''; if (!empty($exceptions[$i]['datetime'])) { $ex_date = new DateObject($exceptions[$i]['datetime'], $exceptions[$i]['tz']); date_timezone_set($ex_date, timezone_open($timezone)); - $EXCEPT = date_format($ex_date, DATE_FORMAT_DATETIME); + $except = date_format($ex_date, DATE_FORMAT_DATETIME); + } + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); } $element['exceptions']['EXDATE'][$i] = array( '#tree' => TRUE, 'datetime' => array( '#name' => 'exceptions|' . $instance, '#type' => $element['#date_repeat_widget'], - '#default_value' => $EXCEPT, - '#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(), - '#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', + '#default_value' => $except, + '#date_timezone' => !empty($element['#date_timezone']) ? + $element['#date_timezone'] : date_default_timezone(), + '#date_format' => $date_format, '#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(), '#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3', '#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within', '#date_flexible' => 0, - ), - 'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']), - 'all_day' => array('#type' => 'hidden', '#value' => 1), - 'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))), - ); + ), + 'tz' => array( + '#type' => 'hidden', + '#value' => $element['#date_timezone'], + ), + 'all_day' => array( + '#type' => 'hidden', + '#value' => 1, + ), + 'granularity' => array( + '#type' => 'hidden', + '#value' => serialize(array('year', 'month', 'day')), + ), + ); } - // collect additions in the same way as exceptions - implements RDATE. + // Collect additions in the same way as exceptions - implements RDATE. if (empty($form_state['num_additions'][$instance])) { $form_state['num_additions'][$instance] = count($additions); } @@ -634,30 +660,45 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), ); - for ($i = 0; $i < max($form_state['num_additions'][$instance], 1) ; $i++) { - $RDATE = ''; + for ($i = 0; $i < max($form_state['num_additions'][$instance], 1); $i++) { + $r_date = ''; if (!empty($additions[$i]['datetime'])) { $rdate = new DateObject($additions[$i]['datetime'], $additions[$i]['tz']); date_timezone_set($rdate, timezone_open($timezone)); - $RDATE = date_format($rdate, DATE_FORMAT_DATETIME); + $r_date = date_format($rdate, DATE_FORMAT_DATETIME); + } + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); } $element['additions']['RDATE'][$i] = array( '#tree' => TRUE, 'datetime' => array( '#type' => $element['#date_repeat_widget'], '#name' => 'additions|' . $instance, - '#default_value' => $RDATE, - '#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(), - '#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', + '#default_value' => $r_date, + '#date_timezone' => !empty($element['#date_timezone']) ? + $element['#date_timezone'] : date_default_timezone(), + '#date_format' => $date_format, '#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(), '#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3', '#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within', '#date_flexible' => 0, - ), - 'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']), - 'all_day' => array('#type' => 'hidden', '#value' => 1), - 'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))), - ); + ), + 'tz' => array( + '#type' => 'hidden', + '#value' => $element['#date_timezone'], + ), + 'all_day' => array( + '#type' => 'hidden', + '#value' => 1, + ), + 'granularity' => array( + '#type' => 'hidden', + '#value' => serialize(array('year', 'month', 'day')), + ), + ); } $element['exceptions']['exceptions_add'] = array( @@ -687,6 +728,9 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { return $element; } +/** + * Add callback to date repeat. + */ function date_repeat_add_exception_callback($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $button_key = array_pop($parents); @@ -694,6 +738,9 @@ function date_repeat_add_exception_callback($form, &$form_state) { return $element; } +/** + * Add addition callback to date repeat. + */ function date_repeat_add_addition_callback($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $button_key = array_pop($parents); @@ -701,6 +748,9 @@ function date_repeat_add_addition_callback($form, &$form_state) { return $element; } +/** + * Add exception to date repeat. + */ function date_repeat_add_exception($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $instance = implode('-', array_slice($parents, 0, count($parents) - 2)); @@ -708,6 +758,9 @@ function date_repeat_add_exception($form, &$form_state) { $form_state['rebuild'] = TRUE; } +/** + * Add addition to date repeat. + */ function date_repeat_add_addition($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $instance = implode('-', array_slice($parents, 0, count($parents) - 2)); @@ -723,8 +776,14 @@ function date_repeat_merge($form_values, $element) { return $form_values; } if (array_key_exists('exceptions', $form_values) || array_key_exists('additions', $form_values)) { - if (!array_key_exists('exceptions', $form_values)) $form_values['exceptions'] = array(); - if (!array_key_exists('additions', $form_values)) $form_values['additions'] = array(); + if (!array_key_exists('exceptions', $form_values)) { + $form_values['exceptions'] = array(); + } + + if (!array_key_exists('additions', $form_values)) { + $form_values['additions'] = array(); + } + $form_values = array_merge($form_values, (array) $form_values['exceptions'], (array) $form_values['additions']); unset($form_values['exceptions']); unset($form_values['additions']); @@ -738,18 +797,22 @@ function date_repeat_merge($form_values, $element) { case 'INTERVAL': $form_values['INTERVAL'] = $form_values['daily']['INTERVAL_child']; break; + case 'every_weekday': $form_values['BYDAY'] = array('MO', 'TU', 'WE', 'TH', 'FR'); break; + case 'every_mo_we_fr': $form_values['BYDAY'] = array('MO', 'WE', 'FR'); break; + case 'every_tu_th': $form_values['BYDAY'] = array('TU', 'TH'); break; } } break; + case 'WEEKLY': if (array_key_exists('weekly', $form_values)) { $form_values = array_merge($form_values, (array) $form_values['weekly']); @@ -758,12 +821,14 @@ function date_repeat_merge($form_values, $element) { } } break; + case 'MONTHLY': if (array_key_exists('monthly', $form_values)) { switch ($form_values['monthly']['day_month']) { case 'BYMONTHDAY_BYMONTH': $form_values['monthly'] = array_merge($form_values['monthly'], (array) $form_values['monthly']['BYMONTHDAY_BYMONTH_child']); break; + case 'BYDAY_BYMONTH': $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY'] = $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY_COUNT'] . $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY_DAY']; $form_values['monthly'] = array_merge($form_values['monthly'], (array) $form_values['monthly']['BYDAY_BYMONTH_child']); @@ -783,12 +848,14 @@ function date_repeat_merge($form_values, $element) { } } break; + case 'YEARLY': if (array_key_exists('yearly', $form_values)) { switch ($form_values['yearly']['day_month']) { case 'BYMONTHDAY_BYMONTH': $form_values['yearly'] = array_merge($form_values['yearly'], (array) $form_values['yearly']['BYMONTHDAY_BYMONTH_child']); break; + case 'BYDAY_BYMONTH': $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY'] = $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY_COUNT'] . $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY_DAY']; $form_values['yearly'] = array_merge($form_values['yearly'], (array) $form_values['yearly']['BYDAY_BYMONTH_child']); @@ -808,6 +875,7 @@ function date_repeat_merge($form_values, $element) { } } break; + default: break; } @@ -823,6 +891,7 @@ function date_repeat_merge($form_values, $element) { case 'COUNT': $form_values['COUNT'] = $form_values['count_child']; break; + case 'UNTIL': $form_values['UNTIL'] = $form_values['until_child']; break; @@ -832,14 +901,23 @@ function date_repeat_merge($form_values, $element) { unset($form_values['count_child']); unset($form_values['until_child']); - if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) unset($form_values['BYDAY']['']); - if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) unset($form_values['BYMONTH']['']); - if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) unset($form_values['BYMONTHDAY']['']); + if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) { + unset($form_values['BYDAY']['']); + } + + if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) { + unset($form_values['BYMONTH']['']); + } + + if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) { + unset($form_values['BYMONTHDAY']['']); + } if (array_key_exists('UNTIL', $form_values) && is_array($form_values['UNTIL']['datetime'])) { $function = $element['#date_repeat_widget'] . '_input_date'; $until_element = $element; - $until_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; + $until_element['#date_format'] = !empty($element['#date_format']) ? + date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; $date = $function($until_element, $form_values['UNTIL']['datetime']); $form_values['UNTIL']['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : ''; } @@ -849,9 +927,14 @@ function date_repeat_merge($form_values, $element) { if (array_key_exists('EXDATE', $form_values) && is_array($form_values['EXDATE'])) { $function = $element['#date_repeat_widget'] . '_input_date'; $exdate_element = $element; + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); + } foreach ($form_values['EXDATE'] as $delta => $value) { if (is_array($value['datetime'])) { - $exdate_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; + $exdate_element['#date_format'] = $date_format; $date = $function($exdate_element, $form_values['EXDATE'][$delta]['datetime']); $form_values['EXDATE'][$delta]['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : ''; } @@ -864,9 +947,14 @@ function date_repeat_merge($form_values, $element) { if (array_key_exists('RDATE', $form_values) && is_array($form_values['RDATE'])) { $function = $element['#date_repeat_widget'] . '_input_date'; $rdate_element = $element; + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); + } foreach ($form_values['RDATE'] as $delta => $value) { if (is_array($value['datetime'])) { - $rdate_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; + $rdate_element['#date_format'] = $date_format; $date = $function($rdate_element, $form_values['RDATE'][$delta]['datetime']); $form_values['RDATE'][$delta]['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : ''; } @@ -910,7 +998,7 @@ function date_repeat_rrule_validate($element, &$form_state) { } /** - * Theme the exception list as a table so the buttons line up + * Theme the exception list as a table so the buttons line up. */ function theme_date_repeat_current_exceptions($vars) { $rows = $vars['rows']; @@ -920,11 +1008,14 @@ function theme_date_repeat_current_exceptions($vars) { $rows_info[] = array(drupal_render($value['action']), drupal_render($value['display'])); } } - return theme('table', array('header' => array(t('Delete'), t('Current exceptions')), 'rows' => $rows_info)); + return theme('table', array( + 'header' => array(t('Delete'), t('Current exceptions')), + 'rows' => $rows_info) + ); } - /** - * Theme the exception list as a table so the buttons line up +/** + * Theme the exception list as a table so the buttons line up. */ function theme_date_repeat_current_additions($rows = array()) { $rows_info = array(); @@ -933,7 +1024,10 @@ function theme_date_repeat_current_additions($rows = array()) { $rows_info[] = array(drupal_render($value['action']), drupal_render($value['display'])); } } - return theme('table', array('header' => array(t('Delete'), t('Current additions')), 'rows' => $rows_info)); + return theme('table', array( + 'header' => array(t('Delete'), t('Current additions')), + 'rows' => $rows_info) + ); } /** @@ -943,7 +1037,13 @@ function theme_date_repeat_rrule($vars) { $element = $vars['element']; $id = drupal_html_id('repeat-settings-fieldset'); $parents = $element['#parents']; - $selector = "{$parents[0]}[{$parents[1]}][{$parents[2]}][show_repeat_settings]"; + + $selector = $parents[0]; + for ($i = 1; $i < count($parents) - 1; $i++) { + $selector .= '[' . $parents[$i] . ']'; + } + $selector .= '[show_repeat_settings]'; + $fieldset = array( '#type' => 'item', '#title' => t('Repeat settings'), @@ -960,6 +1060,9 @@ function theme_date_repeat_rrule($vars) { return drupal_render($fieldset); } +/** + * Filter non zero values. + */ function date_repeat_filter_non_zero_value($value) { return $value !== 0; } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/tests/date_repeat_form.test b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/tests/date_repeat_form.test index 0c5460ba..22d65296 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat/tests/date_repeat_form.test +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat/tests/date_repeat_form.test @@ -25,7 +25,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { // Create and log in our privileged user. $this->privileged_user = $this->drupalCreateUser(array( - 'administer content types', 'administer nodes', 'bypass node access', 'view date repeats' + 'administer content types', 'administer nodes', 'bypass node access', 'view date repeats', 'administer fields' )); $this->drupalLogin($this->privileged_user); diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_repeat_field/date_repeat_field.devel_generate.inc b/profiles/commerce_kickstart/modules/contrib/date/date_repeat_field/date_repeat_field.devel_generate.inc index 3880e8b4..51c3708b 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_repeat_field/date_repeat_field.devel_generate.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_repeat_field/date_repeat_field.devel_generate.inc @@ -1,5 +1,5 @@ 'Repeats', - 'page callback' => 'date_repeat_field_page', - 'page arguments' => array($entity_type, $count), - 'access callback' => 'date_repeat_field_show', - 'access arguments' => array($entity_type, $count), - 'type' => MENU_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'title' => 'Repeats', + 'page callback' => 'date_repeat_field_page', + 'page arguments' => array($entity_type, $count), + 'access callback' => 'date_repeat_field_show', + 'access arguments' => array($entity_type, $count), + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); } } @@ -91,13 +91,13 @@ function date_repeat_field_menu() { else { $path = $entity_type . '/%' . $entity_type; $items[$path . '/repeats'] = array( - 'title' => 'Repeats', - 'page callback' => 'date_repeat_field_page', - 'page arguments' => array($entity_type, 1), - 'access callback' => 'date_repeat_field_show', - 'access arguments' => array($entity_type, 1), - 'type' => MENU_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'title' => 'Repeats', + 'page callback' => 'date_repeat_field_page', + 'page arguments' => array($entity_type, 1), + 'access callback' => 'date_repeat_field_show', + 'access arguments' => array($entity_type, 1), + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); } } @@ -108,10 +108,12 @@ function date_repeat_field_menu() { * Implements hook_permission(). */ function date_repeat_field_permission() { - return array('view date repeats' => array( - 'title' => t('View Repeating Dates'), - 'description' => t('Allow user to see a page with all the times a date repeats.'), - )); + return array( + 'view date repeats' => array( + 'title' => t('View Repeating Dates'), + 'description' => t('Allow user to see a page with all the times a date repeats.'), + ), + ); } /** @@ -195,6 +197,9 @@ function date_repeat_field_bundles() { return $values; } +/** + * Check field is repeat. + */ function date_is_repeat_field($field, $instance = NULL) { if (is_string($field)) { $field = field_info_field($field); @@ -215,7 +220,7 @@ function date_is_repeat_field($field, $instance = NULL) { } } -/* +/** * Implements hook_date_field_insert_alter(). */ function date_repeat_field_date_field_insert_alter(&$items, $context) { @@ -239,7 +244,7 @@ function date_repeat_field_date_field_insert_alter(&$items, $context) { } } -/* +/** * Implements hook_date_field_update_alter(). */ function date_repeat_field_date_field_update_alter(&$items, $context) { @@ -279,6 +284,13 @@ function date_repeat_field_field_widget_form_alter(&$element, &$form_state, $con '#suffix' => '
  • ', '#default_value' => isset($items[$delta]['rrule']) && !empty($items[$delta]['rrule']) ? 1 : 0, ); + + // Make changes if instance is set to be rendered as a regular field. + if (!empty($instance['widget']['settings']['no_fieldset'])) { + $element['#title'] = check_plain($instance['label']); + $element['#description'] = field_filter_xss($instance['description']); + $element['#theme_wrappers'] = array('date_form_element'); + } } } } @@ -340,13 +352,14 @@ function date_repeat_field_widget_validate($element, &$form_state) { // The RRULE has already been created by this point, so go back // to the posted values to see if this was filled out. $error_field_base = implode('][', $element['#parents']); - $error_field_until = $error_field_base . '][rrule][until_child][datetime]['; + $error_field_until = $error_field_base . '][rrule][until_child][datetime]['; if (!empty($item['rrule']) && $rrule_values['range_of_repeat'] === 'UNTIL' && empty($rrule_values['UNTIL']['datetime'])) { switch ($instance['widget']['type']) { case 'date_text': case 'date_popup': form_set_error($error_field_until . 'date', t("Missing value in 'Range of repeat'. (UNTIL).", array(), array('context' => 'Date repeat'))); break; + case 'date_select': form_set_error($error_field_until . 'year', t("Missing value in 'Range of repeat': Year (UNTIL)", array(), array('context' => 'Date repeat'))); form_set_error($error_field_until . 'month', t("Missing value in 'Range of repeat': Month (UNTIL)", array(), array('context' => 'Date repeat'))); @@ -382,8 +395,9 @@ function date_repeat_field_widget_validate($element, &$form_state) { // We only collect a date for UNTIL, but we need it to be inclusive, // so force it to a full datetime element at the last possible second of the day. if (!empty($rrule_values['UNTIL'])) { + $gran = array('year', 'month', 'day', 'hour', 'minute', 'second'); $rrule_values['UNTIL']['datetime'] .= ' 23:59:59'; - $rrule_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); + $rrule_values['UNTIL']['granularity'] = serialize(drupal_map_assoc($gran)); $rrule_values['UNTIL']['all_day'] = 0; } $value = date_repeat_build_dates($rrule, $rrule_values, $field, $item); @@ -418,9 +432,10 @@ function date_repeat_after_build(&$element, &$form_state) { * Pass in either the RRULE or the $form_values array for the RRULE, * whichever is missing will be created when needed. */ +// @codingStandardsIgnoreStart function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) { - - include_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc'); +// @codingStandardsIgnoreEnd + include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc'; $field_name = $field['field_name']; if (empty($rrule)) { @@ -497,8 +512,9 @@ function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $i 'offset2' => date_offset_get($date_end), 'timezone' => $timezone, 'rrule' => $rrule, - ); + ); } + return $value; } @@ -681,9 +697,8 @@ function date_repeat_field_date_field_widget_settings_form_alter(&$form, $contex '#title' => t('Repeat display', array(), array('context' => 'Date repeat')), '#description' => t("Should the repeat options form start out expanded or collapsed? Set to 'Collapsed' to make those options less obtrusive.", array(), array('context' => 'Date repeat')), '#fieldset' => 'date_format', - ); + ); } - } /** diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.change_type.inc b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.change_type.inc index cad2cafd..041c5dea 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.change_type.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.change_type.inc @@ -28,10 +28,14 @@ function date_tools_change_type_form() { // Get the available date fields. foreach ($fields as $field_name => $field) { if ($field['type'] == 'date' || $field['type'] == 'datestamp' || $field['type'] == 'datetime') { - $date_options[$labels[$field['type']]][$field_name] = t('Field @label (@field_name)', array('@label' => $field['widget']['label'], '@field_name' => $field_name, '@type' => $labels[$field['type']])); + $date_options[$labels[$field['type']]][$field_name] = t('Field @label (@field_name)', array( + '@label' => $field['widget']['label'], + '@field_name' => $field_name, + '@type' => $labels[$field['type']] + )); } } - if (sizeof($date_options) < 1) { + if (count($date_options) < 1) { drupal_set_message(t('There are no date fields in this database.')); return $form; } @@ -142,26 +146,31 @@ function date_tools_change_type_form_submit($form, &$form_state) { case 'datestamp': $new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column']; break; + case 'datetime': $new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column']; break; } break; + case 'datestamp': switch ($new_type) { case 'date': $new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column']; break; + case 'datetime': $new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column']; break; } break; + case 'datetime': switch ($new_type) { case 'date': $new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column']; break; + case 'datestamp': $new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column']; break; @@ -178,5 +187,9 @@ function date_tools_change_type_form_submit($form, &$form_state) { db_query($sql); db_query("DROP TABLE {" . $temp_table . "}"); - drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array('@field_name' => $field['widget']['label'], '@old_type' => $labels[$old_type], '@new_type' => $labels[$new_type]))); + drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array( + '@field_name' => $field['widget']['label'], + '@old_type' => $labels[$old_type], + '@new_type' => $labels[$new_type] + ))); } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.info b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.info index dbcff7d9..04bfb03f 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.info @@ -6,9 +6,9 @@ core = 7.x configure = admin/config/date/tools files[] = tests/date_tools.test -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.module b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.module index a7ac5f1e..997808df 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.module @@ -31,7 +31,7 @@ function date_tools_help($section, $arg) { */ function date_tools_permission() { return array( - 'administer date tools' => array( + 'administer date tools' => array( 'title' => t('Administer date tools'), ), ); @@ -68,6 +68,7 @@ function date_tools_menu() { 'file' => 'date_tools.wizard.inc', ); + // @codingStandardsIgnoreStart /** $items['admin/config/date/tools/change'] = array( 'title' => 'Change type', @@ -79,18 +80,18 @@ function date_tools_menu() { 'file' => 'date_tools.change_type.inc', ); */ + // @codingStandardsIgnoreEnd return $items; } /** - * Main Date Tools page + * Main Date Tools page. */ function date_tools_page() { $content = ''; - $content .= t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and related calendar. ', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); - + $content .= t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and related calendar.', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); return $content; } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.wizard.inc b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.wizard.inc index 14bc2759..94a130a4 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.wizard.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_tools/date_tools.wizard.inc @@ -6,6 +6,8 @@ */ /** + * Implements hook_form(). + * * @todo. */ function date_tools_wizard_form() { @@ -59,7 +61,10 @@ function date_tools_wizard_form() { $form['field']['repeat'] = array( '#type' => 'select', '#default_value' => 0, - '#options' => array(0 => t('No'), 1 => t('Yes')), + '#options' => array( + 0 => t('No'), + 1 => t('Yes'), + ), '#title' => t('Show repeating date options'), '#access' => module_exists('date_repeat_field'), ); @@ -72,7 +77,11 @@ function date_tools_wizard_form() { $form['field']['advanced']['todate'] = array( '#type' => 'select', '#default_value' => 'optional', - '#options' => array('' => t('Never'), 'optional' => t('Optional'), 'required' => t('Required')), + '#options' => array( + '' => t('Never'), + 'optional' => t('Optional'), + 'required' => t('Required'), + ), '#title' => t('End Date'), '#description' => t("Display a matching second date field as a 'End date'."), ); @@ -106,7 +115,10 @@ function date_tools_wizard_form() { $form['calendar'] = array( '#type' => 'select', '#default_value' => module_exists('calendar'), - '#options' => array(0 => t('No'), 1 => t('Yes')), + '#options' => array( + 0 => t('No'), + 1 => t('Yes'), + ), '#title' => t('Create a calendar for this date field'), '#access' => module_exists('calendar'), ); @@ -119,13 +131,26 @@ function date_tools_wizard_form() { } /** + * Form validate. + * * @todo. */ function date_tools_wizard_form_validate(&$form, &$form_state) { $bundle = $form_state['values']['bundle']; $field_name = 'field_' . $form_state['values']['field_name']; - $existing_type = db_query("SELECT type FROM {node_type} WHERE type=:bundle", array(':bundle' => $bundle))->fetchField(); - $existing_instance = db_query("SELECT field_name FROM {field_config_instance} WHERE field_name=:field_name AND bundle=:bundle AND entity_type=:entity_type", array(':field_name' => $field_name, ':bundle' => $bundle, ':entity_type' => 'node'))->fetchField(); + + $args = array( + ':field_name' => $field_name, + ':bundle' => $bundle, + ':entity_type' => 'node', + ); + + $query = "SELECT type FROM {node_type} WHERE type=:bundle"; + $existing_type = db_query($query, array(':bundle' => $args[':bundle']))->fetchField(); + + $query = "SELECT field_name FROM {field_config_instance} WHERE field_name=:field_name AND bundle=:bundle AND entity_type=:entity_type"; + $existing_instance = db_query($query, $args)->fetchField(); + if ($existing_type) { drupal_set_message(t('This content type name already exists, adding new field to existing content type.')); } @@ -147,6 +172,8 @@ function date_tools_wizard_form_validate(&$form, &$form_state) { } /** + * Form submit. + * * @todo. */ function date_tools_wizard_form_submit(&$form, &$form_state) { @@ -161,6 +188,8 @@ function date_tools_wizard_form_submit(&$form, &$form_state) { } /** + * Wizard build. + * * @todo. */ function date_tools_wizard_build($form_values) { @@ -201,7 +230,7 @@ function date_tools_wizard_build($form_values) { 'timezone_db' => date_get_timezone_db($tz_handling), 'repeat' => $repeat, 'todate' => !empty($todate) ? $todate : 'optional', - ), + ), ); $instance = array( 'entity_type' => 'node', @@ -275,6 +304,8 @@ function date_tools_wizard_build($form_values) { } /** + * Includes handler. + * * @todo. */ function date_tools_wizard_include() { @@ -285,6 +316,8 @@ function date_tools_wizard_include() { } /** + * Implements hook_field_types(). + * * @todo. */ function date_tools_wizard_field_types() { @@ -296,6 +329,7 @@ function date_tools_wizard_field_types() { } /** + * Implements hook_widget_types(). * @todo. */ function date_tools_wizard_widget_types() { @@ -309,6 +343,8 @@ function date_tools_wizard_widget_types() { } /** + * Tz handler. + * * @todo. */ function date_tools_wizard_tz_handling() { @@ -317,6 +353,8 @@ function date_tools_wizard_tz_handling() { } /** + * Create date tools wizard content type. + * * @todo. */ function date_tools_wizard_create_content_type($name, $bundle, $description, $type_settings = array()) { @@ -332,8 +370,7 @@ function date_tools_wizard_create_content_type($name, $bundle, $description, $ty 'body_label' => 'Body', 'min_word_count' => '0', 'help' => '', - 'node_options' => - array( + 'node_options' => array( 'status' => 1, 'promote' => 1, 'sticky' => 0, @@ -374,8 +411,10 @@ function date_tools_wizard_create_content_type($name, $bundle, $description, $ty 'weight' => -4, 'module' => 'text', ), - 'settings' => array('display_summary' => TRUE), - 'display' => array( + 'settings' => array( + 'display_summary' => TRUE, + ), + 'display' => array( 'default' => array( 'label' => 'hidden', 'type' => 'text_default', diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_tools/tests/date_tools.test b/profiles/commerce_kickstart/modules/contrib/date/date_tools/tests/date_tools.test index 47c05642..07af4a3d 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_tools/tests/date_tools.test +++ b/profiles/commerce_kickstart/modules/contrib/date/date_tools/tests/date_tools.test @@ -28,7 +28,7 @@ class DateToolsTestCase extends DrupalWebTestCase { // Create and log in our privileged user. $this->privileged_user = $this->drupalCreateUser( - array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools') + array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools', 'administer fields') ); $this->drupalLogin($this->privileged_user); diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.info b/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.info index 0f2d7ec3..68b32cc2 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.info +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.info @@ -12,9 +12,9 @@ files[] = includes/date_views_filter_handler_simple.inc files[] = includes/date_views.views.inc files[] = includes/date_views_plugin_pager.inc -; Information added by Drupal.org packaging script on 2014-07-29 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1406653438" +datestamp = "1491562090" diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.install b/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.install index c9697b2d..e1d2e3ef 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.install +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.install @@ -28,3 +28,27 @@ function date_views_uninstall() { variable_del('date_views_week_format_with_year'); variable_del('date_views_week_format_without_year'); } + +/** + * Set default date views variables. + */ +function date_views_update_7200() { + if (!variable_get('date_views_month_format_with_year', FALSE)) { + variable_set('date_views_month_format_with_year', 'F Y'); + } + if (!variable_get('date_views_month_format_without_year', FALSE)) { + variable_set('date_views_month_format_without_year', 'F'); + } + if (!variable_get('date_views_day_format_with_year', FALSE)) { + variable_set('date_views_day_format_with_year', 'l, F j, Y'); + } + if (!variable_get('date_views_day_format_without_year', FALSE)) { + variable_set('date_views_day_format_without_year', 'l, F j'); + } + if (!variable_get('date_views_week_format_with_year', FALSE)) { + variable_set('date_views_week_format_with_year', 'F j, Y'); + } + if (!variable_get('date_views_week_format_without_year', FALSE)) { + variable_set('date_views_week_format_without_year', 'F j'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.module b/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.module index 04bad091..c050e56e 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.module +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/date_views.module @@ -1,5 +1,9 @@ 'Configure settings for date views.', 'page callback' => 'drupal_get_form', 'page arguments' => array('date_views_settings'), - 'access arguments' => array('administer site configuration '), + 'access arguments' => array('administer site configuration'), 'type' => MENU_LOCAL_TASK, ); @@ -86,13 +90,30 @@ function date_views_theme() { 'file' => 'theme.inc', 'path' => "$path/theme", ); - return array( - 'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)), - 'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'), - 'date_calendar_day' => $base + array('variables' => array('date' => NULL)), + return array( + 'date_nav_title' => $base + array( + 'variables' => array( + 'granularity' => NULL, + 'view' => NULL, + 'link' => NULL, + 'format' => NULL, + ), + ), + 'date_views_filter_form' => $base + array( + 'template' => 'date-views-filter-form', + 'render element' => 'form', + ), + 'date_calendar_day' => $base + array( + 'variables' => array( + 'date' => NULL, + ), + ), 'date_views_pager' => $base + array( - 'variables' => array('plugin' => NULL, 'input' => NULL), + 'variables' => array( + 'plugin' => NULL, + 'input' => NULL, + ), // Register a pattern so that it can work like all views templates. 'pattern' => 'date_views_pager__', 'template' => 'date-views-pager', @@ -100,6 +121,9 @@ function date_views_theme() { ); } +/** + * Implements hook_views_api(). + */ function date_views_views_api() { return array( 'api' => 3, @@ -119,7 +143,7 @@ function date_views_views_fetch_fields($base, $type) { } /** - * Identify all potential date/timestamp fields and cache the data. + * Identify all potential date/timestamp fields and cache the data. */ function date_views_fields($base = 'node', $reset = FALSE) { static $fields = array(); @@ -141,8 +165,8 @@ function date_views_fields($base = 'node', $reset = FALSE) { /** * Implements hook_date_views_entities(). - * Map extra Views tables to the entity that holds its date fields, - * needed for Views tables other than the primary tables identified in entity_info(). + * + * Map extra Views tables to the entity that holds its date fields, needed for Views tables other than the primary tables identified in entity_info(). */ function date_views_date_views_extra_tables() { return array( @@ -151,14 +175,13 @@ function date_views_date_views_extra_tables() { } /** - * Helper function to map entity types to the Views base table they use, - * to make it easier to infer the entity type from a base table. + * Helper function to map entity types to the Views base table they use, to make it easier to infer the entity type from a base table. + * + * Views has a new handler called views_handler_field_entity() that loads entities. * - * Views has a new handler called views_handler_field_entity() that loads - * entities, and you can use something like the following to get the - * entity type from a view, but not all our base tables contain the - * entity information we need, (i.e. revisions) so it won't work here - * and we resort to creating information from entity_get_info(). + * And you can use something like the following to get the entity type from a view, but not all our base tables contain the entity information we need, (i.e. revisions). + * + * So it won't work here and we resort to creating information from entity_get_info(). * * // A method to get the entity type for a base table. * $table_data = views_fetch_data($base_table); @@ -193,11 +216,7 @@ function date_views_base_tables() { /** * Implements hook_date_views_fields(). * - * All modules that create custom fields that use the - * 'views_handler_field_date' handler can provide - * additional information here about the type of - * date they create so the date can be used by - * the Date API views date argument and date filter. + * All modules that create custom fields that use the 'views_handler_field_date' handler can provide additional information here about the type of date they create so the date can be used by the Date API views date argument and date filter. */ function date_views_date_views_fields($field) { $values = array( @@ -263,12 +282,15 @@ function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_ case 'year': $args[$pos] = date_pad($view->date_info->year, 4); break; + case 'week': $args[$pos] = date_pad($view->date_info->year, 4) . '-W' . date_pad($view->date_info->week); break; + case 'day': $args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month) . '-' . date_pad($view->date_info->day); break; + default: $args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month); break; @@ -298,9 +320,14 @@ function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_ // if they use exposed filters. return url($view->get_url($args), array( 'query' => date_views_querystring($view), - 'absolute' => $absolute)); + 'absolute' => $absolute, + ) + ); } +/** + * Identifier of a date block. + */ function date_block_identifier($view) { if (!empty($view->block_identifier)) { return $view->block_identifier; @@ -311,12 +338,9 @@ function date_block_identifier($view) { /** * Implements hook_field_views_data_alter(). * - * Create a Views field for each date column we care about - * to supplement the generic 'entity_id' and 'revision_id' - * fields that are automatically created. + * Create a Views field for each date column we care about to supplement the generic 'entity_id' and 'revision_id' fields that are automatically created. * - * Also use friendlier labels to distinguish the start date - * and end date in listings (for fields that use both). + * Also use friendlier labels to distinguish the start date and end date in listings (for fields that use both). */ function date_views_field_views_data_alter(&$result, $field, $module) { if ($module == 'date') { @@ -336,8 +360,8 @@ function date_views_field_views_data_alter(&$result, $field, $module) { $result[$table][$column]['field']['is date'] = TRUE; // Not sure yet if we still need a custom field handler in D7 now that custom formatters are available. // Might still need it to handle grouping of multiple value dates. - //$result[$table][$column]['field']['handler'] = 'date_handler_field_date'; - //$result[$table][$column]['field']['add fields to query'] = TRUE; + // $result[$table][$column]['field']['handler'] = 'date_handler_field_date'; + // $result[$table][$column]['field']['add fields to query'] = TRUE; } // For filters, arguments, and sorts, determine if this column is for @@ -395,12 +419,25 @@ function date_views_field_views_data_alter(&$result, $field, $module) { // translatable string. This is a hack to get it to appear right // before 'end date' in the listing (i.e., in a non-alphabetical, // but more user friendly, order). - $result[$table][$column]['title'] = t('@label - start date (!name)', array('@label' => $label, '!name' => $field['field_name'])); - $result[$table][$column]['title short'] = t('@label - start date', array('@label' => $label)); + $result[$table][$column]['title'] = t('@label - start date (!name)', array( + '@label' => $label, + '!name' => $field['field_name'], + )); + $result[$table][$column]['title short'] = t('@label - start date', array( + '@label' => $label, + )); break; + case 'value2': - $result[$table][$column]['title'] = t('@label - end date (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $this_column)); - $result[$table][$column]['title short'] = t('@label - end date:!column', array('@label' => $label, '!column' => $this_column)); + $result[$table][$column]['title'] = t('@label - end date (!name:!column)', array( + '@label' => $label, + '!name' => $field['field_name'], + '!column' => $this_column, + )); + $result[$table][$column]['title short'] = t('@label - end date:!column', array( + '@label' => $label, + '!column' => $this_column, + )); break; } } @@ -421,18 +458,15 @@ function date_views_form_views_ui_edit_form_alter(&$form, &$form_state, $form_id } /** - * The instanceof function makes this work for any handler that was derived - * from 'views_handler_filter_date' or 'views_handler_argument_date', - * which includes core date fields like the node updated field. + * The instanceof function makes this work for any handler that was derived from 'views_handler_filter_date' or 'views_handler_argument_date', which includes core date fields like the node updated field. * - * The test for $handler->min_date tells us that this is an argument that - * not only is derived from the views date handler but also has been processed - * by the Date Views filter or argument code. -*/ + * The test for $handler->min_date tells us that this is an argument that not only is derived from the views date handler but also has been processed by the Date Views filter or argument code. + */ function date_views_handler_is_date($handler, $type = 'argument') { switch ($type) { case 'filter': return $handler instanceof views_handler_filter_date && !empty($handler->min_date); + case 'argument': return $handler instanceof views_handler_argument_date && !empty($handler->min_date); } @@ -441,8 +475,8 @@ function date_views_handler_is_date($handler, $type = 'argument') { /** * Validation hook for exposed filters that use the select widget. - * This is to ensure the the user completes all parts of the date - * not just some parts. Only needed for the select widget. + * + * This is to ensure the the user completes all parts of the date not just some parts. Only needed for the select widget. */ function date_views_select_validate(&$form, &$form_state) { // If there are no values just return. @@ -453,7 +487,7 @@ function date_views_select_validate(&$form, &$form_state) { $filled = array(); $value = drupal_array_get_nested_value($form_state['input'], $form['#parents']); foreach ($granularity as $part) { - if (!empty($value['value'][$part])) { + if (isset($value['value']) && is_numeric($value['value'][$part])) { $filled[] = $part; } } @@ -464,18 +498,23 @@ function date_views_select_validate(&$form, &$form_state) { case 'year': form_error($form['value'][$part], t('Please choose a year.'), $form_state); break; + case 'month': form_error($form['value'][$part], t('Please choose a month.'), $form_state); break; + case 'day': form_error($form['value'][$part], t('Please choose a day.'), $form_state); break; + case 'hour': form_error($form['value'][$part], t('Please choose an hour.'), $form_state); break; + case 'minute': form_error($form['value'][$part], t('Please choose a minute.'), $form_state); break; + case 'second': form_error($form['value'][$part], t('Please choose a second.'), $form_state); break; @@ -487,8 +526,7 @@ function date_views_select_validate(&$form, &$form_state) { /** * Implements hook_date_formatter_view_alter(). * - * If we are displaying a date from a view, see if we have information about - * which multiple value to display. If so, set the date_id in the entity. + * If we are displaying a date from a view, see if we have information about which multiple value to display. If so, set the date_id in the entity. */ function date_views_date_formatter_pre_view_alter(&$entity, &$variables) { // Some views have no row index. @@ -501,4 +539,4 @@ function date_views_date_formatter_pre_view_alter(&$entity, &$variables) { $entity->date_id = 'date.' . $date_item->$date_id . '.' . $field['field_name'] . '.' . $date_item->$date_delta . '.0'; } } -} \ No newline at end of file +} diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_plugin_display_attachment.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_plugin_display_attachment.inc index 94371f76..779f4d1f 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_plugin_display_attachment.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_plugin_display_attachment.inc @@ -1,6 +1,7 @@ 'date_views', // This just tells our themes are elsewhere. + // This just tells our themes are elsewhere. + 'module' => 'date_views', 'display' => array( // Display plugin for date navigation. 'date_nav' => array( @@ -83,7 +85,7 @@ function date_views_views_plugins() { } /** - * Implements hook_views_data() + * Implements hook_views_data(). */ function date_views_views_data() { $data = array(); @@ -95,12 +97,12 @@ function date_views_views_data() { $data[$base_table]['date_argument'] = array( 'group' => t('Date'), 'title' => t('Date (!base_table)', array('!base_table' => $base_table)), - 'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc). ', array('!base_table' => $base_table)), + 'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc).', array('!base_table' => $base_table)), 'argument' => array( 'handler' => 'date_views_argument_handler', 'empty field name' => t('Undated'), 'is date' => TRUE, - //'skip base' => $base_table, + // 'skip base' => $base_table, ), ); // The flexible date filter. @@ -112,7 +114,7 @@ function date_views_views_data() { 'handler' => 'date_views_filter_handler', 'empty field name' => t('Undated'), 'is date' => TRUE, - //'skip base' => $base_table, + // 'skip base' => $base_table, ), ); } @@ -140,8 +142,9 @@ function date_views_views_data_alter(&$data) { } /** - * Central function for setting up the right timezone values - * in the SQL date handler. + * Central function for setting up the right timezone values. + * + * In the SQL date handler. * * The date handler will use this information to decide if the * database value needs a timezone conversion. @@ -152,30 +155,45 @@ function date_views_views_data_alter(&$data) { */ function date_views_set_timezone(&$date_handler, &$view, $field) { switch ($field['tz_handling']) { - case 'date' : + case 'date': $date_handler->db_timezone = 'UTC'; $date_handler->local_timezone_field = $field['timezone_field']; $date_handler->offset_field = $field['offset_field']; break; + case 'none': $date_handler->db_timezone = date_default_timezone(); $date_handler->local_timezone = date_default_timezone(); break; + case 'utc': $date_handler->db_timezone = 'UTC'; $date_handler->local_timezone = 'UTC'; break; - default : + + default: $date_handler->db_timezone = 'UTC'; $date_handler->local_timezone = date_default_timezone(); break; } } +/** + * Helper function to generate a query string. + * + * @param object $view + * A View object. + * + * @param array $extra_params + * An extra parameters. + * + * @return null/string + * Return a query or NULL. + */ function date_views_querystring($view, $extra_params = array()) { $query_params = array_merge($_GET, $extra_params); // Allow NULL params to be removed from the query string. - foreach ($extra_params AS $key => $value) { + foreach ($extra_params as $key => $value) { if (!isset($value)) { unset($query_params[$key]); } diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler.inc index 61aeafc1..20eedff0 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler.inc @@ -3,12 +3,14 @@ * @file * Date API views argument handler. * This argument combines multiple date arguments into a single argument - * where all fields are controlled by the same date and can be combined with either AND or OR. + * where all fields are controlled by the same date and can be combined + * with either AND or OR. */ /** * Date API argument handler. */ +// @codingStandardsIgnoreStart class date_views_argument_handler extends date_views_argument_handler_simple { /** @@ -198,3 +200,4 @@ class date_views_argument_handler extends date_views_argument_handler_simple { } } +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler_simple.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler_simple.inc index 35f88e00..2839b959 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler_simple.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_argument_handler_simple.inc @@ -7,11 +7,13 @@ /** * Date API argument handler. */ +// @codingStandardsIgnoreStart class date_views_argument_handler_simple extends views_handler_argument_date { /** - * Get granularity and use it to create the formula and a format - * for the results. + * Get granularity. + * + * Use it to create the formula and a format for the results. */ function init(&$view, &$options) { parent::init($view, $options); @@ -29,12 +31,14 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $this->date_handler->local_timezone = date_get_timezone($field['settings']['tz_handling']); } $this->date_handler->granularity = $this->options['granularity']; - // This value needs to be initialized so it exists even if the query doesn't run. + // This value needs to be initialized so + // it exists even if the query doesn't run. $this->date_handler->placeholders = array(); $this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display'); $this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql'); - // $this->arg_format is the format the parent date handler will use to create a default argument. + // $this->arg_format is the format the parent date + // handler will use to create a default argument. $this->arg_format = $this->format(); // Identify the base table for this field. @@ -43,6 +47,9 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } + /** + * {@inheritdoc} + */ function format() { if (!empty($this->options['granularity'])) { return $this->date_handler->views_formats($this->options['granularity']); @@ -53,8 +60,9 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } /** - * Set the empty argument value to the current date, - * formatted appropriately for this argument. + * Set the empty argument value to the current date. + * + * Formatted appropriately for this argument. */ function get_default_argument($raw = FALSE) { $is_default = FALSE; @@ -85,6 +93,7 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $options = parent::option_definition(); $options['year_range'] = array('default' => '-3:+3'); $options['granularity'] = array('default' => 'month'); + $options['granularity_reset'] = array('default' => FALSE); $options['default_argument_type']['default'] = 'date'; $options['add_delta'] = array('default' => ''); $options['use_fromto'] = array('default' => ''); @@ -116,7 +125,9 @@ class date_views_argument_handler_simple extends views_handler_argument_date { '#attributes' => array('class' => array('dependent-options')), '#states' => array( 'visible' => array( - ':input[name="options[default_action]"]' => array('value' => 'summary') + ':input[name="options[default_action]"]' => array( + 'value' => 'summary', + ), ), ), ); @@ -129,23 +140,37 @@ class date_views_argument_handler_simple extends views_handler_argument_date { '#attributes' => array('class' => array('dependent-options')), '#states' => array( 'visible' => array( - ':input[name="options[title_format]"]' => array('value' => 'custom') + ':input[name="options[title_format]"]' => array( + 'value' => 'custom', + ), ), ), ); + // Get default granularity options $options = $this->date_handler->date_parts(); - unset($options['second'], $options['minute']); - $options += array('week' => t('Week', array(), array('context' => 'datetime'))); + // Add the 'week' option. + $options += array( + 'week' => t('Week', array(), array( + 'context' => 'datetime', + )), + ); + $form['granularity'] = array( '#title' => t('Granularity'), '#type' => 'radios', '#options' => $options, '#default_value' => $this->options['granularity'], - '#multiple' => TRUE, '#description' => t("Select the type of date value to be used in defaults, summaries, and navigation. For example, a granularity of 'month' will set the default date to the current month, summarize by month in summary views, and link to the next and previous month when using date navigation."), ); + $form['granularity_reset'] = array( + '#title' => t('Use granularity from argument value'), + '#type' => 'checkbox', + '#default_value' => $this->options['granularity_reset'], + '#description' => t("If the granularity of argument value is different from selected, use it from argument value."), + ); + $form['year_range'] = array( '#title' => t('Date year range'), '#type' => 'textfield', @@ -172,16 +197,18 @@ class date_views_argument_handler_simple extends views_handler_argument_date { '#default_value' => $this->options['add_delta'], '#options' => array('' => t('No'), 'yes' => t('Yes')), '#description' => t('Add an identifier to the view to show which multiple value date fields meet the filter criteria. Note: This option may introduce duplicate values into the view. Required when using multiple value fields in a Calendar or any time you want the node view of multiple value dates to display only the values that match the view filters.'), - // Only let mere mortals tweak this setting for multi-value fields + // Only let mere mortals tweak this setting for multi-value fields. '#access' => $access, ); - } + /** + * {@inheritdoc} + */ function options_validate(&$form, &$form_state) { // It is very important to call the parent function here: parent::options_validate($form, $form_state); - if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) { + if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) { form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9')); } } @@ -209,14 +236,15 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $format = !empty($this->options['title_format_custom']) && !empty($this->options['title_format_custom']) ? $this->options['title_format_custom'] : $this->date_handler->views_formats($this->options['granularity'], 'display'); $range = $this->date_handler->arg_range($this->argument); return date_format_date($range[0], 'custom', $format); - } + } /** - * Provide the argument to use to link from the summary to the next level; - * this will be called once per row of a summary, and used as part of + * Provide the argument to use to link from the summary to the next level. + * + * This will be called once per row of a summary, and used as part of * $view->get_url(). * - * @param $data + * @param object $data * The query results for the row. */ function summary_argument($data) { @@ -234,10 +262,11 @@ class date_views_argument_handler_simple extends views_handler_argument_date { */ function summary_query() { - // @TODO The summary values are computed by the database. Unless the database has - // built-in timezone handling it will use a fixed offset, which will not be - // right for all dates. The only way I can see to make this work right is to - // store the offset for each date in the database so it can be added to the base + // @TODO The summary values are computed by the database. + // Unless the database has built-in timezone handling it will use + // a fixed offset, which will not be right for all dates. + // The only way I can see to make this work right is to store the offset + // for each date in the database so it can be added to the base // date value before the database formats the result. Because this is a huge // architectural change, it won't go in until we start a new branch. $this->formula = $this->date_handler->sql_format($this->sql_format, $this->date_handler->sql_field("***table***.$this->real_field")); @@ -245,7 +274,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { // Now that our table is secure, get our formula. $formula = $this->get_formula(); - // Add the field, give it an alias that does NOT match the actual field name or grouping won't work right. + // Add the field, give it an alias that does NOT match the actual + // field name or grouping won't work right. $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field . '_summary'); $this->query->set_count_field(NULL, $formula, $this->field); @@ -254,20 +284,22 @@ class date_views_argument_handler_simple extends views_handler_argument_date { /** * Inject a test for valid date range before the regular query. + * * Override the parent query to be able to control the $group. */ function query($group_by = FALSE) { - // @TODO Not doing anything with $group_by yet, need to figure out what has to be done. + // @TODO Not doing anything with $group_by yet, + // need to figure out what has to be done. if ($this->date_forbid()) { return; } // See if we need to reset granularity based on an argument value. - // Make sure we don't try to reset to some bogus value if someone has typed in an unexpected argument. - $granularity = $this->date_handler->arg_granularity($this->argument); - if (!empty($granularity)) { + // Make sure we don't try to reset to some bogus value if someone has + // typed in an unexpected argument. + if ($this->options['granularity_reset'] && $granularity = $this->date_handler->arg_granularity($this->argument)) { $this->date_handler->granularity = $granularity; $this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display'); $this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql'); @@ -276,7 +308,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $this->ensure_my_table(); $group = !empty($this->options['date_group']) ? $this->options['date_group'] : 0; - // If requested, add the delta field to the view so we can later find the value that matched our query. + // If requested, add the delta field to the view so + // we can later find the value that matched our query. if (!empty($this->options['add_delta']) && (substr($this->real_field, -6) == '_value' || substr($this->real_field, -7) == '_value2')) { $this->query->add_field($this->table_alias, 'delta'); $real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field); @@ -291,7 +324,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $view_max_placeholder = $this->placeholder(); $this->date_handler->placeholders = array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max); - // Are we comparing this field only or the Start/End date range to the view criteria? + // Are we comparing this field only or the Start/End date range + // to the view criteria? if (!empty($this->options['use_fromto'])) { // The simple case, match the field to the view range. @@ -302,10 +336,14 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } else { - // Look for the intersection of the range of the date field with the range of the view. - // Get the Start/End values for this field. Retrieve using the original table name. - // Swap the current table name (adjusted for relationships) into the query. - // @TODO We may be able to use Views substitutions here, investigate that later. + // Look for the intersection of the range + // of the date field with the range of the view. + // Get the Start/End values for this field. + // Retrieve using the original table name. + // Swap the current table name (adjusted for relationships) + // into the query. + // @TODO We may be able to use Views substitutions here, + // investigate that later. $fields = date_views_fields($this->base_table); $fields = $fields['name']; $fromto = $fields[$this->original_table . '.' . $this->real_field]['fromto']; @@ -321,7 +359,10 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } /** - * Add a callback to determine if we have moved outside the valid date range for this argument. + * Add a callback. + * + * To determine if we have moved outside + * the valid date range for this argument. */ function date_forbid() { if (empty($this->argument)) { @@ -343,3 +384,4 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } } +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_fields.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_fields.inc index a002b088..d497ef60 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_fields.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_fields.inc @@ -5,13 +5,11 @@ */ /** - * Identify all potential date/timestamp fields. + * Identify all potential date/timestamp fields. * - * @return - * array with fieldname, type, and table. - * @see - * date_views_date_views_fields() which implements - * the hook_date_views_fields() for the core date fields. + * @return array + * An array with fieldname, type, and table. + * @see date_views_date_views_fields() */ function _date_views_fields($base = 'node') { @@ -60,7 +58,7 @@ function _date_views_fields($base = 'node') { $handler = views_get_handler($table_name, $field_name, 'filter'); $handler_name = $handler->definition['handler']; - // We don't care about anything but date handlers + // We don't care about anything but date handlers. if (empty($handler->definition['is date'])) { continue; } @@ -72,14 +70,17 @@ function _date_views_fields($base = 'node') { $field = field_info_field($handler->definition['field_name']); $is_field = TRUE; switch ($field['type']) { - case 'date': + case 'date': $sql_type = DATE_ISO; break; + case 'datestamp': break; + case 'datetime': $sql_type = DATE_DATETIME; break; + default: // If this is not a date field, nothing more to do. continue; @@ -88,7 +89,8 @@ function _date_views_fields($base = 'node') { $revision = in_array($base, array('node_revision')) ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT; $db_info = date_api_database_info($field, $revision); $name = $table_name . "." . $field_name; - $granularity = !empty($field['granularity']) ? $field['granularity'] : array('year', 'month', 'day', 'hour', 'minute', 'second'); + $grans = array('year', 'month', 'day', 'hour', 'minute', 'second'); + $granularity = !empty($field['granularity']) ? $field['granularity'] : $grans; $fromto = array( $table_name . '.' . $db_info['columns'][$table_name]['value'], diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler.inc index 0cfc7fcc..ae6944ee 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler.inc @@ -3,9 +3,11 @@ * @file * A flexible, configurable date filter. * This filter combines multiple date filters into a single filter - * where all fields are controlled by the same date and can be combined with either AND or OR. + * where all fields are controlled by the same date and can be combined + * with either AND or OR. */ +// @codingStandardsIgnoreStart class date_views_filter_handler extends date_views_filter_handler_simple { function init(&$view, &$options) { parent::init($view, $options); @@ -40,6 +42,32 @@ class date_views_filter_handler extends date_views_filter_handler_simple { $this->date_combine_conditions('op_contains'); } + function op_empty($field) { + $this->get_query_fields(); + if (empty($this->query_fields)) { + return; + } + + // Add each condition to the custom filter group. + foreach ((array) $this->query_fields as $query_field) { + $field = $query_field['field']; + $this->date_handler = $query_field['date_handler']; + + // Respect relationships when determining the table alias. + if ($field['table_name'] != $this->table || !empty($this->relationship)) { + $this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship); + } + else { + $this->related_table_alias = NULL; + } + + $table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name']; + $field_name = $table_alias . '.' . $field['field_name']; + + parent::op_empty($field_name); + } + } + /** * Combines multiple date WHERE expressions into a single WHERE expression. * @@ -64,6 +92,9 @@ class date_views_filter_handler extends date_views_filter_handler_simple { if ($field['table_name'] != $this->table || !empty($this->relationship)) { $this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship); } + else { + $this->related_table_alias = NULL; + } $table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name']; $field_name = $table_alias . '.' . $field['field_name']; @@ -179,3 +210,4 @@ class date_views_filter_handler extends date_views_filter_handler_simple { } } } +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler_simple.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler_simple.inc index d41f30b6..4fa4c409 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler_simple.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_filter_handler_simple.inc @@ -1,9 +1,11 @@ $operator_values); } if (!isset($form_state['input'][$identifier][$prefix])) { - $form_state['input'][$identifier][$prefix] = $this->value[$prefix]; + // Ensure these exist. + foreach ($granularity as $key) { + $form_state['input'][$identifier][$prefix][$key] = NULL; + } } } else { @@ -530,3 +535,4 @@ class date_views_filter_handler_simple extends views_handler_filter_date { } } +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc index f9594a72..1addd200 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc @@ -2,50 +2,74 @@ /** * @file * Date pager. - * Works with a Date argument, the argument filters the view and the pager provides back/next navigation. + * Works with a Date argument, the argument filters + * the view and the pager provides back/next navigation. * * USER NOTES: * * To use this, add a pager to a view, and choose the option to 'Page by date'. * There are several settings: - * - The pager id: Set an id to be used as the identifier in the url for pager values, defaults to 'date'. - * - Pager position: Choose whether to display the date pager above, below, or both above and below the content. - * - Link format: Choose whether the pager links will be in the simple 'calendar/2011-12' format or the - * more complex 'calendar/?date=2011-12' pager format. The second one is more likely to work correctly - * if the pager is used in blocks and panels. + * - The pager id: Set an id to be used as the identifier + * in the url for pager values, defaults to 'date'. + * - Pager position: Choose whether to display the date + * pager above, below, or both above and below the content. + * - Link format: Choose whether the pager links will be in t + * he simple 'calendar/2011-12' format or the + * more complex 'calendar/?date=2011-12' pager format. + * The second one is more likely to work correctly + * if the pager is used in blocks and panels. * - * The pager works in combination with a Date argument and it will use the date fields and granularity - * set in that argument to create its back/next links. If the view has no Date argument, the pager can - * do nothing. The argument can either be a 'Date' argument that lets you select one or more date fields - * in the argument, or the simple 'Content' argument for an individual date field. It must be an + * The pager works in combination with a Date argument + * and it will use the date fields and granularity + * set in that argument to create its back/next links. + * If the view has no Date argument, the pager can + * do nothing. The argument can either be a 'Date' argument + * that lets you select one or more date fields + * in the argument, or the simple 'Content' argument for an + * individual date field. It must be an * argument that uses the date argument handler. * * DEVELOPER NOTES * - * The pager could technically create a query of its own rather than depending on the date argument to - * set the query, but it has only a limited set of tools to work with because it is a plugin, not a handler: - * it has no knowledge about relationships, it cannot use the ensure_my_table() function, - * plugins are not even invoked in pre_query(), so can't do anything there. + * The pager could technically create a query of its own rather + * than depending on the date argument to + * set the query, but it has only a limited set of tools to work + * with because it is a plugin, not a handler: + * it has no knowledge about relationships, it cannot use the + * ensure_my_table() function, plugins are not even invoked in pre_query(), + * so can't do anything there. * - * My conclusion was that the date pager simply is not powerful enough to create its own queries for - * date fields, which require very complex queries. Instead, we can combine this with a date argument and - * let the argument create the query and let the pager just provide the back/next links. If there is no + * My conclusion was that the date pager simply + * is not powerful enough to create its own queries for + * date fields, which require very complex queries. + * Instead, we can combine this with a date argument and + * let the argument create the query and let the pager + * just provide the back/next links. If there is no * date argument, the pager will do nothing. * - * There are still other problems. The pager is not even initialized until after all the handlers - * have created their queries, so it has no chance to alter values ahead of that. And the argument - * has no knowledge of the pager, so it can't check for pager values before the query is created. + * There are still other problems. The pager is not even + * initialized until after all the handlers + * have created their queries, so it has no chance + * to alter values ahead of that. And the argument + * has no knowledge of the pager, so it can't check + * for pager values before the query is created. * - * The solution used here is to let the argument create the original query. The pager query - * runs after that, so the pager checks to see if there is a pager value that needs to be used in the query. - * The date argument has identified the placeholders it used in the query. So if a change is needed, - * we can swap the pager value into the query created by the date argument and adjust the - * $view->date_info values set by the argument accordingly so the theme will pick up the new information. + * The solution used here is to let the argument create + * the original query. The pager query + * runs after that, so the pager checks to see + * if there is a pager value that needs to be used in the query. + * The date argument has identified the placeholders + * it used in the query. So if a change is needed, + * we can swap the pager value into the query created + * by the date argument and adjust the + * $view->date_info values set by the argument accordingly + * so the theme will pick up the new information. */ /** * Example plugin to handle paging by month. */ +// @codingStandardsIgnoreStart class date_views_plugin_pager extends views_plugin_pager { /** @@ -79,6 +103,7 @@ class date_views_plugin_pager extends views_plugin_pager { $options['link_format'] = array('default' => 'pager'); $options['date_argument'] = array('default' => 'Unknown'); $options['granularity'] = array('default' => 'Unknown'); + $options['skip_empty_pages'] = array('default' => FALSE); return $options; } @@ -110,6 +135,12 @@ class date_views_plugin_pager extends views_plugin_pager { '#default_value' => $this->options['link_format'], '#required' => TRUE, ); + $form['skip_empty_pages'] = array( + '#title' => t('Skip empty pages'), + '#type' => 'checkbox', + '#description' => t('When selected, the pager will not display pages with no result for the given date. This causes a slight performance degradation because two additional queries need to be executed.'), + '#default_value' => $this->options['skip_empty_pages'], + ); $form['date_argument']['#type'] = 'hidden'; $form['date_argument']['#value'] = $this->options['date_argument']; $form['granularity']['#type'] = 'hidden'; @@ -150,13 +181,7 @@ class date_views_plugin_pager extends views_plugin_pager { // Reset values set by argument if pager requires it. if (!empty($value)) { - $argument->argument = $value; - $argument->date_range = $argument->date_handler->arg_range($value); - $argument->min_date = $argument->date_range[0]; - $argument->max_date = $argument->date_range[1]; - // $argument->is_default works correctly for normal arguments, but does not - // work correctly if we are swapping in a new value from the pager. - $argument->is_default = FALSE; + $this->set_argument_value($argument, $value); } // The pager value might move us into a forbidden range, so test it. @@ -164,13 +189,102 @@ class date_views_plugin_pager extends views_plugin_pager { $this->view->build_info['fail'] = TRUE; return; } - - if (empty($this->view->date_info)) $this->view->date_info = new stdClass(); + // Write date_info to store information to be used + // in the theming functions. + if (empty($this->view->date_info)) { + $this->view->date_info = new stdClass(); + } $this->view->date_info->granularity = $argument->date_handler->granularity; $format = $this->view->date_info->granularity == 'week' ? DATE_FORMAT_DATETIME : $argument->sql_format; $this->view->date_info->placeholders = isset($argument->placeholders) ? $argument->placeholders : $argument->date_handler->placeholders; $this->view->date_info->date_arg = $argument->argument; $this->view->date_info->date_arg_pos = $i; + $this->view->date_info->limit = $argument->limit; + $this->view->date_info->url = $this->view->get_url(); + $this->view->date_info->pager_id = $this->options['date_id']; + $this->view->date_info->date_pager_position = $this->options['pager_position']; + $this->view->date_info->date_pager_format = $this->options['link_format']; + $this->view->date_info->skip_empty_pages = $this->options['skip_empty_pages'] == 1; + // Execute two additional queries to find + // the previous and next page with values. + if ($this->view->date_info->skip_empty_pages) { + $q = clone $argument->query; + $field = $argument->table_alias . '.' . $argument->real_field; + $fieldsql = $date_handler->sql_field($field); + $fieldsql = $date_handler->sql_format($format, $fieldsql); + $q->clear_fields(); + $q->orderby = array(); + $q->set_distinct(TRUE, TRUE); + // Date limits of this argument. + $datelimits = $argument->date_handler->arg_range($argument->limit[0] . '--' . $argument->limit[1]); + // Find the first two dates between the minimum date + // and the upper bound of the current value. + $q->add_orderby(NULL, $fieldsql, 'DESC', 'date'); + $this->set_argument_placeholders($this->view->date_info->placeholders, $datelimits[0], $argument->max_date, $q, $format); + + $compiledquery = $q->query(); + $compiledquery->range(0, 2); + $results = $compiledquery->execute()->fetchCol(0); + + $prevdate = array_shift($results); + $prevdatealt = array_shift($results); + // Find the first two dates between the lower bound + // of the current value and the maximum date. + $q->add_orderby(NULL, $fieldsql, 'ASC', 'date'); + $this->set_argument_placeholders($this->view->date_info->placeholders, $argument->min_date, $datelimits[1], $q, $format); + + $compiledquery = $q->query(); + $compiledquery->range(0, 2); + $results = $compiledquery->execute()->fetchCol(0); + + $nextdate = array_shift($results); + $nextdatealt = array_shift($results); + + // Set the default value of the query to $prevfirst or $nextfirst + // when there is no value and $prevsecond or $nextsecond is set. + if (empty($value)) { + // @Todo find out which of $prevdate or $nextdate is closest to the + // default argument date value and choose that one. + if ($prevdate && $prevdatealt) { + $this->set_argument_value($argument, $prevdate); + $value = $prevdate; + $prevdate = $prevdatealt; + // If the first next date is the same as the first previous date, + // move to the following next date. + if ($value == $nextdate) { + $nextdate = $nextdatealt; + $nextdatealt = NULL; + } + } + elseif ($nextdate && $nextdatealt) { + $this->set_argument_value($argument, $nextdate); + $value = $nextdate; + $nextdate = $nextdatealt; + // If the first previous date is the same as the first next date, + // move to the following previous date. + if ($value == $prevdate) { + $prevdate = $prevdatealt; + $prevdatealt = NULL; + } + } + } + else { + // $prevdate and $nextdate are the same as $value, so move to + // the next values. + $prevdate = $prevdatealt; + $nextdate = $nextdatealt; + } + + $this->view->date_info->prev_date = $prevdate ? new DateObject($prevdate, NULL, $format) : NULL; + $this->view->date_info->next_date = $nextdate ? new DateObject($nextdate, NULL, $format) : NULL; + } + else { + $this->view->date_info->prev_date = clone($argument->min_date); + date_modify($this->view->date_info->prev_date, '-1 ' . $argument->date_handler->granularity); + $this->view->date_info->next_date = clone($argument->min_date); + date_modify($this->view->date_info->next_date, '+1 ' . $argument->date_handler->granularity); + } + // Write the date_info properties that depend on the current value. $this->view->date_info->year = date_format($argument->min_date, 'Y'); $this->view->date_info->month = date_format($argument->min_date, 'n');; $this->view->date_info->day = date_format($argument->min_date, 'j'); @@ -178,11 +292,6 @@ class date_views_plugin_pager extends views_plugin_pager { $this->view->date_info->date_range = $argument->date_range; $this->view->date_info->min_date = $argument->min_date; $this->view->date_info->max_date = $argument->max_date; - $this->view->date_info->limit = $argument->limit; - $this->view->date_info->url = $this->view->get_url(); - $this->view->date_info->pager_id = $this->options['date_id']; - $this->view->date_info->date_pager_position = $this->options['pager_position']; - $this->view->date_info->date_pager_format = $this->options['link_format']; } $i++; } @@ -191,20 +300,33 @@ class date_views_plugin_pager extends views_plugin_pager { // If there is pager input and the argument has set the placeholders, // swap the pager value in for the placeholder set by the argument. if (!empty($value) && !empty($this->view->date_info->placeholders)) { - $placeholders = $this->view->date_info->placeholders; - $count = count($placeholders); - foreach ($this->view->query->where as $group => $data) { - foreach ($data['conditions'] as $delta => $condition) { - if (array_key_exists('value', $condition) && is_array($condition['value'])) { - foreach ($condition['value'] as $placeholder => $placeholder_value) { - if (array_key_exists($placeholder, $placeholders)) { - // If we didn't get a match, this is a > $min < $max query that uses the view - // min and max dates as placeholders. - $date = ($count == 2) ? $this->view->date_info->min_date : $this->view->date_info->max_date; - $next_placeholder = array_shift($placeholders); - $this->view->query->where[$group]['conditions'][$delta]['value'][$placeholder] = $date->format($format); - $count--; - } + $this->set_argument_placeholders($this->view->date_info->placeholders, $this->view->date_info->min_date, $this->view->date_info->max_date, $this->view->query, $format); + } + } + + function set_argument_value($argument, $value) { + $argument->argument = $value; + $argument->date_range = $argument->date_handler->arg_range($value); + $argument->min_date = $argument->date_range[0]; + $argument->max_date = $argument->date_range[1]; + // $argument->is_default works correctly for normal arguments, but does not + // work correctly if we are swapping in a new value from the pager. + $argument->is_default = FALSE; + } + + function set_argument_placeholders($placeholders, $mindate, $maxdate, $query, $format) { + $count = count($placeholders); + foreach ($query->where as $group => $data) { + foreach ($data['conditions'] as $delta => $condition) { + if (array_key_exists('value', $condition) && is_array($condition['value'])) { + foreach ($condition['value'] as $placeholder => $placeholder_value) { + if (array_key_exists($placeholder, $placeholders)) { + // If we didn't get a match, this is a > $min < $max query that uses the view + // min and max dates as placeholders. + $date = ($count == 2) ? $mindate : $maxdate; + $next_placeholder = array_shift($placeholders); + $query->where[$group]['conditions'][$delta]['value'][$placeholder] = $date->format($format); + $count--; } } } @@ -230,4 +352,5 @@ class date_views_plugin_pager extends views_plugin_pager { $pager_theme = views_theme_functions('date_views_pager', $this->view, $this->display); return theme($pager_theme, array('plugin' => $this, 'input' => $input)); } -} \ No newline at end of file +} +// @codingStandardsIgnoreEnd diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/date-views-pager.tpl.php b/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/date-views-pager.tpl.php index b12c2e1c..ac8cfb4e 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/date-views-pager.tpl.php +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/date-views-pager.tpl.php @@ -27,8 +27,10 @@ * be used in the l() function, including rel=nofollow. */ ?> - -
    + + + +

    @@ -36,7 +38,11 @@
    • - 'date_nav'))), $prev_url, $prev_options); ?> + 'date_nav')); + print l(t($text), $prev_url, $prev_options); + ?>
    • @@ -46,4 +52,4 @@
    -
    \ No newline at end of file +
    diff --git a/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/theme.inc b/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/theme.inc index 8c42a527..7ca03f3a 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/date/date_views/theme/theme.inc @@ -4,6 +4,7 @@ * @file * Theme files for Date Pager. */ + /** * Jump in and move the pager. */ @@ -15,9 +16,11 @@ function date_views_preprocess_views_view(&$vars) { $vars['header'] .= $vars['pager']; $vars['pager'] = ''; break; + case 'both': $vars['header'] .= $vars['pager']; break; + default: // Already on the bottom. } @@ -66,28 +69,37 @@ function template_preprocess_date_views_pager(&$vars) { } if (empty($date_info->hide_nav)) { - $prev_date = clone($min_date); - date_modify($prev_date, '-1 ' . $granularity); - $next_date = clone($min_date); - date_modify($next_date, '+1 ' . $granularity); - $format = array('year' => 'Y', 'month' => 'Y-m', 'day' => 'Y-m-d'); - switch ($granularity) { - case 'week': - $next_week = date_week(date_format($next_date, 'Y-m-d')); - $prev_week = date_week(date_format($prev_date, 'Y-m-d')); - $next_arg = date_format($next_date, 'o-\W') . date_pad($next_week); - $prev_arg = date_format($prev_date, 'o-\W') . date_pad($prev_week); - break; - default: - $next_arg = date_format($next_date, $format[$granularity]); - $prev_arg = date_format($prev_date, $format[$granularity]); + $prev_date = $date_info->prev_date; + $next_date = $date_info->next_date; + + $format = array('year' => 'Y', 'month' => 'Y-m', 'day' => 'Y-m-d', 'hour' => 'Y-m-d\TH'); + if (!empty($prev_date)) { + switch ($granularity) { + case 'week': + $prev_week = date_week(date_format($prev_date, 'Y-m-d')); + $prev_arg = date_format($prev_date, 'o-\W') . date_pad($prev_week); + break; + default: + $prev_arg = date_format($prev_date, $format[$granularity]); + } + $prev_path = str_replace($date_info->date_arg, $prev_arg, $date_info->url); + $prev_args[$pos] = $prev_arg; + $vars['prev_url'] = date_pager_url($view, NULL, $prev_arg); } - $next_path = str_replace($date_info->date_arg, $next_arg, $date_info->url); - $prev_path = str_replace($date_info->date_arg, $prev_arg, $date_info->url); - $next_args[$pos] = $next_arg; - $prev_args[$pos] = $prev_arg; - $vars['next_url'] = date_pager_url($view, NULL, $next_arg); - $vars['prev_url'] = date_pager_url($view, NULL, $prev_arg); + if (!empty($next_date)) { + switch ($granularity) { + case 'week': + $next_week = date_week(date_format($next_date, 'Y-m-d')); + $next_arg = date_format($next_date, 'o-\W') . date_pad($next_week); + break; + default: + $next_arg = date_format($next_date, $format[$granularity]); + } + $next_path = str_replace($date_info->date_arg, $next_arg, $date_info->url); + $next_args[$pos] = $next_arg; + $vars['next_url'] = date_pager_url($view, NULL, $next_arg); + } + $vars['next_options'] = $vars['prev_options'] = array(); } else { @@ -117,14 +129,17 @@ function template_preprocess_date_views_pager(&$vars) { $prev_title = t('Navigate to previous year'); $next_title = t('Navigate to next year'); break; + case 'month': $prev_title = t('Navigate to previous month'); $next_title = t('Navigate to next month'); break; + case 'week': $prev_title = t('Navigate to previous week'); $next_title = t('Navigate to next week'); break; + case 'day': $prev_title = t('Navigate to previous day'); $next_title = t('Navigate to next day'); @@ -157,34 +172,44 @@ function template_preprocess_date_views_pager(&$vars) { } /** - * Theme the calendar title + * Theme the calendar title. */ function theme_date_nav_title($params) { + $title = ''; $granularity = $params['granularity']; $view = $params['view']; $date_info = $view->date_info; $link = !empty($params['link']) ? $params['link'] : FALSE; $format = !empty($params['format']) ? $params['format'] : NULL; - $format_with_year = variable_get('date_views_' . $granularity . 'format_with_year', 'l, F j, Y'); - $format_without_year = variable_get('date_views_' . $granularity . 'format_without_year', 'l, F j'); + $format_with_year = variable_get('date_views_' . $granularity . '_format_with_year', 'l, F j, Y'); + $format_without_year = variable_get('date_views_' . $granularity . '_format_without_year', 'l, F j'); switch ($granularity) { case 'year': $title = $date_info->year; $date_arg = $date_info->year; break; + case 'month': $format = !empty($format) ? $format : (empty($date_info->mini) ? $format_with_year : $format_without_year); $title = date_format_date($date_info->min_date, 'custom', $format); $date_arg = $date_info->year . '-' . date_pad($date_info->month); break; + case 'day': $format = !empty($format) ? $format : (empty($date_info->mini) ? $format_with_year : $format_without_year); $title = date_format_date($date_info->min_date, 'custom', $format); - $date_arg = $date_info->year . '-' . date_pad($date_info->month) . '-' . date_pad($date_info->day); + $date_arg = $date_info->year; + $date_arg .= '-'; + $date_arg .= date_pad($date_info->month); + $date_arg .= '-'; + $date_arg .= date_pad($date_info->day); break; + case 'week': $format = !empty($format) ? $format : (empty($date_info->mini) ? $format_with_year : $format_without_year); - $title = t('Week of @date', array('@date' => date_format_date($date_info->min_date, 'custom', $format))); + $title = t('Week of @date', array( + '@date' => date_format_date($date_info->min_date, 'custom', $format), + )); $date_arg = $date_info->year . '-W' . date_pad($date_info->week); break; } diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_api.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_api.test index f50020c1..03c9081b 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/tests/date_api.test +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_api.test @@ -132,11 +132,11 @@ class DateAPITestCase extends DrupalWebTestCase { // Test week range with calendar weeks. variable_set('date_first_day', 0); variable_set('date_api_use_iso8601', FALSE); - $expected = '2008-01-27 to 2008-02-03'; + $expected = '2008-01-27 to 2008-02-02'; $result = date_week_range(5, 2008); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test calendar date_week_range(5, 2008): should be $expected, found $value."); - $expected = '2009-01-25 to 2009-02-01'; + $expected = '2009-01-25 to 2009-01-31'; $result = date_week_range(5, 2009); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test calendar date_week_range(5, 2009): should be $expected, found $value."); @@ -144,11 +144,11 @@ class DateAPITestCase extends DrupalWebTestCase { // And now with ISO weeks. variable_set('date_first_day', 1); variable_set('date_api_use_iso8601', TRUE); - $expected = '2008-01-28 to 2008-02-04'; + $expected = '2008-01-28 to 2008-02-03'; $result = date_week_range(5, 2008); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test ISO date_week_range(5, 2008): should be $expected, found $value."); - $expected = '2009-01-26 to 2009-02-02'; + $expected = '2009-01-26 to 2009-02-01'; $result = date_week_range(5, 2009); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test ISO date_week_range(5, 2009): should be $expected, found $value."); @@ -393,9 +393,31 @@ class DateAPITestCase extends DrupalWebTestCase { $input = '23 abc 2012'; $timezone = NULL; $format = 'd M Y'; - $date = new dateObject($input, $timezone, $format); + $date = @new dateObject($input, $timezone, $format); $this->assertNotEqual(count($date->errors), 0, '23 abc 2012 should be an invalid date'); + // Test Granularity. + $input = '2005-06-01 10:30:45'; + $timezone = NULL; + $format = 'Y-m-d H:i:s'; + + $date = new dateObject($input, $timezone, $format); + $date->removeGranularity('hour'); + $date->removeGranularity('second'); + $date->removeGranularity('minute'); + + $value = $date->format($format); + $expected = '2005-06-01'; + $this->assertEqual($expected, $value, "The date with removed granularity should be $expected, found $value."); + + $date->addGranularity('hour'); + $date->addGranularity('second'); + $date->addGranularity('minute'); + + $value = $date->format($format); + $expected = '2005-06-01 10:30:45'; + + $this->assertEqual($expected, $value, "The date with added granularity should be $expected, found $value."); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_field.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_field.test index b2a45a0d..b8aad594 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/tests/date_field.test +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_field.test @@ -16,7 +16,7 @@ abstract class DateFieldBasic extends DrupalWebTestCase { // Create and log in our privileged user. $this->privileged_user = $this->drupalCreateUser( - array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools') + array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools', 'administer fields') ); $this->drupalLogin($this->privileged_user); @@ -52,7 +52,7 @@ abstract class DateFieldBasic extends DrupalWebTestCase { $repeat = !empty($repeat) ? $repeat : 0; $todate = !empty($todate) ? $todate : 'optional'; $widget_type = !empty($widget_type) ? $widget_type : 'date_select'; - $tz_handling = !empty($tz_handing) ? $tz_handling : 'site'; + $tz_handling = !empty($tz_handling) ? $tz_handling : 'site'; $granularity = !empty($granularity) ? $granularity : array('year', 'month', 'day', 'hour', 'minute'); $year_range = !empty($year_range) ? $year_range : '2010:+1'; $input_format = !empty($input_format) ? $input_format : date_default_format($widget_type); @@ -151,6 +151,123 @@ abstract class DateFieldBasic extends DrupalWebTestCase { } + /** + * Creates a date field from an array of settings values. + * + * All values have defaults, only need to specify values that need to be + * different. + */ + protected function createMultiDateField($values = array()) { + extract($values); + + $field_name = !empty($field_name) ? $field_name : 'field_test'; + $entity_type = !empty($entity_type) ? $entity_type : 'node'; + $bundle = !empty($bundle) ? $bundle : 'story'; + $label = !empty($label) ? $label : 'Test'; + $field_type = !empty($field_type) ? $field_type : 'datetime'; + $repeat = !empty($repeat) ? $repeat : 0; + $todate = !empty($todate) ? $todate : 'optional'; + $widget_type = !empty($widget_type) ? $widget_type : 'date_select'; + $this->verbose(!empty($tz_handling)); + $tz_handling = !empty($tz_handling) ? $tz_handling : 'site'; + $granularity = !empty($granularity) ? $granularity : array('year', 'month', 'day', 'hour', 'minute'); + $year_range = !empty($year_range) ? $year_range : '2010:+1'; + $input_format = !empty($input_format) ? $input_format : date_default_format($widget_type); + $input_format_custom = !empty($input_format_custom) ? $input_format_custom : ''; + $text_parts = !empty($text_parts) ? $text_parts : array(); + $increment = !empty($increment) ? $increment : 15; + $default_value = !empty($default_value) ? $default_value : 'now'; + $default_value2 = !empty($default_value2) ? $default_value2 : 'blank'; + $default_format = !empty($default_format) ? $default_format : 'long'; + $cache_enabled = !empty($cache_enabled); + $cache_count = !empty($cache_count) ? $cache_count : 4; + $cardinality = !empty($cardinality) ? $cardinality : 1; + + $field = array( + 'field_name' => $field_name, + 'type' => $field_type, + 'cardinality' => $cardinality, + 'settings' => array( + 'granularity' => $granularity, + 'tz_handling' => $tz_handling, + 'timezone_db' => date_get_timezone_db($tz_handling), + 'repeat' => $repeat, + 'todate' => $todate, + 'cache_enabled' => $cache_enabled, + 'cache_count' => $cache_count, + ), + ); + $instance = array( + 'entity_type' => $entity_type, + 'field_name' => $field_name, + 'label' => $label, + 'bundle' => $bundle, + // Move the date right below the title. + 'weight' => -4, + 'widget' => array( + 'type' => $widget_type, + // Increment for minutes and seconds, can be 1, 5, 10, 15, or 30. + 'settings' => array( + 'increment' => $increment, + // The number of years to go back and forward in drop-down year + // selectors. + 'year_range' => $year_range, + 'input_format' => $input_format, + 'input_format_custom' => $input_format_custom, + 'text_parts' => $text_parts, + 'label_position' => 'above', + 'repeat_collapsed' => 0, + ), + 'weight' => -4, + ), + 'settings' => array( + 'default_value' => $default_value, + 'default_value2' => $default_value2, + ), + ); + + $instance['display'] = array( + 'default' => array( + 'label' => 'above', + 'type' => 'date_default', + 'settings' => array( + 'format_type' => $default_format, + 'show_repeat_rule' => 'show', + 'multiple_number' => '', + 'multiple_from' => '', + 'multiple_to' => '', + 'fromto' => 'both', + ), + 'module' => 'date', + 'weight' => 0 , + ), + 'teaser' => array( + 'label' => 'above', + 'type' => 'date_default', + 'weight' => 0, + 'settings' => array( + 'format_type' => $default_format, + 'show_repeat_rule' => 'show', + 'multiple_number' => '', + 'multiple_from' => '', + 'multiple_to' => '', + 'fromto' => 'both', + ), + 'module' => 'date', + ), + ); + + $field = field_create_field($field); + $instance = field_create_instance($instance); + + field_info_cache_clear(TRUE); + field_cache_clear(TRUE); + + // Look at how the field got configured. + $this->drupalGet("admin/structure/types/manage/$bundle/fields/$field_name"); + $this->drupalGet("admin/structure/types/manage/$bundle/display"); + } + /** * @todo. */ diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_form.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_form.test new file mode 100644 index 00000000..56b89e43 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_form.test @@ -0,0 +1,30 @@ + t('Date Form test'), + 'description' => t('Test Date form functions.') , + 'group' => t('Date'), + ); + } + + public function setUp() { + // Load the date_api module. + parent::setUp('date_test'); + } + + /** + * Tests rendering of a date element in a form. + */ + public function testDateForm() { + $this->drupalGet('date-test/form'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_migrate.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_migrate.test index cdde115f..ec2ae7d8 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/tests/date_migrate.test +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_migrate.test @@ -18,6 +18,7 @@ class DateMigrateExampleUnitTest extends DrupalWebTestCase { 'name' => 'Date Migration', 'description' => 'Test migration into date fields', 'group' => 'Date', + 'dependencies' => array('migrate', 'features'), ); } diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_test/date_test.info b/profiles/commerce_kickstart/modules/contrib/date/tests/date_test/date_test.info new file mode 100644 index 00000000..2f265fa7 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_test/date_test.info @@ -0,0 +1,14 @@ +name = "Date module tests" +description = "Support module for date related testing." +package = Date/Time +version = VERSION +core = 7.x +hidden = TRUE +dependencies[] = date + +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" +core = "7.x" +project = "date" +datestamp = "1491562090" + diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_test/date_test.module b/profiles/commerce_kickstart/modules/contrib/date/tests/date_test/date_test.module new file mode 100644 index 00000000..18540891 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_test/date_test.module @@ -0,0 +1,40 @@ + 'Test form with date element', + 'description' => "Form with date element to make form related tests", + 'page callback' => 'drupal_get_form', + 'page arguments' => array('date_test_sample_form'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Form callback. Generates a test form with date elements. + */ +function date_test_sample_form($form, &$form_state) { + $form['date_test_select'] = array( + '#type' => 'date_select', + '#title' => t('Sample from'), + '#date_format' => 'H:i:s a', + '#default_value' => array( + 'hour' => 7, + 'minute' => 0, + 'second' => 0, + 'ampm' => 'am' + ), + ); + + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_timezone.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_timezone.test index ddbdcef9..9f2905a2 100644 --- a/profiles/commerce_kickstart/modules/contrib/date/tests/date_timezone.test +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_timezone.test @@ -16,6 +16,16 @@ class DateTimezoneTestCase extends DateFieldBasic { ); } + public function setUp() { + parent::setUp(); + // Set the timezone explicitly. Otherwise the site's default timezone is + // used, which defaults to the server timezone when installing Drupal. This + // depends on the environment and is therefore uncertain. + // The Australia/Sydney timezone is chosen so all tests are run using an + // edge case scenario (UTC+10 and DST). + variable_set('date_default_timezone', 'Australia/Sydney'); + } + /** * @todo. */ @@ -23,7 +33,7 @@ class DateTimezoneTestCase extends DateFieldBasic { // Create a date fields with combinations of various timezone handling and // granularity. foreach (array('date', 'datestamp', 'datetime') as $field_type) { - foreach (array('site', 'none', 'date', 'user', 'utc') as $tz_handling) { + foreach (array('site', 'none', 'date', 'user', 'utc', 'Europe/Dublin') as $tz_handling) { foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $max_granularity) { // Skip invalid combinations. if (in_array($max_granularity, array('year', 'month', 'day')) && $tz_handling != 'none') { @@ -50,6 +60,111 @@ class DateTimezoneTestCase extends DateFieldBasic { } } + /** + * Validates timezone handling with a multi-value date field. + */ + public function testMultiUserTimezone() { + // Create date fields with combinations of various types and granularity + // using the "Date's Timezone" strategy. + $field_type = 'datetime'; + $tz_handling = 'date'; + $max_granularity = 'minute'; + + // Create date field + $field_name = "field_test"; + $label = 'Test'; + $options = array( + 'label' => $label, + 'widget_type' => 'date_text', + 'field_name' => $field_name, + 'field_type' => $field_type, + 'input_format' => 'custom', + 'input_format_custom' => 'm/d/Y - H:i:s T', + 'cardinality' => 3, + 'tz_handling' => $tz_handling, + ); + $this->createMultiDateField($options); + + // Submit a date field form with multiple values + $this->dateMultiValueForm($field_name, $field_type, $max_granularity, $tz_handling); + + + $this->deleteDateField($label); + } + + /** + * Tests the submission of a date field's widget form when using unlimited + * cardinality + */ + public function dateMultiValueForm($field_name, $field_type, $max_granularity, $tz_handling) { + variable_set('date_format_long', 'D, m/d/Y - H:i:s T'); + + $edit = array(); + $should_be = array(); + $edit['title'] = $this->randomName(8); + $timezones = array('America/Chicago', 'America/Los_Angeles', 'America/New_York'); + + switch ($max_granularity) { + case 'hour': + $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][0][timezone][timezone]'] = 'America/Chicago'; + $should_be[0] = 'Thu, 10/07/2010 - 10 CDT'; + + $edit[$field_name . '[und][1][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][1][timezone][timezone]'] = 'America/Los_Angeles'; + $should_be[1] = 'Thu, 10/07/2010 - 10 PDT'; + + $edit[$field_name . '[und][2][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][2][timezone][timezone]'] = 'America/New_York'; + $should_be[2] = 'Thu, 10/07/2010 - 10 EDT'; + + break; + case 'minute': + $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][0][timezone][timezone]'] = 'America/Chicago'; + $should_be[0] = 'Thu, 10/07/2010 - 10:30 CDT'; + + $edit[$field_name . '[und][1][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][1][timezone][timezone]'] = 'America/Los_Angeles'; + $should_be[1] = 'Thu, 10/07/2010 - 10:30 PDT'; + + $edit[$field_name . '[und][2][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][2][timezone][timezone]'] = 'America/New_York'; + $should_be[2] = 'Thu, 10/07/2010 - 10:30 EDT'; + + break; + case 'second': + $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][0][timezone][timezone]'] = 'America/Chicago'; + $should_be[0] = 'Thu, 10/07/2010 - 10:30:30 CDT'; + + $edit[$field_name . '[und][1][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][1][timezone][timezone]'] = 'America/Los_Angeles'; + $should_be[1] = 'Thu, 10/07/2010 - 10:30:30 PDT'; + + $edit[$field_name . '[und][2][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][2][timezone][timezone]'] = 'America/New_York'; + $should_be[2] = 'Thu, 10/07/2010 - 10:30:30 EDT'; + break; + } + + $this->drupalPost('node/add/story', $edit, t('Save')); + $this->assertText($edit['title'], "Node has been created"); + + foreach ($should_be as $assertion) { + $this->assertText($assertion, "Found the correct date for a $field_type field using $max_granularity granularity with $tz_handling timezone handling."); + } + + // Goto the edit page and save the node again. + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->drupalGet('node/' . $node->nid . '/edit'); + + // Re-assert the proper date timezones. + foreach ($timezones as $key => $timezone) { + $this->assertOptionSelected('edit-field-test-und-' . $key . '-timezone-timezone', $timezone, "Found the correct timezone $timezone for a $field_type field using $max_granularity granularity with $tz_handling timezone handling."); + } + } + /** * @todo. */ @@ -77,17 +192,32 @@ class DateTimezoneTestCase extends DateFieldBasic { case 'hour': $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10'; $edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11'; - $should_be = 'Thu, 10/07/2010 - 10 to Thu, 10/07/2010 - 11'; + if ($tz_handling == 'utc') { + $should_be = 'Thu, 10/07/2010 - 21 to Thu, 10/07/2010 - 22'; + } + else { + $should_be = 'Thu, 10/07/2010 - 10 to Thu, 10/07/2010 - 11'; + } break; case 'minute': $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; $edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30'; - $should_be = 'Thu, 10/07/2010 - 10:30 to 11:30'; + if ($tz_handling == 'utc') { + $should_be = 'Thu, 10/07/2010 - 21:30 to 22:30'; + } + else { + $should_be = 'Thu, 10/07/2010 - 10:30 to 11:30'; + } break; case 'second': $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30:30'; $edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30:30'; - $should_be = 'Thu, 10/07/2010 - 10:30:30 to 11:30:30'; + if ($tz_handling == 'utc') { + $should_be = 'Thu, 10/07/2010 - 21:30:30 to 22:30:30'; + } + else { + $should_be = 'Thu, 10/07/2010 - 10:30:30 to 11:30:30'; + } break; } $this->drupalPost('node/add/story', $edit, t('Save')); diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_views_pager.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_views_pager.test new file mode 100644 index 00000000..4f5dd97d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_views_pager.test @@ -0,0 +1,129 @@ + 'Date views pager skipping test', + 'description' => "Views date pager, option to skip empty pages test", + 'group' => 'Date', + ); + } + + /** + * Test setup actions. + */ + public function setUp() { + // Load the 'date_views', 'views', 'views_ui', 'ctools' modules. + parent::setUp('date_views', 'views', 'views_ui', 'ctools'); + // Set required permissions. + $permissions = array('administer views', 'administer site configuration'); + // Create admin user and login. + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Create a new view for test. + $view = new view(); + $view->name = 'test_date_pager'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'node'; + $view->human_name = 'test_date_pager'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'test_date_pager'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'date_views_pager'; + $handler->display->display_options['pager']['options']['skip_empty_pages'] = 1; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'node'; + /* Field: Content: Title */ + $handler->display->display_options['fields']['title']['id'] = 'title'; + $handler->display->display_options['fields']['title']['table'] = 'node'; + $handler->display->display_options['fields']['title']['field'] = 'title'; + $handler->display->display_options['fields']['title']['label'] = ''; + $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE; + $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE; + /* Sort criterion: Content: Post date */ + $handler->display->display_options['sorts']['created']['id'] = 'created'; + $handler->display->display_options['sorts']['created']['table'] = 'node'; + $handler->display->display_options['sorts']['created']['field'] = 'created'; + $handler->display->display_options['sorts']['created']['order'] = 'DESC'; + /* Contextual filter: Date: Date (node) */ + $handler->display->display_options['arguments']['date_argument']['id'] = 'date_argument'; + $handler->display->display_options['arguments']['date_argument']['table'] = 'node'; + $handler->display->display_options['arguments']['date_argument']['field'] = 'date_argument'; + $handler->display->display_options['arguments']['date_argument']['default_action'] = 'default'; + $handler->display->display_options['arguments']['date_argument']['default_argument_type'] = 'date'; + $handler->display->display_options['arguments']['date_argument']['summary']['format'] = 'default_summary'; + $handler->display->display_options['arguments']['date_argument']['granularity'] = 'hour'; + $handler->display->display_options['arguments']['date_argument']['date_fields'] = array( + 'node.created' => 'node.created', + ); + /* Filter criterion: Content: Published */ + $handler->display->display_options['filters']['status']['id'] = 'status'; + $handler->display->display_options['filters']['status']['table'] = 'node'; + $handler->display->display_options['filters']['status']['field'] = 'status'; + $handler->display->display_options['filters']['status']['value'] = 1; + $handler->display->display_options['filters']['status']['group'] = 1; + $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE; + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'page_1'); + $handler->display->display_options['path'] = 'test_date_pager'; + + $view->save(); + } + + /** + * Test pager skipping. + */ + public function testPagerSkipping() { + // Go to view admin page. + $this->drupalGet('admin/structure/views/view/display/test_date_pager/edit'); + // Go to pager options. + $this->drupalGet('admin/structure/views/nojs/display/test_date_pager/default/pager_options'); + // Check if "Skip empty pages" text - exist. + $this->assertText('Skip empty pages'); + // Check if field and it's value is correct. + $this->assertFieldByName('pager_options[skip_empty_pages]', '1'); + // Go back to view admin page. + $this->drupalGet('admin/structure/views/view/display/test_date_pager/edit'); + // Check if pager on empty page are gone. + $this->assertNoText('« Prev', 'Previous pager does not exist'); + $this->assertNoText('Next »', 'Next pager does not exist'); + } + + /** + * Test the view page has no PHP warnings. + */ + public function testPagerWarning() { + $this->drupalCreateNode(array('type' => 'blog')); + // Set pager to skip empty pages. + $edit = array( + 'pager_options[skip_empty_pages]' => FALSE, + ); + $this->drupalPost('admin/structure/views/nojs/display/test_date_pager/default/pager_options', $edit, t('Apply')); + // Save the view. + $this->drupalPost('admin/structure/views/view/test_date_pager/edit', array(), t('Save')); + + // Visit view page. This will throw error, if any PHP warnings or errors. + $this->drupalGet('test_date_pager'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/date/tests/date_views_popup.test b/profiles/commerce_kickstart/modules/contrib/date/tests/date_views_popup.test new file mode 100644 index 00000000..e3d4306d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/date/tests/date_views_popup.test @@ -0,0 +1,106 @@ + 'Date Views - Popup Test', + 'description' => 'Tests date popup in Views', + 'group' => 'Date', + ); + } + + /** + * Test setup actions. + */ + public function setUp() { + parent::setUp(); + // Load the 'date_popup', 'date_views', 'views', 'views_ui', 'ctools' modules. + $modules = array('date_popup', 'date_views', 'views', 'views_ui', 'ctools'); + $success = module_enable($modules, TRUE); + $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); + + // Reset/rebuild all data structures after enabling the modules. + $this->resetAll(); + + // Create a date field. + $field_name = "field_test_date_popup"; + $label = 'Test'; + $options = array( + 'label' => 'Test', + 'widget_type' => 'date_popup', + 'field_name' => $field_name, + 'field_type' => 'datetime', + 'input_format' => 'm/d/Y - H:i', + ); + $this->createDateField($options); + + // Set required permissions. + $permissions = array('administer views', 'administer site configuration'); + // Create admin user and login. + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Create the view. + $view = new view(); + $view->name = 'test_date_popup'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'node'; + $view->human_name = 'Test date_popup'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'test_date_popup_page'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'none'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'node'; + /* Field: Content: Title */ + $handler->display->display_options['fields']['title']['id'] = 'title'; + $handler->display->display_options['fields']['title']['table'] = 'node'; + $handler->display->display_options['fields']['title']['field'] = 'title'; + $handler->display->display_options['fields']['title']['label'] = ''; + $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE; + $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE; + /* Filter criterion: Content: test_date_popup (field_test_date_popup) */ + $handler->display->display_options['filters']['field_test_date_popup_value']['id'] = 'field_test_date_popup_value'; + $handler->display->display_options['filters']['field_test_date_popup_value']['table'] = 'field_data_field_test_date_popup'; + $handler->display->display_options['filters']['field_test_date_popup_value']['field'] = 'field_test_date_popup_value'; + $handler->display->display_options['filters']['field_test_date_popup_value']['exposed'] = TRUE; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['operator_id'] = 'field_test_date_popup_value_op'; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['label'] = 'test_date_popup (field_test_date_popup)'; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['operator'] = 'field_test_date_popup_value_op'; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['identifier'] = 'field_test_date_popup_value'; + $handler->display->display_options['filters']['field_test_date_popup_value']['form_type'] = 'date_popup'; + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'page'); + $handler->display->display_options['path'] = 'test-date-popup'; + + $view->save(); + } + + /** + * Test date popup. + */ + public function testDatePopup() { + // Go to view page. + $this->drupalGet('test-date-popup'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/entity/README.txt b/profiles/commerce_kickstart/modules/contrib/entity/README.txt index ed048e42..745dd7fe 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/entity/README.txt @@ -11,7 +11,15 @@ entity CRUD controller, which helps simplifying the creation of new entity types This is an API module. You only need to enable it if a module depends on it or you are interested in using it for development. -This README is for interested developers. If you are not interested in +Advanced usage: +--------------- +You can optimize cache clearing performance by setting the variable +'entity_rebuild_on_flush' to FALSE. This skips rebuilding of feature +components and exported entities during cache flushing. Instead, it is triggered +by the features module only; e.g., when features are reverted. + + +The README below is for interested developers. If you are not interested in developing, you may stop reading now. -------------------------------------------------------------------------------- diff --git a/profiles/commerce_kickstart/modules/contrib/entity/ctools/relationships/entity_property.inc b/profiles/commerce_kickstart/modules/contrib/entity/ctools/relationships/entity_property.inc new file mode 100644 index 00000000..6869146f --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/entity/ctools/relationships/entity_property.inc @@ -0,0 +1,153 @@ + t('Entity property or field (via Entity Metadata Wrapper)'), + 'description' => t('Creates any kind of context for entity properties and fields.'), + 'context' => 'entity_entity_property_context', + 'required context' => new ctools_context_required(t('Entity'), 'entity'), + 'edit form' => 'entity_entity_property_edit_form', + 'edit form validate' => 'entity_entity_property_edit_form_validate', + 'defaults' => array( + 'selector' => '', + 'target_context' => 'entity', + 'concatenator' => ',' + ), +); + +/** + * Return a new context based on an existing context. + */ +function entity_entity_property_context($context, $conf) { + $plugin = $conf['name']; + + // If unset it wants a generic, unfilled context, which is just NULL. + if (empty($context->data)) { + return ctools_context_create_empty(isset($conf['target_context']) ? $conf['target_context'] : 'entity', NULL); + } + + list($part1, $entity_type) = explode(':', $context->plugin); + + if (isset($context->data) && $entity = $context->data) { + // Apply the selector. + $wrapper = entity_metadata_wrapper($entity_type, $entity); + $parts = explode(':', $conf['selector']); + + try { + foreach ($parts as $part) { + if (!($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper)) { + $wrapper = NULL; + $value = NULL; + continue; + } + $wrapper = $wrapper->get($part); + } + } + catch (EntityMetadataWrapperException $e) { + $wrapper = NULL; + $value = NULL; + } + + if (isset($wrapper)) { + $value = $wrapper->value(); + } + + // Massage list values. + if ($wrapper instanceof EntityListWrapper) { + $value = $wrapper[0]->value(); + $argument = implode($conf['concatenator'], $wrapper->value(array('identifier' => TRUE))); + } + elseif ($wrapper instanceof EntityDrupalWrapper) { + $argument = $wrapper->getIdentifier(); + } + + // Bail out if we were unable to retrieve the value. + if (!isset($value)) { + return ctools_context_create_empty(isset($conf['target_context']) ? $conf['target_context'] : 'entity', NULL); + } + + $context = ctools_context_create($conf['target_context'], $value); + // Provide a suiting argument if context is used as argument. + if (isset($argument)) { + $context->argument = $argument; + } + return $context; + } +} + +function entity_entity_property_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + + $form['selector'] = array( + '#type' => 'textfield', + '#title' => t('Data selector'), + '#description' => t('Any valid data selector, e.g. "title" to select a node\'s title, or "field_tags:1" to select the second tag.'), + '#default_value' => $conf['selector'], + '#required' => TRUE, + ); + $form['concatenator'] = array( + '#title' => t('Concatenator (if multiple)'), + '#type' => 'select', + '#options' => array(',' => ', (AND)', '+' => '+ (OR)'), + '#default_value' => $conf['concatenator'], + '#prefix' => '
    ', + '#suffix' => '
    ', + '#description' => t("When the resulting value is multiple valued and the context is passed on to a view as argument, the value can be concatenated in the form of 1+2+3 (for OR) or 1,2,3 (for AND)."), + ); + return $form; +} + +function entity_entity_property_edit_form_validate($form, &$form_state) { + $context_key = $form_state['values']['context']; + $context = $form_state['contexts'][$context_key]; + $entity_type = $context->type[2]; + + try { + $all_properties = entity_get_all_property_info($entity_type); + $wrapper = entity_metadata_wrapper($entity_type, NULL, array('property info' => $all_properties)); + $parts = explode(':', $form_state['values']['selector']); + foreach ($parts as $part) { + if (!($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper)) { + form_set_error('selector', t('Unable to apply the data selector part %key.'. array('%key' => $part))); + continue; + } + $wrapper = $wrapper->get($part); + } + $type = entity_entity_property_map_data_type($wrapper->type()); + $form_state['conf']['target_context'] = $type; + } + catch (EntityMetadataWrapperException $e) { + form_set_error('selector', t('Unable to apply the data selector on entity type %type. @reason', array('@reason' => $e->getMessage(), '%type' => $entity_type))); + } + + // Auto-generate a sane identifier. + if ($form_state['values']['identifier'] == $form['identifier']['#default_value']) { + $form_state['values']['identifier'] = 'Entity property ' . $entity_type . ':' . check_plain($form_state['values']['selector']); + } +} + +/** + * Maps an entity-property data type to ctools types. + */ +function entity_entity_property_map_data_type($type) { + // Remove list<> wrappers from types. + while ($item_type = entity_property_list_extract_type($type)) { + $type = $item_type; + } + // Massage data type of entites to c + if (entity_get_info($type)) { + $type = "entity:$type"; + } + if ($type == 'text') { + $type = 'string'; + } + return $type; +} diff --git a/profiles/commerce_kickstart/modules/contrib/entity/entity.features.inc b/profiles/commerce_kickstart/modules/contrib/entity/entity.features.inc index e266aaf5..0ec3e618 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/entity.features.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/entity.features.inc @@ -7,6 +7,12 @@ /** * Returns the configured entity features controller. + * + * @param string $type + * The entity type to get the controller for. + * + * @return EntityDefaultFeaturesController + * The configured entity features controller. */ function entity_features_get_controller($type) { $static = &drupal_static(__FUNCTION__); @@ -85,6 +91,7 @@ class EntityDefaultFeaturesController { $fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey}); foreach ($fields as $name => $field) { $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; + $pipe['field_instance'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; } } } @@ -125,9 +132,7 @@ class EntityDefaultFeaturesController { */ function revert($module = NULL) { if ($defaults = features_get_default($this->type, $module)) { - foreach ($defaults as $name => $entity) { - entity_delete($this->type, $name); - } + entity_delete_multiple($this->type, array_keys($defaults)); } } } @@ -146,6 +151,8 @@ function entity_features_api() { } /** + * Implements hook_features_export_options(). + * * Features component callback. */ function entity_features_export_options($a1, $a2 = NULL) { @@ -157,6 +164,8 @@ function entity_features_export_options($a1, $a2 = NULL) { } /** + * Implements hook_features_export(). + * * Features component callback. */ function entity_features_export($data, &$export, $module_name = '', $entity_type) { @@ -164,6 +173,8 @@ function entity_features_export($data, &$export, $module_name = '', $entity_type } /** + * Implements hook_features_export_render(). + * * Features component callback. */ function entity_features_export_render($module, $data, $export = NULL, $entity_type) { @@ -171,8 +182,23 @@ function entity_features_export_render($module, $data, $export = NULL, $entity_t } /** + * Implements hook_features_revert(). + * * Features component callback. */ function entity_features_revert($module = NULL, $entity_type) { return entity_features_get_controller($entity_type)->revert($module); } + +/** + * Implements hook_features_post_restore(). + * + * Rebuild all defaults when a features rebuild is triggered - even the ones not + * handled by features itself. + */ +function entity_features_post_restore($op, $items = array()) { + if ($op == 'rebuild') { + // Use features rebuild to rebuild the features independent exports too. + entity_defaults_rebuild(); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/entity/entity.info b/profiles/commerce_kickstart/modules/contrib/entity/entity.info index ff88ce54..0b865a04 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/entity.info +++ b/profiles/commerce_kickstart/modules/contrib/entity/entity.info @@ -25,9 +25,9 @@ files[] = views/handlers/entity_views_handler_field_uri.inc files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc files[] = views/handlers/entity_views_handler_relationship.inc files[] = views/plugins/entity_views_plugin_row_entity_view.inc -; Information added by Drupal.org packaging script on 2015-02-25 -version = "7.x-1.6" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1424876582" +datestamp = "1518620551" diff --git a/profiles/commerce_kickstart/modules/contrib/entity/entity.install b/profiles/commerce_kickstart/modules/contrib/entity/entity.install index f6f8cc2f..118820b8 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/entity.install +++ b/profiles/commerce_kickstart/modules/contrib/entity/entity.install @@ -13,6 +13,14 @@ function entity_enable() { entity_entitycache_installed_modules(); } +/** + * Implements hook_uninstall(). + */ +function entity_uninstall() { + // Delete variables. + variable_del('entity_rebuild_on_flush'); +} + /** * The entity API modules have been merged into a single module. */ diff --git a/profiles/commerce_kickstart/modules/contrib/entity/entity.module b/profiles/commerce_kickstart/modules/contrib/entity/entity.module index d4a882cd..da0acf69 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/entity.module +++ b/profiles/commerce_kickstart/modules/contrib/entity/entity.module @@ -184,8 +184,17 @@ function entity_ui_entity_page_view($entity) { * Gets the page title for the passed operation. */ function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { - module_load_include('inc', 'entity', 'includes/entity.ui'); - $label = entity_label($entity_type, $entity); + if (isset($entity)) { + module_load_include('inc', 'entity', 'includes/entity.ui'); + $label = entity_label($entity_type, $entity); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + } + else { + $info = entity_get_info($entity_type); + $label = $info['label']; + $bundle = NULL; + } + switch ($op) { case 'view': return $label; @@ -200,12 +209,7 @@ function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { case 'export': return t('Export @label', array('@label' => $label)); } - if (isset($entity)) { - list(, , $bundle) = entity_extract_ids($entity_type, $entity); - } - else { - $bundle = NULL; - } + return entity_ui_get_action_title($op, $entity_type, $bundle); } @@ -600,7 +604,7 @@ function entity_id($entity_type, $entity) { * content language of the current request. * @param $page * (optional) If set will control if the entity is rendered: if TRUE - * the entity will be rendered without its title, so that it can be embeded + * the entity will be rendered without its title, so that it can be embedded * in another context. If FALSE the entity will be displayed with its title * in a mode suitable for lists. * If unset, the page mode will be enabled if the current path is the URI @@ -1076,16 +1080,18 @@ function entity_flush_caches() { // case of enabling or disabling modules we already rebuild defaults in // entity_modules_enabled() and entity_modules_disabled(), so we do not need // to do it again. - if (current_path() != 'admin/modules/list/confirm') { + // Also check if rebuilding on cache flush is explicitly disabled. + if (current_path() != 'admin/modules/list/confirm' && variable_get('entity_rebuild_on_flush', TRUE)) { entity_defaults_rebuild(); } // Care about entitycache tables. if (module_exists('entitycache')) { $tables = array(); - foreach (entity_crud_get_info() as $entity_type => $entity_info) { - if (isset($entity_info['module']) && !empty($entity_info['entity cache'])) { - $tables[] = 'cache_entity_' . $entity_type; + $tables_created = variable_get('entity_cache_tables_created'); + if (is_array($tables_created)) { + foreach ($tables_created as $module => $entity_cache_tables) { + $tables = array_merge($tables, $entity_cache_tables); } } return $tables; @@ -1372,7 +1378,7 @@ function entity_get_extra_fields_controller($type = NULL) { * Returns a property wrapper for the given data. * * If an entity is wrapped, the wrapper can be used to retrieve further wrappers - * for the entitity properties. For that the wrapper support chaining, e.g. you + * for the entity properties. For that the wrapper support chaining, e.g. you * can use a node wrapper to get the node authors mail address: * * @code @@ -1563,7 +1569,7 @@ function _entity_info_add_metadata(&$entity_info) { * Implements hook_ctools_plugin_directory(). */ function entity_ctools_plugin_directory($module, $plugin) { - if ($module == 'ctools' && $plugin == 'content_types') { - return 'ctools/content_types'; + if ($module == 'ctools') { + return "ctools/$plugin"; } } diff --git a/profiles/commerce_kickstart/modules/contrib/entity/entity.test b/profiles/commerce_kickstart/modules/contrib/entity/entity.test index 03c2551a..fd8cea12 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/entity.test +++ b/profiles/commerce_kickstart/modules/contrib/entity/entity.test @@ -1342,6 +1342,74 @@ class EntityMetadataNodeRevisionAccessTestCase extends DrupalWebTestCase { } } +/** + * Tests basic entity_access() functionality for taxonomy terms. + */ +class EntityMetadataTaxonomyAccessTestCase extends EntityWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Entity Metadata Taxonomy Access', + 'description' => 'Test entity_access() for taxonomy terms', + 'group' => 'Entity API', + ); + } + + /** + * Asserts entity_access() correctly grants or denies access. + */ + function assertTaxonomyMetadataAccess($ops, $term, $account) { + foreach ($ops as $op => $result) { + $msg = t("entity_access() returns @result with operation '@op'.", array('@result' => $result ? 'TRUE' : 'FALSE', '@op' => $op)); + $access = entity_access($op, 'taxonomy_term', $term, $account); + $this->assertEqual($result, $access, $msg); + } + } + + /** + * @inheritdoc + */ + function setUp() { + parent::setUp('entity', 'taxonomy'); + // Clear permissions for authenticated users. + db_delete('role_permission') + ->condition('rid', DRUPAL_AUTHENTICATED_RID) + ->execute(); + } + + /** + * Runs basic tests for entity_access() function. + */ + function testTaxonomyMetadataAccess() { + $vocab = $this->createVocabulary(); + $term = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => $vocab, + ))->save()->value(); + // Clear permissions static cache to get new taxonomy permissions. + drupal_static_reset('checkPermissions'); + + // Check assignment of view permissions. + $user1 = $this->drupalCreateUser(array('access content')); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $term, $user1); + + // Check assignment of edit permissions. + $user2 = $this->drupalCreateUser(array('edit terms in ' . $vocab->vid)); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => TRUE, 'delete' => FALSE), $term, $user2); + + // Check assignment of delete permissions. + $user3 = $this->drupalCreateUser(array('delete terms in ' . $vocab->vid)); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => FALSE, 'delete' => TRUE), $term, $user3); + + // Check assignment of view, edit, delete permissions. + $user4 = $this->drupalCreateUser(array('access content', 'edit terms in ' . $vocab->vid, 'delete terms in ' . $vocab->vid)); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $term, $user4); + + // Check assignment of administration permissions. + $user5 = $this->drupalCreateUser(array('administer taxonomy')); + $this->assertTaxonomyMetadataAccess(array('create' => TRUE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $term, $user5); + } +} + /** * Tests provided entity property info of the core modules. */ @@ -1466,6 +1534,7 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { $book = array('bid' => $node->nid, 'plid' => $node->book['mlid']); $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => $book)); $node3 = $this->drupalCreateNode(array('type' => 'page')); + $node4 = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => 0, 'plid' => -1))); // Test whether the properties work. $wrapper = entity_metadata_wrapper('node', $node2); @@ -1477,6 +1546,10 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { $wrapper = entity_metadata_wrapper('node', $node3); $this->assertEmpty($wrapper, 'book'); $this->assertEmptyArray($wrapper, 'book_ancestors'); + + // Test a book node which is not contained in a hierarchy. + $wrapper = entity_metadata_wrapper('node', $node4); + $this->assertEmptyArray($wrapper, 'book_ancestors'); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/entity/entity_token.info b/profiles/commerce_kickstart/modules/contrib/entity/entity_token.info index b579ced1..c59c6502 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/entity_token.info +++ b/profiles/commerce_kickstart/modules/contrib/entity/entity_token.info @@ -5,9 +5,9 @@ files[] = entity_token.tokens.inc files[] = entity_token.module dependencies[] = entity -; Information added by Drupal.org packaging script on 2015-02-25 -version = "7.x-1.6" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1424876582" +datestamp = "1518620551" diff --git a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.controller.inc b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.controller.inc index f675a63a..5e86b529 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.controller.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.controller.inc @@ -107,7 +107,7 @@ interface EntityAPIControllerInterface extends DrupalEntityControllerInterface { * content language of the current request. * @param $page * (optional) If set will control if the entity is rendered: if TRUE - * the entity will be rendered without its title, so that it can be embeded + * the entity will be rendered without its title, so that it can be embedded * in another context. If FALSE the entity will be displayed with its title * in a mode suitable for lists. * If unset, the page mode will be enabled if the current path is the URI diff --git a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.inc b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.inc index be24499d..2f504f36 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.inc @@ -5,6 +5,168 @@ * Provides a base class for entities. */ +/** + * Interface for class based entities. + */ +interface EntityInterface { + + /** + * Returns the internal, numeric identifier. + * + * Returns the numeric identifier, even if the entity type has specified a + * name key. In the latter case, the numeric identifier is supposed to be used + * when dealing generically with entities or internally to refer to an entity, + * i.e. in a relational database. If unsure, use Entity:identifier(). + */ + public function internalIdentifier(); + + /** + * Returns the entity identifier, i.e. the entities name or numeric id. + * + * @return + * The identifier of the entity. If the entity type makes use of a name key, + * the name is returned, else the numeric id. + * + * @see entity_id() + */ + public function identifier(); + + /** + * Returns the info of the type of the entity. + * + * @see entity_get_info() + */ + public function entityInfo(); + + /** + * Returns the type of the entity. + */ + public function entityType(); + + /** + * Returns the bundle of the entity. + * + * @return + * The bundle of the entity. Defaults to the entity type if the entity type + * does not make use of different bundles. + */ + public function bundle(); + + /** + * Returns the EntityMetadataWrapper of the entity. + * + * @return EntityDrupalWrapper + * An EntityMetadataWrapper containing the entity. + */ + public function wrapper(); + + /** + * Returns the label of the entity. + * + * Modules may alter the label by specifying another 'label callback' using + * hook_entity_info_alter(). + * + * @see entity_label() + */ + public function label(); + + /** + * Returns the uri of the entity just as entity_uri(). + * + * Modules may alter the uri by specifying another 'uri callback' using + * hook_entity_info_alter(). + * + * @see entity_uri() + */ + public function uri(); + + /** + * Checks if the entity has a certain exportable status. + * + * @param $status + * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE, + * ENTITY_OVERRIDDEN or ENTITY_FIXED. + * + * @return bool + * For exportable entities TRUE if the entity has the status, else FALSE. + * In case the entity is not exportable, NULL is returned. + * + * @see entity_has_status() + */ + public function hasStatus($status); + + /** + * Permanently saves the entity. + * + * @see entity_save() + */ + public function save(); + + /** + * Permanently deletes the entity. + * + * @see entity_delete() + */ + public function delete(); + + /** + * Exports the entity. + * + * @see entity_export() + */ + public function export($prefix = ''); + + /** + * Generate an array for rendering the entity. + * + * @see entity_view() + */ + public function view($view_mode = 'full', $langcode = NULL, $page = NULL); + + /** + * Builds a structured array representing the entity's content. + * + * @see entity_build_content() + */ + public function buildContent($view_mode = 'full', $langcode = NULL); + + /** + * Gets the raw, translated value of a property or field. + * + * Supports retrieving field translations as well as i18n string translations. + * + * Note that this returns raw data values, which might not reflect what + * has been declared for hook_entity_property_info() as no 'getter callbacks' + * are invoked or no referenced entities are loaded. For retrieving values + * reflecting the property info make use of entity metadata wrappers, see + * entity_metadata_wrapper(). + * + * @param $property + * The name of the property to return; e.g., 'title'. + * @param $langcode + * (optional) The language code of the language to which the value should + * be translated. If set to NULL, the default display language is being + * used. + * + * @return + * The raw, translated property value; or the raw, un-translated value if no + * translation is available. + * + * @todo Implement an analogous setTranslation() method for updating. + */ + public function getTranslation($property, $langcode = NULL); + + /** + * Checks whether the entity is the default revision. + * + * @return Boolean + * + * @see entity_revision_is_default() + */ + public function isDefaultRevision(); + +} + /** * A common class for entities. * @@ -25,7 +187,7 @@ * public $count = 0; * @endcode */ -class Entity { +class Entity implements EntityInterface { protected $entityType; protected $entityInfo; @@ -34,9 +196,7 @@ class Entity { protected $wrapper; /** - * Creates a new entity. - * - * @see entity_create() + * {@inheritdoc} */ public function __construct(array $values = array(), $entityType = NULL) { if (empty($entityType)) { @@ -61,62 +221,42 @@ class Entity { } /** - * Returns the internal, numeric identifier. - * - * Returns the numeric identifier, even if the entity type has specified a - * name key. In the latter case, the numeric identifier is supposed to be used - * when dealing generically with entities or internally to refer to an entity, - * i.e. in a relational database. If unsure, use Entity:identifier(). + * {@inheritdoc} */ public function internalIdentifier() { return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL; } /** - * Returns the entity identifier, i.e. the entities name or numeric id. - * - * @return - * The identifier of the entity. If the entity type makes use of a name key, - * the name is returned, else the numeric id. - * - * @see entity_id() + * {@inheritdoc} */ public function identifier() { return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL; } /** - * Returns the info of the type of the entity. - * - * @see entity_get_info() + * {@inheritdoc} */ public function entityInfo() { return $this->entityInfo; } /** - * Returns the type of the entity. + * {@inheritdoc} */ public function entityType() { return $this->entityType; } /** - * Returns the bundle of the entity. - * - * @return - * The bundle of the entity. Defaults to the entity type if the entity type - * does not make use of different bundles. + * {@inheritdoc} */ public function bundle() { return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType; } /** - * Returns the EntityMetadataWrapper of the entity. - * - * @return EntityDrupalWrapper - * An EntityMetadataWrapper containing the entity. + * {@inheritdoc} */ public function wrapper() { if (empty($this->wrapper)) { @@ -130,12 +270,7 @@ class Entity { } /** - * Returns the label of the entity. - * - * Modules may alter the label by specifying another 'label callback' using - * hook_entity_info_alter(). - * - * @see entity_label() + * {@inheritdoc} */ public function label() { // If the default label flag is enabled, this is being invoked recursively. @@ -165,12 +300,7 @@ class Entity { } /** - * Returns the uri of the entity just as entity_uri(). - * - * Modules may alter the uri by specifying another 'uri callback' using - * hook_entity_info_alter(). - * - * @see entity_uri() + * {@inheritdoc} */ public function uri() { if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_uri') { @@ -188,17 +318,7 @@ class Entity { } /** - * Checks if the entity has a certain exportable status. - * - * @param $status - * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE, - * ENTITY_OVERRIDDEN or ENTITY_FIXED. - * - * @return - * For exportable entities TRUE if the entity has the status, else FALSE. - * In case the entity is not exportable, NULL is returned. - * - * @see entity_has_status() + * {@inheritdoc} */ public function hasStatus($status) { if (!empty($this->entityInfo['exportable'])) { @@ -207,18 +327,14 @@ class Entity { } /** - * Permanently saves the entity. - * - * @see entity_save() + * {@inheritdoc} */ public function save() { return entity_get_controller($this->entityType)->save($this); } /** - * Permanently deletes the entity. - * - * @see entity_delete() + * {@inheritdoc} */ public function delete() { $id = $this->identifier(); @@ -228,55 +344,28 @@ class Entity { } /** - * Exports the entity. - * - * @see entity_export() + * {@inheritdoc} */ public function export($prefix = '') { return entity_get_controller($this->entityType)->export($this, $prefix); } /** - * Generate an array for rendering the entity. - * - * @see entity_view() + * {@inheritdoc} */ public function view($view_mode = 'full', $langcode = NULL, $page = NULL) { return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page); } /** - * Builds a structured array representing the entity's content. - * - * @see entity_build_content() + * {@inheritdoc} */ public function buildContent($view_mode = 'full', $langcode = NULL) { return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode); } /** - * Gets the raw, translated value of a property or field. - * - * Supports retrieving field translations as well as i18n string translations. - * - * Note that this returns raw data values, which might not reflect what - * has been declared for hook_entity_property_info() as no 'getter callbacks' - * are invoked or no referenced entities are loaded. For retrieving values - * reflecting the property info make use of entity metadata wrappers, see - * entity_metadata_wrapper(). - * - * @param $property_name - * The name of the property to return; e.g., 'title'. - * @param $langcode - * (optional) The language code of the language to which the value should - * be translated. If set to NULL, the default display language is being - * used. - * - * @return - * The raw, translated property value; or the raw, un-translated value if no - * translation is available. - * - * @todo Implement an analogous setTranslation() method for updating. + * {@inheritdoc} */ public function getTranslation($property, $langcode = NULL) { $all_info = entity_get_all_property_info($this->entityType); @@ -296,11 +385,7 @@ class Entity { } /** - * Checks whether the entity is the default revision. - * - * @return Boolean - * - * @see entity_revision_is_default() + * {@inheritdoc} */ public function isDefaultRevision() { if (!empty($this->entityInfo['entity keys']['revision'])) { diff --git a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.property.inc b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.property.inc index 38e4fd7b..e8714e67 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.property.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.property.inc @@ -69,7 +69,7 @@ function entity_property_info_defaults() { * (optiona) The entity type to return properties for. * * @return - * An array of info about properties. If the type is ommitted, all known + * An array of info about properties. If the type is omitted, all known * properties are returned. */ function entity_get_all_property_info($entity_type = NULL) { diff --git a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.ui.inc b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.ui.inc index 1826da40..24e3c2b9 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.ui.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.ui.inc @@ -127,8 +127,8 @@ class EntityDefaultUIController { * Use per bundle form ids if possible, such that easy per bundle alterations * are supported too. * - * Note that for performance reasons, this method is only invoked for forms, - * which receive the entity_type as first argument. Thus any forms added, must + * Note that for performance reasons, this method is invoked only for forms + * which receive the entity_type as first argument. Thus any forms added must * follow that pattern. * * @see entity_forms() @@ -645,7 +645,7 @@ function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $o */ function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) { // Now equals entity_ui_form_defaults() but is still here to keep backward - // compatability. + // compatibility. return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op); } @@ -760,7 +760,7 @@ function entity_ui_validate_machine_name($element, &$form_state) { function theme_entity_ui_overview_item($variables) { $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']); if ($variables['name']) { - $output .= ' (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')'; + $output .= ' (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')'; } return $output; } diff --git a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.wrapper.inc b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.wrapper.inc index 06b89adf..860d2c33 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.wrapper.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/includes/entity.wrapper.inc @@ -119,7 +119,11 @@ abstract class EntityMetadataWrapper { */ public function set($value) { if (!$this->validate($value)) { - throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + throw new EntityMetadataWrapperException(t('Invalid data value given. Be sure it matches the required data type and format. Value at !location: !value.', array( + // An exception's message is output through check_plain(). + '!value' => is_array($value) || is_object($value) ? var_export($value, TRUE) : $value, + '!location' => $this->debugIdentifierLocation(), + ))); } $this->clear(); $this->data = $value; @@ -231,6 +235,21 @@ abstract class EntityMetadataWrapper { return !empty($this->info['parent']) ? $this->info['parent']->propertyAccess($this->info['name'], $op, $account) : TRUE; } + /** + * Returns a string to use to identify this wrapper in error messages. + * + * @return + * A string that identifies this wrapper and its chain of ancestors, of the + * form 'grandparentidentifier->parentidentifier->identifier'. + */ + public function debugIdentifierLocation() { + $debug = $this->info['name']; + if (isset($this->info['parent'])) { + $debug = $this->info['parent']->debugIdentifierLocation() . '->' . $debug; + } + return $debug; + } + /** * Prepare for serializiation. */ @@ -734,7 +753,11 @@ class EntityDrupalWrapper extends EntityStructureWrapper { */ public function set($value) { if (!$this->validate($value)) { - throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + throw new EntityMetadataWrapperException(t('Invalid data value given. Be sure it matches the required data type and format. Value at !location: !value.', array( + // An exception's message is output through check_plain(). + '!value' => is_array($value) || is_object($value) ? var_export($value, TRUE) : $value, + '!location' => $this->debugIdentifierLocation(), + ))); } if ($this->info['type'] == 'entity' && $value === $this) { // Nothing to do. @@ -821,7 +844,12 @@ class EntityDrupalWrapper extends EntityStructureWrapper { } else { // This is not a property, so fallback on entity access. - return $this->entityAccess($op == 'edit' ? 'update' : 'view', $account); + if ($op == 'edit') { + // If the operation is "edit" determine if its actually a "create" for + // new un-saved entities, or an "update" for existing ones. + return $this->entityAccess($this->getIdentifier() ? 'update' : 'create', $account); + } + return $this->entityAccess('view', $account); } } @@ -909,6 +937,27 @@ class EntityDrupalWrapper extends EntityStructureWrapper { } } + /** + * Returns a string to use to identify this wrapper in error messages. + */ + public function debugIdentifierLocation() { + // An entity wrapper can be at the top of the chain or a part of it. + if (isset($this->info['name'])) { + // This wrapper is part of a chain, eg in the position node->author. + // Return the name. + $debug = $this->info['name']; + } + else { + // This is a wrapper for an actual entity: return its type and id. + $debug = $this->info['type'] . '(' . $this->getIdentifier() . ')'; + } + + if (isset($this->info['parent'])) { + $debug = $this->info['parent']->debugIdentifierLocation() . '->' . $debug; + } + return $debug; + } + /** * Prepare for serializiation. */ @@ -1067,7 +1116,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega */ public function getIterator() { // In case there is no data available, just iterate over the first item. - return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys(parent::value()) : array(0)); + return new EntityMetadataWrapperIterator($this, ($this->dataAvailable() && is_array(parent::value())) ? array_keys(parent::value()) : array(0)); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/entity/modules/callbacks.inc b/profiles/commerce_kickstart/modules/contrib/entity/modules/callbacks.inc index 26f802e6..ee156ab3 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/modules/callbacks.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/modules/callbacks.inc @@ -29,7 +29,7 @@ function entity_metadata_book_get_properties($node, array $options, $name, $enti case 'book_ancestors': $ancestors = array(); - while (!empty($node->book['plid'])) { + while (!empty($node->book['plid']) && $node->book['plid'] != -1) { $link = book_link_load($node->book['plid']); array_unshift($ancestors, $link['nid']); $node = node_load($link['nid']); @@ -670,9 +670,11 @@ function entity_metadata_field_file_validate_item($items, $context) { function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) { // First deal with the case where a $node is provided. if (isset($node)) { - if ($op == 'create') { + if (empty($node->vid) && in_array($op, array('create', 'update'))) { + // This is a new node or the original node. if (isset($node->type)) { - return node_access($op, $node->type, $account); + $op = empty($node->nid) || !empty($node->is_new) ? 'create' : 'update'; + return node_access($op, $op == 'create' ? $node->type : $node, $account); } else { throw new EntityMalformedException('Permission to create a node was requested but no node type was given.'); @@ -796,14 +798,35 @@ function entity_metadata_comment_properties_access($op, $property, $entity = NUL * Access callback for the taxonomy entities. */ function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) { - if ($entity_type == 'taxonomy_vocabulary') { - return user_access('administer taxonomy', $account); - } - if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) { + // If user has administer taxonomy permission then no further checks. + if (user_access('administer taxonomy', $account)) { return TRUE; } - if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') { - return TRUE; + switch ($op) { + case "view": + if (user_access('access content', $account)) { + return TRUE; + } + break; + case "update": + if ($entity_type == 'taxonomy_term') { + return user_access("edit terms in $entity->vid", $account); + } + break; + case "create": + if ($entity_type == 'taxonomy_term') { + // Check for taxonomy_access_fix contrib module which adds additional + // permissions to create new terms in a given vocabulary. + if (function_exists('taxonomy_access_fix_access')) { + return taxonomy_access_fix_access('add terms', $entity->vocabulary_machine_name); + } + } + break; + case "delete": + if ($entity_type == 'taxonomy_term') { + return user_access("delete terms in $entity->vid", $account); + } + break; } return FALSE; } diff --git a/profiles/commerce_kickstart/modules/contrib/entity/modules/node.info.inc b/profiles/commerce_kickstart/modules/contrib/entity/modules/node.info.inc index 3512d9a0..f146a7e8 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/modules/node.info.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/modules/node.info.inc @@ -163,4 +163,10 @@ function entity_metadata_node_entity_property_info_alter(&$info) { 'auto creation' => 'entity_property_create_array', 'field' => TRUE, ); + + // Make it a list if cardinality is not 1. + $field_body = field_info_field('body'); + if (isset($field_body) && $field_body['cardinality'] != 1) { + $info['node']['properties']['body']['type'] = 'list'; + } } diff --git a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_feature.info b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_feature.info index 80d2a908..524fdfaf 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_feature.info +++ b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_feature.info @@ -6,9 +6,9 @@ files[] = entity_feature.module dependencies[] = entity_test hidden = TRUE -; Information added by Drupal.org packaging script on 2015-02-25 -version = "7.x-1.6" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1424876582" +datestamp = "1518620551" diff --git a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.info b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.info index d34326c9..b7cceabf 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.info +++ b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.info @@ -7,9 +7,9 @@ files[] = entity_test.install dependencies[] = entity hidden = TRUE -; Information added by Drupal.org packaging script on 2015-02-25 -version = "7.x-1.6" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1424876582" +datestamp = "1518620551" diff --git a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.module b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.module index 55855688..727797ae 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.module +++ b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test.module @@ -2,7 +2,7 @@ /** * @file - * Test moduel for the entity API. + * Test module for the entity API. */ /** diff --git a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test_i18n.info b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test_i18n.info index 8adf04af..4ca048af 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test_i18n.info +++ b/profiles/commerce_kickstart/modules/contrib/entity/tests/entity_test_i18n.info @@ -5,9 +5,9 @@ dependencies[] = i18n_string package = Multilingual - Internationalization core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-02-25 -version = "7.x-1.6" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1424876582" +datestamp = "1518620551" diff --git a/profiles/commerce_kickstart/modules/contrib/entity/theme/entity.theme.inc b/profiles/commerce_kickstart/modules/contrib/entity/theme/entity.theme.inc index 9238dfdb..fc0ba7c1 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/theme/entity.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/theme/entity.theme.inc @@ -80,9 +80,12 @@ function template_preprocess_entity_property(&$variables, $hook) { ); // Populate the content with sensible defaults. - if (!isset($variables['content'])) { + if (!isset($element['#content'])) { $variables['content'] = entity_property_default_render_value_by_type($element['#entity_wrapped']->{$element['#property_name']}); } + else { + $variables['content'] = $element['#content']; + } } /** diff --git a/profiles/commerce_kickstart/modules/contrib/entity/views/entity.views.inc b/profiles/commerce_kickstart/modules/contrib/entity/views/entity.views.inc index 59ebaa48..a0179c0c 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/views/entity.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/views/entity.views.inc @@ -605,6 +605,28 @@ class EntityDefaultViewsController { ); break; + case 'duration': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'entity_views_handler_field_duration', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_numeric', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_numeric', + ), + ); + break; + case 'uri': $return += $description + array( 'field' => array( diff --git a/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_field_handler_helper.inc b/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_field_handler_helper.inc index 0077f4a3..6bb4fbff 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_field_handler_helper.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_field_handler_helper.inc @@ -18,7 +18,7 @@ class EntityFieldHandlerHelper { * Provide appropriate default options for a handler. */ public static function option_definition($handler) { - if (entity_property_list_extract_type($handler->definition['type'])) { + if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) { $options['list']['contains']['mode'] = array('default' => 'collapse'); $options['list']['contains']['separator'] = array('default' => ', '); $options['list']['contains']['type'] = array('default' => 'ul'); @@ -32,7 +32,7 @@ class EntityFieldHandlerHelper { * Provide an appropriate default option form for a handler. */ public static function options_form($handler, &$form, &$form_state) { - if (entity_property_list_extract_type($handler->definition['type'])) { + if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) { $form['list']['mode'] = array( '#type' => 'select', '#title' => t('List handling'), diff --git a/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_handler_area_entity.inc b/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_handler_area_entity.inc index f3746942..0c5b7143 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_handler_area_entity.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/views/handlers/entity_views_handler_area_entity.inc @@ -40,7 +40,7 @@ class entity_views_handler_area_entity extends views_handler_area { $form['entity_id'] = array( '#type' => 'textfield', '#title' => t('Entity id'), - '#description' => t('Choose the entity you want to display in the area.'), + '#description' => t('Choose the entity you want to display in the area. To render an entity given by a contextual filter use "%1" for the first argument, "%2" for the second, etc.'), '#default_value' => $this->options['entity_id'], ); @@ -105,6 +105,9 @@ class entity_views_handler_area_entity extends views_handler_area { * Render an entity using the view mode. */ public function render_entity($entity_type, $entity_id, $view_mode) { + $tokens = $this->get_render_tokens(); + // Replace argument tokens in entity id. + $entity_id = strtr($entity_id, $tokens); if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) { $entity = entity_load_single($entity_type, $entity_id); if (!empty($this->options['bypass_access']) || entity_access('view', $entity_type, $entity)) { @@ -117,4 +120,31 @@ class entity_views_handler_area_entity extends views_handler_area { return ''; } } + + /** + * Get the 'render' tokens to use for advanced rendering. + * + * This runs through all of the fields and arguments that + * are available and gets their values. This will then be + * used in one giant str_replace(). + */ + function get_render_tokens() { + $tokens = array(); + if (!empty($this->view->build_info['substitutions'])) { + $tokens = $this->view->build_info['substitutions']; + } + $count = 0; + foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) { + $token = '%' . ++$count; + if (!isset($tokens[$token])) { + $tokens[$token] = ''; + } + // Use strip tags as there should never be HTML in the path. + // However, we need to preserve special characters like " that + // were removed by check_plain(). + $tokens['%' . $count] = $handler->argument; + } + + return $tokens; + } } diff --git a/profiles/commerce_kickstart/modules/contrib/entity/views/plugins/entity_views_plugin_row_entity_view.inc b/profiles/commerce_kickstart/modules/contrib/entity/views/plugins/entity_views_plugin_row_entity_view.inc index db72b5f5..5e738a8c 100644 --- a/profiles/commerce_kickstart/modules/contrib/entity/views/plugins/entity_views_plugin_row_entity_view.inc +++ b/profiles/commerce_kickstart/modules/contrib/entity/views/plugins/entity_views_plugin_row_entity_view.inc @@ -88,6 +88,9 @@ class entity_views_plugin_row_entity_view extends views_plugin_row { public function render($values) { if ($entity = $this->get_value($values)) { + // Add the view object as views_plugin_row_node_view::render() would. + // Otherwise the views theme suggestions won't work properly. + $entity->view = $this->view; $render = $this->rendered_content[entity_id($this->entity_type, $entity)]; return drupal_render($render); } diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/entityreference/LICENSE.txt old mode 100755 new mode 100644 diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/entityreference/PATCHES.txt deleted file mode 100644 index 70a44c35..00000000 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/PATCHES.txt +++ /dev/null @@ -1,4 +0,0 @@ -The following patches have been applied to this project: -- http://drupal.org/files/1580348-universal-formatters-17.patch - -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.devel_generate.inc b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.devel_generate.inc index f6ab0144..fab46dda 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.devel_generate.inc +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.devel_generate.inc @@ -19,9 +19,13 @@ function _entityreference_devel_generate($object, $field, $instance, $bundle) { // Get all the entity that are referencable here. $referencable_entity = entityreference_get_selection_handler($field, $instance)->getReferencableEntities(); if (is_array($referencable_entity) && !empty($referencable_entity)) { - // Get a random key. - foreach ($referencable_entity as $type => $eids) { - $object_field['target_id'] = array_rand($eids); + // $referencable_entity is keyed by bundle type. + $random_bundle = array_rand($referencable_entity); + if (!empty($random_bundle)) { + $target_id = array_rand($referencable_entity[$random_bundle]); + if (!empty($referencable_entity[$random_bundle][$target_id])) { + $object_field['target_id'] = $target_id; + } } } return $object_field; diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.feeds.inc b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.feeds.inc index 3f641415..3da1479c 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.feeds.inc +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.feeds.inc @@ -125,10 +125,15 @@ function entityreference_feeds_set_target($source, $entity, $target, $value) { break; case 'label': $options = $handler->getReferencableEntities($value, '='); - $options = reset($options); - $etids = array_keys($options); - // Use the first matching entity. - $entity_id = reset($etids); + if ($options) { + $options = reset($options); + $etids = array_keys($options); + // Use the first matching entity. + $entity_id = reset($etids); + } + else { + $entity_id = NULL; + } break; } /* diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.info b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.info index d6348ef5..5b2a0f74 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.info +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.info @@ -1,18 +1,23 @@ name = Entity Reference description = Provides a field that can reference other entities. -core = 7.x package = Fields +core = 7.x + dependencies[] = entity dependencies[] = ctools +test_dependencies[] = feeds +test_dependencies[] = views + ; Migrate handler. files[] = entityreference.migrate.inc -; Our plugins interfaces and abstract implementations. +; Plugins interfaces and abstract implementations. files[] = plugins/selection/abstract.inc files[] = plugins/selection/views.inc files[] = plugins/behavior/abstract.inc +; Views integration. files[] = views/entityreference_plugin_display.inc files[] = views/entityreference_plugin_style.inc files[] = views/entityreference_plugin_row_fields.inc @@ -22,10 +27,11 @@ files[] = tests/entityreference.handlers.test files[] = tests/entityreference.taxonomy.test files[] = tests/entityreference.admin.test files[] = tests/entityreference.feeds.test +files[] = tests/entityreference.entity_translation.test -; Information added by packaging script on 2013-11-20 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2017-08-16 +version = "7.x-1.5" core = "7.x" project = "entityreference" -datestamp = "1384973110" +datestamp = "1502895850" diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.install b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.install index 4e248efd..ce610185 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.install +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.install @@ -41,6 +41,7 @@ function entityreference_field_schema($field) { } // Invoke the behaviors to allow them to change the schema. + module_load_include('module', 'entityreference'); foreach (entityreference_get_behavior_handlers($field) as $handler) { $handler->schema_alter($schema, $field); } @@ -161,4 +162,29 @@ function entityreference_update_7002() { 'not null' => TRUE, )); } -} \ No newline at end of file +} + +/** + * Implements hook_update_N(). + * + * Remove duplicate rows in the taxonomy_index table. + */ +function entityreference_update_7100() { + if (db_table_exists('taxonomy_index')) { + if (db_table_exists('taxonomy_index_tmp')) { + db_drop_table('taxonomy_index_tmp'); + } + + $tx_schema = drupal_get_schema('taxonomy_index'); + db_create_table('taxonomy_index_tmp', $tx_schema); + $select = db_select('taxonomy_index', 'tx'); + $select->fields('tx', array('nid', 'tid')); + $select->groupBy('tx.nid'); + $select->groupBy('tx.tid'); + $select->addExpression('MAX(sticky)', 'sticky'); + $select->addExpression('MAX(created)', 'created'); + db_insert('taxonomy_index_tmp')->from($select)->execute(); + db_drop_table('taxonomy_index'); + db_rename_table('taxonomy_index_tmp', 'taxonomy_index'); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.migrate.inc b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.migrate.inc index 0e95bf0a..35737b52 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.migrate.inc +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.migrate.inc @@ -1,12 +1,11 @@ 'target_id', diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.module b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.module index 2c026ac3..c0c9f58f 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.module +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/entityreference.module @@ -1,5 +1,12 @@ array( + 'variables' => array('label' => NULL, 'item' => NULL, 'settings' => NULL, 'uri' => NULL), + ), + 'entityreference_entity_id' => array( + 'variables' => array('item' => NULL, 'settings' => NULL), + ), + ); +} + /** * Implements hook_menu(). */ @@ -163,7 +184,7 @@ function entityreference_get_behavior_handlers($field, $instance = NULL) { /** * Get the behavior handler for a given entityreference field and instance. * - * @param $handler + * @param $behavior * The behavior handler name. */ function _entityreference_get_behavior_handler($behavior) { @@ -220,13 +241,15 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance if ($ids) { $valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities(array_keys($ids)); - $invalid_entities = array_diff_key($ids, array_flip($valid_ids)); - if ($invalid_entities) { - foreach ($invalid_entities as $id => $delta) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'entityreference_invalid_entity', - 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)), - ); + if (!empty($valid_ids)) { + $invalid_entities = array_diff_key($ids, array_flip($valid_ids)); + if ($invalid_entities) { + foreach ($invalid_entities as $id => $delta) { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'entityreference_invalid_entity', + 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)), + ); + } } } } @@ -398,6 +421,9 @@ function entityreference_field_settings_form($field, $instance, $has_data) { return $form; } +/** + * Callback for custom element processing. + */ function _entityreference_field_settings_process($form, $form_state) { $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field']; $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance']; @@ -479,11 +505,17 @@ function _entityreference_field_settings_process($form, $form_state) { return $form; } +/** + * Custom callback for ajax processing. + */ function _entityreference_field_settings_ajax_process($form, $form_state) { _entityreference_field_settings_ajax_process_element($form, $form); return $form; } +/** + * Helper function for custom ajax processing. + */ function _entityreference_field_settings_ajax_process_element(&$element, $main_form) { if (isset($element['#ajax']) && $element['#ajax'] === TRUE) { $element['#ajax'] = array( @@ -498,6 +530,9 @@ function _entityreference_field_settings_ajax_process_element(&$element, $main_f } } +/** + * Custom callback for element processing. + */ function _entityreference_form_process_merge_parent($element) { $parents = $element['#parents']; array_pop($parents); @@ -505,11 +540,17 @@ function _entityreference_form_process_merge_parent($element) { return $element; } +/** + * Helper function to remove blank elements. + */ function _entityreference_element_validate_filter(&$element, &$form_state) { $element['#value'] = array_filter($element['#value']); form_set_value($element, $element['#value'], $form_state); } +/** + * Implements hook_validate(). + */ function _entityreference_field_settings_validate($form, &$form_state) { // Store the new values in the form state. $field = $form['#field']; @@ -545,6 +586,9 @@ function entityreference_field_instance_settings_form($field, $instance) { return $form; } +/** + * Implements hook_field_settings_form(). + */ function _entityreference_field_instance_settings_form($form, $form_state) { $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field']; $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance']; @@ -562,6 +606,9 @@ function _entityreference_field_instance_settings_form($form, $form_state) { return $form; } +/** + * Implements hook_validate(). + */ function _entityreference_field_instance_settings_validate($form, &$form_state) { // Store the new values in the form state. $instance = $form['#instance']; @@ -793,7 +840,7 @@ function entityreference_query_entityreference_alter(QueryAlterableInterface $qu function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { // Ensure that the entity target type exists before displaying the widget. $entity_info = entity_get_info($field['settings']['target_type']); - if (empty($entity_info)){ + if (empty($entity_info)) { return; } $entity_type = $instance['entity_type']; @@ -818,7 +865,9 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan // Build an array of entities ID. foreach ($items as $item) { - $entity_ids[] = $item['target_id']; + if (isset($item['target_id'])) { + $entity_ids[] = $item['target_id']; + } } // Load those entities and loop through them to extract their labels. @@ -879,6 +928,9 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan } } +/** + * Implements hook_validate(). + */ function _entityreference_autocomplete_validate($element, &$form_state, $form) { // If a value was entered into the autocomplete... $value = ''; @@ -903,6 +955,9 @@ function _entityreference_autocomplete_validate($element, &$form_state, $form) { form_set_value($element, $value, $form_state); } +/** + * Implements hook_validate(). + */ function _entityreference_autocomplete_tags_validate($element, &$form_state, $form) { $value = array(); // If a value was entered into the autocomplete... @@ -949,7 +1004,8 @@ function entityreference_field_widget_error($element, $error) { * The entity type. * @param $bundle_name * The bundle name. - * @return + * + * @return bool * True if user can access this menu item. */ function entityreference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) { @@ -981,10 +1037,11 @@ function entityreference_autocomplete_access_callback($type, $field_name, $entit */ function entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') { // If the request has a '/' in the search text, then the menu system will have - // split it into multiple arguments and $string will only be a partial. We want - // to make sure we recover the intended $string. + // split it into multiple arguments and $string will only be a partial. + // We want to make sure we recover the intended $string. $args = func_get_args(); - // Shift off the $type, $field_name, $entity_type, $bundle_name, and $entity_id args. + // Shift off the $type, $field_name, $entity_type, + // $bundle_name, and $entity_id args. array_shift($args); array_shift($args); array_shift($args); @@ -1020,6 +1077,7 @@ function entityreference_autocomplete_callback($type, $field_name, $entity_type, */ function entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id = '', $string = '') { $matches = array(); + $prefix = ''; $entity = NULL; if ($entity_id !== 'NULL') { @@ -1034,7 +1092,8 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); if ($type == 'tags') { - // The user enters a comma-separated list of tags. We only autocomplete the last tag. + // The user enters a comma-separated list of tags. + // We only autocomplete the last tag. $tags_typed = drupal_explode_tags($string); $tag_last = drupal_strtolower(array_pop($tags_typed)); if (!empty($tag_last)) { @@ -1043,19 +1102,22 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta } else { // The user enters a single tag. - $prefix = ''; $tag_last = $string; } if (isset($tag_last)) { // Get an array of matching entities. $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10); - + $denied_label = t(ENTITYREFERENCE_DENIED); // Loop through the products and convert them into autocomplete output. foreach ($entity_labels as $values) { foreach ($values as $entity_id => $label) { + // Never autocomplete entities that aren't accessible. + if ($label == $denied_label) { + continue; + } $key = "$label ($entity_id)"; - // Strip things like starting/trailing white spaces, line breaks and tags. + // Strip starting/trailing white spaces, line breaks and tags. $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key))))); // Names containing commas or quotes must be wrapped in quotes. if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { @@ -1069,7 +1131,7 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta drupal_json_output($matches); } -/** + /** * Introspects field and instance settings, and determines the correct settings * for the functioning of the formatter. * @@ -1106,6 +1168,7 @@ function entityreference_field_formatter_info() { 'field types' => array('entityreference'), 'settings' => array( 'link' => FALSE, + 'bypass_access' => FALSE, ), ), 'entityreference_entity_id' => array( @@ -1120,6 +1183,7 @@ function entityreference_field_formatter_info() { 'settings' => array( 'view_mode' => 'default', 'links' => TRUE, + 'use_content_language' => TRUE, ), ), ); @@ -1132,8 +1196,16 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_ $display = $instance['display'][$view_mode]; $settings = $display['settings']; $field_type_settings = entityreference_field_type_settings($field); + $element = array(); if ($display['type'] == 'entityreference_label') { + $element['bypass_access'] = array( + '#title' => t('Show entity labels regardless of user access'), + '#description' => t("All entities in the field will be shown, without checking them for access. If the 'Link' setting is also enabled, an entity which the user does not have access to view will show without a link."), + '#type' => 'checkbox', + '#default_value' => $settings['bypass_access'], + ); + $element['link'] = array( '#title' => t('Link label to the referenced entity'), '#type' => 'checkbox', @@ -1163,6 +1235,12 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_ '#title' => t('Show links'), '#default_value' => $settings['links'], ); + + $element['use_content_language'] = array( + '#type' => 'checkbox', + '#title' => t('Use current content language'), + '#default_value' => $settings['use_content_language'], + ); } return $element; @@ -1180,6 +1258,7 @@ function entityreference_field_formatter_settings_summary($field, $instance, $vi if ($display['type'] == 'entityreference_label') { $summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link'); + $summary[] = $settings['bypass_access'] ? t('Show labels regardless of access') : t('Respect entity access for label visibility'); } if ($display['type'] == 'entityreference_entity_view') { @@ -1190,6 +1269,7 @@ function entityreference_field_formatter_settings_summary($field, $instance, $vi } $summary[] = t('Rendered as @mode', array('@mode' => $view_mode_label)); $summary[] = !empty($settings['links']) ? t('Display links') : t('Do not display links'); + $summary[] = !empty($settings['use_content_language']) ? t('Use current content language') : t('Use field language'); } return implode('
    ', $summary); @@ -1226,7 +1306,7 @@ function entityreference_field_formatter_prepare_view($entity_type, $entities, $ foreach ($items[$id] as $delta => $item) { // Check whether the referenced entity could be loaded. - if (isset($target_entities[$item[$column]])) { + if (isset($target_entities[$item[$column]]) && isset($target_entities[$item[$column]])) { // Replace the instance value with the term data. $items[$id][$delta]['entity'] = $target_entities[$item[$column]]; // Check whether the user has access to the referenced entity. @@ -1258,38 +1338,74 @@ function entityreference_field_formatter_view($entity_type, $entity, $field, $in $target_type = $field_type_settings['entity_type']; $column = $field_type_settings['column']; - // Rebuild the items list to contain only those with access. - foreach ($items as $key => $item) { - if (empty($item['access'])) { - unset($items[$key]); - } - } - switch ($display['type']) { case 'entityreference_label': $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); foreach ($items as $delta => $item) { - $label = $handler->getLabel($item['entity']); - // If the link is to be displayed and the entity has a uri, display a link. - // Note the assignment ($url = ) here is intended to be an assignment. - if ($display['settings']['link'] && ($uri = entity_uri($target_type, $item['entity']))) { - $result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options'])); + // Skip an item that is not accessible, unless we're allowing output of + // entity labels without considering access. + if (empty($item['access']) && !$display['settings']['bypass_access']) { + continue; } - else { - $result[$delta] = array('#markup' => check_plain($label)); + + // Calling EntityReferenceHandler::getLabel() would make a repeated, + // wasteful call to entity_access(). + $label = entity_label($field['settings']['target_type'], $item['entity']); + + // Check if the settings and access allow a link to be displayed. + $display_link = $display['settings']['link'] && $item['access']; + + $uri = NULL; + + // If the link is allowed and the entity has a uri, display a link. + if ($display_link) { + $uri = entity_uri($target_type, $item['entity']); } + + $result[$delta] = array( + '#theme' => 'entityreference_label', + '#label' => $label, + '#item' => $item, + '#uri' => $uri, + '#settings' => array( + 'display' => $display['settings'], + 'field' => $field['settings'], + ), + ); } break; case 'entityreference_entity_id': foreach ($items as $delta => $item) { - $result[$delta] = array('#markup' => check_plain($item[$column])); + // Skip an item that is not accessible. + if (empty($item['access'])) { + continue; + } + + $result[$delta] = array( + '#theme' => 'entityreference_entity_id', + '#item' => $item, + '#settings' => array( + 'display' => $display['settings'], + 'field' => $field['settings'], + ), + ); } break; case 'entityreference_entity_view': + $target_langcode = $langcode; + if (!empty($settings['use_content_language']) && !empty($GLOBALS['language_content']->language)) { + $target_langcode = $GLOBALS['language_content']->language; + } + foreach ($items as $delta => $item) { + // Skip an item that is not accessible. + if (empty($item['access'])) { + continue; + } + // Protect ourselves from recursive rendering. static $depth = 0; $depth++; @@ -1297,9 +1413,9 @@ function entityreference_field_formatter_view($entity_type, $entity, $field, $in throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $target_type, '@entity_id' => $item[$column]))); } - $entity = clone $item['entity']; - unset($entity->content); - $result[$delta] = entity_view($target_type, array($item[$column] => $entity), $settings['view_mode'], $langcode, FALSE); + $target_entity = clone $item['entity']; + unset($target_entity->content); + $result[$delta] = entity_view($target_type, array($item[$column] => $target_entity), $settings['view_mode'], $target_langcode, FALSE); if (empty($settings['links']) && isset($result[$delta][$target_type][$column]['links'])) { $result[$delta][$target_type][$item[$column]]['links']['#access'] = FALSE; @@ -1326,3 +1442,44 @@ function entityreference_views_api() { 'path' => drupal_get_path('module', 'entityreference') . '/views', ); } + +/** + * Theme label. + * + * @ingroup themeable. + */ +function theme_entityreference_label($vars) { + $label = $vars['label']; + $settings = $vars['settings']; + $item = $vars['item']; + $uri = $vars['uri']; + + $output = ''; + + // If the link is to be displayed and the entity has a uri, display a link. + // Note the assignment ($url = ) here is intended to be an assignment. + if ($settings['display']['link'] && isset($uri['path'])) { + $output .= l($label, $uri['path'], $uri['options']); + } + else { + $output .= check_plain($label); + } + + return $output; +} + +/** + * Theme entity_id + * + * @ingroup themeable. + */ +function theme_entityreference_entity_id($vars) { + $settings = $vars['settings']; + $item = $vars['item']; + + $output = ''; + + $output = check_plain($item['target_id']); + + return $output; +} diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info b/profiles/commerce_kickstart/modules/contrib/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info index 88ad7b5d..70bcc4f2 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info @@ -4,9 +4,9 @@ core = 7.x package = Fields dependencies[] = entityreference -; Information added by packaging script on 2013-11-20 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2017-08-16 +version = "7.x-1.5" core = "7.x" project = "entityreference" -datestamp = "1384973110" +datestamp = "1502895850" diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php index 43ac693f..075b54d7 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php @@ -144,18 +144,20 @@ protected function buildNodeIndex($node) { // already inserted in taxonomy_build_node_index(). $tid_all = array_diff($tid_all, $original_tid_all); - // Insert index entries for all the node's terms. + // Insert index entries for all the node's terms, preventing duplicates. if (!empty($tid_all)) { - $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created')); foreach ($tid_all as $tid) { - $query->values(array( + $row = array( 'nid' => $node->nid, 'tid' => $tid, 'sticky' => $sticky, 'created' => $node->created, - )); + ); + $query = db_merge('taxonomy_index') + ->key($row) + ->fields($row); + $query->execute(); } - $query->execute(); } } } diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php index 444a74cc..6ec28a4b 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php @@ -208,7 +208,11 @@ public function validateReferencableEntities(array $ids) { * Implements EntityReferenceHandler::validateAutocompleteInput(). */ public function validateAutocompleteInput($input, &$element, &$form_state, $form) { - $entities = $this->getReferencableEntities($input, '=', 6); + $bundled_entities = $this->getReferencableEntities($input, '=', 6); + $entities = array(); + foreach($bundled_entities as $entities_list) { + $entities += $entities_list; + } if (empty($entities)) { // Error if there are no entities available for a required field. form_error($element, t('There are no entities matching "%value"', array('%value' => $input))); @@ -305,7 +309,7 @@ protected function reAlterQuery(SelectQueryInterface $query, $tag, $base_table) */ public function getLabel($entity) { $target_type = $this->field['settings']['target_type']; - return entity_access('view', $target_type, $entity) ? entity_label($target_type, $entity) : t('- Restricted access -'); + return entity_access('view', $target_type, $entity) ? entity_label($target_type, $entity) : t(ENTITYREFERENCE_DENIED); } /** @@ -339,9 +343,11 @@ public function ensureBaseTable(SelectQueryInterface $query) { // Join the known base-table. $target_type = $this->field['settings']['target_type']; $entity_info = entity_get_info($target_type); + $target_type_base_table = $entity_info['base table']; $id = $entity_info['entity keys']['id']; + // Return the alias of the table. - return $query->innerJoin($target_type, NULL, "%alias.$id = $alias.entity_id"); + return $query->innerJoin($target_type_base_table, NULL, "%alias.$id = $alias.entity_id"); } } @@ -543,7 +549,7 @@ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAI if ($vocabulary = taxonomy_vocabulary_machine_name_load($bundle)) { if ($terms = taxonomy_get_tree($vocabulary->vid, 0, NULL, TRUE)) { foreach ($terms as $term) { - $options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain($term->name); + $options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain(entity_label('taxonomy_term', $term)); } } } diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php index 1b036a7d..cbed33b1 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php @@ -9,12 +9,13 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio * Implements EntityReferenceHandler::getInstance(). */ public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL) { - return new EntityReference_SelectionHandler_Views($field, $instance); + return new EntityReference_SelectionHandler_Views($field, $instance, $entity); } - protected function __construct($field, $instance) { + protected function __construct($field, $instance, $entity) { $this->field = $field; $this->instance = $instance; + $this->entity = $entity; } /** @@ -52,13 +53,32 @@ public static function settingsForm($field, $instance) { ); $default = !empty($view_settings['args']) ? implode(', ', $view_settings['args']) : ''; + $description = t('Provide a comma separated list of arguments to pass to the view.') . '
    ' . t('This field supports tokens.'); + + if (!module_exists('token')) { + $description .= '
    ' . t('Install the token module to get more tokens and display available once.', array('@url' => 'http://drupal.org/project/token')); + } + $form['view']['args'] = array( '#type' => 'textfield', '#title' => t('View arguments'), '#default_value' => $default, '#required' => FALSE, - '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + '#description' => $description, + '#maxlength' => '512', ); + if (module_exists('token')) { + // Get the token type for the entity type our field is in (a type 'taxonomy_term' has a 'term' type token). + $info = entity_get_info($instance['entity_type']); + + $form['view']['tokens'] = array( + '#theme' => 'token_tree', + '#token_types' => array($info['token type']), + '#global_types' => TRUE, + '#click_insert' => TRUE, + '#dialog' => TRUE, + ); + } } else { $form['view']['no_view_help'] = array( @@ -84,6 +104,7 @@ protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $ return FALSE; } $this->view->set_display($display_name); + $this->view->pre_execute(); // Make sure the query is not cached. $this->view->is_cacheable = FALSE; @@ -104,7 +125,7 @@ protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $ */ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { $display_name = $this->field['settings']['handler_settings']['view']['display_name']; - $args = $this->field['settings']['handler_settings']['view']['args']; + $args = $this->handleArgs($this->field['settings']['handler_settings']['view']['args']); $result = array(); if ($this->initializeView($match, $match_operator, $limit)) { // Get the results. @@ -133,12 +154,14 @@ function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') function validateReferencableEntities(array $ids) { $display_name = $this->field['settings']['handler_settings']['view']['display_name']; - $args = $this->field['settings']['handler_settings']['view']['args']; + $args = $this->handleArgs($this->field['settings']['handler_settings']['view']['args']); $result = array(); if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) { // Get the results. $entities = $this->view->execute_display($display_name, $args); - $result = array_keys($entities); + if (!empty($entities)) { + $result = array_keys($entities); + } } return $result; } @@ -164,6 +187,49 @@ public function entityFieldQueryAlter(SelectQueryInterface $query) { } + /** + * Handles arguments for views. + * + * Replaces tokens using token_replace(). + * + * @param array $args + * Usually $this->field['settings']['handler_settings']['view']['args']. + * + * @return array + * The arguments to be send to the View. + */ + protected function handleArgs($args) { + if (!module_exists('token')) { + return $args; + } + + // Parameters for token_replace(). + $data = array(); + $options = array('clear' => TRUE); + + if ($entity = $this->entity) { + // D7 HACK: For new entities, entity and revision id are not set. This leads to + // * token replacement emitting PHP warnings + // * views choking on empty arguments + // We workaround this by filling in '0' for these IDs + // and use a clone to leave no traces of our unholy doings. + $info = entity_get_info($this->instance['entity_type']); + if (!isset($entity->{$info['entity keys']['id']})) { + $entity = clone $entity; + $entity->{$info['entity keys']['id']} = '0'; + if (!empty($info['entity keys']['revision'])) { + $entity->{$info['entity keys']['revision']} = '0'; + } + } + + $data[$info['token type']] = $entity; + } + // Replace tokens for each argument. + foreach ($args as $key => $arg) { + $args[$key] = token_replace($arg, $data, $options); + } + return $args; + } } function entityreference_view_settings_validate($element, &$form_state, $form) { diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/abstract.inc b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/abstract.inc index 1d2ea0d3..a4805b1f 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/abstract.inc +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/plugins/selection/abstract.inc @@ -21,8 +21,9 @@ interface EntityReference_SelectionHandler { * Return a list of referencable entities. * * @return - * An array of referencable entities, which keys are entity ids and - * values (safe HTML) labels to be displayed to the user. + * A nested array of entities, the first level is keyed by the + * entity bundle, which contains an array of entity labels (safe HTML), + * keyed by the entity ID. */ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0); diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.admin.test b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.admin.test index 9a121198..ec78e7bc 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.admin.test +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.admin.test @@ -21,7 +21,7 @@ class EntityReferenceAdminTestCase extends DrupalWebTestCase { parent::setUp(array('field_ui', 'entity', 'ctools', 'entityreference')); // Create test user. - $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields')); $this->drupalLogin($this->admin_user); // Create content type, with underscores. diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.feeds.test b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.feeds.test index 4dc1901f..32248cc0 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.feeds.test +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.feeds.test @@ -17,6 +17,7 @@ class FeedsMapperFieldTestCase extends DrupalWebTestCase{ 'name' => 'Feeds integration (field mapper)', 'description' => 'Test Feeds Mapper support for fields.', 'group' => 'Entity Reference', + 'dependencies' => array('feeds'), ); } @@ -28,10 +29,6 @@ class FeedsMapperFieldTestCase extends DrupalWebTestCase{ module_enable(array('entityreference_feeds_test'), TRUE); $this->resetAll(); - if (!module_exists('feeds')) { - return; - } - $permissions[] = 'access content'; $permissions[] = 'administer site configuration'; $permissions[] = 'administer content types'; @@ -157,10 +154,6 @@ class FeedsMapperFieldTestCase extends DrupalWebTestCase{ * Basic test loading a double entry CSV file. */ public function test() { - if (!module_exists('feeds')) { - return; - } - $this->drupalLogin($this->admin_user); $this->drupalGet('admin/structure/types/manage/article/fields'); $this->assertText('Ref - entity ID', t('Found Entity reference field %field.', array('%field' => 'field_er_id'))); diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.handlers.test b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.handlers.test index b88e1069..1367b44a 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.handlers.test +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.handlers.test @@ -194,6 +194,21 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { ), ); $this->assertReferencable($field, $referencable_tests, 'Node handler (admin)'); + + // Verify autocomplete input validation. + $handler = entityreference_get_selection_handler($field); + $element = array( + '#parents' => array('element_name'), + ); + $form_state = array(); + $form = array(); + $value = $handler->validateAutocompleteInput($nodes['published1']->title, $element, $form_state, $form); + $this->assertEqual($value, $nodes['published1']->nid); + + $invalid_input = $this->randomName(); + $value = $handler->validateAutocompleteInput($invalid_input, $element, $form_state, $form); + $this->assertNull($value); + $this->assertEqual(form_get_error($element), t('There are no entities matching "%value"', array('%value' => $invalid_input))); } /** @@ -256,7 +271,7 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { ), 'result' => array( 'user' => array( - $users['admin']->uid => '- Restricted access -', + $users['admin']->uid => ENTITYREFERENCE_DENIED, $users['non_admin']->uid => $user_labels['non_admin'], ), ), diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.taxonomy.test b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.taxonomy.test index 6e4afb78..94b3f568 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.taxonomy.test +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/entityreference.taxonomy.test @@ -112,4 +112,52 @@ class EntityReferenceTaxonomyTestCase extends DrupalWebTestCase { $this->assertFalse(taxonomy_select_nodes(1)); } + /** + * Add a second ER field from node/article to taxonomy. + * + * This should not cause {taxonomy_index} to receive duplicate entries. + */ + protected function setupForIndexDuplicates() { + // Create an entity reference field. + $field = array( + 'entity_types' => array('node'), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'taxonomy_term', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'field_entityreference_term2', + 'type' => 'entityreference', + ); + $field = field_create_field($field); + $instance = array( + 'field_name' => 'field_entityreference_term2', + 'bundle' => 'article', + 'entity_type' => 'node', + ); + + // Enable the taxonomy-index behavior. + $instance['settings']['behaviors']['taxonomy-index']['status'] = TRUE; + field_create_instance($instance); + } + + /** + * Make sure the index only contains one entry for a given node->term + * reference, even when multiple ER fields link from the node bundle to terms. + */ + public function testIndexDuplicates() { + // Extra setup for this test: add another ER field on this content type. + $this->setupForIndexDuplicates(); + + // Assert node insert with reference to term in first field. + $tid = 1; + $settings = array(); + $settings['type'] = 'article'; + $settings['field_entityreference_term'][LANGUAGE_NONE][0]['target_id'] = $tid; + $node = $this->drupalCreateNode($settings); + + $this->assertEqual(taxonomy_select_nodes($tid), array($node->nid)); + } } diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info index 311ae3f4..5bbf9b6f 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info @@ -8,9 +8,9 @@ dependencies[] = feeds dependencies[] = feeds_ui dependencies[] = entityreference -; Information added by packaging script on 2013-11-20 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2017-08-16 +version = "7.x-1.5" core = "7.x" project = "entityreference" -datestamp = "1384973110" +datestamp = "1502895850" diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_display.inc b/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_display.inc index f13e88a8..cca36509 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_display.inc +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_display.inc @@ -81,8 +81,9 @@ class entityreference_plugin_display extends views_plugin_display { $field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias]; } else { - $this->view->query->add_field($this->view->field[$field_alias]->options['table'], $this->view->field[$field_alias]->real_field, $this->view->field[$field_alias]->options['field'], array()); - $field = $this->view->query->fields[$this->view->field[$field_alias]->options['field']]; + $field_table = $this->view->query->ensure_table($this->view->field[$field_alias]->table, $this->view->field[$field_alias]->relationship); + $this->view->query->add_field($field_table, $this->view->field[$field_alias]->real_field, $this->view->field[$field_alias]->field, array()); + $field = $this->view->query->fields[$this->view->field[$field_alias]->field]; } // Add an OR condition for the field $conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE'); diff --git a/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_style.inc b/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_style.inc index fadaa9ee..b72a2357 100644 --- a/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_style.inc +++ b/profiles/commerce_kickstart/modules/contrib/entityreference/views/entityreference_plugin_style.inc @@ -26,7 +26,7 @@ class entityreference_plugin_style extends views_plugin_style { '#title' => t('Search fields'), '#options' => $options, '#required' => TRUE, - '#default_value' => $this->options['search_fields'], + '#default_value' => isset($this->options['search_fields']) ? $this->options['search_fields'] : array(), '#description' => t('Select the field(s) that will be searched when using the autocomplete widget.'), '#weight' => -3, ); diff --git a/profiles/commerce_kickstart/modules/contrib/eva/eva-display-entity-view.tpl.php b/profiles/commerce_kickstart/modules/contrib/eva/eva-display-entity-view.tpl.php index a0317bf9..cd6ab11f 100755 --- a/profiles/commerce_kickstart/modules/contrib/eva/eva-display-entity-view.tpl.php +++ b/profiles/commerce_kickstart/modules/contrib/eva/eva-display-entity-view.tpl.php @@ -39,6 +39,12 @@
    + +
    + +
    + +
    diff --git a/profiles/commerce_kickstart/modules/contrib/eva/eva.info b/profiles/commerce_kickstart/modules/contrib/eva/eva.info index aafbd193..0967cc4a 100644 --- a/profiles/commerce_kickstart/modules/contrib/eva/eva.info +++ b/profiles/commerce_kickstart/modules/contrib/eva/eva.info @@ -6,9 +6,9 @@ package = Views files[] = eva_plugin_display_entity.inc -; Information added by drupal.org packaging script on 2012-07-31 -version = "7.x-1.2" +; Information added by Drupal.org packaging script on 2016-08-08 +version = "7.x-1.3" core = "7.x" project = "eva" -datestamp = "1343701935" +datestamp = "1470619440" diff --git a/profiles/commerce_kickstart/modules/contrib/eva/eva.module b/profiles/commerce_kickstart/modules/contrib/eva/eva.module index 25cf5b4e..5de36e1e 100755 --- a/profiles/commerce_kickstart/modules/contrib/eva/eva.module +++ b/profiles/commerce_kickstart/modules/contrib/eva/eva.module @@ -14,24 +14,32 @@ function eva_views_api() { /** - * Implements hook_content_extra_fields(). + * Implements hook_field_extra_fields(). */ function eva_field_extra_fields() { $extras = array(); $views = eva_get_views(); - + foreach ($views as $entity => $data) { foreach ($data as $view) { - foreach ($view['bundles'] as $bundle) { + if (!empty($view['bundles'])) { + $bundles = $view['bundles']; + } + // If no bundles are set, apply to all bundles. + else { + $entity_info = entity_get_info($entity); + $bundles = array_keys($entity_info['bundles']); + } + foreach ($bundles as $bundle) { $extras[$entity][$bundle]['display'][$view['name'] . '_' . $view['display']] = array( - 'label' => (empty($view['title'])) ? $view['name'] : $view['title'], - 'description' => $view['title'], + 'label' => (empty($view['title'])) ? $view['name'] : $view['title'], + 'description' => $view['title'], 'weight' => 10, ); // Provide a separate extra field for the exposed form if there is any. - if ($view['exposed form']) { + if (!empty($view['exposed form']) && !empty($view['exposed form split'])) { $extras[$entity][$bundle]['display'][$view['name'] . '_' . $view['display'] . '_' . 'form'] = array( - 'label' => ((empty($view['title'])) ? $view['name'] : $view['title']) . ' (' . t('Exposed form') . ')', + 'label' => ((empty($view['title'])) ? $view['name'] : $view['title']) . ' (' . t('Exposed form') . ')', 'description' => t('The exposed filter form of the view.'), 'weight' => 9, ); @@ -49,22 +57,21 @@ function eva_field_extra_fields() { * This is a terrible, terrible hack that should not be necessary; taxonomy and * some other entity types use fields, but don't implement hook_entity_view(). * We have to ALTER those entity types after they're built. For the time being, - * we'll use a list of special cases to trigger this special handling. + * we'll use a list of special cases to trigger this special handling. */ function eva_entity_view_alter(&$build, $type) { $view_mode = $build['#view_mode']; - $language = $build['#language']; - $entity_data = entity_get_info($type); - $entity = _eva_extract_entity_from_build($build); + if (!$entity = _eva_extract_entity_from_build($build, $type)) { + return; + } $entity_ids = entity_extract_ids($type, $entity); - $settings = field_view_mode_settings($type, $entity_ids[2]); $fields = field_extra_fields_get_display($type, $entity_ids[2], $view_mode); $views = eva_get_views($type); foreach ($views as $info) { - $longname = $info['name'] .'_'. $info['display']; + $longname = $info['name'] . '_' . $info['display']; if (isset($fields[$longname]) && $fields[$longname]['visible']) { if ($view = views_get_view($info['name'])) { $view->set_display($info['display']); @@ -93,17 +100,18 @@ function eva_entity_view_alter(&$build, $type) { } /** - * Get a list of views and displays attached to speficic entities. + * Gets a list of views and displays attached to specific entities. * * This function will cache its results into the views cache, so it gets * cleared by Views appropriately. * - * @param $type - * The entity type we want to retrieve views for. If NULL is - * specified, views for all entity types will be returned. - * @param $reset - * Force a rebuild of the data. - * @return + * @param string|null $type + * (optional) The entity type we want to retrieve views for. If NULL is + * specified, views for all entity types will be returned. Defaults to NULL. + * @param bool $reset + * (optional) Force a rebuild of the data. Defaults to FALSE. + * + * @return array * An array of view name/display name values, or an empty array(). */ function eva_get_views($type = NULL, $reset = FALSE) { @@ -111,7 +119,7 @@ function eva_get_views($type = NULL, $reset = FALSE) { if (!isset($used_views) || $reset) { views_include('cache'); - + // If we're not resetting, check the Views cache. if (!$reset) { $cache = views_cache_get("eva"); @@ -122,6 +130,7 @@ function eva_get_views($type = NULL, $reset = FALSE) { // If it's still empty rebuild it. if (!isset($used_views)) { + $used_views = array(); // Trigger a rebuild of the views object cache, which may not be fully loaded. ctools_include('export'); ctools_export_load_object_reset('views_view'); @@ -139,6 +148,7 @@ function eva_get_views($type = NULL, $reset = FALSE) { 'display' => $display_id, 'bundles' => $view->display_handler->get_option('bundles'), 'exposed form' => $view->display_handler->uses_exposed(), + 'exposed form split' => $view->display_handler->get_option('exposed_form_as_field'), ); $view->destroy(); } @@ -162,23 +172,27 @@ function eva_get_views($type = NULL, $reset = FALSE) { * entities, and contrib entities based on EntityAPI all store their junk in * different slots of the build array. See http://drupal.org/node/1170198. * - * @param $build - * The token string defined by the view. - * @param $entity_data - * The token type. + * @param array $build + * The build array as passed to hook_entity_view_alter(). + * @param string $entity_type + * (optional) The entity type. Defaults to NULL. * - * I hate you, Milkman Dan. + * @return object|bool + * Either the entity object, or FALSE if it cannot be found. */ -function _eva_extract_entity_from_build($build) { +function _eva_extract_entity_from_build($build, $entity_type = NULL) { // EntityAPI often sticks stuff in here. if (!empty($build['#entity'])) { return $build['#entity']; } - + // Other entities stick them here! elseif (!empty($build['#' . $build['#entity_type']])) { return $build['#' . $build['#entity_type']]; } + elseif ($entity_type && !empty($build['#' . $entity_type])) { + return $build['#' . $entity_type]; + } // Some entities are naughty. elseif ($build['#entity_type'] == 'user') { @@ -194,13 +208,14 @@ function _eva_extract_entity_from_build($build) { /** * Get view arguments array from string that contains tokens * - * @param $string + * @param string $string * The token string defined by the view. - * @param $type + * @param string $type * The token type. - * @param $object + * @param object $object * The object being used for replacement data (typically a node). - * @return + * + * @return array * An array of argument values. * * @todo: security? diff --git a/profiles/commerce_kickstart/modules/contrib/eva/eva.theme.inc b/profiles/commerce_kickstart/modules/contrib/eva/eva.theme.inc index d60caf2c..b64c0db6 100755 --- a/profiles/commerce_kickstart/modules/contrib/eva/eva.theme.inc +++ b/profiles/commerce_kickstart/modules/contrib/eva/eva.theme.inc @@ -10,6 +10,7 @@ function template_preprocess_eva_display_entity_view(&$vars) { $view = $vars['view']; $display = $view->display_handler; $vars['title'] = $display->get_option('show_title') ? filter_xss_admin($view->get_title()) : ''; + $vars['exposed_form_as_field'] = $display->get_option('exposed_form_as_field'); } function template_process_eva_display_entity_view(&$vars) { diff --git a/profiles/commerce_kickstart/modules/contrib/eva/eva_plugin_display_entity.inc b/profiles/commerce_kickstart/modules/contrib/eva/eva_plugin_display_entity.inc index ae7366e4..906a0c00 100755 --- a/profiles/commerce_kickstart/modules/contrib/eva/eva_plugin_display_entity.inc +++ b/profiles/commerce_kickstart/modules/contrib/eva/eva_plugin_display_entity.inc @@ -12,6 +12,7 @@ class eva_plugin_display_entity extends views_plugin_display { $options['argument_mode'] = array('default' => 'id'); $options['default_argument'] = array('default' => ''); $options['show_title'] = 0; + $options['exposed_form_as_field'] = 0; return $options; } @@ -51,7 +52,7 @@ class eva_plugin_display_entity extends views_plugin_display { $options['bundles'] = array( 'category' => 'entity_view', 'title' => t('Bundles'), - 'value' => empty($bundle_names) ? t('None') : implode(', ', $bundle_names), + 'value' => empty($bundle_names) ? t('All') : implode(', ', $bundle_names), ); $argument_mode = $this->get_option('argument_mode'); @@ -67,6 +68,12 @@ class eva_plugin_display_entity extends views_plugin_display { 'value' => $this->get_option('show_title') ? t('Yes') : t('No'), ); + $options['exposed_form_as_field'] = array( + 'category' => 'entity_view', + 'title' => t('Exposed Form as Field'), + 'value' => $this->get_option('exposed_form_as_field') ? t('Yes') : t('No'), + ); + if (module_exists('token')) { // We must load token values here to show them on the options form. drupal_add_js(drupal_get_path('module', 'token') . '/token.js'); @@ -81,12 +88,13 @@ class eva_plugin_display_entity extends views_plugin_display { function options_form(&$form, &$form_state) { // It is very important to call the parent function here: parent::options_form($form, $form_state); - + $entity_info = entity_get_info(); $entity_type = $this->get_option('entity_type'); switch ($form_state['section']) { case 'entity_type': + $entity_names = array(); foreach ($entity_info as $type => $info) { if (!empty($info['view modes'])) { $entity_names[$type] = $info['label']; @@ -104,13 +112,14 @@ class eva_plugin_display_entity extends views_plugin_display { break; case 'bundles': + $options = array(); foreach ($entity_info[$entity_type]['bundles'] as $bundle => $info) { $options[$bundle] = $info['label']; } $form['#title'] .= t('Bundles'); $form['bundles'] = array( '#type' => 'checkboxes', - '#title' => t("Attach this display to the following bundles"), + '#title' => t("Attach this display to the following bundles. If no bundles are selected, the display will be attached to all."), '#options' => $options, '#default_value' => $this->get_option('bundles'), ); @@ -138,7 +147,7 @@ class eva_plugin_display_entity extends views_plugin_display { '#collapsible' => TRUE, '#states' => array( 'visible' => array( - 'input:[name=argument_mode]' => array('value' => 'token'), + ':input[name=argument_mode]' => array('value' => 'token'), ), ), ); @@ -166,6 +175,14 @@ class eva_plugin_display_entity extends views_plugin_display { '#default_value' => $this->get_option('show_title'), ); break; + case 'exposed_form_as_field': + $form['#title'] .= t('Exposed Form as Field'); + $form['exposed_form_as_field'] = array( + '#type' => 'checkbox', + '#title' => t('Split off Exposed Form as Separate Field'), + '#default_value' => $this->get_option('exposed_form_as_field'), + '#description' => t('Check this box to have a separate field for this view\'s exposed form on the "Manage Display" tab'), + ); } } @@ -185,7 +202,7 @@ class eva_plugin_display_entity extends views_plugin_display { // only one on the new type, we can select it automatically. Otherwise // we need to wipe the options and start over. $new_entity_info = entity_get_info($new_entity); - + $new_bundle_keys = array_keys($new_entity_info['bundles']); $new_bundles = array(); if (count($new_bundle_keys) == 1) { @@ -209,6 +226,10 @@ class eva_plugin_display_entity extends views_plugin_display { case 'show_title': $this->set_option('show_title', $form_state['values']['show_title']); break; + case 'exposed_form_as_field': + $this->set_option('exposed_form_as_field', $form_state['values']['exposed_form_as_field']); + break; + } } @@ -219,12 +240,6 @@ class eva_plugin_display_entity extends views_plugin_display { if (empty($entity_type)) { $errors[] = t('Display @display must have an entity type selected.', array('@display' => $this->display->display_title)); } - - $bundles = $this->get_option('bundles'); - if (empty($bundles)) { - $errors[] = t('Display @display must have at least one bundle selected.', array('@display' => $this->display->display_title)); - } - return $errors; } @@ -239,7 +254,7 @@ class eva_plugin_display_entity extends views_plugin_display { $entity = $this->view->current_entity; $entity_type = $this->view->display_handler->get_option('entity_type'); $entity_info = entity_get_info($entity_type); - + $arg_mode = $this->view->display_handler->get_option('argument_mode'); if ($arg_mode == 'token') { if ($token_string = $this->view->display_handler->get_option('default_argument')) { @@ -250,7 +265,7 @@ class eva_plugin_display_entity extends views_plugin_display { foreach ($token_values as $key => $value) { $new_args[$key] = $value; } - + $this->view->args = $new_args; } } @@ -289,4 +304,5 @@ class eva_plugin_display_entity extends views_plugin_display { return $data; } } + } diff --git a/profiles/commerce_kickstart/modules/contrib/facetapi/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/facetapi/PATCHES.txt index 345b3534..cb079ae1 100644 --- a/profiles/commerce_kickstart/modules/contrib/facetapi/PATCHES.txt +++ b/profiles/commerce_kickstart/modules/contrib/facetapi/PATCHES.txt @@ -2,4 +2,4 @@ The following patches have been applied to this project: - https://drupal.org/files/1616518-term_remove_link-24.patch - https://www.drupal.org/files/issues/notice_undefined-2378693-3.patch -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file +This file was automatically generated by Drush Make (http://drupal.org/project/drush). diff --git a/profiles/commerce_kickstart/modules/contrib/features/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/features/PATCHES.txt index e29b9b98..69da25f6 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/PATCHES.txt +++ b/profiles/commerce_kickstart/modules/contrib/features/PATCHES.txt @@ -1,6 +1,5 @@ The following patches have been applied to this project: - http://drupal.org/files/issues/features-fix-modules-enabled-2143765-1.patch - https://www.drupal.org/files/issues/ignore_hidden_modules-2479803-1.patch -- https://www.drupal.org/files/issues/2534138-field-base-exception-catch-1.patch -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file +This file was automatically generated by Drush Make (http://drupal.org/project/drush). diff --git a/profiles/commerce_kickstart/modules/contrib/features/features.admin.inc b/profiles/commerce_kickstart/modules/contrib/features/features.admin.inc index 8360e974..46752c89 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/features.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/features.admin.inc @@ -88,6 +88,12 @@ function features_settings_form($form, $form_state) { '#default_value' => variable_get('features_rebuild_on_flush', TRUE), '#description' => t('If you have a large site with many features, you may experience lag on full cache clear. If disabled, features will rebuild only when viewing the features list or saving the modules list.'), ); + $form['general']['features_rebuild_modules_page'] = array( + '#type' => 'checkbox', + '#title' => t('Rebuild features on accessing modules list page'), + '#default_value' => variable_get('features_rebuild_modules_page', FALSE), + '#description' => t('If you have a large site with many features, you may experience lag on accessing the modules administration page. If disabled, features will not rebuild when viewing the modules list.'), + ); return system_settings_form($form); } @@ -109,7 +115,7 @@ function features_export_form($form, $form_state, $feature = NULL) { $feature_name = !empty($feature->name) ? $feature->name : ''; $form = array( - '#attributes' => array('class' => array('features-export-form')), + '#attributes' => array('class' => array('features-export-form', 'clearfix')), '#feature' => isset($feature) ? $feature : NULL, ); $form['info'] = array( @@ -756,7 +762,7 @@ function features_export_form_rebuild($form, &$form_state) { function features_export_components_json($feature_name) { module_load_include('inc', 'features', 'features.export'); - $export = array(); + $export = array('features' => array()); if (!empty($_POST['items'])) { $excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array(); $stub = array(); @@ -1143,7 +1149,7 @@ function features_admin_form($form, $form_state) { // As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the // the array. We add it late, but at the beginning of the array because that // keeps us away from trouble. - $form = array('packages' => array('#type' => 'vertical_tabs')) + $form; + $form = array_merge(array('packages' => array('#type' => 'vertical_tabs')), $form); $form['buttons'] = array( '#theme' => 'features_form_buttons', @@ -1328,7 +1334,7 @@ function features_form_submit(&$form, &$form_state) { // page callback rather than as part of the submit handler as some modules // have includes/other directives of importance in hooks that have already // been called in this page load. - $form_state['redirect'] = 'admin/structure/features/cleanup/clear'; + $form_state['redirect'] = array('admin/structure/features/cleanup', array('query' => array('token' => drupal_get_token()))); $features = $form['#features']; if (!empty($features)) { @@ -1352,21 +1358,19 @@ function features_form_rebuild() { } /** - * Form for clearing cache after enabling a feature. + * Callback for clearing cache after enabling a feature. */ -function features_cleanup_form($form, $form_state, $cache_clear = FALSE) { - // Clear caches if we're getting a post-submit redirect that requests it. - if ($cache_clear) { +function features_cleanup() { + if (!empty($_GET['token']) && drupal_valid_token($_GET['token'])) { drupal_flush_all_caches(); - // The following functions need to be run because drupal_flush_all_caches() // runs rebuilds in the wrong order. The node type cache is rebuilt *after* // the menu is rebuilt, meaning that the menu tree is stale in certain // circumstances after drupal_flush_all_caches(). We rebuild again. menu_rebuild(); - } - drupal_goto('admin/structure/features'); + } + return MENU_NOT_FOUND; } /** diff --git a/profiles/commerce_kickstart/modules/contrib/features/features.api.php b/profiles/commerce_kickstart/modules/contrib/features/features.api.php index 6a13b0c1..d7c7edec 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/features.api.php +++ b/profiles/commerce_kickstart/modules/contrib/features/features.api.php @@ -42,7 +42,7 @@ * are declared "dynamically" or are part of a family of components. * * 'alter_type': What type of alter hook this hook uses. 'normal' is called - * after the main hook is called. 'inline' is embeded within the default hook + * after the main hook is called. 'inline' is embedded within the default hook * and may not be implemented by some default hooks. * 'none' is no alter hook exists. Defaults to 'normal' * @@ -310,7 +310,7 @@ function hook_features_pipe_COMPONENT_alter(&$pipe, $data, $export) { * The module being exported contained in $export['module_name']. */ function hook_features_pipe_alter(&$pipe, $data, $export) { - if ($export['component'] == 'node' && in_array($data, 'my-node-type')) { + if ($export['component'] == 'node' && in_array('my-node-type', $data)) { $pipe['dependencies'][] = 'mymodule'; } } diff --git a/profiles/commerce_kickstart/modules/contrib/features/features.drush.inc b/profiles/commerce_kickstart/modules/contrib/features/features.drush.inc index 6fdbc263..696825d6 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/features.drush.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/features.drush.inc @@ -32,6 +32,12 @@ function features_drush_command() { ), 'drupal dependencies' => array('features'), 'aliases' => array('fl', 'features'), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'list', + 'field-labels' => array('name' => 'Name', 'feature' => 'Feature', 'status' => 'Status', 'version' => 'Version', 'state' => 'State'), + 'output-data-type' => 'format-table', + ), ); $items['features-export'] = array( 'description' => "Export a feature from your site into a module.", @@ -207,12 +213,12 @@ function drush_features_list() { } module_load_include('inc', 'features', 'features.export'); - $rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State'))); // Sort the Features list before compiling the output. $features = features_get_features(NULL, TRUE); ksort($features); + $rows = array(); foreach ($features as $k => $m) { switch (features_get_storage($m->name)) { case FEATURES_DEFAULT: @@ -230,16 +236,19 @@ function drush_features_list() { ($m->status == 0 && ($status == 'all' || $status == 'disabled')) || ($m->status == 1 && ($status == 'all' || $status == 'enabled')) ) { - $rows[] = array( - $m->info['name'], - $m->name, - $m->status ? dt('Enabled') : dt('Disabled'), - $m->info['version'], - $storage + $rows[$k] = array( + 'name' => $m->info['name'], + 'feature' => $m->name, + 'status' => $m->status ? dt('Enabled') : dt('Disabled'), + 'version' => $m->info['version'], + 'state' => $storage ); } } - drush_print_table($rows, TRUE); + if (version_compare(DRUSH_VERSION, '6.0', '<')) { + drush_print_table($rows, TRUE); + } + return $rows; } /** @@ -337,8 +346,13 @@ function _drush_features_component_filter($all_components, $patterns = array(), // Rewrite * to %. Let users use both as wildcard. $pattern = strtr($pattern, array('*' => '%')); $sources = array(); - $source_pattern = strtok($pattern, ':'); - $component_pattern = strtok(':'); + if (strpos($pattern, ':') !== FALSE) { + list($source_pattern, $component_pattern) = explode(':', $pattern, 2); + } + else { + $source_pattern = $pattern; + $component_pattern = ''; + } // If source is empty, use a pattern. if ($source_pattern == '') { $source_pattern = '%'; @@ -607,9 +621,18 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) { drush_op('mkdir', $directory); } if (is_dir($directory)) { + // Ensure that the export will be created in the English language. + // The export language must be set before flushing caches as that can + // result into translatables being statically cached. + $language = _features_export_language(); + drupal_flush_all_caches(); $export = _drush_features_generate_export($info, $module_name); $files = features_export_render($export, $module_name, TRUE); + + // Restore the language + _features_export_language($language); + // Copy any files if _files key is there. if (!empty($files['_files'])) { foreach ($files['_files'] as $file_name => $file_info) { @@ -685,7 +708,7 @@ function _drush_features_generate_export(&$info, &$module_name) { } else { // Split version number parts. - $pattern = '/([0-9]-[a-z]+([0-9])+)/'; + $pattern = '/([0-9]-[a-z]+([0-9]+))/'; $matches = array(); preg_match($pattern, $version_minor, $matches); $number = array_pop($matches); @@ -797,7 +820,7 @@ function drush_features_revert() { } } else { - drush_features_list(); + drush_print_table(drush_features_list()); return; } } @@ -851,7 +874,7 @@ function drush_features_revert_all() { */ function drush_features_diff() { if (!$args = func_get_args()) { - drush_features_list(); + drush_print_table(drush_features_list()); return; } $module = $args[0]; diff --git a/profiles/commerce_kickstart/modules/contrib/features/features.export.inc b/profiles/commerce_kickstart/modules/contrib/features/features.export.inc index 5cf2a8c8..5045b131 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/features.export.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/features.export.inc @@ -46,7 +46,7 @@ function features_populate($info, $module_name) { */ function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) { // Ensure that the export will be created in the english language. - _features_set_export_language(); + $language = _features_export_language(); if ($reset) { drupal_static_reset(__FUNCTION__); @@ -92,6 +92,7 @@ function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) } } } + _features_export_language($language); return $export; } @@ -843,6 +844,13 @@ function features_get_default($component, $module_name = NULL, $alter = TRUE, $r /** * Get a map of components to their providing modules. + * + * @param string $component + * @param string $attribute + * @param callable $callback + * @param bool $reset + * + * @return array|bool */ function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) { $map = &drupal_static(__FUNCTION__, array()); @@ -891,6 +899,9 @@ function features_get_default_map($component, $attribute = NULL, $callback = NUL * Retrieve an array of features/components and their current states. */ function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) { + // Ensure that the export will be created in the English language. + $language = _features_export_language(); + if ($reset) { drupal_static_reset(__FUNCTION__); } @@ -901,7 +912,7 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE // Retrieve only rebuildable components if requested. features_include(); - $components = array_keys(features_get_components()); + $components = array_keys(features_get_components(NULL, NULL, $reset)); if ($rebuild_only) { foreach ($components as $k => $component) { if (!features_hook($component, 'features_rebuild')) { @@ -984,6 +995,9 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE foreach ($return as $k => $v) { $return[$k] = array_intersect_key($return[$k], array_flip($components)); } + + _features_export_language($language); + return $return; } @@ -1006,17 +1020,14 @@ function _features_linetrim($code) { * @param bool $remove_empty if set, remove null or empty values for assoc arrays. */ function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) { - // make a deep copy of data to prevent problems when removing recursion later. - $array = unserialize(serialize($array)); + $array = features_remove_recursion($array); if (isset($component)) { $ignore_keys = _features_get_ignore_keys($component); // remove keys to be ignored - // doing this now allows us to better control which recursive parts are removed if (count($ignore_keys)) { _features_remove_ignores($array, $ignore_keys); } } - features_remove_recursion($array); _features_sanitize($array, $remove_empty); } @@ -1069,81 +1080,45 @@ function _features_is_assoc($array) { /** * Removes recursion from an object or array. * - * @param $item - * An object or array passed by reference. - */ -function features_remove_recursion(&$item) { - $uniqid = __FUNCTION__ . mt_rand(); // use of uniqid() here impacts performance - $stack = array(); - return _features_remove_recursion($item, $stack, $uniqid); -} - -/** - * Helper to removes recursion from an object/array. + * Taken from https://code.google.com/p/formaldehyde/source/browse/trunk/formaldehyde.php + * Also used in node_export module * - * @param $item - * An object or array passed by reference. + * @param $o mixed + * @return mixed + * returns a copy of the object or array with recursion removed */ -function _features_remove_recursion(&$object, &$stack = array(), $uniqid) { - if ((is_object($object) || is_array($object)) && $object) { - $in_stack = FALSE; - foreach ($stack as &$item) { - if (_features_is_ref_to($object, $item, $uniqid)) { - $in_stack = TRUE; - break; - } - } - unset($item); - - if (!$in_stack) { - $stack[] = $object; - foreach ($object as $key => &$subobject) { - if (_features_remove_recursion($subobject, $stack, $uniqid)) { - if (is_object($object)) { - unset($object->$key); - } - else { - unset($object[$key]); - } +function features_remove_recursion($o) { + static $replace; + if (!isset($replace)) { + $replace = create_function( + '$m', + '$r="\x00{$m[1]}ecursion_features";return \'s:\'.strlen($r.$m[2]).\':"\'.$r.$m[2].\'";\';' + ); + } + if (is_array($o) || is_object($o)) { + $re = '#(r|R):([0-9]+);#'; + $serialize = serialize($o); + if (preg_match($re, $serialize)) { + $last = $pos = 0; + while (false !== ($pos = strpos($serialize, 's:', $pos))) { + $chunk = substr($serialize, $last, $pos - $last); + if (preg_match($re, $chunk)) { + $length = strlen($chunk); + $chunk = preg_replace_callback($re, $replace, $chunk); + $serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last)); + $pos += strlen($chunk) - $length; } + $pos += 2; + $last = strpos($serialize, ':', $pos); + $length = substr($serialize, $pos, $last - $pos); + $last += 4 + $length; + $pos = $last; } - unset($subobject); - } - else { - return TRUE; + $serialize = substr($serialize, 0, $last) . preg_replace_callback($re, $replace, substr($serialize, $last)); + $o = unserialize($serialize); } } - return FALSE; -} - -/** - * Helper function in determining equality of arrays. Credit to http://stackoverflow.com/a/4263181 - * - * @see _features_remove_recursion() - * - * @param $a - * object a - * @param $b - * object b - * @return bool - * - */ -function _features_is_ref_to(&$a, &$b, $uniqid) { - if (is_object($a) && is_object($b)) { - return ($a === $b); - } - - $temp_a = $a; - $temp_b = $b; - - $b = $uniqid; - - if ($a === $uniqid) $return = true; - else $return = false; - - $a = $temp_a; - $b = $temp_b; - return $return; + return $o; } /** @@ -1162,7 +1137,7 @@ function _features_remove_ignores(&$item, $ignore_keys, $level = -1) { if (!is_array($item) && !is_object($item)) { return; } - foreach ($item as $key => $value) { + foreach ($item as $key => &$value) { if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) { if ($is_object) { unset($item->$key); @@ -1175,6 +1150,7 @@ function _features_remove_ignores(&$item, $ignore_keys, $level = -1) { _features_remove_ignores($value, $ignore_keys, $level+1); } } + unset($value); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/features/features.info b/profiles/commerce_kickstart/modules/contrib/features/features.info index e18cc8f1..d28a617b 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/features.info +++ b/profiles/commerce_kickstart/modules/contrib/features/features.info @@ -3,12 +3,16 @@ description = "Provides feature management for Drupal." core = 7.x package = "Features" files[] = tests/features.test +test_dependencies[] = image +test_dependencies[] = strongarm +test_dependencies[] = taxonomy +test_dependencies[] = views configure = admin/structure/features/settings -; Information added by Drupal.org packaging script on 2015-06-24 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2016-04-18 +version = "7.x-2.10" core = "7.x" project = "features" -datestamp = "1435165997" +datestamp = "1461011641" diff --git a/profiles/commerce_kickstart/modules/contrib/features/features.module b/profiles/commerce_kickstart/modules/contrib/features/features.module index 94e00830..c489dfbc 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/features.module +++ b/profiles/commerce_kickstart/modules/contrib/features/features.module @@ -84,8 +84,7 @@ function features_menu() { $items['admin/structure/features/cleanup'] = array( 'title' => 'Cleanup', 'description' => 'Clear cache after enabling/disabling a feature.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('features_cleanup_form', 4), + 'page callback' => 'features_cleanup', 'type' => MENU_CALLBACK, 'file' => 'features.admin.inc', 'weight' => 1, @@ -128,7 +127,7 @@ function features_menu() { 'description' => 'Display components of a feature.', 'page callback' => 'drupal_get_form', 'page arguments' => array('features_admin_components', 3), - 'load arguments' => array(3, TRUE), + 'load arguments' => array(TRUE), 'access callback' => 'user_access', 'access arguments' => array('administer features'), 'type' => MENU_CALLBACK, @@ -147,7 +146,7 @@ function features_menu() { 'description' => 'Recreate an existing feature.', 'page callback' => 'drupal_get_form', 'page arguments' => array('features_export_form', 3), - 'load arguments' => array(3, TRUE), + 'load arguments' => array(TRUE), 'access callback' => 'user_access', 'access arguments' => array('administer features'), 'type' => MENU_LOCAL_TASK, @@ -160,7 +159,7 @@ function features_menu() { 'description' => 'Compare default and current feature.', 'page callback' => 'features_feature_diff', 'page arguments' => array(3, 5), - 'load arguments' => array(3, TRUE), + 'load arguments' => array(TRUE), 'access callback' => 'features_access_override_actions', 'access arguments' => array(3), 'type' => MENU_LOCAL_TASK, @@ -173,7 +172,7 @@ function features_menu() { 'description' => 'Lock a feature or components.', 'page callback' => 'features_admin_lock', 'page arguments' => array(3, 5, 6), - 'load arguments' => array(3, TRUE, TRUE), + 'load arguments' => array(TRUE), 'access arguments' => array('administer features'), 'type' => MENU_CALLBACK, 'file' => 'features.admin.inc', @@ -183,7 +182,7 @@ function features_menu() { 'description' => 'Javascript status call back.', 'page callback' => 'features_feature_status', 'page arguments' => array(3), - 'load arguments' => array(3, TRUE), + 'load arguments' => array(TRUE), 'access callback' => 'user_access', 'access arguments' => array('administer features'), 'type' => MENU_CALLBACK, @@ -278,7 +277,11 @@ function features_flush_caches() { features_get_modules(NULL, TRUE); } } - return array('cache_features'); + + if (db_table_exists('cache_features')) { + return array('cache_features'); + } + return array(); } /** @@ -515,7 +518,7 @@ function features_load_feature($name, $reset = FALSE) { * Return a module 'object' including .info information. * * @param $name - * The name of the module to retrieve information for. If ommitted, + * The name of the module to retrieve information for. If omitted, * an array of all available modules will be returned. * @param $reset * Whether to reset the cache. @@ -757,9 +760,9 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) { $cache->data = $data; } if (!empty($name)) { - return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : array(); + return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : FALSE; } - return !empty($cache->data[$type]) ? $cache->data[$type] : array(); + return !empty($cache->data[$type]) ? $cache->data[$type] : FALSE; } /** @@ -910,7 +913,7 @@ function features_access_override_actions($feature) { features_include(); module_load_include('inc', 'features', 'features.export'); - $access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)) && user_access('administer features'); + $access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_DEFAULT, FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)); } return $access[$feature->name]; } @@ -921,7 +924,9 @@ function features_access_override_actions($feature) { * Implements hook_form_alter() for system_modules form(). */ function features_form_system_modules_alter(&$form) { - features_rebuild(); + if (variable_get('features_rebuild_modules_page', FALSE)) { + features_rebuild(); + } } /** @@ -1223,12 +1228,14 @@ function features_feature_unlock($feature, $component = NULL) { } /** - * Sets the current language to english to ensure a proper export. + * Sets/Returns the current language to english to ensure a proper export. */ -function _features_set_export_language() { - // Ensure this is only done if the language isn't already en. - // This can be called multiple times - ensure the handling is done just once. - if ($GLOBALS['language']->language != 'en' && !drupal_static(__FUNCTION__)) { +function _features_export_language($language = NULL) { + $current = $GLOBALS['language']; + if (isset($language)) { + $GLOBALS['language'] = $language; + } + elseif ($GLOBALS['language']->language != 'en') { // Create the language object as language_default() does. $GLOBALS['language'] = (object) array( 'language' => 'en', @@ -1243,57 +1250,8 @@ function _features_set_export_language() { 'weight' => 0, 'javascript' => '', ); - // Ensure that static caches are cleared, as they might contain language - // specific information. But keep some important ones. The call below - // accesses a non existing key and requests to reset it. In such cases the - // whole caching data array is returned. - $static = drupal_static(uniqid('', TRUE), NULL, TRUE); - drupal_static_reset(); - // Restore some of the language independent, runtime state information to - // keep everything working and avoid unnecessary double processing. - $static_caches_to_keep = array( - 'conf_path', - 'system_list', - 'ip_address', - 'drupal_page_is_cacheable', - 'list_themes', - 'drupal_page_header', - 'drupal_send_headers', - 'drupal_http_headers', - 'language_list', - 'module_implements', - 'drupal_alter', - 'path_is_admin', - 'path_get_admin_paths', - 'drupal_match_path', - 'menu_get_custom_theme', - 'menu_get_item', - 'arg', - 'drupal_system_listing', - 'drupal_parse_info_file', - 'libraries_get_path', - 'module_hook_info', - 'drupal_add_js', - 'drupal_add_js:jquery_added', - 'drupal_add_library', - 'drupal_get_library', - 'drupal_add_css', - 'menu_set_active_trail', - 'menu_link_get_preferred', - 'menu_set_active_menu_names', - 'theme_get_registry', - 'features_get_components', - 'features_get_components_by_key', - ); - foreach ($static_caches_to_keep as $cid) { - if (isset($static[$cid])) { - $data = &drupal_static($cid); - $data = $static[$cid]; - } - } - $called = &drupal_static(__FUNCTION__); - $called = TRUE; } + return $current; } /** @@ -1330,6 +1288,11 @@ function features_features_ignore($component) { case 'field': $ignores['locked'] = 1; break; + case 'field_base': + $ignores['indexes'] = 0; + break; + case 'taxonomy': + $ignores['hierarchy'] = 0; } return $ignores; } diff --git a/profiles/commerce_kickstart/modules/contrib/features/includes/features.contact.inc b/profiles/commerce_kickstart/modules/contrib/features/includes/features.contact.inc new file mode 100644 index 00000000..af9b9193 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/features/includes/features.contact.inc @@ -0,0 +1,115 @@ + array( + 'name' => t('Contact categories'), + 'feature_source' => TRUE, + 'default_hook' => 'contact_categories_defaults', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + ); +} + +/** + * Implements hook_features_export_options(). + */ +function contact_categories_features_export_options() { + $options = array(); + $categories = db_select('contact', 'c')->fields('c')->execute()->fetchAll(); + foreach ($categories as $category) { + $options["$category->category"] = "$category->category"; + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function contact_categories_features_export($data, &$export, $module_name = '') { + $export['dependencies']['features'] = 'features'; + $export['dependencies']['contact'] = 'contact'; + + foreach ($data as $name) { + $export['features']['contact_categories'][$name] = $name; + } + + return array(); +} + +/** + * Implements hook_features_export_render(). + */ +function contact_categories_features_export_render($module, $data, $export = NULL) { + $render = array(); + foreach ($data as $name) { + $export_category = db_select('contact', 'c') + ->fields('c', array('cid', 'category')) + ->condition('category', $name, 'LIKE') + ->execute() + ->fetchAll(); + if (isset($export_category[0]->cid) && ($category = contact_load($export_category[0]->cid))) { + unset($category['cid']); + $render[$name] = $category; + } + } + return array('contact_categories_defaults' => ' return ' . features_var_export($render, ' ') . ';'); +} + +/** + * Implements hook_features_revert(). + */ +function contact_categories_features_revert($module) { + return contact_categories_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function contact_categories_features_rebuild($module) { + if ($defaults = features_get_default('contact_categories', $module)) { + foreach ($defaults as $default_category) { + $existing_categories = db_select('contact', 'c') + ->fields('c', array('cid', 'category')) + ->execute() + ->fetchAll(); + if ($existing_categories) { + foreach ($existing_categories as $existing_category) { + if ($default_category['category'] == $existing_category->category) { + db_update('contact') + ->fields( + array( + 'recipients' => $default_category['recipients'], + 'reply' => $default_category['reply'], + 'weight' => $default_category['weight'], + 'selected' => $default_category['selected'], + ) + ) + ->condition('category', $existing_category->category, '=') + ->execute(); + } + else { + db_merge('contact') + ->key(array('category' => $default_category['category'])) + ->fields($default_category) + ->execute(); + } + } + } + else { + db_merge('contact') + ->key(array('category' => $default_category['category'])) + ->fields($default_category) + ->execute(); + } + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/features/includes/features.field.inc b/profiles/commerce_kickstart/modules/contrib/features/includes/features.field.inc index 01202b64..849081dd 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/includes/features.field.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/includes/features.field.inc @@ -158,7 +158,7 @@ function field_base_features_export_render($module, $data, $export = NULL) { unset($field['storage']); } // If we still have a storage declaration here it means that a non-default - // storage type was altered into to the field definition. And noone would + // storage type was altered into to the field definition. And no one would // never need to change the 'details' key, so don't render it. if (isset($field['storage']['details'])) { unset($field['storage']['details']); @@ -170,10 +170,10 @@ function field_base_features_export_render($module, $data, $export = NULL) { $field_identifier = features_var_export($identifier); if (features_field_export_needs_wrap($field_prefix, $field_identifier)) { $code[] = rtrim($field_prefix); - $code[] = " // {$field_identifier}"; + $code[] = " // {$field_identifier}."; } else { - $code[] = $field_prefix . $field_identifier; + $code[] = $field_prefix . $field_identifier . '.'; } $code[] = " \$field_bases[{$field_identifier}] = {$field_export};"; $code[] = ""; @@ -201,10 +201,10 @@ function field_instance_features_export_render($module, $data, $export = NULL) { $instance_identifier = features_var_export($identifier); if (features_field_export_needs_wrap($instance_prefix, $instance_identifier)) { $code[] = rtrim($instance_prefix); - $code[] = " // {$instance_identifier}"; + $code[] = " // {$instance_identifier}."; } else { - $code[] = $instance_prefix . $instance_identifier; + $code[] = $instance_prefix . $instance_identifier . '.'; } $code[] = " \$field_instances[{$instance_identifier}] = {$field_export};"; $code[] = ""; @@ -420,7 +420,7 @@ function field_features_export_render($module, $data, $export = NULL) { unset($field['field_config']['storage']); } // If we still have a storage declaration here it means that a non-default - // storage type was altered into to the field definition. And noone would + // storage type was altered into to the field definition. And no one would // never need to change the 'details' key, so don't render it. if (isset($field['field_config']['storage']['details'])) { unset($field['field_config']['storage']['details']); @@ -572,5 +572,6 @@ function features_field_load($identifier) { * @see https://www.drupal.org/node/1354 */ function features_field_export_needs_wrap($prefix, $identifier) { - return (strlen($prefix) + strlen($identifier) > 80); + // Check for 79 characters, since the comment ends with a full stop. + return (strlen($prefix) + strlen($identifier) > 79); } diff --git a/profiles/commerce_kickstart/modules/contrib/features/includes/features.locale.inc b/profiles/commerce_kickstart/modules/contrib/features/includes/features.locale.inc index 126d177d..258d5413 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/includes/features.locale.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/includes/features.locale.inc @@ -141,8 +141,8 @@ function _features_language_save($language) { } // Update Existing language. else { - // @TODO: get properties from schema. - $properties = array('language', 'name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight', 'javascript'); + // Get field list from table schema. + $properties = drupal_schema_fields_sql('languages'); // The javascript hash is not in the imported data but should be empty if (!isset($language->javascript)) { $language->javascript = ''; diff --git a/profiles/commerce_kickstart/modules/contrib/features/includes/features.menu.inc b/profiles/commerce_kickstart/modules/contrib/features/includes/features.menu.inc index 6ba83e6e..edd47514 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/includes/features.menu.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/includes/features.menu.inc @@ -103,7 +103,6 @@ function menu_custom_features_export_render($module, $data) { $code[] = features_translatables_export($translatables, ' '); } - $code[] = ''; $code[] = ' return $menus;'; $code = implode("\n", $code); return array('menu_default_menu_custom' => $code); @@ -248,7 +247,7 @@ function menu_links_features_export_render($module, $data, $export = NULL) { unset($link['plid']); unset($link['mlid']); - $code[] = " // Exported menu link: {$new_identifier}"; + $code[] = " // Exported menu link: {$new_identifier}."; $code[] = " \$menu_links['{$new_identifier}'] = ". features_var_export($link, ' ') .";"; $translatables[] = $link['link_title']; } @@ -316,6 +315,7 @@ function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) { foreach ($unordered as $link) { $identifier = menu_links_features_identifier($link); $ordered[$identifier] = 0; + $all_links[$identifier] = $link; } asort($ordered); } diff --git a/profiles/commerce_kickstart/modules/contrib/features/includes/features.node.inc b/profiles/commerce_kickstart/modules/contrib/features/includes/features.node.inc index f40341af..25a2c1cd 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/includes/features.node.inc +++ b/profiles/commerce_kickstart/modules/contrib/features/includes/features.node.inc @@ -145,11 +145,14 @@ function node_features_disable_feature($module) { * When a features module is enabled, modify any node types it provides so * they can no longer be deleted manually through the content types UI. * + * Update the database cache of node types if needed. + * * @param $module * Name of module that has been enabled. */ function node_features_enable_feature($module) { if ($default_types = features_get_default('node', $module)) { + $rebuild = FALSE; foreach ($default_types as $type_name => $type_info) { // Ensure the type exists. if ($type_info = node_type_load($type_name)) { @@ -160,6 +163,12 @@ function node_features_enable_feature($module) { $type_info->disabled = 0; node_type_save($type_info); } + else { + $rebuild = TRUE; + } + } + if ($rebuild) { + node_types_rebuild(); } } } diff --git a/profiles/commerce_kickstart/modules/contrib/features/tests/features.test b/profiles/commerce_kickstart/modules/contrib/features/tests/features.test index bbdeb0b9..025ef23c 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/tests/features.test +++ b/profiles/commerce_kickstart/modules/contrib/features/tests/features.test @@ -218,7 +218,7 @@ class FeaturesEnableTestCase extends DrupalWebTestCase { /** - * Tests intergration of ctools for features. + * Tests integration of ctools for features. */ class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase { protected $profile = 'testing'; diff --git a/profiles/commerce_kickstart/modules/contrib/features/tests/features_test/features_test.info b/profiles/commerce_kickstart/modules/contrib/features/tests/features_test/features_test.info index 50085c55..04f01d33 100644 --- a/profiles/commerce_kickstart/modules/contrib/features/tests/features_test/features_test.info +++ b/profiles/commerce_kickstart/modules/contrib/features/tests/features_test/features_test.info @@ -21,9 +21,9 @@ features[user_permission][] = create features_test content features[views_view][] = features_test hidden = 1 -; Information added by Drupal.org packaging script on 2015-06-24 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2016-04-18 +version = "7.x-2.10" core = "7.x" project = "features" -datestamp = "1435165997" +datestamp = "1461011641" diff --git a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.export.inc b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.export.inc index 49182b62..745f9e06 100644 --- a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.export.inc +++ b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.export.inc @@ -51,19 +51,6 @@ function features_override_make_key($keys) { } } -/** - * Returns an array of keys to be ignored for various exportables - * @param $component - * The component to retrieve ignore_keys from. - */ -function features_get_ignore_keys($component) { - static $cache; - if (!isset($cache[$component])) { - $cache[$component] = module_invoke_all('features_override_ignore', $component); - } - return $cache[$component]; -} - /** * Calculates what overrides exist for by component/element. * @@ -107,36 +94,6 @@ function features_override_get_overrides($component_key = FALSE, $element_key = return $cache; } -/** - * Helper function in determining equality of arrays. Credit to http://stackoverflow.com/a/4263181 - * - * @see _features_override_remove_recursion() - * - * @param $a - * object a - * @param $b - * object b - * @return bool - * - */ -function _features_override_is_ref_to(&$a, &$b, $uniqid) { - if (is_object($a) && is_object($b)) { - return ($a === $b); - } - - $temp_a = $a; - $temp_b = $b; - - $b = $uniqid; - - if ($a === $uniqid) $return = true; - else $return = false; - - $a = $temp_a; - $b = $temp_b; - return $return; -} - /** * Get overrides for specific module/component. * @@ -195,20 +152,17 @@ function features_override_module_component_overrides($module, $component, $rese // Can't use _features_sanitize as that resets some keys. _features_override_sanitize($normal); _features_override_sanitize($default); - // make a deep copy of data to prevent problems when removing recursion later - $default_copy = unserialize(serialize($default)); - $normal_copy = unserialize(serialize($normal)); - $ignore_keys = features_get_ignore_keys($component); + $default_copy = features_remove_recursion($default); + $normal_copy = features_remove_recursion($normal); + + $ignore_keys = _features_get_ignore_keys($component); // remove keys to be ignored // doing this now allows us to better control which recursive parts are removed if (count($ignore_keys)) { - _features_override_remove_ignores($default_copy, $ignore_keys); - _features_override_remove_ignores($normal_copy, $ignore_keys); + _features_remove_ignores($default_copy, $ignore_keys); + _features_remove_ignores($normal_copy, $ignore_keys); } - // now remove any remaining recursion - features_override_remove_recursion($default_copy); - features_override_remove_recursion($normal_copy); $component_overrides = array(); if ($normal && is_array($normal) || is_object($normal)) { @@ -394,83 +348,6 @@ function features_override_export_keys($keys) { return $line; } -/** - * Removes recursion from an object or array. - * - * @param $item - * An object or array passed by reference. - */ -function features_override_remove_recursion(&$item) { - $uniqid = __FUNCTION__ . mt_rand(); // use of uniqid() here impacts performance - $stack = array(); - _features_override_remove_recursion($item, $stack, $uniqid); -} - -/** - * Helper to removes recursion from an object/array. - * - * @param $item - * An object or array passed by reference. - */ -function _features_override_remove_recursion(&$object, &$stack = array(), $uniqid) { - if ((is_object($object) || is_array($object)) && $object) { - $in_stack = FALSE; - foreach($stack as &$item) { - if(_features_override_is_ref_to($object, $item, $uniqid)) { - $in_stack = TRUE; - break; - } - } - if(!$in_stack) { - $stack[] = $object; - foreach ($object as $key => &$subobject) { - if (_features_override_remove_recursion($subobject, $stack, $uniqid)) { - if (is_object($object)) { - unset($object->$key); - } - else { - unset($object[$key]); - } - } - } - } else { - return TRUE; - } - } - return FALSE; -} - -/** - * Helper to removes a set of keys an object/array. - * - * @param $item - * An object or array passed by reference. - * @param $ignore_keys - * Array of keys to be ignored. Values are the level of the key. - * @param $level - * Level of key to remove. Up to 2 levels deep because $item can still be - * recursive - */ -function _features_override_remove_ignores(&$item, $ignore_keys, $level = -1) { - $is_object = is_object($item); - if (!is_array($item) && !is_object($item)) { - return; - } - foreach ($item as $key => $value) { - if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) { - if ($is_object) { - unset($item->$key); - } - else { - unset($item[$key]); - } - } - elseif (($level < 2) && (is_array($value) || is_object($value))) { - _features_override_remove_ignores($value, $ignore_keys, $level+1); - } - } -} - /** * Drupal-friendly var_export(). * diff --git a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.hooks.inc b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.hooks.inc index 83d1ab92..5d848ed0 100644 --- a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.hooks.inc +++ b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.hooks.inc @@ -27,6 +27,7 @@ function image_features_override_export_render_addition($alter, $element) { $value_export = features_override_var_export($alter['value'], ' '); $code[] = ""; $code[] = " if (!isset(" . $component_start . "['storage']) || " . $component_start . "['storage'] == IMAGE_STORAGE_DEFAULT) {"; + $code[] = " " . $component_start . "['effects'] = array_values(" . $component_start . "['effects']" . ');'; $code[] = " " . $component_start . $code_line . ' = ' . $value_export . ';'; $code[] = " }"; } diff --git a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.info b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.info index ed6ed981..48327e8a 100644 --- a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.info +++ b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.info @@ -5,9 +5,9 @@ dependencies[] = ctools dependencies[] = features package = "Features" -; Information added by Drupal.org packaging script on 2014-08-01 -version = "7.x-2.0-rc2" +; Information added by Drupal.org packaging script on 2015-10-14 +version = "7.x-2.0-rc3" core = "7.x" project = "features_override" -datestamp = "1406903928" +datestamp = "1444843045" diff --git a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.module b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.module index 1c83e920..22040263 100644 --- a/profiles/commerce_kickstart/modules/contrib/features_override/features_override.module +++ b/profiles/commerce_kickstart/modules/contrib/features_override/features_override.module @@ -63,7 +63,7 @@ function features_override_menu_alter(&$items) { 'description' => 'Compare default and current feature.', 'page callback' => 'features_override_feature_diff', 'page arguments' => array(3, 5), - 'load arguments' => array(3, TRUE), + 'load arguments' => array(TRUE), 'access callback' => 'features_access_override_actions', 'access arguments' => array(3), 'type' => MENU_LOCAL_TASK, @@ -72,44 +72,6 @@ function features_override_menu_alter(&$items) { ); } -/** - * Implements hook_features_override_ignore(). - */ -function features_override_features_override_ignore($component) { - // Determine which keys need to be ignored for override diff for various components. - // value is shows how many levels deep the key is - $ignores = array(); - switch ($component) { - case 'views_view': - $ignores['current_display'] = 0; - $ignores['display_handler'] = 0; - $ignores['handler'] = 2; - $ignores['query'] = 0; - $ignores['localization_plugin'] = 0; - // Views automatically adds these two on export to set values. - $ignores['api_version'] = 0; - $ignores['disabled'] = 0; - break; - case 'image': - $ignores['module'] = 0; - $ignores['name'] = 0; - $ignores['storage'] = 0; - // Various properities are loaded into the effect in image_styles. - $ignores['summary theme'] = 2; - $ignores['module'] = 2; - $ignores['label'] = 2; - $ignores['help'] = 2; - $ignores['form callback'] = 2; - $ignores['effect callback'] = 2; - $ignores['dimensions callback'] = 2; - break; - case 'field': - $ignores['locked'] = 1; - break; - } - return $ignores; -} - /** * Implements hook_modules_enabled(). * diff --git a/profiles/commerce_kickstart/modules/contrib/features_override/features_override_form.js b/profiles/commerce_kickstart/modules/contrib/features_override/features_override_form.js index 4103cab4..46db6b4a 100644 --- a/profiles/commerce_kickstart/modules/contrib/features_override/features_override_form.js +++ b/profiles/commerce_kickstart/modules/contrib/features_override/features_override_form.js @@ -14,7 +14,7 @@ $parent_label.append('' + Drupal.t('view') + ''); } - var $child_checkboxes = $('input[type=checkbox][name^="sources[features_overrides]"][value^=' + this.value + ']').each(function (i) { + var $child_checkboxes = $('input[type=checkbox][name^="sources[features_overrides]"][value^="' + this.value + '"]').each(function (i) { if (Drupal.settings.features_override_links['sub'][this.value]) { $($(this).parent()).find('label').append('' + Drupal.t('view') + ''); } diff --git a/profiles/commerce_kickstart/modules/contrib/fences/CHANGELOG.txt b/profiles/commerce_kickstart/modules/contrib/fences/CHANGELOG.txt index 41aeb0e4..a3f516d1 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/CHANGELOG.txt +++ b/profiles/commerce_kickstart/modules/contrib/fences/CHANGELOG.txt @@ -1,3 +1,13 @@ +Fences 7.x-1.2, 2015-09-24 +-------------------------- +- #2573745 by JohnAlbin: Un-configured fields appear to be configured to "no wrapper" + +Fences 7.x-1.1, 2015-09-21 +-------------------------- +- #1857230 by Xen, JohnAlbin: Add option to use the "normal" Drupal field output +- #1711490 by dalberts69: @theme_name not properly replaced in UI +- #1561244 by RoySegall and JohnAlbin: Undefined Index (with installation profiles and features?) + Fences 7.x-1.0, 2012-04-26 -------------------------- - #1352202: Add option to limit field classes diff --git a/profiles/commerce_kickstart/modules/contrib/fences/PATCHES.txt b/profiles/commerce_kickstart/modules/contrib/fences/PATCHES.txt deleted file mode 100644 index a6e1e968..00000000 --- a/profiles/commerce_kickstart/modules/contrib/fences/PATCHES.txt +++ /dev/null @@ -1,5 +0,0 @@ -The following patches have been applied to this project: -- http://drupal.org/files/undefined-index-1561244-7.patch -- http://drupal.org/files/fences-default_markup_option-1857230-2.patch - -This file was automatically generated by Drush Make (http://drupal.org/project/drush). \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/fences/fences.admin.inc b/profiles/commerce_kickstart/modules/contrib/fences/fences.admin.inc index 64f68dae..bee22426 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/fences.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/fences/fences.admin.inc @@ -14,36 +14,34 @@ function _fences_theme($existing, $type, $theme, $path) { $supported_hooks = array('field'); foreach ($supported_hooks as $hook) { - // If there are no implementation then skip the iteration. - if (empty($existing[$hook])) { - continue; - } - foreach ($fences[$hook] as $suggestion => $data) { - // Manually register the suggestions for a module, but let hook_theme - // auto-discover theme-owned suggestions as it normally does. - if ($fences[$hook][$suggestion]['type'] == 'module') { - $hook_suggestion = $hook . '__fences_' . str_replace('-', '_', $suggestion); - $hook_suggestions = array($hook_suggestion); - // Register the "-multiple" suggestion if that template was found. - if ($fences[$hook][$suggestion]['multiple']) { - $hook_suggestions[] = $hook_suggestion . '_multiple'; - } - foreach ($hook_suggestions as $name) { - $hooks[$name] = array( - 'base hook' => $hook, - 'render element' => $existing[$hook]['render element'], - 'type' => $fences[$hook][$suggestion]['type'], - 'theme path' => $existing[$hook]['theme path'], - 'template' => str_replace('_', '-', $name), - 'path' => $fences[$hook][$suggestion]['path'], - ); + if (!empty($existing[$hook])) { + foreach ($fences[$hook] as $suggestion => $data) { + // Manually register the suggestions for a module, but let hook_theme + // auto-discover theme-owned suggestions as it normally does. + if ($fences[$hook][$suggestion]['type'] == 'module') { + $hook_suggestion = $hook . '__fences_' . str_replace('-', '_', $suggestion); + $hook_suggestions = array($hook_suggestion); + // Register the "-multiple" suggestion if that template was found. + if ($fences[$hook][$suggestion]['multiple']) { + $hook_suggestions[] = $hook_suggestion . '_multiple'; + } + foreach ($hook_suggestions as $name) { + $hooks[$name] = array( + 'base hook' => $hook, + 'render element' => $existing[$hook]['render element'], + 'type' => $fences[$hook][$suggestion]['type'], + 'theme path' => $existing[$hook]['theme path'], + 'template' => str_replace('_', '-', $name), + 'path' => $fences[$hook][$suggestion]['path'], + ); + } } } } } // Register theme hook suggestions for field_collection's entity. - if (module_exists('entity')) { + if (module_exists('entity') && !empty($existing['entity'])) { $hooks['entity__fences_no_wrapper'] = array( 'base hook' => 'entity', 'render element' => $existing['entity']['render element'], @@ -116,8 +114,7 @@ function _fences_get_fences_suggestion_info(&$fences) { // Create a list of the default theme and its base themes. $theme_default = $GLOBALS['conf']['theme_default']; - // We can't use list_themes() here because of #761608. - $theme_data = _system_rebuild_theme_data(); + $theme_data = list_themes(); if (isset($theme_data[$theme_default]->base_themes)) { foreach (array_keys($theme_data[$theme_default]->base_themes) as $base_theme) { $themes[$base_theme] = 'base_theme_engine'; @@ -166,7 +163,7 @@ function _fences_get_fences_suggestion_info(&$fences) { 'label' => $suggestion, 'element' => $suggestion, 'description' => t('A <@tag> tag', array('@tag' => $suggestion)), - 'groups' => array(t('Provided by @theme_name', array('@theme_name', $theme))), + 'groups' => array(t('Provided by @theme_name', array('@theme_name' => $theme))), 'type' => $type, ); } @@ -212,9 +209,6 @@ function _fences_get_fences_options($theme_hook) { unset($options['no_wrapper']); $options = array('no_wrapper' => $no_wrapper) + $options; } - if (!variable_get('fences_default_markup', 0)) { - $options = array('' => t('Use Drupal standard markup')) + $options; - } return $options; } @@ -225,10 +219,13 @@ function _fences_get_fences_options($theme_hook) { function _fences_form_field_ui_field_edit_form_alter(&$form, &$form_state) { $suggestion = fences_get_suggestion($form['#instance']['entity_type'], $form['#instance']['bundle'], $form['#instance']['field_name']); + // Make the default markup selection match the Fences default markup setting. + $default_markup = variable_get('fences_default_markup', 0) ? 'div' : 'div_div_div'; + $form['instance']['fences_wrapper'] = array( '#type' => 'select', '#title' => t('Wrapper markup'), - '#default_value' => $suggestion ? $suggestion : '', + '#default_value' => $suggestion ? $suggestion : $default_markup, '#options' => fences_get_fences_options('field'), '#description' => t('Choose the HTML to use to wrap the field.'), ); diff --git a/profiles/commerce_kickstart/modules/contrib/fences/fences.api.php b/profiles/commerce_kickstart/modules/contrib/fences/fences.api.php index 967470c3..3338bc43 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/fences.api.php +++ b/profiles/commerce_kickstart/modules/contrib/fences/fences.api.php @@ -29,7 +29,7 @@ function hook_fences_suggestion_info() { 'label' => t('paragraph'), // The HTML element(s) used by the suggestion. This will be added to the // label in the UI to provide additional context. If multiple elements are - // used they should be seperated by spaces, e.g. 'pre code'. + // used they should be separated by spaces, e.g. 'pre code'. 'element' => 'p', // A short description used in the UI when selecting a suggestion. 'description' => t('A paragraph; multiple values are each wrapped in a

    '), diff --git a/profiles/commerce_kickstart/modules/contrib/fences/fences.fences.inc b/profiles/commerce_kickstart/modules/contrib/fences/fences.fences.inc index 88fbd576..fbf7e0f0 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/fences.fences.inc +++ b/profiles/commerce_kickstart/modules/contrib/fences/fences.fences.inc @@ -99,6 +99,12 @@ function fences_fences_suggestion_info() { 'description' => t('Generic container; only use as a last resort'), 'groups' => array(t('Block-level')), ), + 'div_div_div' => array( + 'label' => t('division (nested)'), + 'element' => 'div div div', + 'description' => t("Drupal's default field markup"), + 'groups' => array(t('Block-level')), + ), 'em' => array( 'label' => t('emphasis'), 'element' => 'em', diff --git a/profiles/commerce_kickstart/modules/contrib/fences/fences.info b/profiles/commerce_kickstart/modules/contrib/fences/fences.info index b6262694..17dee886 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/fences.info +++ b/profiles/commerce_kickstart/modules/contrib/fences/fences.info @@ -8,9 +8,9 @@ dependencies[] = field configure = admin/config/content/fences -; Information added by drupal.org packaging script on 2012-04-25 -version = "7.x-1.0" +; Information added by Drupal.org packaging script on 2015-09-24 +version = "7.x-1.2" core = "7.x" project = "fences" -datestamp = "1335373578" +datestamp = "1443071044" diff --git a/profiles/commerce_kickstart/modules/contrib/fences/fences.module b/profiles/commerce_kickstart/modules/contrib/fences/fences.module index 118d811d..6a080034 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/fences.module +++ b/profiles/commerce_kickstart/modules/contrib/fences/fences.module @@ -169,7 +169,10 @@ function fences_preprocess_field(&$variables) { $suggestion = 'div'; } - if ($suggestion) { + // Add a theme hook suggestion for the configured suggestion, unless the + // suggestion is "div_div_div", in which case we should use Drupal's default + // field markup instead of a theme hook suggestion. + if ($suggestion && $suggestion !== 'div_div_div') { // Make fences' suggestions low priority by placing them at the front of the queue. $suggestion = 'field__fences_' . $suggestion; if (count($variables['items']) > 1) { diff --git a/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-div-div-div.tpl.php b/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-div-div-div.tpl.php new file mode 100644 index 00000000..09f8b7b5 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-div-div-div.tpl.php @@ -0,0 +1,20 @@ + elements. Note: This template is never used; instead + * Drupal core's theme_field() is used. + * + * @see http://developers.whatwg.org/grouping-content.html#the-div-element + */ +?> +

    > + +
    >
    + +
    > + $item): ?> +
    >
    + +
    +
    diff --git a/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-figcaption.tpl.php b/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-figcaption.tpl.php index b2eeccfa..a50f0601 100644 --- a/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-figcaption.tpl.php +++ b/profiles/commerce_kickstart/modules/contrib/fences/templates/field--fences-figcaption.tpl.php @@ -1,7 +1,7 @@ element. + * Wrap all field values in a single
    element. * * @see http://developers.whatwg.org/grouping-content.html#the-figcaption-element * diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/README.txt index 5a8a4f14..060563bb 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/i18n/README.txt @@ -20,7 +20,7 @@ For support, please create a support request for this module's project: Support questions by email to the module maintainer will be simply ignored. Use the issue tracker. -Now if you want professional (paid) support the module maintainer may be available occassionally. +Now if you want professional (paid) support the module maintainer may be available occasionally. Drop me a message to check availability and hourly rates, http://reyero.net/en/contact ==================================================================== diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info index d4f5c430..971f5de8 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info @@ -8,9 +8,9 @@ files[] = i18n_object.inc files[] = i18n.test configure = admin/config/regional/i18n -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n.test b/profiles/commerce_kickstart/modules/contrib/i18n/i18n.test index 491d7ef7..2a82e17a 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n.test +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n.test @@ -11,7 +11,7 @@ class Drupali18nTestCase extends DrupalWebTestCase { function setUpLanguages($admin_permissions = array()) { // Setup admin user. - $this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate interface'), $admin_permissions)); + $this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer fields', 'administer blocks', 'access administration pages', 'translate interface'), $admin_permissions)); $this->drupalLogin($this->admin_user); @@ -362,6 +362,7 @@ class Drupali18nTestCase extends DrupalWebTestCase { function resetCaches() { drupal_static_reset('locale_url_outbound_alter'); drupal_static_reset('language_list'); + drupal_language_initialize(); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/README.txt new file mode 100644 index 00000000..5d0ddb87 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/README.txt @@ -0,0 +1,74 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Troubleshooting +* Maintainers + + +INTRODUCTION +------------ + +The Block languages module, part of the Internationalization (https://www.drupal.org/project/i18n) package, allows the user to configure for which languages each block is visible. + +* For a full description of the module, visit https://www.drupal.org/node/1279698 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +Internationalization - https://www.drupal.org/project/i18n + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views - https://www.drupal.org/project/i18nviews +* Language Icons - https://www.drupal.org/project/languageicons +* Translation Overview - https://www.drupal.org/project/translation_overview +* Localization Client - https://www.drupal.org/project/l10n_client +* Internationalization contributions - https://www.drupal.org/project/i18n_contrib + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +The settings for visibility per language are provided under Visibility Settings via the Languages tab when configuring a block. + +The Languages tab also provides a setting for whether the block is translatable. For custom blocks, the block title and the block content will be translatable. For blocks defined by modules, only the block title will be translatable. If "Make this block translatable" is selected, a Translate tab will appear for that block. This tab provides a UI for adding translations of the block in each available language. + + +TROUBLESHOOTING +--------------- + +Conflicts with Context + +The Block languages module conflicts with the Context module, which alters how blocks are rendered. This issue can be tracked in the Internationalization issue queue: http://drupal.org/node/1343044 + +String Errors + +The user must allow your used string format to be translated on admin/config/regional/i18n/strings or you are going to have a error message like "The string blocks:block:1:body for textgroup blocks is not allowed for translation because of its text format." + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info index 0e599035..53b4b889 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info @@ -8,9 +8,9 @@ files[] = i18n_block.inc files[] = i18n_block.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.test b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.test index b1e46809..e33d99ef 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.test +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.test @@ -72,15 +72,16 @@ class i18nBlocksTestCase extends Drupali18nTestCase { $this->clickLink(t('translate')); + // Title is a textarea, body is a text_format. $this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':title]', $translations['title']['es']); - $this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':body]', $translations['body']['es']); + $this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':body][value]', $translations['body']['es']); // Update the translation. $translations['title']['es'] = $this->randomName(10); $translations['body']['es'] = $this->randomName(20); $edit = array( 'strings[blocks:block:' . $box2['delta'] . ':title]' => $translations['title']['es'], - 'strings[blocks:block:' . $box2['delta'] . ':body]' => $translations['body']['es'], + 'strings[blocks:block:' . $box2['delta'] . ':body][value]' => $translations['body']['es'], ); $this->drupalPost(NULL, $edit, t('Save translation')); $this->i18nAssertTranslations($translations['title'], '', 'Updated block title translation displayed.'); diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/README.txt new file mode 100644 index 00000000..cc62ab7f --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/README.txt @@ -0,0 +1,67 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Contact translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, helps with the multilingual configuration of the site contact forms. This module makes contact categories and replies available for translation. + +* For a full description of the module, visit https://www.drupal.org/node/1396984 + +* To submit bug reports and feature suggestions, or to track changes, visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. See https://drupal.org/documentation/install/modules-themes/modules-7 for further information. + + +CONFIGURATION +------------- + +1. Enable the Contact translation module included with the Internationalization module. +2. Go to Administration > Structure > Contact form. +3. Click edit for the form to configure. +4. Click the Translate tab. +5. Click the translate link for a language. +6. Translate the Category and Auto-reply text. +7. Click Save translation. +8. Repeat steps 5 to 7 for each language. +9. Repeat steps 2 to 8 for all forms. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info index 7bad71d2..13e576a6 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info @@ -5,9 +5,9 @@ dependencies[] = i18n_string package = Multilingual - Internationalization core = 7.x -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/README.txt new file mode 100644 index 00000000..cb7a5e5e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/README.txt @@ -0,0 +1,65 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Field translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, allows for translation of text associated with a field's settings including the label, help text, default value, and list options. + +* For a full description of the module, visit this page https://www.drupal.org/node/1279346 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://drupal.org/documentation/install/modules-themes/modules-7 for further information. + + +CONFIGURATION +------------- + +1. Enable the Field translation module included with Internationalization. +2. Go to Administration > Configuration > Regional and language > Translate interface. +3. Click on Filter Translatable Strings field set and limit search to fields. +4. Edit the desired field and Save. +For the translation to be displayed, you need to use some of the Field Formatters provided by this module whose name usually ends up in 'translated'. For most core fields it is Default translated. + +Note: The Field Translation module does not provide content translation for fields. This functionality is provided by the Entity Translation (ET) module. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info index 552eb392..baf7ac72 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info @@ -6,9 +6,9 @@ package = Multilingual - Internationalization core = 7.x files[] = i18n_field.inc files[] = i18n_field.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.module index eadd2f5c..f78f9c0e 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.module @@ -446,3 +446,69 @@ function i18n_field_type_info($type = NULL, $property = NULL) { return $info; } } + +/** + * Implements hook_field_info_alter(). + */ +function i18n_field_field_info_alter(&$field_info) { + foreach(array_keys($field_info) as $type) { + $field_info[$type]['property_callbacks'][] = 'i18n_field_entity_property_callback'; + } +} + +/** + * Callback to translate entity property info for a fields. + * + * @see entity_metadata_field_entity_property_info() + * @see entity_metadata_field_default_property_callback() + * @see i18n_field_i18n_object_info_alter() + * @see hook_module_implements_alter() + */ +function i18n_field_entity_property_callback(&$info, $entity_type, $field, $instance, $field_type) { + global $language; + + // This could create a endless recursion if it's called during rebuilding the + // cache for i18n_object_info(). So if the cache of i18n_object_info isn't + // available yet we assume the worst case, leave the info alone but trigger a + // rebuild of the property when hook_i18n_object_info_alter is invoked. At + // that point the info is available and we can rely on it. + if (!$info = &drupal_static('i18n_object_info')) { + $i18n_field_entity_property_callback_fallback = &drupal_static(__FUNCTION__); + $i18n_field_entity_property_callback_fallback = TRUE; + return; + } + + $name = $field['field_name']; + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name]; + $property['label'] = i18n_field_translate_property($instance, 'label', $language->language); +} + +/** + * Implements hook_i18n_object_info_alter(). + */ +function i18n_field_i18n_object_info_alter(&$info) { + if (drupal_static('i18n_field_entity_property_callback')) { + if ($info = drupal_static('i18n_object_info')) { + // Clean static and permanent cache of the data and then re-run the property + // building. + drupal_static_reset('entity_get_property_info'); + cache_clear_all('entity_property_info:' . $GLOBALS['language']->language, 'cache'); + entity_get_property_info(); + } + else { + watchdog('i18n_field', 'Unable to run fall-back handling for entity property translation due missing "i18n_object_info" cache', array(), WATCHDOG_WARNING); + } + } +} + +/** + * Implements hook_module_implements_alter(). + */ +function i18n_field_module_implements_alter(&$implementations, $hook) { + if ($hook == 'i18n_object_info_alter') { + // Move our hook implementation to the bottom. + $group = $implementations['i18n_field']; + unset($implementations['i18n_field']); + $implementations['i18n_field'] = $group; + } +} \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/README.txt new file mode 100644 index 00000000..e8ab6bf4 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/README.txt @@ -0,0 +1,80 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Multilingual forum module, part of the Internationalization (https://www.drupal.org/project/i18n) package, helps with the multilingual configuration of the site’s forums. + +* For a full description of the module visit https://www.drupal.org/node/1396988 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + +INSTALLATION + +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. See https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +To configure forum vocabulary + +1. Enable the Multilingual forum module included with Internationalization. +2. Go to Administration > Structure > Taxonomy. +3. Click "edit vocabulary" for the Forums vocabulary. +4. Choose the translation mode (Localize or Translate). +5. Click "Save and translate" button. +6. Click "translate" link for a language. +7. Translate the "Name" and "Description" for the forum. +8. Click "Save translation" button. +9. Repeat steps 6 to 8 for each language. + +To configure forum terms + +1. Go to Administration > Structure > Forums. +2. Click "edit" link for a forum or container. +3. Click "Translate" tab. +4. Click "translate" link for a language. +5. Translate the "Name" and "Description" for the term. +6. Click "Save translation" button. +7. Repeat steps 4 to 6 for each language. +8. Repeat all steps for all terms. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info index afd96583..d857630c 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info @@ -7,9 +7,9 @@ package = Multilingual - Internationalization core = 7.x files[] = i18n_forum.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/README.txt new file mode 100644 index 00000000..76eef55e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/README.txt @@ -0,0 +1,89 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Troubleshooting +* Maintainers + + +INTRODUCTION +------------ + +The Menu translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, allows users to select a translation mode for each menu. + +* For a full description of the module, visit this page: +https://www.drupal.org/node/1113982 + +* To submit bug reports and feature suggestions, or to track changes: +https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + +To link menu item menus to nodes, it is useful to have the following modules: + +* Entity translation i18n menu module, a sub module of Entity translation (https://www.drupal.org/project/entity_translation) +* Menu translation node module (https://www.drupal.org/project/i18n_menu_node) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information + + +CONFIGURATION +------------- + +Language-specific menus +1. To create or edit a menu, navigate to Structure > Menus > (menu to edit) > Edit. +2. In the Translation mode section, choose Fixed Language and a Language field will appear. +3. Select a language, select Save, and add or update the menu items as needed. +The menu block will only appear when viewing content in the same language. + +There are three modes available: + +Translate and Localize: +You can create one menu for all languages, and translate or localize each menu item. There are two ways that menu items will be translated. +1. You can set a language when creating a custom menu item so that the menu item will only show up for that language. Menu items that link to nodes in a particular language will be treated this way. +2. You can localize other custom menu items without a language (for example, menu items linking to Views pages). Use the Translate tab to translate the menu item title and description. Translators can also use the "Translate interface" pages to translate these menu items. + +Fixed Language: +If you choose Fixed Language, you'll have to set up a separate menu in each language. This could become tedious if have a lot of languages enabled on your site, but is useful if the content or menu structure is different for each language. + +No Multilingual Options: +Only the menu will be translatable. + +TROUBLESHOOTING +--------------- + +A menu item linked to a node will be displayed only when the node language matches the page language. This is due to how the menu system works and the "Language selection" feature in i18n. Therefore, to get translated menus items that link to nodes, you first need translated content. For more information visit https://www.drupal.org/docs/7/multilingual/translating-content. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info index 7a6821c7..f1f4bfc8 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info @@ -10,9 +10,9 @@ core = 7.x files[] = i18n_menu.inc files[] = i18n_menu.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.module index 8bcf6796..5f8645f9 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.module @@ -53,6 +53,24 @@ function i18n_menu_menu_alter(&$items) { $items['admin/structure/menu/item/%menu_link'] = $items['admin/structure/menu/item/%menu_link/edit']; $items['admin/structure/menu/item/%menu_link']['type'] = MENU_CALLBACK; $items['admin/structure/menu/item/%menu_link/edit']['type'] = MENU_DEFAULT_LOCAL_TASK; + $items['admin/structure/menu/manage/%menu']['title callback'] = 'i18n_menu_menu_overview_title'; +} + +/** + * Preprocess theme_menu_admin_overview to translate menu name and description + * + * @param $variables + */ +function i18n_menu_preprocess_menu_admin_overview(&$variables) { + $variables['title'] = i18n_string(array('menu', 'menu', $variables['name'], 'title'), $variables['title']); + $variables['description'] = i18n_string(array('menu', 'menu', $variables['name'], 'description'), $variables['description']); +} + +/** + * Title callback for the menu overview page and links. + */ +function i18n_menu_menu_overview_title($menu) { + return i18n_string(array('menu', 'menu', $menu['menu_name'], 'title'), $menu['title']); } /** @@ -379,7 +397,7 @@ function i18n_menu_localize_tree($tree, $langcode = NULL) { if (_i18n_menu_link_process($item['link'])) { if (!_i18n_menu_link_is_visible($item['link'], $langcode)) { // Remove links for other languages than current. - // Links with language wont be localized. + // Links with language won't be localized. unset($tree[$index]); // @todo Research whether the above has any advantage over: // $item['hidden'] = TRUE; @@ -515,9 +533,11 @@ function _i18n_menu_link_description($link, $langcode = NULL) { * Check whether this link is to be processed by i18n_menu and start processing. */ function _i18n_menu_link_process(&$link) { - // Only visible links that have a language property and haven't been processed - // before. We also check that they belong to a menu with language options. - if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && empty($link['hidden']) && i18n_menu_mode($link['menu_name'])) { + // Only links that have a language property and haven't been processed before. + // We also translate links marked as hidden because core breadcrumbs ignore + // that flag and excluding them would basically interfere with core behaviour. + // We also check that they belong to a menu with language options. + if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && i18n_menu_mode($link['menu_name'])) { // Mark so it won't be processed twice. $link['i18n_menu'] = TRUE; // Skip if administering this menu or this menu item. @@ -579,7 +599,7 @@ function _i18n_menu_link_is_visible($link, $langcode = NULL) { } /** - * Get localizable properties for menu link checking agains the router item. + * Get localizable properties for menu link checking against the router item. */ function _i18n_menu_link_localizable_properties($link) { $props = array(); @@ -588,7 +608,7 @@ function _i18n_menu_link_localizable_properties($link) { // If the title callback is 't' and the link title matches the router title // it will be localized by core, not by i18n_menu. if (!$router || - (empty($router['title_callback']) || $router['title_callback'] != 't') || + (empty($router['title_callback']) || ($router['title_callback'] != 't' || !empty($link['customized']))) || (empty($router['title']) || $router['title'] != $link['link_title']) ) { $props[] = 'title'; @@ -597,7 +617,7 @@ function _i18n_menu_link_localizable_properties($link) { if (!empty($link['options']['attributes']['title'])) { // If the description matches the router description, it will be localized // by core. - if (!$router || empty($router['description']) || $router['description'] != $link['options']['attributes']['title']) { + if (!$router || empty($router['description']) || ($router['description'] != $link['options']['attributes']['title']) || !empty($link['customized'])) { $props[] = 'description'; } } @@ -723,15 +743,19 @@ function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) { * Add a "translate" link in operations column for each menu item. */ function i18n_menu_form_menu_overview_form_alter(&$form, &$form_state) { - foreach (element_children($form) as $element) { - if (substr($element, 0, 5) == 'mlid:') { - $mlid = $form[$element]['#item']['mlid']; - if (i18n_get_object('menu', $mlid)->get_translate_access()) { - $form[$element]['operations']['translate'] = array( - '#type' => 'link', - '#title' => t('translate'), - '#href' => "admin/structure/menu/item/{$mlid}/translate", - ); + if (i18n_menu_mode($form['#menu']['menu_name'], I18N_MODE_MULTIPLE)) { + foreach (element_children($form) as $element) { + if (substr($element, 0, 5) == 'mlid:') { + $item = $form[$element]["#item"]; + $mlid = $form[$element]['#item']['mlid']; + if (i18n_get_object('menu', $mlid)->get_translate_access()) { + $form[$element]['operations']['translate'] = array( + '#type' => 'link', + '#title' => t('translate'), + '#href' => "admin/structure/menu/item/{$mlid}/translate", + ); + $form[$element]['title']['#markup'] = l(_i18n_menu_link_title($item), $item['href'], $item['localized_options']); + } } } } @@ -898,93 +922,25 @@ function i18n_menu_query_features_menu_link_alter($query) { } /** - * Implements hook_init(). - */ -function i18n_menu_init() { - - // The only way to override the default preferred menu link for a path is to - // inject it into the static cache of the function menu_link_get_preferred(). - - // The problem with the default implementation is that it does not take the - // language of a menu link into account. Whe having different menu trees for - // different menus, this means that the active trail will not work for all but - // one language. - - // The code below is identical to the mentioned function except the added - // language condition on the query. - - // TODO: Adding an alter tag to the query would allow to do this with a simple - // hook_query_alter() implementation. - - $preferred_links = &drupal_static('menu_link_get_preferred'); - - $path = $_GET['q']; - - // Look for the correct menu link by building a list of candidate paths, - // which are ordered by priority (translated hrefs are preferred over - // untranslated paths). Afterwards, the most relevant path is picked from - // the menus, ordered by menu preference. - $item = menu_get_item($path); - $path_candidates = array(); - // 1. The current item href. - $path_candidates[$item['href']] = $item['href']; - // 2. The tab root href of the current item (if any). - if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) { - $path_candidates[$tab_root['href']] = $tab_root['href']; - } - // 3. The current item path (with wildcards). - $path_candidates[$item['path']] = $item['path']; - // 4. The tab root path of the current item (if any). - if (!empty($tab_root)) { - $path_candidates[$tab_root['path']] = $tab_root['path']; - } - // Retrieve a list of menu names, ordered by preference. - $menu_names = menu_get_active_menu_names(); - // Use an illegal menu name as the key for the preferred menu link. - $selected_menu = MENU_PREFERRED_LINK; - // Put the selected menu at the front of the list. - array_unshift($menu_names, $selected_menu); - - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - // Weight must be taken from {menu_links}, not {menu_router}. - $query->addField('ml', 'weight', 'link_weight'); - $query->fields('m'); - $query->condition('ml.link_path', $path_candidates, 'IN'); - - // Only look menu links with none or the current language. - $query->condition('ml.language', array(LANGUAGE_NONE, i18n_language_interface()->language), 'IN'); - - // Sort candidates by link path and menu name. - $candidates = array(); - foreach ($query->execute() as $candidate) { - $candidate['weight'] = $candidate['link_weight']; - $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; - // Add any menus not already in the menu name search list. - if (!in_array($candidate['menu_name'], $menu_names)) { - $menu_names[] = $candidate['menu_name']; - } - } - - // Store the most specific link for each menu. Also save the most specific - // link of the most preferred menu in $preferred_link. - foreach ($path_candidates as $link_path) { - if (isset($candidates[$link_path])) { - foreach ($menu_names as $menu_name) { - if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) { - $candidate_item = $candidates[$link_path][$menu_name]; - $map = explode('/', $path); - _menu_translate($candidate_item, $map); - if ($candidate_item['access']) { - $preferred_links[$path][$menu_name] = $candidate_item; - if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) { - // Store the most specific link. - $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item; - } - } - } + * Implements hook_query_TAG_alter() + * + * Using tag 'preferred_menu_links' added in menu_link_get_preferred(). + * See http://drupal.org/node/1854134 + */ +function i18n_menu_query_preferred_menu_links_alter(QueryAlterableInterface $query) { + global $language; + // Get queried tables. + $tables = $query->getTables(); + + foreach ($tables as $alias => $table) { + if ($table['table'] == 'menu_links') { + // Add language filter, ensuring that we don't have any collision when + // determining the active menu trail when there are multiple menu items + // with same link path but different languages. + if ($language) { + $query->condition('language', array($language->language, LANGUAGE_NONE), 'IN'); } + break; } } } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/README.txt new file mode 100644 index 00000000..2c4bc402 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/README.txt @@ -0,0 +1,71 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Multilingual content module, part of the Internationalization (https://www.drupal.org/project/i18n) package, provides extended multilingual options for nodes. These options help accommodate a variety of translation workflows by controlling how the language for nodes is set. + +Note that the Multilingual content module lives in the i18n_node directory in the Internationalization package. Don't confuse this module with the core Content translation module. + +* For a full description of the module visit https://www.drupal.org/node/1279644 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION + +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. See https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +For each content type, the following Extended language options are available under the Multilingual Settings tab +1. "Set current language as default for new content" can be useful for content that is community-generated. +2. "Require language" prevents users from creating 'Language neutral' nodes. +3. "Lock language" prevents users from changing the language of a node after it's created. + +Site-wide Settings for Node Translation +1. There are also site-wide settings provided to help streamline how multilingual content is created. Navigate to Config > Regional and language > Multilingual settings > Node Options. +2. "Switch interface for translating" switches the language of the user interface to the chosen language when a user translates a node. This is useful if users speak the language in which the translation is written. It means that after the node translation is saved, the language of the UI will match the language of the node. +3. "Hide content translation links" will prevent the language switcher links from appearing in nodes and teasers. This is useful if a language switcher block is enabled on the site. +4.You can also select the default language for new nodes if the corresponding content type doesn't have language support. By default, it is set to be in the default language of the site, but this can be changed to be language neutral. This is a useful option when thinking about forward compatibility for adding multilingual support to these content types in the future. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info index 4522510e..4dc29755 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info @@ -9,9 +9,9 @@ configure = admin/config/regional/i18n/node files[]=i18n_node.test files[]=i18n_node.variable.inc -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.module index 8c2e38e3..bd51ad2d 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.module @@ -222,8 +222,16 @@ function i18n_node_language_mode($type) { function i18n_node_node_prepare($node) { $options = variable_get('i18n_node_options_' . $node->type, array()); if (i18n_node_type_enabled($node) && empty($node->nid) && !i18n_object_langcode($node) && in_array('current', $options)) { + $default = variable_get('i18n_node_default_language_for_' . $node->type, '-- current --'); + // Set current language for new nodes if option enabled - $node->language = i18n_language_content()->language; + if ($default === '-- current --') { + $node->language = i18n_language_content()->language; + } + // If a custom language was specified, apply it. + else { + $node->language = $default; + } } } @@ -417,7 +425,16 @@ function i18n_node_form_node_type_form_alter(&$form, &$form_state) { // Some settings about node languages. Add variables for node type from variable definition if ($form['#node_type']->type) { variable_type_include('node_type'); - $form['i18n'] += node_variable_type_subform($form['#node_type']->type, array('i18n_node_options', 'i18n_node_extended')); + $form['i18n'] += node_variable_type_subform($form['#node_type']->type, array('i18n_node_options', 'i18n_node_default_language_for', 'i18n_node_extended')); + // Only show custom default language field if "current" is checked. + $form['i18n']['i18n_node_default_language_for']['#states'] = array( + 'visible' => array( + ':input[name="i18n_node_options[current]"]' => array('checked' => TRUE), + ), + 'required' => array( + ':input[name="i18n_node_options[current]"]' => array('checked' => TRUE), + ), + ); } // Add disabled message if ($disabled) { @@ -506,8 +523,11 @@ function _i18n_node_form_node_form_alter($form, &$form_state) { } } elseif (variable_get('i18n_node_default_language_none', 0) && !isset($form['#node']->nid)) { - // Override locale module setting default language to nodes. It is already in form_state. - $form['language']['#value'] = $form_state['values']['language'] = LANGUAGE_NONE; + // Only do this if the language is really disabled + if (variable_get('language_content_type_' . $node->type, 0) == 0) { + // Override locale module setting default language to nodes. It is already in form_state. + $form['language']['#value'] = $form_state['values']['language'] = LANGUAGE_NONE; + } } // Translate field names for title and body for the node edit form. if (!empty($form['title']['#title'])) { diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.variable.inc b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.variable.inc index 9362fc3f..b9b0495f 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.variable.inc +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.variable.inc @@ -45,13 +45,30 @@ function i18n_node_variable_info($options = array()) { 'repeat' => array( 'type' => 'options', 'options' => array( - 'current' => t('Set current language as default for new content.', array(), $options), + // Note: this was previously used only to mark new, translatable nodes + // with the current language of the user. Now, this setting is extended + // to allow a specific language to be chosen (defaulting to the current + // language). This was done for backwards compatibility reasons. + 'current' => t('Set custom language as default for new content.', array(), $options), 'required' => t('Require language (Do not allow Language Neutral).', array(), $options), 'lock' => t('Lock language (Cannot be changed).', array(), $options), ), ), 'group' => 'i18n', ); + // This field will only be displayed if "current" is checked above. + $variables['i18n_node_default_language_for_[node_type]'] = array( + 'type' => 'multiple', + 'title' => t('Custom default language', array(), $options), + 'repeat' => array( + 'type' => 'select', + 'options' => array_merge(array( + '-- current --' => t('Current language') + ), locale_language_list('name')), + 'default' => '-- current --', + ), + 'group' => 'i18n', + ); $variables['i18n_node_extended_[node_type]'] = array( 'type' => 'multiple', 'title' => t('Extended language support'), diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info index 10f62e7c..bb0adcd5 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info @@ -6,9 +6,9 @@ core = 7.x files[] = i18n_path.inc files[] = i18n_path.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/README.txt new file mode 100644 index 00000000..3d10403b --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/README.txt @@ -0,0 +1,68 @@ +CONTENTS OF THIS FILE +--------------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Redirect translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, improves search engine optimization (SEO) for multilingual websites. + +It redirects anonymous users (including web crawlers) to the translation of the page in the requested language, if it exists, using a 301 redirect code. + +* For a full description of the module, visit this page: +https://www.drupal.org/node/1280468 + +* To submit bug reports and feature suggestions, or to track changes: +https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization (https://www.drupal.org/project/i18n) + +The Translation redirect module requires the implementation of hook_i18n_translate_path by another module for the redirect page to be determined. Currently, the Multilingual content, Path translation, and Taxonomy translation modules implement this hook. + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +No configuration is necessary. + +For node translation, enable the Multilingual content and the Translation redirect modules. For non-node pages, the redirection hook must be implemented by the relevant module. +For example, for taxonomy pages, you should enable the Taxonomy translation module because the module provides the necessary hook code. If you are using the Path translation module to create translation sets for non-node pages, then it implements the hook code for determining the redirection page. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info index 6f6ee43d..97af54ff 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info @@ -4,9 +4,9 @@ dependencies[] = i18n package = Multilingual - Internationalization core = 7.x -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/README.txt new file mode 100644 index 00000000..8f13ddf0 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/README.txt @@ -0,0 +1,67 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Multilingual select module, part of the Internationalization (https://www.drupal.org/project/i18n) package, allows the user to define whether content is filtered by language on pages provided by Drupal core. + +* For a full description of the module visit https://www.drupal.org/node/1279512 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization (https://www.drupal.org/project/i18n) +* Variable (https://www.drupal.org/project/variable) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. See https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +The module allows you to configure whether or not to filter pages by the current language. If the Taxonomy translation submodule is also enabled, an option will be available for how taxonomy term pages are filtered. It also allows the exclusion of the module’s language selection handling for some elements and certain paths. + +1. Enable the Multilingual select module included with Internationalization. +2. Go to Configuration > Regional and language > Multilingual settings > Selection. +3. Select nodes and taxonomy can be filtered by language in the Content to Filter by Language field set. +4. The "Content Selection Mode" allows content with specific tags to be skipped by entering a list of tags. +5. The "Enable for Specific Pages" field set allows specific pages to be included by path. +6. After making choices click Save configuration. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info index 7c47197f..f9c221d2 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info @@ -6,9 +6,9 @@ core = 7.x configure = admin/config/regional/i18n/select files[] = i18n_select.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.module index bcba5782..9063fe9d 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.module @@ -82,11 +82,11 @@ function i18n_select_mode($type = NULL) { } /** - * Check current path to enable selection + * Check current path to enable selection. * - * This works pretty much like block visibility + * This works pretty much like block visibility. * - * @return boolean + * @return bool * TRUE if content selection should be enabled for this page. */ function i18n_select_page() { @@ -100,8 +100,11 @@ function i18n_select_page() { // with different case. Ex: /Page, /page, /PAGE. $pages = drupal_strtolower($pages); if ($visibility < I18N_SELECT_PAGE_PHP) { - // Convert the Drupal path to lowercase - $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); + // @see views_ajax() + // @see I18NSelectAdminViewsAjax::testViewsAjaxWithoutSkippingTags() + $path = isset($_REQUEST['view_path']) ? $_REQUEST['view_path'] : $_GET['q']; + // Convert the Drupal path to lowercase. + $path = drupal_strtolower(drupal_get_path_alias($path)); // Compare the lowercase internal and lowercase path alias (if any). $page_match = drupal_match_path($path, $pages); if ($path != $_GET['q']) { @@ -109,8 +112,8 @@ function i18n_select_page() { } // When $visibility has a value of 0 (I18N_SELECT_PAGE_NOTLISTED), // the block is displayed on all pages except those listed in $pages. - // When set to 1 (I18N_SELECT_PAGE_LISTED), it is displayed only on those - // pages listed in $pages. + // When set to 1 (I18N_SELECT_PAGE_LISTED), it is displayed only on + // those pages listed in $pages. $mode = !($visibility xor $page_match); } elseif (module_exists('php')) { @@ -121,7 +124,7 @@ function i18n_select_page() { } } else { - // No pages defined, still respect the setting (unlike blocks) + // No pages defined, still respect the setting (unlike blocks). $mode = $visibility == I18N_SELECT_PAGE_NOTLISTED; } } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.test b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.test index b46fb986..5af85431 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.test +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.test @@ -84,3 +84,87 @@ class i18nSelectTestCase extends Drupali18nTestCase { } } } + +/** + * Test case for AJAX queries on "views/ajax" when view on admin page. + */ +class I18NSelectAdminViewsAjax extends Drupali18nTestCase { + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => t('I18N select Admin Views (AJAX)'), + 'group' => 'Internationalization', + 'description' => t('Test AJAX requests to the "views/ajax" when view located on "admin/*" and list of skipping tags is empty.'), + // Skip this test when "admin_views" module does not exists. + 'dependencies' => array('admin_views'), + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp('translation', 'i18n_variable', 'i18n_select', 'admin_views'); + parent::setUpLanguages(array('access content overview')); + parent::setUpContentTranslation(); + } + + /** + * Test AJAX of a view without skipping tags for selection. + * + * @see i18n_select_page() + */ + public function testViewsAjaxWithoutSkippingTags() { + // If this variable will have the "views" value then this test will not + // have sense. For instance, we want apply language selection filter + // for views and remove "views" from "i18n_select_skip_tags" variable. + // In this case all AJAX for views, on administration part of the site, + // will be broken because the "i18n_select_page()" function will work + // with "views/ajax" path instead of, for example, "admin/content". + variable_set('i18n_select_skip_tags', ''); + + // Create one hundred of nodes. + for ($i = 1; $i <= 100; $i++) { + // Create every second node on Spanish language and + // every first - on English. + $node = $this->createNode('page', "Node $i", '', $i % 2 ? $this->default_language : $this->secondary_language); + + // Update "changed" in order to sort the content by updating date. In + // other case all nodes will be with the same date and not arranged in + // order. + db_update('node') + ->fields(array('changed' => strtotime("+ $i minute"))) + ->condition('nid', $node->nid) + ->execute(); + } + + $this->drupalGet('admin/content'); + + // Check that latest node exists at the top. + $this->assertText('Node 100'); + // Check that our page contains fifty nodes (the latest must be 51). + $this->assertNoText('Node 50'); + + // Test $_REQUEST['view_path']. There's no form to submit to, so + // drupalPost() won't work here. This just tests a direct $_POST + // request instead. + $this->curlExec(array( + CURLOPT_URL => $this->getAbsoluteUrl('views/ajax'), + CURLOPT_POST => TRUE, + CURLOPT_POSTFIELDS => http_build_query(array( + 'page' => 1, + 'view_path' => 'admin/content', + 'view_name' => 'admin_views_node', + 'view_display_id' => 'system_1', + )), + )); + + // Check that we are successfully switched to a new page of content. + $this->assertText('Node 50'); + $this->assertNoText('Node 100'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/README.txt new file mode 100644 index 00000000..0e6645ae --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/README.txt @@ -0,0 +1,72 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The String translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, provides support for other modules to translate user-defined strings. This is an API module that must be enabled only when required by other modules in the i18n package. + + +* For a full description of the module, visit this page: +https://www.drupal.org/node/1279668 + +* To submit bug reports and feature suggestions, or to track changes: +https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +Strings will be translated from the source languages. By default the source language is the site's default language, so changing the default language could break these translations. + +1. You can set which language is used as the source language via Administration > Configuration > Regional and language > Multilingual settings > Strings. By default, only plain strings are enabled, so regular blocks are not fully translatable. +2. To allow Filtered HTML, Full HTML or Plain text select the appropriate radio box(es) and Save configuration. +3. To select the strings to be translated navigate to Administration > Configuration > Regional and language > Translate interface and select on Stings vertical tab. From here you can select which text groups to translate and select the Refresh strings tab. + + +FAQ +--- + +The String translation module allows you to configure which text formats are translatable. Formats like PHP Filter and Full HTML are translated before they are processed, so allowing a translator to edit these can be a security risk. This is particularly problematic when importing translations in bulk from a CSV file, since the translator's access to the import formats isn't verified by Drupal. After updating this setting, be sure to refresh the strings via Administration > Configuration > Regional and language > Translate interface > Strings so that strings in forbidden formats are deleted. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.inc b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.inc index 33fa444d..0b2d4fcf 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.inc +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.inc @@ -1166,10 +1166,17 @@ class i18n_string_object_wrapper extends i18n_object_wrapper { $info = is_array($info) ? $info : array('title' => $info); $field_name = isset($info['field']) ? $info['field'] : $field; $value = $this->get_field($field_name); + if (is_array($value) && isset($value['value'])) { + $format = isset($value['format']) ? $value['format'] : NULL; + $value = $value['value']; + } + else { + $format = isset($info['format']) ? $this->get_field($info['format']) : NULL; + } $strings[$this->get_textgroup()][$string_type][$object_id][$field] = array( 'string' => is_array($value) || isset($info['empty']) && $value === $info['empty'] ? NULL : $value, 'title' => $info['title'], - 'format' => isset($info['format']) ? $this->get_field($info['format']) : NULL, + 'format' => $format, 'name' => array_merge($object_keys, array($field)), ); } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info index 301a54ec..48a76da9 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info @@ -10,9 +10,9 @@ files[] = i18n_string.inc files[] = i18n_string.test configure = admin/config/regional/i18n/strings -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.install b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.install index 100d7c04..30e8cd26 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.install +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.install @@ -29,7 +29,9 @@ function i18n_string_install() { i18n_string_update_7001(); } // Create new index in {locales_source}, performance improvement in sites with i18n. - db_add_index('locales_source', 'textgroup_context', array('textgroup', 'context')); + if (!db_index_exists('locales_source', 'textgroup_context')) { + db_add_index('locales_source', 'textgroup_context', array('textgroup', array('context', 50))); + } } /** @@ -106,7 +108,7 @@ function i18n_string_schema() { ), 'primary key' => array('lid'), 'indexes' => array( - 'group_context' => array('textgroup', 'context'), + 'group_context' => array('textgroup', array('context', 50)), ), ); return $schema; @@ -238,7 +240,9 @@ function i18n_string_update_7001() { * Create new index in {locales_source}, performance improvement in sites with i18n. */ function i18n_string_update_7002() { - db_add_index('locales_source', 'textgroup_context', array('textgroup', 'context')); + if (!db_index_exists('locales_source', 'textgroup_context')) { + db_add_index('locales_source', 'textgroup_context', array('textgroup', array('context', 50))); + } } @@ -269,4 +273,4 @@ function i18n_string_update_7002() { * Node type * nodetype:type:[type]:[property] -> node:type:[type]:[property] * Property names: title -> title_label - */ \ No newline at end of file + */ diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.module index 9c232c08..209b654c 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.module @@ -258,6 +258,34 @@ function i18n_string_locale_translate_import_form_submit($form, &$form_state) { } } +/** + * Implements hook_element_info_alter(). + * + * We need to do this on the element info level as wysiwyg also does so and form + * API (incorrectly) does not merge in the defaults for values that are arrays. + */ +function i18n_string_element_info_alter(&$types) { + $types['text_format']['#pre_render'][] = 'i18n_string_pre_render_text_format'; +} + +/** + * The '#pre_render' function to alter the text format element in a translation. + * The text format for a translation is taken form the original, so the text + * format drop down should be disabled. + * + * @param array $element + * The text_format element which will be rendered. + * + * @return array + * The altered text_format element with a disabled "Text format" select. + */ +function i18n_string_pre_render_text_format($element) { + if (!empty($element['#i18n_string_is_translation'])) { + $element['format']['format']['#attributes']['disabled'] = TRUE; + } + return $element; +} + /** * Check if translation is required for this language code. * @@ -334,7 +362,10 @@ function i18n_string_update_context($oldname, $newname) { } /** - * Get textgroup handler + * Get textgroup handler. + * + * @return i18n_string_textgroup_default + * */ function i18n_string_textgroup($textgroup) { $groups = &drupal_static(__FUNCTION__); @@ -519,11 +550,12 @@ function i18n_string_multiple($operation, $name, $strings, $options = array()) { * * This function is intended to return translations for plain strings that have NO text format * - * @param $name + * @param array|string name * Array or string concatenated with ':' that contains textgroup and string context - * @param $string - * String in default language or array of strings to be translated - * @param $options + * @param array|string $string + * A string in the default language, a string wth format (array with keys + * value and format),or an array of strings (without format) to be translated. + * @param array $options * An associative array of additional options, with the following keys: * - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page. * - 'filter' Filtering callback to apply to the translated string only @@ -531,8 +563,13 @@ function i18n_string_multiple($operation, $name, $strings, $options = array()) { * - 'callback' Callback to apply to the result (both to translated or untranslated string * - 'sanitize' Whether to filter the translation applying the text format if any, default is TRUE * - 'sanitize default' Whether to filter the default value if no translation found, default is FALSE + * + * @return string */ function i18n_string_translate($name, $string, $options = array()) { + if (is_array($string) && isset($string['value'])) { + $string = $string['value']; + } if (is_array($string)) { return i18n_string_translate_list($name, $string, $options); } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.pages.inc b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.pages.inc index 4d0da59c..fd657b5a 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.pages.inc +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.pages.inc @@ -170,14 +170,16 @@ function i18n_string_translate_page_form_base($form, $langcode, $redirect = NULL /** * Create field elements for strings + * + * @param i18n_string_object[] $strings + * @param string $langcode + * + * @return array */ function i18n_string_translate_page_form_strings($strings, $langcode) { - $formats = filter_formats(); + global $user; + $form = array(); foreach ($strings as $item) { - // We may have a source or not. Load it, our string may get the format from it. - $source = $item->get_source(); - $format_id = $source ? $source->format : $item->format; - $description = ''; // Check permissions to translate this string, depends on format, etc.. if ($message = $item->check_translate_access()) { // We'll display a disabled element with the reason it cannot be translated. @@ -188,27 +190,31 @@ function i18n_string_translate_page_form_strings($strings, $langcode) { $disabled = FALSE; $description = ''; // If we don't have a source and it can be translated, we create it. - if (!$source) { + if (!$item->get_source()) { // Enable messages just as a reminder these strings are not being updated properly. $status = $item->update(array('messages' => TRUE)); if ($status === FALSE || $status === SAVED_DELETED) { // We don't have a source string so nothing to translate here $disabled = TRUE; } - else { - $source = $item->get_source(); - } } } $default_value = $item->format_translation($langcode, array('langcode' => $langcode, 'sanitize' => FALSE, 'debug' => FALSE)); + $available_formats = array_keys(filter_formats($user)); + if (!in_array($item->format, $available_formats)) { + $item->format = NULL; + } $form[$item->get_name()] = array( '#title' => $item->get_title(), - '#type' => 'textarea', + '#type' => $item->format ? 'text_format' : 'textarea', '#default_value' => $default_value, + '#format' => $item->format, + // This will trigger i18n_string_pre_render_text_format() to actually + // alter the element. + '#i18n_string_is_translation' => TRUE, '#disabled' => $disabled, - '#description' => $description . _i18n_string_translate_format_help($format_id), - //'#i18n_string_format' => $source ? $source->format : 0, + '#description' => $description, // If disabled, provide smaller textarea (that can be expanded anyway). '#rows' => $disabled ? 1 : min(ceil(str_word_count($default_value) / 12), 10), // Change the parent for disabled strings so we don't get empty values later @@ -226,6 +232,16 @@ function i18n_string_translate_page_form_submit($form, &$form_state) { foreach ($form_state['values']['strings'] as $name => $value) { $count++; list($textgroup, $context) = i18n_string_context(explode(':', $name)); + if (is_array($value)) { + if (isset($value['value'])) { + $value = $value['value']; + $form_state['values']['strings'][$name] = $value; + } + else { + form_set_error("strings][$name", t('Unable to get the translated string value.')); + watchdog('locale', 'Unable to get the translated string value, string array is: %string', array('%string' => var_dump($value)), WATCHDOG_WARNING); + } + } $result = i18n_string_textgroup($textgroup)->update_translation($context, $form_state['values']['langcode'], $value); $success += ($result ? 1 : 0); } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.test b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.test index f11c7564..d4563405 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.test +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.test @@ -53,6 +53,28 @@ class i18nStringTestCase extends Drupali18nTestCase { $this->assertEqual($translation, $translations[$key][$language->language], "The right $language->name ($language->language) translation has been retrieved for $name, $translation"); } } + + // Test that regular strings can be translated. Use 'Built-in interface' as + // filter, and translate first one. + $search = array( + 'language' => 'all', + 'translation' => 'all', + 'group' => 'default', + 'string' => '', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->clickLink(t('edit')); + // Just add a random translation. + $translation = $this->randomString(); + $edit = array(); + foreach ($this->getOtherLanguages() as $language) { + $langcode = $language->language; + $edit["translations[$langcode]"] = $translation; + } + $this->drupalPost(NULL, $edit, t('Save translations')); + $this->assertText(t('The string has been saved.'), t('The string has been saved.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + } /** diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/README.txt index ce7ea6aa..c106dcb6 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/README.txt @@ -3,7 +3,7 @@ README.txt ========== Drupal module: i18n_sync (Synchronization) -This module will handle content synchronization accross translations. +This module will handle content synchronization across translations. The available list of fields to synchronize will include standard node fields and cck fields. To have aditional fields, add the list in a variable in the settings.php file, like this: diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info index 2f1cd55e..eec7f24f 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info @@ -1,5 +1,5 @@ name = Synchronize translations -description = Synchronizes taxonomy and fields accross translations of the same content. +description = Synchronizes taxonomy and fields across translations of the same content. dependencies[] = i18n dependencies[] = translation package = Multilingual - Internationalization @@ -10,9 +10,9 @@ files[] = i18n_sync.install files[] = i18n_sync.module.inc files[] = i18n_sync.node.inc files[] = i18n_sync.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.module index 8aaadbec..bf5f37d4 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.module @@ -33,7 +33,7 @@ function i18n_sync($status = NULL) { function i18n_sync_help($path, $arg) { switch ($path) { case 'admin/help#i18n_sync' : - $output = '

    ' . t('This module synchronizes content taxonomy and fields accross translations:') . '

    '; + $output = '

    ' . t('This module synchronizes content taxonomy and fields across translations:') . '

    '; $output .= '

    ' . t('First you need to select which fields should be synchronized. Then, after a node has been updated, all enabled vocabularies and fields will be synchronized as follows:') . '

    '; $output .= '
      '; $output .= '
    • ' . t('All the node fields selected for synchronization will be set to the same value for all translations.') . '
    • '; diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.node.inc b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.node.inc index 8ccad676..8b09427a 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.node.inc +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.node.inc @@ -98,7 +98,7 @@ function i18n_sync_node_translation_nodereference_field(&$node, &$translation, $ * Example: * English A references English B and English C. * English A and B are translated to German A and B, but English C is not. - * The syncronization from English A to German A would it German B and English C. + * The synchronization from English A to German A would it German B and English C. */ function i18n_sync_node_translation_reference_field(&$reference_node, $default_value, $langcode) { if (isset($reference_node->tnid) && translation_supported_type($reference_node->type)) { diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/README.txt new file mode 100644 index 00000000..0813883a --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/README.txt @@ -0,0 +1,86 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The Taxonomy translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, provides multiple options to translate taxonomy vocabularies and terms. For each vocabulary, there are four types of behaviors to choose from: Language-independent terms, Language-specific terms, Localized terms, and Mixed-language vocabulary. + +* For a full description of the module visit https://www.drupal.org/node/1114016 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization - https://www.drupal.org/project/i18n + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views - https://www.drupal.org/project/i18nviews +* Language Icons - https://www.drupal.org/project/languageicons +* Translation Overview - https://www.drupal.org/project/translation_overview +* Localization Client - https://www.drupal.org/project/l10n_client +* Internationalization contributions - https://www.drupal.org/project/i18n_contrib + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +Language-independent terms - only vocabulary will be translatable. +1. Navigate to Structure > Taxonomy. +2. Select the "edit vocabulary" link. +3. Select the "No multilingual options for terms". + +Language-specific terms - vocabulary is only used for content in that language. The terms will only be available if the term language matches the UI language. +1. Navigate to Structure > Taxonomy and select the "edit vocabulary link". +2. Choose "Fixed Language" and a Language drop-down field will be displayed. +3. Select the language. +4. Select "Fixed Language" and Save. + +Localized terms - Terms are common for all languages, but their name and description may be localized. +1. Navigate to Structure > Taxonomy > vocabulary-to-edit > Edit. +2. Select "Localize" and select Save. +3. Edit a term and there will be a Translate tab. Select this tab. +4. Select Translate, translate the Name and Description, select "Save translation", and repeat for all languages. +5. Repeat the process for all terms. +6. Navigate to Structure > Content types > term-to-edit > Manage display. +7. By default, the term reference is set to Link. Change this to "Link (localized)" and Save. +The vocabulary will be appropriate for the language. + +Mixed-language vocabulary - Use for vocabularies with terms in multiple languages. +1. Navigate to Structure > Taxonomy > vocabulary-to-edit > Edit. +2. Select the Translate radio button and Save. +3. Edit a vocabulary term and there will be a new Language field. Choose a language and then select Save and translate. +4. There are two options, the user can either select "Add translation link" or the user can select an existing term in the Select translations form. +5. Create translations for the terms and add terms for specific languages only. +Now if the user edits a node associated with this vocabulary, only the relevant terms will appear. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info index 2412c40e..da9281e4 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info @@ -11,9 +11,9 @@ files[] = i18n_taxonomy.pages.inc files[] = i18n_taxonomy.admin.inc files[] = i18n_taxonomy.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.module index 40533489..add24f56 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.module @@ -213,7 +213,12 @@ function i18n_taxonomy_field_formatter_view($entity_type, $entity, $field, $inst ); } else { - $term = $item['taxonomy_term']; + if (isset($item['taxonomy_term'])) { + $term = $item['taxonomy_term']; + } + else { + $term = taxonomy_term_load($item['tid']); + } $uri = entity_uri('taxonomy_term', $term); $element[$delta] = array( '#type' => 'link', @@ -372,10 +377,19 @@ function i18n_taxonomy_field_prepare_translation($entity_type, $entity, $field, * The array of valid terms for this field, keyed by term id. */ function i18n_taxonomy_allowed_values($field) { + global $language; $options = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { - if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) { + if (i18n_taxonomy_vocabulary_mode($vocabulary->vid) == I18N_MODE_TRANSLATE) { + $parent = i18n_taxonomy_translation_term_tid($tree['parent'], NULL, $tree['parent']); + $language = i18n_language_context(); + $terms = i18n_taxonomy_get_tree($vocabulary->vid, $language->language, $parent); + } + else { + $terms = taxonomy_get_tree($vocabulary->vid, $tree['parent']); + } + if ($terms) { foreach ($terms as $term) { $options[$term->tid] = str_repeat('-', $term->depth) . i18n_taxonomy_term_name($term); } @@ -1020,6 +1034,10 @@ function i18n_taxonomy_localize_terms($terms) { if (!i18n_string_translate_langcode()) { return $terms; } + // $terms is not a valid array or term. + if (empty($terms)) { + return $terms; + } $object_info = i18n_object_info('taxonomy_term'); $list = is_array($terms) ? $terms : array($terms); foreach ($list as $index => $term) { @@ -1087,7 +1105,7 @@ function i18n_taxonomy_get_tree($vid, $langcode, $parent = 0, $max_depth = NULL, $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); $result = $query ->addTag('translatable') - ->addTag('term_access') + ->addTag('taxonomy_term_access') ->fields('t') ->fields('h', array('parent')) ->condition('t.vid', $vid) @@ -1261,3 +1279,22 @@ function i18n_taxonomy_modules_enabled($modules) { } } } + +/** + * Implements hook_views_pre_render(). + */ +function i18n_taxonomy_views_pre_render(&$view) { + if(!module_exists('rules_scheduler')) { + global $language; + + foreach ($view->result as $delta => $term){ + if (isset($term->tid)) { + i18n_string_translate_langcode($language->language); + $localized_term = i18n_taxonomy_localize_terms(taxonomy_term_load($term->tid)); + $term->tid = $localized_term->tid; + $term->taxonomy_term_data_name = $localized_term->name; + $term->taxonomy_term_data_description = $localized_term->description; + } + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc index ab77cbc1..8d8f8c6f 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc @@ -100,7 +100,7 @@ function _i18n_taxonomy_autocomplete($langcode, $vids, $tags_typed = '') { $query = db_select('taxonomy_term_data', 't') ->fields('t', array('tid', 'name')); $query->addTag('translatable'); - $query->addTag('term_access'); + $query->addTag('taxonomy_term_access'); // Disable i18n_select for this query $query->addTag('i18n_select'); // Add language condition diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info index 190173c5..47f12951 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info @@ -6,9 +6,9 @@ core = 7.x files[] = i18n_translation.inc -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.module index de4aad4e..3fa78c8d 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.module @@ -20,7 +20,7 @@ class I18nTranslationSetController extends DrupalDefaultEntityController { * @param $queried_entities * Associative array of query results, keyed on the entity ID. * @param $revision_id - * ID of the revision that was loaded, or FALSE if teh most current revision + * ID of the revision that was loaded, or FALSE if the most current revision * was loaded. */ protected function attachLoad(&$queried_entities, $revision_id = FALSE) { diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/README.txt new file mode 100644 index 00000000..67eedcc1 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/README.txt @@ -0,0 +1,63 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + + +INTRODUCTION +------------ + +The User mail translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, translates emails sent from the User module. + +* For a full description of the module, visit this page: +https://www.drupal.org/node/133977 + +* To submit bug reports and feature suggestions, or to track changes: +https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +Internationalization (https://www.drupal.org/project/i18n) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +To configure email translations +1. Navigate to Administration > Configuration > Regional and language > Multilingual settings and select the variables tab. +2. Select the "User emails" tab. Select the variables you would like translated and Save configuration. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info index 7c6ee3ba..eb7a364d 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info @@ -4,9 +4,9 @@ core = 7.x package = Multilingual - Internationalization dependencies[] = i18n_variable -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.module b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.module index 4d13b3ba..bd7d5dda 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.module +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.module @@ -9,7 +9,7 @@ */ function i18n_user_mail_alter(&$message) { if ($message['module'] == 'user') { - $language = $message['language']; + $language = (isset($message['language']) ? $message['language'] : language_default()); $variables = array('user' => $message['params']['account']); $key = $message['key']; @@ -54,7 +54,7 @@ function i18n_user_user_mail_tokens(&$replacements, $data, $options) { */ function i18n_user_user_pass_reset_url($account) { $timestamp = REQUEST_TIME; - return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE, 'language' => i18n_language($account->language))); + return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE, 'language' => i18n_language($account->language))); } /** @@ -65,5 +65,5 @@ function i18n_user_user_pass_reset_url($account) { */ function i18n_user_user_cancel_url($account) { $timestamp = REQUEST_TIME; - return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE, 'language' => i18n_language($account->language))); + return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE, 'language' => i18n_language($account->language))); } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/README.txt b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/README.txt new file mode 100644 index 00000000..39e0f2bb --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/README.txt @@ -0,0 +1,67 @@ +CONTENTS OF THIS FILE +--------------------- + +* Introduction +* Requirements +* Recommended modules +* Installation +* Configuration +* Maintainers + +INTRODUCTION +------------ + +The Variable translation module, part of the Internationalization (https://www.drupal.org/project/i18n) package, allows the user to translate text and settings that are stored in Drupal as variables. These variables include text such as 'site name' and 'site slogan', as well as settings like 'Default front page' and 'Default 404 page'. + +* For a full description of the module, visit https://www.drupal.org/node/1113374 + +* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/i18n + + +REQUIREMENTS +------------ + +This module requires the following modules: + +* Internationalization (https://www.drupal.org/project/i18n) +* Variable (https://www.drupal.org/project/variable) + + +RECOMMENDED MODULES +------------------- + +* Internationalization Views (https://www.drupal.org/project/i18nviews) +* Language Icons (https://www.drupal.org/project/languageicons) +* Translation Overview (https://www.drupal.org/project/translation_overview) +* Localization Client (https://www.drupal.org/project/l10n_client) +* Internationalization contributions (https://www.drupal.org/project/i18n_contrib) + + +INSTALLATION +------------ + +* This is a submodule of the Internationalization module. Install the Internationalization module as you would normally install a contributed Drupal module. See https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. + + +CONFIGURATION +------------- + +To enable multilingual variables + +1. Enable the Variable translation module included with the Internationalization package. +2. Go to Administration > Configuration > Regional and language > Multilingual settings. +3. Click on the Variables tab. +4. Select the variables that will be multilingual. +5. Click Save configuration button. + +Once you have the correct settings, they'll be marked with "This is a multilingual variable" when you go to the corresponding administration pages. You must switch the site language while in the administration pages to set the variables for each language. A language switcher link will appear at the top of each administrative page that has multilingual variables. + + +MAINTAINERS +----------- + +* Jose Reyero - https://www.drupal.org/u/jose-reyero +* Florian Weber (webflo) - https://www.drupal.org/u/webflo +* Peter Philipp - https://www.drupal.org/u/das-peter +* Joseph Olstad - https://www.drupal.org/u/joseph.olstad +* Nathaniel Catchpole - https://www.drupal.org/u/catch diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info index 6956a25d..7a26ab61 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info @@ -10,9 +10,9 @@ configure = admin/config/regional/i18n/variable files[] = i18n_variable.class.inc files[] = i18n_variable.test -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info b/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info index 9ccf441d..1ec747ae 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info @@ -7,9 +7,9 @@ package = Testing core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2015-01-26 -version = "7.x-1.12" +; Information added by Drupal.org packaging script on 2017-04-18 +version = "7.x-1.17" core = "7.x" project = "i18n" -datestamp = "1422286982" +datestamp = "1492537747" diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/README b/profiles/commerce_kickstart/modules/contrib/inline_conditions/README deleted file mode 100644 index 5b16f678..00000000 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/README +++ /dev/null @@ -1,12 +0,0 @@ -It is common for a rule to be generated based on an entity (discounts, shipping rates, etc). - -This module allows conditions to be defined on the entity add / edit form, and -those conditions are later mapped to rules conditions when the rule is generated. - -Inline Conditions are specially defined (hook_inline_condition_info()) and -consist of a configure callback (provides a user-facing form) and a build -callback (adds the actual condition to the rule). -Integration consists of creating a field of the "inline_conditions" type on the -entity, and later calling inline_conditions_build_rule() from the implementation -of hook_default_rules_configuration(). -See inline_conditions.api.php for more information. diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/README.md b/profiles/commerce_kickstart/modules/contrib/inline_conditions/README.md new file mode 100644 index 00000000..01a4f6fd --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/README.md @@ -0,0 +1,21 @@ +## CONTENTS OF THIS FILE + + * Description + + +## DESCRIPTION + +It is common for a rule to be generated based on an entity (discounts, shipping +rates, etc). + +This module allows conditions to be defined on the entity add / edit form, and +those conditions are later mapped to rules conditions when the rule is +generated. + +Inline Conditions are specially defined (hook_inline_condition_info()) and +consist of a configure callback (provides a user-facing form) and a build +callback (adds the actual condition to the rule). Integration consists of +creating a field of the "inline_conditions" type on the entity, and later +calling inline_conditions_build_rule() from the implementation of +hook_default_rules_configuration(). See inline_conditions.api.php for more +information. diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions-rtl.css b/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions-rtl.css deleted file mode 100644 index 1d0cf82b..00000000 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions-rtl.css +++ /dev/null @@ -1,35 +0,0 @@ -/** -* inline_conditions main css file. -*/ - -/** -* Condition area. -*/ -.inline-conditions-container .condition-wrapper { - float: right; -} -.inline-conditions-container .condition-wrapper .form-item label { - float: right; /* RTL */ -} -/* Throbber */ -.inline-conditions-container .condition-wrapper .form-item .ajax-progress { - display: inline; - margin-top: 0; /* RTL */ - left: 0; - right: 62px; /* RTL */ - width: auto; /* RTL */ -} -.inline-conditions-container .remove-condition .form-submit { - float: right; -} -.inline-conditions-container .remove-condition .ajax-progress .message { - float: right; - padding-right: 5px; - padding-left: 0; /* RTL */ -} - -/* order conditions */ -.inline-conditions-container div.condition-instructions { - margin-right: 85px; - margin-left: 0; /* RTL */ -} diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions.css b/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions.css index 0bbe796c..4b326736 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions.css +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/css/inline_conditions.css @@ -1,91 +1,44 @@ /** -* inline_conditions main css file. -*/ - -/** -* Discount conditions block. -*/ -.inline-conditions-container .form-wrapper { - margin: 10px; -} -.inline-conditions-container .even, -.inline-conditions-container .odd { - margin-top: 2px; - padding: 10px 5px; -} -.inline-conditions-container .odd { - background-color: #eee; -} + * @file + * Inline conditions main stylesheet. + * + * This file exposes the common style rules for the inline_conditions field + * widget and its child elements should be displayed. + */ -/** -* Condition area. +/* + Wrappers for select, checkbox, checkboxes and textfield elements are + displayed inline. + The #id selector is used to counter core's tr.odd .form-item styles. */ -.inline-conditions-container .condition-wrapper { - display: block; - float: left; /* LTR */ - position: relative; -} -.inline-conditions-container .condition-wrapper .form-item label { +#inline-conditions-inline_conditions .inline-conditions-settings-wrapper .form-wrapper, +#inline-conditions-inline_conditions .inline-conditions-settings-wrapper .form-type-select, +#inline-conditions-inline_conditions .inline-conditions-settings-wrapper .form-type-checkbox, +#inline-conditions-inline_conditions .inline-conditions-settings-wrapper .form-type-checkboxes, +#inline-conditions-inline_conditions .inline-conditions-settings-wrapper .form-type-textfield { display: inline-block; - width: 80px; -} -/* Throbber */ -.inline-conditions-container .condition-wrapper .form-item .ajax-progress { - position: absolute; - left: 62px; /* LTR */ -} -.inline-conditions-container .condition-wrapper .form-item .ajax-progress .message { - display: none; + vertical-align: top; } -.inline-conditions-container .condition-logic { - position: relative; -} -.inline-conditions-container .condition-logic select { - float: left; /* LTR */ - margin-right: 10px; +/* Labels inside wrappers are displayed inline. */ +.inline-conditions-settings-wrapper .form-item label:not(.option) { + display: inline; + vertical-align: top; } -.inline-conditions-container .remove-condition { - position: relative; -} -.inline-conditions-container .remove-condition .form-submit { - float: left; /* LTR */ - margin: 0 10px; -} -.inline-conditions-container .remove-condition .ajax-progress .message { - float: left; /* LTR */ - padding-left: 5px; /* LTR */ +/* Make bigger the logical operators to make them even more clear. */ +.inline-conditions-logic-operator { + font-size: 1.2em; } -/* order conditions */ -.inline-conditions-container .form-item { - margin: 0; -} -.inline-conditions-container .form-submit { - margin: 0; +/* Add vertical space to direct children of the settings wrapper. */ +.inline-conditions-settings-wrapper > .form-item { + margin-top: 0.25em; + margin-bottom: 0.25em; } -.inline-conditions-container .form-submit.add-new-condition { - margin-top: 10px; -} -.inline-conditions-container div.condition-instructions { - color: #6d6d6d; - font-style: italic; - display: block; - font-size: smaller; - /* Equal to ..inline-conditions-container .condition-wrapper .form-item label width+5px */ - margin-left: 85px; /* LTR */ -} -.inline-conditions-container .product-taxonomy .form-type-radio { - display: block; - margin: 0; - padding: 0; -} -.inline-conditions-container .product-taxonomy .form-type-radio > label { - color: #6d6d6d; - font-style: italic; - font-size: smaller; -} -.commerce-discount-form .negate-condition input.form-checkbox + label::before { - top: inherit; + +/* Ensure alignment of controls in the cells. */ +#inline-conditions-inline_conditions th, +#inline-conditions-inline_conditions td { + vertical-align: top; } diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.api.php b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.api.php index a486dbdd..262f850a 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.api.php +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.api.php @@ -31,7 +31,15 @@ * operate on the same entity type -> commerce_order). * - callbacks: An array of callbacks: * - configure: (Optional) Returns a configuration form embedded into the - * field widget, and used to configure the inline condition. + * field widget, and used to configure the inline condition. The following + * list of parameters will be passed to the configure callback function: + * - condition_settings (Array): an array containing all configured + * settings; typically this will match the values of the form elements + * defined in the 'configure' callback. It should be transformed into + * an array of parameter values as the rules condition needs them. + * - instance (Array): The field instance array (which includes the entity + * information that is related to the condition, such as the ID). + * - delta (Int): The current field delta defined as an integer. * - build: [Do not use if rule condition name is set] Gets the rule and any * settings added by the configure callback, then builds and adds an actual * rules condition to the rule. Also, if the rule condition name key is set, diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.field.inc b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.field.inc new file mode 100644 index 00000000..83e092bd --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.field.inc @@ -0,0 +1,381 @@ + array( + 'label' => t('Inline conditions'), + 'description' => t('This field stores conditions that are later added to a matching rule.'), + // entity_type is the type of the entity operated on by the matching rule. + 'instance_settings' => array('entity_type' => NULL), + 'default_widget' => 'inline_conditions', + 'default_formatter' => 'hidden', + 'no_ui' => TRUE, + 'property_type' => 'inline_conditions', + 'property_callbacks' => array('inline_conditions_field_property_callback'), + ), + ); +} + +/** + * Implements hook_field_widget_info(). + */ +function inline_conditions_field_widget_info() { + return array( + 'inline_conditions' => array( + 'label' => t('Inline conditions'), + 'field types' => array('inline_conditions'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + 'settings' => array(), + ), + ); +} + +/** + * Implements hook_field_load(). + * + * Prepare items array in order to be usable with inline_condition field widget. + */ +function inline_conditions_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { + // Loop on every entities given. + foreach ($entities as $id => $entity) { + // Ensures that field is inline_conditions type. + if ($field['type'] == 'inline_conditions') { + + foreach ($items[$id] as $delta => $item) { + // Ensure condition_settings is unserialised. + if (is_string($item['condition_settings'])) { + // Unserialize the field settings. + $item['condition_settings'] = unserialize($item['condition_settings']); + + // Look up for the value of the logic operators. + if (isset($item['condition_settings']['condition_negate'])) { + $item['condition_negate'] = $item['condition_settings']['condition_negate']; + unset($item['condition_settings']['condition_negate']); + } + if (isset($item['condition_settings']['condition_logic_operator'])) { + $item['condition_logic_operator'] = $item['condition_settings']['condition_logic_operator']; + unset($item['condition_settings']['condition_logic_operator']); + } + + // Replace item value. + $items[$id][$delta] = $item; + } + } + } + } +} + +/** + * Implements hook_field_insert(). + * + * Call inline_conditions_prepare_field_items() in order to rebuild items. + * + * @see inline_conditions_field_prepare_items() + */ +function inline_conditions_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Ensures that field is inline_conditions type. + if ($field['type'] == 'inline_conditions') { + inline_conditions_field_prepare_items($items); + } +} + +/** + * Implements hook_field_update(). + * + * Call inline_conditions_prepare_field_items() in order to rebuild items. + * + * @see inline_conditions_field_prepare_items() + */ +function inline_conditions_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Ensures that field is inline_conditions type. + if ($field['type'] == 'inline_conditions') { + inline_conditions_field_prepare_items($items); + } +} + +/** + * Implements hook_field_is_empty(). + */ +function inline_conditions_field_is_empty($item, $field) { + return empty($item['condition_name']); +} + +/** + * Alter $items array and prepare it to be saved. + * + * Serialize the condition_settings column. + * + * @param array $items + * A referenced array of field items. + * + * @see inline_conditions_field_load() + * @see inline_conditions_field_insert() + */ +function inline_conditions_field_prepare_items(&$items) { + // A simple way to check if array is a multi-dimensional array. + if (is_array($items)) { + foreach ($items as $delta => $item) { + // Ensures that $item has a condition name. + if (!empty($item['condition_name'])) { + // Ensure that condition_settings is not serialized. + if (is_array($item['condition_settings'])) { + $condition_settings = array_merge($item['condition_settings'], array( + // Store the rule condition logic operators. + 'condition_negate' => isset($item['condition_negate']) ? $item['condition_negate'] : NULL, + 'condition_logic_operator' => isset($item['condition_logic_operator']) ? $item['condition_logic_operator'] : NULL, + )); + $item['condition_settings'] = serialize($condition_settings); + } + // Replace item value. + $items[$delta] = $item; + } + } + } +} + +/** + * Implements hook_field_widget_form(). + */ +function inline_conditions_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + // Ensure this .inline_conditions files are loaded when the form is rebuilt + // from the cache. + foreach (inline_conditions_get_info_by_module() as $module => $condition) { + $form_state['build_info']['files']["form_ic_$module"] = drupal_get_path('module', $module) . "/$module.inline_conditions.inc"; + } + + // Prepare a list of all possible conditions. + $condition_info = inline_conditions_get_info_by_type(empty($instance['settings']['entity_type']) ? 'commerce_order' : $instance['settings']['entity_type'], $instance['entity_type']); + + // Build main wrapper id. + $main_wrapper_id = sprintf('inline-conditions-%s', $field['field_name']); + + $element = array( + '#type' => 'container', + '#id' => $main_wrapper_id, + '#caption' => $element['#title'], + '#theme' => 'inline_conditions_table', + '#element_validate' => array('inline_conditions_field_widget_form_validate'), + ) + $element; + + // Attach module style sheet to widget form. + $form['#attached']['css'][] = drupal_get_path('module', 'inline_conditions') . '/css/inline_conditions.css'; + + // If "Add a new condition" button has been pressed. + if (isset($form_state['triggering_element']['#condition_action'])) { + switch ($form_state['triggering_element']['#condition_action']) { + case 'and': + $items[] = array( + 'condition_name' => '', + 'condition_settings' => array(), + 'condition_logic_operator' => INLINE_CONDITIONS_AND, + ); + break; + + case 'or': + $items[] = array( + 'condition_name' => '', + 'condition_settings' => array(), + 'condition_logic_operator' => INLINE_CONDITIONS_OR, + ); + break; + + case 'remove': + unset($items[$form_state['triggering_element']['#element_delta']]); + // Rebuild array keys. + $items = array_values($items); + break; + } + } + + // If no items found, create an empty item for display purposes. + if (empty($items)) { + $items[] = array('condition_name' => '', 'condition_settings' => array()); + } + + foreach ($items as $delta => $item) { + $settings_wrapper_id = sprintf('inline-conditions-settings-wrapper-%s-%s', $instance['id'], $delta); + + // Condition selector. + $element[$delta]['condition_name'] = array( + '#type' => 'select', + '#options' => inline_conditions_options_list($condition_info), + '#default_value' => $item['condition_name'], + '#ajax' => array( + 'callback' => 'inline_conditions_form_ajax_callback', + 'wrapper' => $settings_wrapper_id, + ), + '#condition_action' => 'select', + // Identifies the condition operated upon. + '#element_delta' => $delta, + '#limit_validation_errors' => array(), + ); + + // Condition settings. + $element[$delta]['condition_settings'] = array( + '#type' => 'value', + '#value' => array(), + ); + + // A condition has been selected, and a "configure" callback exists. Fills + // up the condition_settings key with form elements returned by condition + // configure callback. + if (!empty($item['condition_name']) && !empty($condition_info[$item['condition_name']]['callbacks']['configure'])) { + $callback = $condition_info[$item['condition_name']]['callbacks']['configure']; + $element[$delta]['condition_settings'] = $callback($item['condition_settings'], $instance, $delta); + $element[$delta]['#attributes'] = array('class' => array('container-inline')); + } + + // Merge additional properties for the ajax wrapping. + $element[$delta]['condition_settings'] += array( + '#prefix' => sprintf('
      ', $settings_wrapper_id, 'inline-conditions-settings-wrapper'), + '#suffix' => '
      ', + ); + + $element[$delta]['condition_negate'] = array( + '#type' => 'checkbox', + '#title' => t('Negate'), + '#default_value' => !empty($item['condition_negate']) ? $item['condition_negate'] : FALSE, + ); + + // Remove condition button. + $element[$delta]['remove_condition'] = array( + '#type' => 'button', + '#name' => $field['field_name'] . '-' . $instance['id'] . '-' . $delta . '-remove_condition', + '#value' => t('Remove'), + '#ajax' => array( + 'callback' => 'inline_conditions_form_ajax_callback', + 'wrapper' => 'inline-conditions-' . $field['field_name'], + 'effect' => 'fade', + ), + '#element_delta' => $delta, + '#condition_action' => 'remove', + '#executes_submit_callback' => FALSE, + '#limit_validation_errors' => array(), + ); + + // Add an option list to select logical disjunctions for each inline + // condition. + if ($delta > 0) { + // Add an option list to select the logic operator for the current + // condition. + $element[$delta]['condition_logic_operator'] = array( + '#type' => 'select', + '#options' => array( + INLINE_CONDITIONS_OR => t('OR'), + INLINE_CONDITIONS_AND => t('AND'), + ), + '#default_value' => isset($item['condition_logic_operator']) ? $item['condition_logic_operator'] : INLINE_CONDITIONS_AND, + '#attributes' => array('class' => array('inline-conditions-logic-operator')), + '#weight' => -50, + ); + } + } + + // Prepare the action buttons. + $element['and_condition'] = array( + '#type' => 'button', + '#name' => sprintf('%s-%s-and-condition', $field['field_name'], $instance['id']), + '#value' => t('Attach AND condition'), + '#ajax' => array( + 'callback' => 'inline_conditions_form_ajax_callback', + 'wrapper' => $main_wrapper_id, + 'effect' => 'fade', + ), + '#attributes' => array('class' => array('and-condition')), + '#element_delta' => $element['#delta'], + '#condition_action' => 'and', + '#limit_validation_errors' => array(), + ); + $element['or_condition'] = array( + '#type' => 'button', + '#name' => sprintf('%s-%s-or-condition', $field['field_name'], $instance['id']), + '#value' => t('Attach OR condition'), + '#ajax' => array( + 'callback' => 'inline_conditions_form_ajax_callback', + 'wrapper' => $main_wrapper_id, + 'effect' => 'fade', + ), + '#attributes' => array('class' => array('or-condition')), + '#element_delta' => $element['#delta'], + '#condition_action' => 'or', + '#limit_validation_errors' => array(), + ); + + return $element; +} + +/** + * Callback : Validate inline_conditions_field_widget_form. + * + * @param array $element + * A form element array containing basic properties for the widget: + * - #entity_type: The name of the entity the field is attached to. + * - #bundle: The name of the field bundle the field is contained in. + * - #field_name: The name of the field. + * - #language: The language the field is being edited in. + * - #field_parents: The 'parents' space for the field in the form. Most + * widgets can simply overlook this property. This identifies the + * location where the field values are placed within + * $form_state['values'], and is used to access processing information + * for the field through the field_form_get_state() and + * field_form_set_state() functions. + * - #columns: A list of field storage columns of the field. + * - #title: The sanitized element label for the field instance, ready for + * output. + * - #description: The sanitized element description for the field instance, + * ready for output. + * - #required: A Boolean indicating whether the element value is required; + * for required multiple value fields, only the first widget's values are + * required. + * - #delta: The order of this item in the array of subelements; see $delta + * above. + * @param array $form_state + * An associative array containing the current state of the form. + * @param array $form + * The form structure where widgets are being attached to. This might be a + * full form structure, or a sub-element of a larger form. + */ +function inline_conditions_field_widget_form_validate($element, &$form_state, $form) { + // Remove action buttons values from the form state values. + $value = drupal_array_get_nested_value($form_state['values'], $element['#parents']); + $value = array_intersect_key($value, array_flip(array_filter(array_keys($value), 'is_numeric'))); + drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value, TRUE); +} + +/** + * Ajax callback for the Inline Conditions form elements. + * + * @param array $form + * The form array. + * @param array &$form_state + * The reference of form_state array. + * + * @return array + * Return form element to display. + */ +function inline_conditions_form_ajax_callback($form, &$form_state) { + $element_parents = array_slice($form_state['triggering_element']['#array_parents'], 0, -2); + $element = drupal_array_get_nested_value($form, $element_parents); + + // Triggered when user selects a condition. + if ($form_state['triggering_element']['#condition_action'] == 'select') { + $delta = $form_state['triggering_element']['#element_delta']; + // We are returning the condition settings because it's a dynamic element, + // other elements don't need to be refreshed. + return $element[$delta]['condition_settings']; + } + else { + return $element; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.info b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.info index 0f899c9c..c827b211 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.info +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.info @@ -4,9 +4,9 @@ package = Rules core = 7.x dependencies[] = rules -; Information added by Drupal.org packaging script on 2015-05-18 -version = "7.x-1.0-alpha5" +; Information added by Drupal.org packaging script on 2017-11-20 +version = "7.x-1.0-rc1" core = "7.x" project = "inline_conditions" -datestamp = "1431982983" +datestamp = "1511212687" diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.install b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.install index b0ecce4c..6e265752 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.install +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.install @@ -28,29 +28,3 @@ function inline_conditions_field_schema($field) { ); } } - -/** - * Implements hook_enable(). - * - * @see module_enable() - * @see hook_install() - * @see hook_modules_enabled() - */ -function inline_conditions_enable() { -} - -/** - * Perform necessary actions before module is disabled. - * - * Run clear caches and cron job in order to delete all related fields. - * - * @see hook_uninstall() - * @see hook_modules_disabled() - */ -function inline_conditions_disable() { - // Clear caches. - cache_clear_all(); - - // Run the cron job. - drupal_cron_run(); -} diff --git a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.module b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.module index bfb28459..377db317 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.module +++ b/profiles/commerce_kickstart/modules/contrib/inline_conditions/inline_conditions.module @@ -1,13 +1,17 @@ array( + 'render element' => 'element', + ), + ); +} + +/** + * Returns HTML for an individual inline conditions widget. + * + * @param array $variables + * An associative array containing: + * - element: A render element representing the widget. + * + * @ingroup themeable + * + * @return string + * The HTML to be rendered. + */ +function theme_inline_conditions_table($variables) { + $element = $variables['element']; + + // Initialize the variable which will store our table rows. + foreach (element_children($element) as $key) { + if (is_numeric($key) && $key > 0) { + // Render the logical operator form element and pop it into the upper row. + $rows[] = array( + 'data' => array( + array( + 'data' => $element[$key]['condition_logic_operator'], + 'colspan' => 4, + ), + ), + ); + } + + if (is_numeric($key)) { + $rows[] = array( + 'data' => array( + // Column: "Apply to". + array('data' => $element[$key]['condition_name']), + // Column: "Settings". + array('data' => $element[$key]['condition_settings']), + // Column: "Negate". + array('data' => $element[$key]['condition_negate']), + // Column: "Remove". + array('data' => $element[$key]['remove_condition']), + ), + ); + } + } + + // Render action buttons. + $rows[] = array( + 'data' => array( + array( + 'data' => array( + $element['and_condition'], + $element['or_condition'], + ), + 'colspan' => 4, + ), + ), + ); + + return theme('table', array( + 'header' => array(t('Apply to'), t('Settings'), t('Negate'), t('Remove')), + 'rows' => $rows, + 'attributes' => array('class' => array('inline-conditions-table')), + 'caption' => isset($element['#caption']) ? $element['#caption'] : NULL, + )); +} + /** * Implements hook_modules_enabled(). */ @@ -191,44 +272,6 @@ function _inline_conditions_autocomplete_validate($element, &$form_state, $form) form_set_value($element, $value, $form_state); } -/** - * Implements hook_field_info(). - */ -function inline_conditions_field_info() { - return array( - 'inline_conditions' => array( - 'label' => t('Inline conditions'), - 'description' => t( - 'This field stores conditions that are later added to a matching rule.' - ), - // entity_type is the type of the entity operated on by the matching rule. - 'instance_settings' => array('entity_type' => NULL), - 'default_widget' => 'inline_conditions', - 'default_formatter' => 'hidden', - 'no_ui' => TRUE, - 'property_type' => 'inline_conditions', - 'property_callbacks' => array('inline_conditions_field_property_callback'), - ), - ); -} - -/** - * Implements hook_field_widget_info(). - */ -function inline_conditions_field_widget_info() { - return array( - 'inline_conditions' => array( - 'label' => t('Inline conditions'), - 'field types' => array('inline_conditions'), - 'behaviors' => array( - 'multiple values' => FIELD_BEHAVIOR_CUSTOM, - 'default value' => FIELD_BEHAVIOR_NONE, - ), - 'settings' => array(), - ), - ); -} - /** * Property callback for IC module. */ @@ -238,7 +281,7 @@ function inline_conditions_field_property_callback(&$info, $entity_type, $field, entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type); // Alter both getter & setter callbacks. - $property = & $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; $property['getter callback'] = 'inline_conditions_entity_metadata_field_property_get'; $property['setter callback'] = 'inline_conditions_entity_metadata_field_property_set'; } @@ -284,344 +327,6 @@ function inline_conditions_entity_metadata_field_property_set($entity, $name, $v drupal_static_reset('field_language'); } - -/** - * Implements hook_field_load(). - * - * Prepare items array in order to be usable with inline_condition field widget. - */ -function inline_conditions_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { - // Loop on every entities given. - foreach ($entities as $id => $entity) { - // Ensures that field is inline_conditions type. - if ($field['type'] == 'inline_conditions') { - - foreach ($items[$id] as $delta => $item) { - // Ensure condition_settings is unserialised. - if (is_string($item['condition_settings'])) { - // Unserialize the field settings. - $item['condition_settings'] = unserialize($item['condition_settings']); - - // Look up for the value of the logic operators. - if (isset($item['condition_settings']['condition_negate'])) { - $item['condition_negate'] = $item['condition_settings']['condition_negate']; - unset($item['condition_settings']['condition_negate']); - } - if (isset($item['condition_settings']['condition_logic_operator'])) { - $item['condition_logic_operator'] = $item['condition_settings']['condition_logic_operator']; - unset($item['condition_settings']['condition_logic_operator']); - } - - // Replace item value. - $items[$id][$delta] = $item; - } - } - } - } -} - -/** - * Implements hook_field_insert(). - * - * Call inline_conditions_prepare_field_items() in order to rebuild items. - * - * @see inline_conditions_field_prepare_items() - */ -function inline_conditions_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { - // Ensures that field is inline_conditions type. - if ($field['type'] == 'inline_conditions') { - inline_conditions_field_prepare_items($items); - } -} - -/** - * Implements hook_field_update(). - * - * Call inline_conditions_prepare_field_items() in order to rebuild items. - * - * @see inline_conditions_field_prepare_items() - */ -function inline_conditions_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { - // Ensures that field is inline_conditions type. - if ($field['type'] == 'inline_conditions') { - inline_conditions_field_prepare_items($items); - } -} - -/** - * Implements hook_field_is_empty(). - */ -function inline_conditions_field_is_empty($item, $field) { - return empty($item['condition_name']); -} - - -/** - * Alter $items array and prepare it to be saved. - * - * Serialize the condition_settings column. - * - * @param array $items - * A referenced array of field items. - * - * @see inline_conditions_field_load() - * @see inline_conditions_field_insert() - */ -function inline_conditions_field_prepare_items(&$items) { - // A simple way to check if array is a multi-dimensional array. - if (is_array($items)) { - foreach ($items as $delta => $item) { - // Ensures that $item has a condition name. - if (!empty($item['condition_name'])) { - // Ensure that condition_settings is not serialized. - if (is_array($item['condition_settings'])) { - $item['condition_settings'] = serialize( - array_merge( - $item['condition_settings'], array( - // Store the rule condition logic operators. - 'condition_negate' => isset($item['condition_negate']) ? $item['condition_negate'] : NULL, - 'condition_logic_operator' => isset($item['condition_logic_operator']) ? $item['condition_logic_operator'] : NULL, - ) - ) - ); - } - // Replace item value. - $items[$delta] = $item; - } - } - } -} - -/** - * Implements hook_field_widget_form(). - */ -function inline_conditions_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - // Ensure this .inline_conditions files are loaded when the form is rebuilt - // from the cache. - foreach (inline_conditions_get_info_by_module() as $module => $condition) { - $form_state['build_info']['files']["form_ic_$module"] = drupal_get_path('module', $module) . "/$module.inline_conditions.inc"; - } - - // Get entity_type setting from the current instance. - $entity_type_setting = empty($instance['settings']['entity_type']) ? 'commerce_order' : $instance['settings']['entity_type']; - - // Prepare a list of all possible conditions. - $condition_info = inline_conditions_get_info_by_type($entity_type_setting, $instance['entity_type']); - $condition_list = array('' => t('- All -')); - foreach ($condition_info as $condition_name => $info) { - $condition_list[$condition_name] = $info['label']; - } - - $element = array( - '#type' => 'container', - '#prefix' => '
      ', - '#suffix' => '
      ', - '#element_validate' => array('inline_conditions_field_widget_form_validate'), - ) + $element; - - // Attach module cascade style sheet to inline_conditions_field_widget_form. - $form['#attached']['css'][] = drupal_get_path('module', 'inline_conditions') . '/css/inline_conditions.css'; - // Load rtl.css file if needed. - if ($GLOBALS['language']->direction == 1) { - $form['#attached']['css'][] = drupal_get_path('module', 'inline_conditions') . '/css/inline_conditions-rtl.css'; - } - - // If "Add a new condition" button has been pressed. - if (isset($form_state['triggering_element']['#condition_action'])) { - if ($form_state['triggering_element']['#condition_action'] == 'add') { - // Ensure we are on the right row. - $items[] = array( - 'condition_name' => '', - 'condition_settings' => array(), - ); - } - // If "Remove" button has been pressed. - elseif ($form_state['triggering_element']['#condition_action'] == 'remove') { - $condition_id = $form_state['triggering_element']['#element_delta']; - unset($items[$condition_id]); - // Rebuild array keys. - $items = array_values($items); - } - } - - // If no items found, create an empty item for display purposes. - if (empty($items)) { - $items[] = array('condition_name' => '', 'condition_settings' => array()); - } - - foreach ($items as $delta => $item) { - $id = 'inline-conditions-' . $field['field_name'] . '-' . $instance['id'] . '-' . $delta; - $zebra = ($delta % 2) ? 'odd' : 'even'; - $element[$delta] = array( - '#type' => 'container', - '#prefix' => '
      ', - '#suffix' => '
      ', - ); - - // Condition selector. - $element[$delta]['condition_name'] = array( - '#type' => 'select', - '#title' => t('Apply to:'), - '#options' => $condition_list, - '#default_value' => $item['condition_name'], - '#ajax' => array( - 'callback' => 'inline_conditions_form_ajax_callback', - 'wrapper' => $id, - ), - '#prefix' => '
      ', - '#condition_action' => 'select', - // Identifies the condition operated upon. - '#element_delta' => $delta, - ); - - $element[$delta]['condition_settings'] = array( - '#type' => 'value', - '#value' => array(), - ); - - // A condition has been selected, and a "configure" callback exists. - if (!empty($item['condition_name']) && !empty($condition_info[$item['condition_name']]['callbacks']['configure'])) { - $callback = $condition_info[$item['condition_name']]['callbacks']['configure']; - $element[$delta]['condition_settings'] = $callback($item['condition_settings']); - $element[$delta]['#attributes'] = array('class' => array('container-inline')); - } - - $element[$delta]['condition_negate'] = array( - '#type' => 'checkbox', - '#title' => t('Negate'), - '#default_value' => !empty($item['condition_negate']) ? $item['condition_negate'] : FALSE, - '#prefix' => '
      ', - '#suffix' => '
      ', - ); - - // Remove condition button. - $element[$delta]['remove_condition'] = array( - '#type' => 'button', - '#name' => $field['field_name'] . '-' . $instance['id'] . '-' . $delta . '-remove_condition', - '#value' => t('Remove'), - '#ajax' => array( - 'callback' => 'inline_conditions_form_ajax_callback', - 'wrapper' => 'inline-conditions-' . $field['field_name'], - 'effect' => 'fade', - ), - // closing condition-wrapper div. - '#prefix' => '
      ', - '#suffix' => '
      ', - '#element_delta' => $delta, - '#condition_action' => 'remove', - '#executes_submit_callback' => FALSE, - '#limit_validation_errors' => array(), - ); - - // Add an option list to select logical disjunctions for each inline - // condition. - if ($delta > 0) { - // Remove the default condition selector label and also remove the prefix - // used as HTML wrapper for the current condition. - unset($element[$delta]['condition_name']['#title'], $element[$delta]['condition_name']['#prefix']); - // Add an option list to select the logic operator for the current - // condition. - $element[$delta]['condition_logic_operator'] = array( - '#type' => 'select', - '#options' => array( - INLINE_CONDITIONS_OR => t('Or:'), - INLINE_CONDITIONS_AND => t('And:'), - ), - '#default_value' => isset($item['condition_logic_operator']) ? $item['condition_logic_operator'] : INLINE_CONDITIONS_AND, - '#weight' => -50, - '#prefix' => '
      ', - ); - } - } - - // Add the "Add another" button. - $element['add_more'] = array( - '#type' => 'button', - '#name' => $field['field_name'] . '-' . $instance['id'] . '-add_condition', - '#value' => t('Add a new condition'), - '#ajax' => array( - 'callback' => 'inline_conditions_form_ajax_callback', - 'wrapper' => 'inline-conditions-' . $field['field_name'], - 'effect' => 'fade', - ), - '#attributes' => array('class' => array('add-new-condition')), - '#element_delta' => $element['#delta'], - '#condition_action' => 'add', - '#limit_validation_errors' => array(), - ); - - return $element; -} - -/** - * Callback : Validate inline_conditions_field_widget_form. - * - * @param array $element - * A form element array containing basic properties for the widget: - * - #entity_type: The name of the entity the field is attached to. - * - #bundle: The name of the field bundle the field is contained in. - * - #field_name: The name of the field. - * - #language: The language the field is being edited in. - * - #field_parents: The 'parents' space for the field in the form. Most - * widgets can simply overlook this property. This identifies the - * location where the field values are placed within - * $form_state['values'], and is used to access processing information - * for the field through the field_form_get_state() and - * field_form_set_state() functions. - * - #columns: A list of field storage columns of the field. - * - #title: The sanitized element label for the field instance, ready for - * output. - * - #description: The sanitized element description for the field instance, - * ready for output. - * - #required: A Boolean indicating whether the element value is required; - * for required multiple value fields, only the first widget's values are - * required. - * - #delta: The order of this item in the array of subelements; see $delta - * above. - * @param array $form_state - * An associative array containing the current state of the form. - * @param array $form - * The form structure where widgets are being attached to. This might be a - * full form structure, or a sub-element of a larger form. - * - * @return bool - * Return a boolean to validate or not form elements. - */ -function inline_conditions_field_widget_form_validate($element, &$form_state, $form) { - // Support removing the conditions from the entity. - if (isset($form_state['triggering_element']['#condition_action']) && $form_state['triggering_element']['#condition_action'] == 'remove') { - $element['#parents'][] = $form_state['triggering_element']['#element_delta']; - drupal_array_set_nested_value($form_state['values'], $element['#parents'], array(), TRUE); - drupal_array_set_nested_value($form_state['input'], $element['#parents'], array(), TRUE); - } -} - -/** - * Ajax callback for the Inline Conditions form elements. - * - * @param array $form - * The form array. - * @param array &$form_state - * The reference of form_state array. - * - * @return array - * Return form element to display. - */ -function inline_conditions_form_ajax_callback($form, &$form_state) { - $element_parents = array_slice($form_state['triggering_element']['#array_parents'], 0, -2); - $element = drupal_array_get_nested_value($form, $element_parents); - - // Triggered when user selects a condition. - if ($form_state['triggering_element']['#condition_action'] == 'select') { - $delta = $form_state['triggering_element']['#element_delta']; - - return $element[$delta]; - } - else { - return $element; - } -} - /** * Defines a callback to add condition(s) to the given rule. * @@ -633,7 +338,7 @@ function inline_conditions_form_ajax_callback($form, &$form_state) { * @param array $field_values * An array of values from an inline_conditions field. * - * @see hook_inline_conditions_prebuild_alter() + * @see hook_inline_conditions_build_alter() */ function inline_conditions_build(RulesReactionRule $rule, $field_values) { if (!empty($field_values)) { @@ -699,7 +404,6 @@ function inline_conditions_build(RulesReactionRule $rule, $field_values) { } // No logical operator found, so we put the condition in the temp array. - // It will be added to next condition using a logical operator. $temp = $condition; } @@ -711,7 +415,8 @@ function inline_conditions_build(RulesReactionRule $rule, $field_values) { $rule->condition($or); } - // If a condition is still present in the temp var, attach it to the rule. + // If a condition is still present in the temp var, attach it to the rule + // using an AND operator. if (isset($temp)) { $rule->condition($temp); } @@ -729,7 +434,7 @@ function inline_conditions_build(RulesReactionRule $rule, $field_values) { * An array of conditions. */ function inline_conditions_get_info($condition_name = NULL) { - $conditions = & drupal_static(__FUNCTION__); + $conditions = &drupal_static(__FUNCTION__); if (!isset($conditions)) { $conditions = array(); @@ -738,7 +443,19 @@ function inline_conditions_get_info($condition_name = NULL) { $condition_info += array( // Remember the providing module. 'module' => $module, + 'callbacks' => array(), + ); + // Provide default callbacks based on condition name when they are using + // the MODULE_CONDITION condition name's naming pattern. + $callback_prefix = $condition; + if (strpos($callback_prefix, $module . '_') !== 0) { + $callback_prefix = $module . '_' . $condition; + } + $condition_info['callbacks'] += array( + 'configure' => $callback_prefix . '_configure', + 'build' => $callback_prefix . '_build', ); + $conditions[$condition] = $condition_info; } } @@ -792,13 +509,11 @@ function inline_conditions_get_info_by_type($entity_type, $parent_entity_type) { /** * Get inline conditions per module name. * - * Return an array of conditions keyed by module name. - * * @param string $module * The module name. * * @return array - * An array of conditions. + * An array of inline conditions keyed by module name. */ function inline_conditions_get_info_by_module($module = NULL) { $filtered_conditions = array(); @@ -807,5 +522,29 @@ function inline_conditions_get_info_by_module($module = NULL) { $filtered_conditions[$condition['module']][$name] = $condition; } + // Apply module filter if it's set. + if (!empty($module)) { + $filtered_conditions = !empty($filtered_conditions[$module]) ? array($module => $filtered_conditions[$module]) : array(); + } + return $filtered_conditions; } + +/** + * Returns an options list of inline conditions per type. + * + * @param array $condition_info + * An array of inline conditions. + * + * @return array + * An array of inline conditions keyed by condition machine name. + */ +function inline_conditions_options_list($condition_info) { + // Complete an array ready to be used as value for a select form element. + $condition_list = array('' => t('- All -')); + foreach ($condition_info as $condition_name => $info) { + $condition_list[$condition_name] = $info['label']; + } + + return $condition_list; +} diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/commerce_product.inline_entity_form.inc b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/commerce_product.inline_entity_form.inc index 07b57247..5dcb6f21 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/commerce_product.inline_entity_form.inc +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/commerce_product.inline_entity_form.inc @@ -334,6 +334,16 @@ class CommerceProductInlineEntityFormController extends EntityInlineEntityFormCo return $attributes; } + /** + * Overrides EntityInlineEntityFormController::createClone(). + */ + public function createClone($entity) { + $cloned_entity = parent::createClone($entity); + $cloned_entity->sku = NULL; + + return $cloned_entity; + } + /** * Overrides EntityInlineEntityFormController::save(). * diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/entity.inline_entity_form.inc b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/entity.inline_entity_form.inc index 353faac7..89a42461 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/entity.inline_entity_form.inc +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/entity.inline_entity_form.inc @@ -144,6 +144,7 @@ class EntityInlineEntityFormController { $defaults['allow_new'] = TRUE; $defaults['allow_existing'] = FALSE; $defaults['match_operator'] = 'CONTAINS'; + $defaults['allow_clone'] = FALSE; $defaults['delete_references'] = FALSE; $labels = $this->defaultLabels(); $defaults['override_labels'] = FALSE; @@ -200,12 +201,16 @@ class EntityInlineEntityFormController { $form['match_operator']['#access'] = FALSE; } + $form['allow_clone'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to clone @label.', array('@label' => $labels['plural'])), + '#default_value' => isset($this->settings['allow_clone'])? $this->settings['allow_clone'] : FALSE, + ); $form['delete_references'] = array( '#type' => 'checkbox', '#title' => t('Delete referenced @label when the parent entity is deleted.', array('@label' => $labels['plural'])), '#default_value' => $this->settings['delete_references'], ); - $form['override_labels'] = array( '#type' => 'checkbox', '#title' => t('Override labels'), @@ -377,6 +382,44 @@ class EntityInlineEntityFormController { return IEF_ENTITY_UNLINK_DELETE; } + /** + * Creates a clone of the given entity. + * + * Copies the entity_ui_clone_entity() approach, extending it to unset + * additional unique properties. + * + * @param $entity + * The entity to clone. + * + * @return + * The unsaved cloned entity. + */ + public function createClone($entity) { + $cloned_entity = clone $entity; + $cloned_entity->is_new = TRUE; + + $entity_info = entity_get_info($this->entityType); + // Make sure the status of a cloned exportable is custom. + if (!empty($entity_info['exportable'])) { + $status_key = isset($entity_info['entity keys']['status']) ? $entity_info['entity keys']['status'] : 'status'; + $cloned_entity->$status_key = ENTITY_CUSTOM; + } + // Unset properties specified as entity keys. + foreach (array('id', 'name', 'revision') as $key) { + if (!empty($entity_info['entity keys'][$key])) { + $cloned_entity->{$entity_info['entity keys'][$key]} = NULL; + } + } + // Unset other common properties. + foreach (array('created', 'changed', 'uid', 'revision_timestamp', 'revision_uid') as $property) { + if (isset($cloned_entity->{$property})) { + $cloned_entity->{$property} = NULL; + } + } + + return $cloned_entity; + } + /** * Permanently saves the given entity. * diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/node.inline_entity_form.inc b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/node.inline_entity_form.inc index ceccb3ac..4d481cbd 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/node.inline_entity_form.inc +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/node.inline_entity_form.inc @@ -85,4 +85,18 @@ class NodeInlineEntityFormController extends EntityInlineEntityFormController { $function($entity_form['#entity'], $entity_form, $child_form_state); } } + + /** + * Overrides EntityInlineEntityFormController::createClone(). + */ + public function createClone($entity) { + global $user; + + $cloned_entity = parent::createClone($entity); + $cloned_entity->tnid = NULL; + $cloned_entity->name = isset($user->name) ? $user->name : NULL; + $cloned_entity->path = NULL; + + return $cloned_entity; + } } diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/taxonomy_term.inline_entity_form.inc b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/taxonomy_term.inline_entity_form.inc index b6ef7ff3..bd2ec593 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/taxonomy_term.inline_entity_form.inc +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/includes/taxonomy_term.inline_entity_form.inc @@ -82,7 +82,7 @@ class TaxonomyTermInlineEntityFormController extends EntityInlineEntityFormContr '#title' => t('Description'), '#default_value' => $term->description, '#format' => $term->format, - '#weight' => $extra_fields['description']['weight'], + '#weight' => !empty($extra_fields['description']) ? $extra_fields['description']['weight'] : -4, ); $langcode = entity_language('taxonomy_term', $term); diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.info b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.info index 9d14ed1c..7452543c 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.info +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.info @@ -3,6 +3,8 @@ description = "Provides a widget for inline management (creation, modification, package = Fields dependencies[] = entity dependencies[] = system (>7.14) +test_dependencies[] = ctools +test_dependencies[] = entityreference core = 7.x files[] = includes/entity.inline_entity_form.inc @@ -10,10 +12,12 @@ files[] = includes/node.inline_entity_form.inc files[] = includes/taxonomy_term.inline_entity_form.inc files[] = includes/commerce_product.inline_entity_form.inc files[] = includes/commerce_line_item.inline_entity_form.inc +files[] = tests/inline_entity_form_test_base.test +files[] = tests/multiple_values_widget.test -; Information added by Drupal.org packaging script on 2015-06-17 -version = "7.x-1.6" +; Information added by Drupal.org packaging script on 2016-04-16 +version = "7.x-1.8" core = "7.x" project = "inline_entity_form" -datestamp = "1434553381" +datestamp = "1460849408" diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.module b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.module index 2fa09b1d..c1a7d823 100644 --- a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.module +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/inline_entity_form.module @@ -397,7 +397,6 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins $widget = $instance['widget']; $settings = inline_entity_form_settings($field, $instance); $entity_info = entity_get_info($settings['entity_type']); - $cardinality = $field['cardinality']; $controller = inline_entity_form_get_controller($instance); // The current entity type is not supported, execution can't continue. if (!$controller) { @@ -478,6 +477,11 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins } } + // Prepare cardinality information. + $cardinality = $field['cardinality']; + $entity_count = count($form_state['inline_entity_form'][$ief_id]['entities']); + $cardinality_reached = ($cardinality > 0 && $entity_count == $cardinality); + // Build the appropriate widget. // The "Single value" widget assumes it is operating on a required single // value reference field with 1 allowed bundle. @@ -532,6 +536,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins '#tree' => TRUE, '#theme' => 'inline_entity_form_entity_table', '#entity_type' => $settings['entity_type'], + '#cardinality' => (int) $cardinality, ); // Get the fields that should be displayed in the table. @@ -620,6 +625,25 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins '#ief_row_form' => 'edit', ); } + // Add the clone button, if allowed. + // The clone form follows the same semantics as the create form, so + // it's opened below the table. + if ($controller->getSetting('allow_clone') && !$cardinality_reached + && entity_access('create', $controller->entityType(), $entity)) { + $row['actions']['ief_entity_clone'] = array( + '#type' => 'submit', + '#value' => t('Clone'), + '#name' => 'ief-' . $ief_id . '-entity-clone-' . $key, + '#limit_validation_errors' => array(array_merge($parents, array('actions'))), + '#ajax' => array( + 'callback' => 'inline_entity_form_get_element', + 'wrapper' => $wrapper, + ), + '#submit' => array('inline_entity_form_open_form'), + '#ief_row_delta' => $key, + '#ief_form' => 'clone', + ); + } // If 'allow_existing' is on, the default removal operation is unlink // and the access check for deleting happens inside the controller @@ -643,7 +667,6 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins } } - $entity_count = count($form_state['inline_entity_form'][$ief_id]['entities']); if ($cardinality > 1) { // Add a visual cue of cardinality count. $message = t('You have added @entities_count out of @cardinality_count allowed @label.', array( @@ -656,14 +679,18 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins ); } // Do not return the rest of the form if cardinality count has been reached. - if ($cardinality > 0 && $entity_count == $cardinality) { + if ($cardinality_reached) { return $element; } - // Try to open the add form (if it's the only allowed action, the - // field is required and empty, and there's only one allowed bundle). - if (empty($form_state['inline_entity_form'][$ief_id]['entities'])) { - if (count($settings['create_bundles']) == 1 && $instance['required'] && !$controller->getSetting('allow_existing')) { + $hide_cancel = FALSE; + // If the field is required and empty try to open one of the forms. + if (empty($form_state['inline_entity_form'][$ief_id]['entities']) && $instance['required']) { + if ($controller->getSetting('allow_existing') && !$controller->getSetting('allow_new')) { + $form_state['inline_entity_form'][$ief_id]['form'] = 'ief_add_existing'; + $hide_cancel = TRUE; + } + elseif (count($settings['create_bundles']) == 1 && $controller->getSetting('allow_new') && !$controller->getSetting('allow_existing')) { $bundle = reset($settings['create_bundles']); // The parent entity type and bundle must not be the same as the inline @@ -673,6 +700,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins $form_state['inline_entity_form'][$ief_id]['form settings'] = array( 'bundle' => $bundle, ); + $hide_cancel = TRUE; } } } @@ -695,6 +723,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins $bundles[$bundle_name] = $bundle_info['label']; } } + asort($bundles); $element['actions']['bundle'] = array( '#type' => 'select', @@ -757,18 +786,25 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins if ($form_state['inline_entity_form'][$ief_id]['form'] == 'add') { $element['form']['#op'] = 'add'; $element['form'] += inline_entity_form_entity_form($controller, $element['form'], $form_state); - - // Hide the cancel button if the reference field is required but - // contains no values. That way the user is forced to create an entity. - if (!$controller->getSetting('allow_existing') && $instance['required'] - && empty($form_state['inline_entity_form'][$ief_id]['entities']) - && count($settings['create_bundles']) == 1) { - $element['form']['actions']['ief_add_cancel']['#access'] = FALSE; - } } elseif ($form_state['inline_entity_form'][$ief_id]['form'] == 'ief_add_existing') { $element['form'] += inline_entity_form_reference_form($controller, $element['form'], $form_state); } + elseif ($form_state['inline_entity_form'][$ief_id]['form'] == 'clone') { + $element['form']['#op'] = 'clone'; + $element['form'] += inline_entity_form_entity_form($controller, $element['form'], $form_state); + } + + // Pre-opened forms can't be closed in order to force the user to + // add / reference an entity. + if ($hide_cancel) { + if (isset($element['form']['actions']['ief_add_cancel'])) { + $element['form']['actions']['ief_add_cancel']['#access'] = FALSE; + } + elseif (isset($element['form']['actions']['ief_reference_cancel'])) { + $element['form']['actions']['ief_reference_cancel']['#access'] = FALSE; + } + } // No entities have been added. Remove the outer fieldset to reduce // visual noise caused by having two titles. @@ -801,16 +837,19 @@ function inline_entity_form_form_alter(&$form, &$form_state, $form_id) { } if ($submit_element) { - $submit = array_merge(array('inline_entity_form_trigger_submit'), $form['#submit']); + // Merge the IEF submit handler with the button level submit handlers if + // available. Otherwise use the form level submit handlers. if (!empty($submit_element['#submit'])){ - $submit = array_merge($submit, $submit_element['#submit']); - // $form['#submit'] and $submit_element['#submit'] might have matching - // callbacks, resulting in duplicates and double processing. - $submit_element['#submit'] = array_unique($submit); + $submit = array_merge(array('inline_entity_form_trigger_submit'), (array) $submit_element['#submit']); } - else{ - $submit_element['#submit'] = $submit; + else { + $submit = array_merge(array('inline_entity_form_trigger_submit'), (array) $form['#submit']); } + + // Reduce any duplicates on the off chance the IEF submit handler was + // already a part of the array used. + $submit_element['#submit'] = array_unique($submit); + $submit_element['#ief_submit_all'] = TRUE; } } @@ -856,6 +895,14 @@ function inline_entity_form_entity_form($controller, $entity_form, &$form_state) $entity_form['#title'] = t('Add new @type_singular', array('@type_singular' => $labels['singular'])); $save_label = t('Create @type_singular', array('@type_singular' => $labels['singular'])); } + elseif ($entity_form['#op'] == 'clone') { + // Clone the entity. + $form_settings = $form_state['inline_entity_form'][$entity_form['#ief_id']]['form settings']; + $entity = $form_state['inline_entity_form'][$entity_form['#ief_id']]['entities'][$form_settings['source']]['entity']; + $entity_form['#entity'] = $controller->createClone($entity); + $entity_form['#title'] = t('Clone @type_singular', array('@type_singular' => $labels['singular'])); + $save_label = t('Clone @type_singular', array('@type_singular' => $labels['singular'])); + } // Retrieve the form provided by the controller. $entity_form = $controller->entityForm($entity_form, $form_state); @@ -888,13 +935,13 @@ function inline_entity_form_entity_form($controller, $entity_form, &$form_state) ); // Add the appropriate submit handlers and their related data. - if ($entity_form['#op'] == 'add') { - $entity_form['actions']['ief_add_save']['#submit'] = array( + if (in_array($entity_form['#op'], array('add', 'clone'))) { + $entity_form['actions']['ief_' . $entity_form['#op'] . '_save']['#submit'] = array( 'inline_entity_form_trigger_submit', 'inline_entity_form_close_child_forms', 'inline_entity_form_close_form', ); - $entity_form['actions']['ief_add_cancel']['#submit'] = array( + $entity_form['actions']['ief_' . $entity_form['#op'] . '_cancel']['#submit'] = array( 'inline_entity_form_close_child_forms', 'inline_entity_form_close_form', 'inline_entity_form_cleanup_form_state', @@ -976,7 +1023,7 @@ function inline_entity_form_entity_form_submit($entity_form, &$form_state) { $controller->entityFormSubmit($entity_form, $form_state); inline_entity_form_cleanup_entity_form_state($entity_form, $form_state); - if ($entity_form['#op'] == 'add') { + if (in_array($entity_form['#op'], array('add', 'clone'))) { // Determine the correct weight of the new element. $weight = 0; if (!empty($form_state['inline_entity_form'][$ief_id]['entities'])) { @@ -1106,7 +1153,7 @@ function inline_entity_form_reference_form_validate(&$reference_form, &$form_sta foreach ($form_state['inline_entity_form'][$ief_id]['entities'] as $key => $value) { if ($value['entity'] == $attach_entity) { form_set_error($parents_path . '][existing_entity', t('The selected @label has already been added.', array('@label' => $labels['singular']))); - unset($attach_entity); + break; } } } @@ -1301,10 +1348,14 @@ function inline_entity_form_open_form($form, &$form_state) { $form_values = drupal_array_get_nested_value($form_state['values'], $parents); $form_state['inline_entity_form'][$ief_id]['form'] = $form_state['triggering_element']['#ief_form']; + $form_state['inline_entity_form'][$ief_id]['form settings'] = array(); + // Special code for the add form. if (!empty($form_values['actions']['bundle'])) { - $form_state['inline_entity_form'][$ief_id]['form settings'] = array( - 'bundle' => $form_values['actions']['bundle'], - ); + $form_state['inline_entity_form'][$ief_id]['form settings']['bundle'] = $form_values['actions']['bundle']; + } + // Special code for the clone form. + if (isset($form_state['triggering_element']['#ief_row_delta'])) { + $form_state['inline_entity_form'][$ief_id]['form settings']['source'] = $form_state['triggering_element']['#ief_row_delta']; } } @@ -1496,11 +1547,11 @@ function inline_entity_form_field_attach_submit($parent_entity_type, $parent_ent uasort($values['entities'], 'drupal_sort_weight'); // Go through the IEF data and assemble a list of ids. $entity_ids = array(); - $need_reset = false; + $need_reset = FALSE; foreach ($values['entities'] as $item) { if ($item['needs_save']) { $controller->save($item['entity'], $context); - $need_reset = true; + $need_reset = TRUE; } list($entity_id) = entity_extract_ids($entity_type, $item['entity']); @@ -1609,15 +1660,9 @@ function theme_inline_entity_form_entity_table($variables) { $form = $variables['form']; $entity_type = $form['#entity_type']; $fields = $form['#table_fields']; + $has_tabledrag = inline_entity_form_has_tabledrag($form); // Sort the fields by weight. uasort($fields, 'drupal_sort_weight'); - // If one of the rows is in form context, disable tabledrag. - $has_tabledrag = TRUE; - foreach (element_children($form) as $key) { - if (!empty($form[$key]['form'])) { - $has_tabledrag = FALSE; - } - } $header = array(); if ($has_tabledrag) { @@ -1696,7 +1741,7 @@ function theme_inline_entity_form_entity_table($variables) { } $data = drupal_render($renderable_data); } - elseif ($field['type'] == 'callback' && isset($field['render_callback']) && function_exists($field['render_callback'])) { + elseif ($field['type'] == 'callback' && isset($field['render_callback']) && is_callable($field['render_callback'])) { $data = call_user_func($field['render_callback'], $entity_type, $entity); } @@ -1732,6 +1777,31 @@ function theme_inline_entity_form_entity_table($variables) { } } +/** + * Returns whether tabledrag should be enabled for the given table. + * + * @param $element + * The form element representing the IEF table. + * + * @return + * TRUE if tabledrag should be enabled, FALSE otherwise. + */ +function inline_entity_form_has_tabledrag($element) { + $children = element_children($element); + // If there is only one row, disable tabletrag. + if (count($children) == 1) { + return FALSE; + } + // If one of the rows is in form context, disable tabledrag. + foreach ($children as $key) { + if (!empty($element[$key]['form'])) { + return FALSE; + } + } + + return TRUE; +} + /** * Implements hook_field_widget_error(). */ diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/inline_entity_form_test_base.test b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/inline_entity_form_test_base.test new file mode 100644 index 00000000..2ea97911 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/inline_entity_form_test_base.test @@ -0,0 +1,70 @@ +xpath($xpath)) { + foreach ($elements[0]->attributes() as $name => $value) { + if ($name == 'name') { + $retval = $value; + break; + } + } + } + return $retval; + } + + /** + * Passes if no node is found for the title. + * + * @param $title + * Node title to check. + * @param $message + * Message to display. + */ + protected function assertNoNodeByTitle($title, $message = '') { + if (!$message) { + $message = "No node with title: $title"; + } + $node = $this->getNodeByTitle($title); + + $this->assertTrue(empty($node), $message); + } + + /** + * Passes if node is found for the title. + * + * @param $title + * Node title to check. + * @param $message + * Message to display. + */ + protected function assertNodeByTitle($title, $bundle = NULL, $message = '') { + if (!$message) { + $message = "Node with title found: $title"; + } + $node = $this->getNodeByTitle($title); + if ($this->assertTrue(!empty($node), $message)) { + if ($bundle) { + $this->assertEqual($node->bundle(), $bundle, "Node is correct bundle: $bundle"); + } + } + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info new file mode 100644 index 00000000..b8ec37c5 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info @@ -0,0 +1,18 @@ +name = Inline entity form test +description = Contains all content types required for IEF testing +core = 7.x +package = Testing +hidden = TRUE + +dependencies[] = entityreference +dependencies[] = image +dependencies[] = inline_entity_form +dependencies[] = number +dependencies[] = text + +; Information added by Drupal.org packaging script on 2016-04-16 +version = "7.x-1.8" +core = "7.x" +project = "inline_entity_form" +datestamp = "1460849408" + diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.module b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.module new file mode 100644 index 00000000..8f463486 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.module @@ -0,0 +1,364 @@ + 'ief_reference_type', + 'name' => t('IEF reference type'), + 'base' => 'node_content', + 'description' => t('Entity reference content type.'), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'ief_simple_single', + 'name' => t('IEF simple single'), + 'base' => 'node_content', + 'description' => t('Content type for IEF simple widget testing.'), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'ief_test_multiple', + 'name' => t('IEF test multiple'), + 'base' => 'node_content', + 'description' => t('Content type for IEF multiple widget testing.'), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'ief_test_custom', + 'name' => t('IEF test custom'), + 'base' => 'node_content', + 'description' => t('Content type for IEF custom form testing.'), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'ief_test_nested1', + 'name' => t('IEF test nested1'), + 'base' => 'node_content', + 'description' => t('Content type for IEF custom form testing.'), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'ief_test_nested2', + 'name' => t('IEF test nested2'), + 'base' => 'node_content', + 'description' => t('Content type for IEF custom form testing.'), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'ief_test_nested3', + 'name' => t('IEF test nested3'), + 'base' => 'node_content', + 'description' => st("Use basic pages for your static content, such as an 'About us' page."), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + ); + + foreach ($types as $type) { + $type = node_type_set_defaults($type); + node_type_save($type); + } + + // Create fields. + field_info_cache_clear(); + + $field = array( + 'field_name' => 'field_all_bundles', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_first_name', + 'type' => 'text', + 'cardinality' => 1, + 'settings' => array( + 'max_length' => 255, + ), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_unrelatedimage', + 'type' => 'image', + 'cardinality' => 1, + 'settings' => array( + 'default_image' => 0, + 'uri_scheme' => 'public', + ), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_last_name', + 'type' => 'text', + 'cardinality' => 1, + 'settings' => array( + 'max_length' => 255, + ), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_multiple_nodes', + 'type' => 'entityreference', + 'cardinality' => -1, + 'required' => TRUE, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array( + 'ief_reference_type' => 'ief_reference_type', + ), + ), + ), + ); + field_create_field($field); + + $field_bases['field_positive_int'] = array( + 'active' => 1, + 'cardinality' => 1, + 'deleted' => 0, + 'entity_types' => array(), + 'field_name' => 'field_positive_int', + 'indexes' => array(), + 'locked' => 0, + 'module' => 'number', + 'settings' => array(), + 'translatable' => 0, + 'type' => 'number_integer', + ); + + $field = array( + 'field_name' => 'field_positive_int', + 'type' => 'number_integer', + 'cardinality' => 1, + 'settings' => array(), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_single_node', + 'type' => 'entityreference', + 'cardinality' => 1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array( + 'ief_test_custom' => 'ief_test_custom', + ), + ), + ), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_test_ref_nested1', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array( + 'ief_test_nested2' => 'ief_test_nested2', + ), + ), + ), + ); + field_create_field($field); + + $field = array( + 'field_name' => 'field_test_ref_nested2', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array( + 'ief_test_nested3' => 'ief_test_nested3', + ), + ), + ), + ); + field_create_field($field); + + + // Create field instances. + $instance = array( + 'bundle' => 'ief_reference_type', + 'entity_type' => 'node', + 'field_name' => 'field_first_name', + 'label' => 'First name', + 'required' => TRUE, + 'widget' => array('type' => 'text_textfield'), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_reference_type', + 'entity_type' => 'node', + 'field_name' => 'field_last_name', + 'label' => 'Last name', + 'required' => TRUE, + 'widget' => array('type' => 'text_textfield'), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_simple_single', + 'entity_type' => 'node', + 'field_name' => 'field_single_node', + 'label' => 'Single node', + 'widget' => array('type' => 'inline_entity_form_single'), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_test_multiple', + 'entity_type' => 'node', + 'field_name' => 'field_all_bundles', + 'label' => 'All bundles', + 'widget' => array( + 'type' => 'inline_entity_form', + 'settings' => array( + 'fields' => array(), + 'type_settings' => array( + 'allow_clone' => 0, + 'allow_existing' => 1, + 'allow_new' => 1, + 'delete_references' => 0, + 'label_plural' => 'nodes', + 'label_singular' => 'node', + 'match_operator' => 'CONTAINS', + 'override_labels' => 0, + ), + ), + ), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_test_multiple', + 'entity_type' => 'node', + 'field_name' => 'field_unrelatedimage', + 'label' => 'Image', + 'widget' => array('type' => 'image_image'), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_test_multiple', + 'entity_type' => 'node', + 'field_name' => 'field_multiple_nodes', + 'label' => 'Multiple nodes', + 'required' => TRUE, + 'widget' => array( + 'type' => 'inline_entity_form', + 'settings' => array( + 'fields' => array(), + 'type_settings' => array( + 'allow_clone' => 0, + 'allow_existing' => 0, + 'allow_new' => 1, + 'delete_references' => 0, + 'label_plural' => 'nodes', + 'label_singular' => 'node', + 'match_operator' => 'CONTAINS', + 'override_labels' => 0, + ), + ), + ), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_test_custom', + 'entity_type' => 'node', + 'field_name' => 'field_positive_int', + 'label' => 'Positive int', + 'widget' => array('type' => 'number'), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_test_nested1', + 'entity_type' => 'node', + 'field_name' => 'field_test_ref_nested1', + 'label' => 'Multiple nodes', + 'required' => TRUE, + 'widget' => array( + 'type' => 'inline_entity_form', + 'settings' => array( + 'fields' => array(), + 'type_settings' => array( + 'allow_existing' => 0, + 'allow_new' => 1, + 'delete_references' => 0, + 'label_plural' => 'nodes 2', + 'label_singular' => 'node 2', + 'match_operator' => 'CONTAINS', + 'override_labels' => 1, + ), + ), + ), + ); + field_create_instance($instance); + + $instance = array( + 'bundle' => 'ief_test_nested2', + 'entity_type' => 'node', + 'field_name' => 'field_test_ref_nested2', + 'label' => 'Multiple nodes', + 'required' => TRUE, + 'widget' => array( + 'type' => 'inline_entity_form', + 'settings' => array( + 'fields' => array(), + 'type_settings' => array( + 'allow_existing' => 0, + 'allow_new' => 1, + 'delete_references' => 0, + 'label_plural' => 'nodes 3', + 'label_singular' => 'node 3', + 'match_operator' => 'CONTAINS', + 'override_labels' => 1, + ), + ), + ), + ); + field_create_instance($instance); +} diff --git a/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/multiple_values_widget.test b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/multiple_values_widget.test new file mode 100644 index 00000000..99d170ee --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/inline_entity_form/tests/multiple_values_widget.test @@ -0,0 +1,695 @@ + 'Multiple values widget', + 'description' => 'Tests the "Inline Entity Form" multiple values widget.', + 'group' => 'Inline entity form', + ); + } + + protected function setUp() { + $modules = $modules = array( + 'inline_entity_form_test', + 'field', + 'field_ui', + ); + parent::setUp($modules); + + $this->administrator_user = $this->drupalCreateUser(array( + 'create ief_reference_type content', + 'edit any ief_reference_type content', + 'delete any ief_reference_type content', + 'create ief_test_multiple content', + 'edit any ief_test_multiple content', + 'delete any ief_test_multiple content', + 'edit any ief_test_nested1 content', + 'edit any ief_test_nested2 content', + 'edit any ief_test_nested3 content', + 'view own unpublished content', + 'administer content types', + 'administer nodes', + )); + $this->drupalLogin($this->administrator_user); + + $this->formContentAddUrl = 'node/add/ief-test-multiple'; + } + + /** + * Tests if form behaves correctly when field is empty. + */ + public function testEmptyFieldIEF() { + // Don't allow addition of existing nodes. + $this->setAllowExisting(FALSE); + $this->drupalGet($this->formContentAddUrl); + + $this->assertFieldByName('field_multiple_nodes[und][form][title]', NULL, 'Title field on inline form exists.'); + $this->assertFieldByName('field_multiple_nodes[und][form][field_first_name][und][0][value]', NULL, 'First name field on inline form exists.'); + $this->assertFieldByName('field_multiple_nodes[und][form][field_last_name][und][0][value]', NULL, 'Last name field on inline form exists.'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Create node"]', NULL, 'Found "Create node" submit button'); + + // Allow addition of existing nodes. + $this->setAllowExisting(TRUE); + $this->drupalGet($this->formContentAddUrl); + + $this->assertNoFieldByName('field_multiple_nodes[und][form][title]]', NULL, 'Title field does not appear.'); + $this->assertNoFieldByName('field_multiple_nodes[und][form][field_first_name][und][0][value]', NULL, 'First name field does not appear.'); + $this->assertNoFieldByName('field_multiple_nodes[und][form][field_last_name][und][0][value]', NULL, 'Last name field does not appear.'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Add new node"]', NULL, 'Found "Add new node" submit button'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Add existing node"]', NULL, 'Found "Add existing node" submit button'); + + // Now submit 'Add new node' button. + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add new node" and @id="edit-field-multiple-nodes-und-actions-ief-add"]')); + + $this->assertFieldByName('field_multiple_nodes[und][form][title]', NULL, 'Title field on inline form exists.'); + $this->assertFieldByName('field_multiple_nodes[und][form][field_first_name][und][0][value]', NULL, 'First name field on inline form exists.'); + $this->assertFieldByName('field_multiple_nodes[und][form][field_last_name][und][0][value]', NULL, 'Second name field on inline form exists.'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Create node"]', NULL, 'Found "Create node" submit button'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Cancel"]', NULL, 'Found "Cancel" submit button'); + + // Now submit 'Add Existing node' button. + $this->drupalGet($this->formContentAddUrl); + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add existing node" and @id="edit-field-multiple-nodes-und-actions-ief-add-existing"]')); + + $this->assertFieldByName('field_multiple_nodes[und][form][entity_id]', NULL, 'Existing entity reference autocomplete field found.'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Add node"]', NULL, 'Found "Add node" submit button'); + $this->assertFieldByXpath('//input[@type="submit" and @value="Cancel"]', NULL, 'Found "Cancel" submit button'); + } + + /** + * Tests creation of entities. + */ + public function testEntityCreation() { + // Allow addition of existing nodes. + $this->setAllowExisting(TRUE); + $this->drupalGet($this->formContentAddUrl); + + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add new node" and @id="edit-field-multiple-nodes-und-actions-ief-add"]')); + $this->assertResponse(200, 'Opening new inline form was successful.'); + + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Create node" and @id="edit-field-multiple-nodes-und-form-actions-ief-add-save"]')); + $this->assertResponse(200, 'Submitting empty form was successful.'); + $this->assertText('First name field is required.', 'Validation failed for empty "First name" field.'); + $this->assertText('Last name field is required.', 'Validation failed for empty "Last name" field.'); + $this->assertText('Title field is required.', 'Validation failed for empty "Title" field.'); + + // Create ief_reference_type node in IEF. + $this->drupalGet($this->formContentAddUrl); + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add new node" and @id="edit-field-multiple-nodes-und-actions-ief-add"]')); + $this->assertResponse(200, 'Opening new inline form was successful.'); + + $edit = array( + 'field_multiple_nodes[und][form][title]' => 'Some reference', + 'field_multiple_nodes[und][form][field_first_name][und][0][value]' => 'John', + 'field_multiple_nodes[und][form][field_last_name][und][0][value]' => 'Doe', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @value="Create node" and @id="edit-field-multiple-nodes-und-form-actions-ief-add-save"]')); + $this->assertResponse(200, 'Creating node via inline form was successful.'); + + // Tests if correct fields appear in the table. + $this->assertTrue((bool) $this->xpath('//td[@class="inline-entity-form-node-title" and contains(.,"Some reference")]'), 'Node title field appears in the table'); + $this->assertTrue((bool) $this->xpath('//td[@class="inline-entity-form-node-status" and contains(.,"Published")]'), 'Node status field appears in the table'); + + // Tests if edit and remove buttons appear. + $this->assertTrue((bool) $this->xpath('//input[@type="submit" and @value="Edit"]'), 'Edit button appears in the table.'); + $this->assertTrue((bool) $this->xpath('//input[@type="submit" and @value="Remove"]'), 'Remove button appears in the table.'); + + // Test edit functionality. + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Edit"]')); + $edit = array( + 'field_multiple_nodes[und][entities][0][form][title]' => 'Some changed reference', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @value="Update node"]')); + $this->assertTrue((bool) $this->xpath('//td[@class="inline-entity-form-node-title" and contains(.,"Some changed reference")]'), 'Node title field appears in the table'); + $this->assertTrue((bool) $this->xpath('//td[@class="inline-entity-form-node-status" and contains(.,"Published")]'), 'Node status field appears in the table'); + + // Make sure unrelated AJAX submit doesn't save the referenced entity. + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Upload"]')); + $node = $this->drupalGetNodeByTitle('Some changed reference'); + $this->assertFalse($node, 'Referenced node was not saved during unrelated AJAX submit.'); + + // Create ief_test_multiple node. + $edit = array('title' => 'Some title'); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertResponse(200, 'Saving parent entity was successful.'); + + // Checks values of created entities. + $node = $this->drupalGetNodeByTitle('Some changed reference'); + $this->assertTrue($node, 'Created ief_reference_type node ' . $node->title); + $this->assertTrue($node->field_first_name[LANGUAGE_NONE][0]['value'] == 'John', 'First name in reference node set to John'); + $this->assertTrue($node->field_last_name[LANGUAGE_NONE][0]['value'] == 'Doe', 'Last name in reference node set to Doe'); + + $parent_node = $this->drupalGetNodeByTitle('Some title'); + $this->assertTrue($parent_node, 'Created ief_test_multiple node ' . $parent_node->title); + $this->assertTrue($parent_node->field_multiple_nodes[LANGUAGE_NONE][0]['target_id'] == $node->nid, 'Refererence node id set to ' . $node->nid); + } + + /** + * Tests the entity creation with different bundles nested in each other. + * + * ief_test_nested1 -> ief_test_nested2 -> ief_test_nested3 + */ + public function testNestedEntityCreationWithDifferentBundlesAjaxSubmit() { + $required_possibilities = array( + FALSE, + TRUE, + ); + foreach ($required_possibilities as $required) { + $this->setupNestedMultipleForm($required); + + $nested3_title = 'nested3 title steps ' . ($required ? 'required' : 'not required'); + $nested2_title = 'nested2 title steps ' . ($required ? 'required' : 'not required'); + $nested1_title = 'nested1 title steps ' . ($required ? 'required' : 'not required'); + $edit = array( + 'field_test_ref_nested1[und][form][field_test_ref_nested2][und][form][title]' => $nested3_title, + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @value="Create node 3"]')); + $this->assertText($nested3_title, 'Title of second nested node found.'); + $this->assertFalse($this->drupalGetNodeByTitle($nested3_title), 'Second nested entity is not saved yet.'); + + $edit = array( + 'field_test_ref_nested1[und][form][title]' => $nested2_title, + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @value="Create node 2"]')); + $this->assertText($nested2_title, 'Title of first nested node found.'); + $this->assertFalse($this->drupalGetNodeByTitle($nested2_title), 'First nested entity is not saved yet.'); + + $edit = array( + 'title' => $nested1_title, + ); + $this->drupalPost(NULL, $edit, t('Save')); + $nested1_node = $this->drupalGetNodeByTitle($nested1_title); + $this->assertEqual($nested1_title, $nested1_node->title, "First node's title looks correct."); + $this->assertEqual('ief_test_nested1', $nested1_node->type, "First node's type looks correct."); + if ($this->assertNotNull($nested1_node->field_test_ref_nested1[LANGUAGE_NONE][0]['target_id'], 'Second node was created.')) { + $creatednested2 = node_load($nested1_node->field_test_ref_nested1[LANGUAGE_NONE][0]['target_id']); + $this->assertEqual($nested2_title, $creatednested2->title, "Second node's title looks correct."); + $this->assertEqual('ief_test_nested2', $creatednested2->type, "Second node's type looks correct."); + if ($this->assertNotNull($creatednested2->field_test_ref_nested2[LANGUAGE_NONE][0]['target_id'], 'Third node was created')) { + $creatednested3 = node_load($creatednested2->field_test_ref_nested2[LANGUAGE_NONE][0]['target_id']); + $this->assertEqual($nested3_title, $creatednested3->title, "Third node's title looks correct."); + $this->assertEqual('ief_test_nested3', $creatednested3->type, "Third node's type looks correct."); + + $this->checkNestedEntityEditing($nested1_node, TRUE); + } + } + } + } + + /** + * Tests the entity creation with different bundles nested in each other. + * + * ief_test_nested1 -> ief_test_nested2 -> ief_test_nested3 + */ + public function testNestedEntityCreationWithDifferentBundlesNoAjaxSubmit() { + $required_possibilities = array( + FALSE, + TRUE, + ); + + foreach ($required_possibilities as $required) { + $this->setupNestedMultipleForm($required); + + $nested3_title = 'nested3 title single ' . ($required ? 'required' : 'not required'); + $nested2_title = 'nested2 title single ' . ($required ? 'required' : 'not required'); + $nested1_title = 'nested1 title single ' . ($required ? 'required' : 'not required'); + + $edit = array( + 'title' => $nested1_title, + 'field_test_ref_nested1[und][form][title]' => $nested2_title, + 'field_test_ref_nested1[und][form][field_test_ref_nested2][und][form][title]' => $nested3_title, + ); + $this->drupalPost(NULL, $edit, t('Save')); + $nested1_node = $this->drupalGetNodeByTitle($nested1_title); + $this->assertEqual($nested1_title, $nested1_node->title, "First node's title looks correct."); + $this->assertEqual('ief_test_nested1', $nested1_node->type, "First node's type looks correct."); + $creatednested2 = node_load($nested1_node->field_test_ref_nested1[LANGUAGE_NONE][0]['target_id']); + $this->assertEqual($nested2_title, $creatednested2->title, "Second node's title looks correct."); + $this->assertEqual('ief_test_nested2', $creatednested2->type, "Second node's type looks correct."); + $creatednested3 = node_load($creatednested2->field_test_ref_nested2[LANGUAGE_NONE][0]['target_id']); + $this->assertEqual($nested3_title, $creatednested3->title, "Third node's title looks correct."); + $this->assertEqual('ief_test_nested3', $creatednested3->type, "Third node's type looks correct."); + $this->checkNestedEntityEditing($nested1_node, FALSE); + } + } + + /** + * Tests if editing and removing entities work. + */ + public function testEntityEditingAndRemoving() { + // Allow addition of existing nodes. + $this->setAllowExisting(TRUE); + + // Create three ief_reference_type entities. + $referenceNodes = $this->createReferenceContent(3); + $field_multiple_nodes = array(); + foreach($referenceNodes as $nid){ + $field_multiple_nodes[LANGUAGE_NONE][] = array('target_id' => $nid); + } + $this->drupalCreateNode(array( + 'type' => 'ief_test_multiple', + 'title' => 'Some title', + 'field_multiple_nodes' => $field_multiple_nodes, + )); + $parent_node = $this->drupalGetNodeByTitle('Some title'); + + // Edit the second entity. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + $cell = $this->xpath('//table[@id="ief-entity-table-edit-field-multiple-nodes-und-entities"]/tbody/tr[@class="ief-row-entity draggable even"]/td[@class="inline-entity-form-node-title"]'); + $title = (string) $cell[0]; + + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-entities-1-actions-ief-entity-edit"]')); + $this->assertResponse(200, 'Opening inline edit form was successful.'); + + $edit = array( + 'field_multiple_nodes[und][entities][1][form][field_first_name][und][0][value]' => 'John', + 'field_multiple_nodes[und][entities][1][form][field_last_name][und][0][value]' => 'Doe', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-entities-1-form-actions-ief-edit-save"]')); + $this->assertResponse(200, 'Saving inline edit form was successful.'); + + // Save the ief_test_multiple node. + $this->drupalPost(NULL, array(), t('Save')); + $this->assertResponse(200, 'Saving parent entity was successful.'); + + // Checks values of changed entities. + $node = $this->drupalGetNodeByTitle($title, TRUE); + $this->assertTrue($node->field_first_name[LANGUAGE_NONE][0]['value'] == 'John', 'First name in reference node changed to John'); + $this->assertTrue($node->field_last_name[LANGUAGE_NONE][0]['value'] == 'Doe', 'Last name in reference node changed to Doe'); + + // Delete the last entity. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + $cell = $this->xpath('//table[@id="ief-entity-table-edit-field-multiple-nodes-und-entities"]/tbody//tr[3]//td[@class="inline-entity-form-node-title"]'); + $title = (string) $cell[0]; + + $this->drupalPost(NULL, array(), 'Remove'); + $this->assertResponse(200, 'Opening inline remove confirm form was successful.'); + $this->assertText('Are you sure you want to remove', 'Remove warning message is displayed.'); + + $edit = array('field_multiple_nodes[und][entities][2][form][delete]' => '1'); + $this->drupalPost(NULL, $edit, 'Remove'); + + $this->assertResponse(200, 'Removing inline entity was successful.'); + $this->assertNoText($title, 'Deleted inline entity is not present on the page.'); + + // Save the ief_test_multiple node. + $this->drupalPost(NULL, array(), t('Save')); + $this->assertResponse(200, 'Saving parent node was successful.'); + + $deleted_node = $this->drupalGetNodeByTitle($title); + $this->assertTrue(empty($deleted_node), 'The inline entity was deleted from the site.'); + + // Checks that entity does nor appear in IEF. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + $this->assertNoText($title, 'Deleted inline entity is not present on the page after saving parent.'); + + // Delete the last entity reference only, don't delete the node. The last + // entity now is second referenced entity because we already deleted one + // in previous step. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + $cell = $this->xpath('//table[@id="ief-entity-table-edit-field-multiple-nodes-und-entities"]/tbody//tr[2]//td[@class="inline-entity-form-node-title"]'); + $title = (string) $cell[0]; + + $this->drupalPost(NULL, array(), 'Remove'); + $this->assertResponse(200, 'Opening inline remove confirm form was successful.'); + + $this->drupalPost(NULL, array(), 'Remove'); + $this->assertResponse(200, 'Removing inline entity was successful.'); + + // Save the ief_test_multiple node. + $this->drupalPost(NULL, array(), t('Save')); + $this->assertResponse(200, 'Saving parent node was successful.'); + + // Checks that entity does nor appear in IEF. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + $this->assertNoText($title, 'Deleted inline entity is not present on the page after saving parent.'); + + // Checks that entity is not deleted. + $node = $this->drupalGetNodeByTitle($title, TRUE); + $this->assertTrue($node, 'Reference node not deleted'); + } + + /** + * Tests if referencing existing entities work. + */ + public function testReferencingExistingEntities() { + // Allow addition of existing nodes. + $this->setAllowExisting(TRUE); + + // Create three ief_reference_type entities. + $referenceNodes = $this->createReferenceContent(3); + + // Create a node for every bundle available. + $bundle_nodes = $this->createNodeForEveryBundle(); + + // Create ief_test_multiple node with first ief_reference_type node and first + // node from bundle nodes. + $this->drupalCreateNode(array( + 'type' => 'ief_test_multiple', + 'title' => 'Some title', + 'multi' => array(1), + 'all_bundles' => key($bundle_nodes), + )); + // Remove first node since we already added it. + unset($bundle_nodes[key($bundle_nodes)]); + + $parent_node = $this->drupalGetNodeByTitle('Some title', TRUE); + + // Add remaining existing reference nodes. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + for ($i = 2; $i <= 3; $i++) { + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add existing node" and @id="edit-field-multiple-nodes-und-actions-ief-add-existing"]')); + $this->assertResponse(200, 'Opening reference form was successful.'); + $title = 'Some reference ' . $i; + $edit = array( + 'field_multiple_nodes[und][form][entity_id]' => $title . ' (' . $referenceNodes[$title] . ')', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-form-actions-ief-reference-save"]')); + $this->assertResponse(200, 'Adding new referenced entity was successful.'); + } + // Add all remaining nodes from all bundles. + foreach ($bundle_nodes as $id => $title) { + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add existing node" and @id="edit-field-all-bundles-und-actions-ief-add-existing"]')); + $this->assertResponse(200, 'Opening reference form was successful.'); + $edit = array( + 'field_all_bundles[und][form][entity_id]' => $title . ' (' . $id . ')', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @id="edit-field-all-bundles-und-form-actions-ief-reference-save"]')); + $this->assertResponse(200, 'Adding new referenced entity was successful.'); + } + // Save the node. + $this->drupalPost(NULL, array(), t('Save')); + $this->assertResponse(200, 'Saving parent for was successful.'); + + // Check if entities are referenced. + $this->drupalGet('node/' . $parent_node->nid . '/edit'); + for ($i = 2; $i <= 3; $i++) { + $cell = $this->xpath('//table[@id="ief-entity-table-edit-field-multiple-nodes-und-entities"]/tbody/tr[' . ($i - 1) . ']/td[@class="inline-entity-form-node-title"]'); + $this->assertTrue($cell[0] == 'Some reference ' . $i, 'Found reference node title "Some reference ' . $i . '" in the IEF table.'); + } + // Check if all remaining nodes from all bundles are referenced. + $count = 1; + foreach ($bundle_nodes as $id => $title) { + $cell = $this->xpath('//table[@id="ief-entity-table-edit-field-all-bundles-und-entities"]/tbody/tr[' . $count . ']/td[@class="inline-entity-form-node-title"]'); + $this->assertTrue($cell[0] == $title, 'Found reference node title "' . $title . '" in the IEF table.'); + $count++; + } + } + + /** + * Tests if a referenced content can be edited while the referenced content is + * newer than the referencing parent node. + */ + public function testEditedInlineEntityValidation() { + $this->setAllowExisting(TRUE); + + // Create referenced content. + $referenced_nodes = $this->createReferenceContent(1); + $field_multiple_nodes = array(); + foreach($referenced_nodes as $nid){ + $field_multiple_nodes[LANGUAGE_NONE][] = array('target_id' => $nid); + } + + // Create first referencing node. + $this->drupalCreateNode(array( + 'type' => 'ief_test_multiple', + 'title' => 'First referencing node', + 'field_multiple_nodes' => $field_multiple_nodes, + )); + $first_node = $this->drupalGetNodeByTitle('First referencing node'); + + // Create second referencing node. + $this->drupalCreateNode(array( + 'type' => 'ief_test_multiple', + 'title' => 'Second referencing node', + 'field_multiple_nodes' => $field_multiple_nodes, + )); + $second_node = $this->drupalGetNodeByTitle('Second referencing node'); + + // Edit referenced content in first node. + $this->drupalGet('node/' . $first_node->nid . '/edit'); + + // Edit referenced node. + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Edit" and @id="edit-field-multiple-nodes-und-entities-0-actions-ief-entity-edit"]')); + $edit = array( + 'field_multiple_nodes[und][entities][0][form][title]' => 'Some reference updated', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @value="Update node" and @id="edit-field-multiple-nodes-und-entities-0-form-actions-ief-edit-save"]')); + + // Save the first node after editing the reference. + $edit = array('title' => 'First node updated'); + $this->drupalPost(NULL, $edit, t('Save')); + + // The changed value of the referenced content is now newer than the + // changed value of the second node. + + // Edit referenced content in second node. + $this->drupalGet('node/' . $second_node->nid . '/edit'); + + // Edit referenced node. + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Edit" and @id="edit-field-multiple-nodes-und-entities-0-actions-ief-entity-edit"]')); + $edit = array( + 'field_multiple_nodes[und][entities][0][form][title]' => 'Some reference updated the second time', + ); + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @value="Update node" and @id="edit-field-multiple-nodes-und-entities-0-form-actions-ief-edit-save"]')); + + // Save the second node after editing the reference. + $edit = array('title' => 'Second node updated'); + $this->drupalPost(NULL, $edit, t('Save')); + + // Check if the referenced content could be edited. + $this->assertNoText('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.', 'The referenced content could be edited.'); + } + + /** + * Tests create access on IEF Multiple content type. + */ + public function testMultipleEntityCreate() { + $user = $this->drupalCreateUser(array( + 'create ief_test_multiple content', + )); + $this->drupalLogin($user); + + $this->drupalGet('node/add/ief-test-multiple'); + $this->assertNoFieldByName('field_all_bundles[und][actions][bundle]', NULL, 'Bundle select is not shown when only one bundle is available.'); + $this->assertNoFieldByName('field_multiple_nodes[und][form][title]', NULL); + + $user = $this->drupalCreateUser(array( + 'create ief_test_multiple content', + 'create ief_reference_type content' + )); + $this->drupalLogin($user); + + $this->drupalGet('node/add/ief-test-multiple'); + $this->assertFieldByName('field_all_bundles[und][actions][bundle]', NULL, 'Bundle select is shown when more than one bundle is available.'); + $this->assertTrue((bool) $this->xpath('//select[@id="edit-field-all-bundles-und-actions-bundle"]//option[@value="ief_reference_type"]'), 'IEF Reference type is available'); + $this->assertTrue((bool) $this->xpath('//select[@id="edit-field-all-bundles-und-actions-bundle"]//option[@value="ief_test_multiple"]'), 'IEF test multiple is available'); + $this->assertFieldByName('field_multiple_nodes[und][form][title]'); + } + + /** + * Tests entity create access is correct on nested IEF forms. + */ + public function testNestedEntityCreateAccess() { + $permissions = array( + 'create ief_test_nested1 content', + 'create ief_test_nested2 content', + ); + $this->setupNestedMultipleForm(TRUE, $permissions); + $this->assertFieldByName('title'); + $this->assertFieldByName('field_test_ref_nested1[und][form][title]'); + $this->assertNoFieldByName('field_test_ref_nested1[und][form][field_test_ref_nested2][und][form][title]', NULL); + + $this->setupNestedMultipleForm(FALSE, $permissions); + $this->assertNoFieldByXPath('//input[@type="submit" and @value="Create node 3"]'); + } + + /** + * Creates ief_reference_type nodes which shall serve as reference nodes. + * + * @param int $numNodes + * The number of nodes to create + * @return array + * Array of created node ids keyed by labels. + */ + protected function createReferenceContent($numNodes = 3) { + $retval = array(); + for ($i = 1; $i <= $numNodes; $i++) { + $this->drupalCreateNode(array( + 'type' => 'ief_reference_type', + 'title' => 'Some reference ' . $i, + 'field_first_name' => 'First Name ' . $i, + 'field_last_name' => 'Last Name ' . $i, + )); + $node = $this->drupalGetNodeByTitle('Some reference ' . $i); + $this->assertTrue($node, 'Created ief_reference_type node "' . $node->title . '"'); + $retval[$node->title] = $node->nid; + } + return $retval; + } + + /** + * Sets allow_existing IEF setting. + * + * @param bool $flag + * "allow_existing" flag to be set. + */ + protected function setAllowExisting($flag) { + $edit = array( + 'instance[widget][settings][type_settings][allow_existing]' => $flag, + ); + $this->drupalPost('admin/structure/types/manage/ief-test-multiple/fields/field_multiple_nodes', $edit, t('Save settings')); + $this->drupalGet('admin/structure/types/manage/ief-test-multiple/fields/field_multiple_nodes'); + } + + /** + * Creates a node for every node bundle. + * + * @return array + * Array of node titles keyed by ids. + */ + protected function createNodeForEveryBundle() { + $retval = array(); + $bundles = node_type_get_types(); + foreach ($bundles as $id => $value) { + $this->drupalCreateNode(array('type' => $id, 'title' => $value->name)); + $node = $this->drupalGetNodeByTitle($value->name); + $this->assertTrue($node, 'Created node "' . $node->title . '"'); + $retval[$node->nid] = $value->name; + } + return $retval; + } + + /** + * Set up the ief_test_nested1 node add form. + * + * Sets the nested fields' required settings. + * Gets the form. + * Opens the inline entity forms if they are not required. + * + * @param boolean $required + * Whether the fields are required. + * @param array $permissions + * (optional) Permissions to sign testing user in with. You may pass in an + * empty array (default) to use the all the permissions necessary create and + * edit nodes on the form. + */ + protected function setupNestedMultipleForm($required, $permissions = array()) { + $this->drupalLogin($this->administrator_user); + $edit = array( + 'instance[required]' => $required, + ); + $this->drupalPost('admin/structure/types/manage/ief-test-nested1/fields/field_test_ref_nested1', $edit, t('Save settings')); + $this->drupalPost('admin/structure/types/manage/ief-test-nested2/fields/field_test_ref_nested2', $edit, t('Save settings')); + + if (!$permissions) { + $permissions = array( + 'create ief_test_nested1 content', + 'create ief_test_nested2 content', + 'create ief_test_nested3 content', + 'edit any ief_test_nested1 content', + 'edit any ief_test_nested2 content', + 'edit any ief_test_nested3 content', + ); + } + $this->user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->user); + + $this->drupalGet('node/add/ief-test-nested1'); + + if (!$required) { + // Open inline forms if not required. + if (in_array('create ief_test_nested2 content', $permissions)) { + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add new node 2"]')); + } + if (in_array('create ief_test_nested3 content', $permissions)) { + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add new node 3"]')); + } + } + } + + /** + * Closes the existing node form on the "multi" field. + */ + protected function cancelExistingMultiForm($edit) { + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//div[@id="edit-field-multiple-nodes"]//input[@type="submit" and @value="Cancel"]')); + $this->assertNoFieldByName('field_multiple_nodes[und][form][entity_id]', NULL, 'Existing entity reference autocomplete field removed.'); + } + + /** + * Opens the existing node form on the "multi" field. + */ + protected function openMultiExistingForm() { + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @value="Add existing node" and @id="edit-field-multiple-nodes-und-actions-ief-add-existing"]')); + $this->assertResponse(200, 'Opening reference form was successful.'); + $this->assertFieldByName('field_multiple_nodes[und][form][entity_id]', NULL, 'Existing entity reference autocomplete field found.'); + } + + /** + * Checks that nested IEF entity references can be edit and saved. + * + * @param $node + * Top level node of type ief_test_nested1 to check. + * @param bool $ajax_submit + * Whether IEF form widgets should be submitted via AJax or left open. + * + */ + protected function checkNestedEntityEditing($node, $ajax_submit = TRUE) { + $this->drupalGet("node/{$node->nid}/edit"); + $level_1_node = node_load($node->field_test_ref_nested1[LANGUAGE_NONE][0]['target_id']); + $level_2_node = node_load($level_1_node->field_test_ref_nested2[LANGUAGE_NONE][0]['target_id']); + $level_2_node_update_title = $level_2_node->title . ' - updated'; + //edit-test-ref-nested1-entities-0-actions-ief-entity-edit + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-test-ref-nested1-und-entities-0-actions-ief-entity-edit"]')); + //edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-entities-0-actions-ief-entity-edit + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-test-ref-nested1-und-entities-0-form-field-test-ref-nested2-und-entities-0-actions-ief-entity-edit"]')); + $edit['field_test_ref_nested1[und][entities][0][form][field_test_ref_nested2][und][entities][0][form][title]'] = $level_2_node_update_title; + if ($ajax_submit) { + // Close IEF Forms with AJAX posts + //edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-form-inline-entity-form-entities-0-form-actions-ief-edit-save + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @id="edit-field-test-ref-nested1-und-entities-0-form-field-test-ref-nested2-und-entities-0-form-actions-ief-edit-save"]')); + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-test-ref-nested1-und-entities-0-form-actions-ief-edit-save"]')); + $this->drupalPost(NULL, array(), t('Save')); + } + else { + $this->drupalPost(NULL, $edit, t('Save')); + } + $level_2_node = node_load($level_2_node->nid, NULL, TRUE); + $this->assertEqual($level_2_node_update_title, $level_2_node->title); + } + + /** + * Checks that an invalid value for an existing node will be display the expected error. + * + * @param $existing_node_text + * The text to enter into the existing node text field. + * @param $expected_error + * The error message that is expected to be shown. + */ + protected function checkExistingValidationExpectation($existing_node_text, $expected_error) { + $edit = array( + 'field_multiple_nodes[und][form][entity_id]' => $existing_node_text, + ); + $this->openMultiExistingForm(); + + $this->drupalPostAjax(NULL, $edit, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-form-actions-ief-reference-save"]')); + $this->assertText($expected_error); + $this->cancelExistingMultiForm($edit); + } + + +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/CHANGELOG.txt b/profiles/commerce_kickstart/modules/contrib/libraries/CHANGELOG.txt index e3c94119..33fa5e8a 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/CHANGELOG.txt +++ b/profiles/commerce_kickstart/modules/contrib/libraries/CHANGELOG.txt @@ -1,4 +1,20 @@ +Libraries 7.x-2.3, 2016-05-12 +----------------------------- +#1884246 by BR0kEN, tstoeckler et al: Allow downloading libraries via Drush. +by tstoeckler: Allow detecting all libraries by calling libraries_detect(). +by tstoeckler: Prevent LibrariesWebTestBase from being displayed in the UI. +#819610 by tstoeckler: Add tests for the Libraries UI. +#1884246 by BR0kEN, tstoeckler: Show the provider in drush libraries-list +#819610 by Pol, tstoeckler: Show the provider in the UI. +#2634732 by Rob Holmes, tstoeckler: Sort libraries by title in the UI. +#2585395 by robinsonsarah01: Allow object methods as version callbacks. +#819610 by tstoeckler, Pol: Provide a status report for library information. +#2352251 by netw3rker: Fix incorrect hook name in libraries.api.php. +#2352237 by netw3rker, tstoeckler: Allow clearing the libraries cache from Drush. +#2193969 by tstoeckler: Avoid warnings for stale library caches. +#2287529 by drupalshrek, tstoeckler: Update installation link in README.txt. + Libraries 7.x-2.2, 2014-02-09 ----------------------------- #2046919 by tstoeckler: Clarify 'version' docs. diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/LICENSE.txt b/profiles/commerce_kickstart/modules/contrib/libraries/LICENSE.txt old mode 100755 new mode 100644 diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/README.txt b/profiles/commerce_kickstart/modules/contrib/libraries/README.txt index 70a77256..200bc537 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/libraries/README.txt @@ -16,10 +16,11 @@ Bug reports, feature suggestions and latest developments: -- INSTALLATION -- -* Install as usual, see http://drupal.org/node/70151 for further information. - Note that installing external libraries is separate from installing this - module and should happen in the sites/all/libraries directory. See - http://drupal.org/node/1440066 for more information. +* Install as usual, see + https://www.drupal.org/documentation/install/modules-themes/modules-7 for + further information. Note that installing external libraries is separate from + installing this module and should happen in the sites/all/libraries directory. + See http://drupal.org/node/1440066 for more information. -- CONTACT -- diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.admin.inc b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.admin.inc new file mode 100644 index 00000000..8d9e0fbd --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.admin.inc @@ -0,0 +1,547 @@ + $library) { + $actions = array(); + + if ($library['vendor url']) { + $actions[] = l('Homepage', $library['vendor url']); + } + if ($library['download url']) { + $actions[] = l('Download', $library['download url']); + } + + $rows[] = array( + 'data' => array( + l($library['name'], 'admin/reports/libraries/' . $machine_name), + ($library['installed'] ? t('OK') : drupal_ucfirst($library['error'])), + (isset($library['version']) ? $library['version'] : ''), + libraries_admin_get_provider_with_type($library), + implode(' | ', $actions), + ), + 'class' => ($library['installed'] ? array('ok') : array('error')), + ); + } + + $form['libraries']['list'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('There are currently no libraries installed'), + ); + + return $form; +} + +/** + * Form generation callback for the status overview for a single library. + * + * This is a form instead of a page to allow easier extending in contributed + * modules. + * + * @param array $form + * An associative array containing the structure of the form. + * @param array $form_state + * A keyed array containing the current state of the form. + * @param array $library + * A library information array. + * + * @return array|null + * The form array for the status form or NULL if the library was not found. + * + * @todo Add some var_export($library)-style output + */ +function libraries_admin_library_status_form(array $form, array &$form_state, $library) { + drupal_set_title(t('Status report for library %library', array('%library' => $library['name'])), PASS_THROUGH); + + if ($library['installed']) { + drupal_set_message(t('The %name library is installed correctly.', array('%name' => $library['name']))); + $form['status'] = libraries_admin_status_table($library); + } + else { + drupal_set_message($library['error message'], 'error'); + switch ($library['error']) { + case 'not found': + $form['instructions'] = libraries_admin_instructions_missing($library); + break; + + case 'not detected': + $form['instructions'] = libraries_admin_instructions_undetected($library);; + break; + + case 'not supported': + $form['instructions'] = libraries_admin_instructions_unsupported($library); + break; + + case 'missing dependency': + $form['instructions']['instruction']['#markup'] = t('There a missing dependency in your configuration that prevent this library to work properly.') . '
      '; + break; + + case 'incompatible dependency': + $form['instructions']['instruction']['#markup'] = t('There an incompatible dependency in your configuration that prevent this library to work properly.') . '
      '; + break; + } + } + + return $form; +} + + +/** + * Displays a table of status information about a library. + * + * @param array $library + * A library information array. + * + * @return array + * A renderable array containing a table with status information. + */ +function libraries_admin_status_table(array $library) { + $header = array(array( + // @todo The title implies that other type of information is displayed, as + // well, but this is currently not the case. + // @todo Use CSS instead of a element. + 'data' => '' . t('General information') . '', + 'colspan' => 2, + 'class' => 'table-heading', + 'no_striping' => TRUE, + )); + + $rows = array(); + // @todo Use CSS instead of elements. + $rows['name'] = array('' . t('Name') . '', check_plain($library['name'])); + $rows['machine_name'] = array('' . t('Machine name') . '', check_plain($library['machine name'])); + if ($library['vendor url']) { + $rows['vendor_url'] = array('' . t('Vendor URL') . '', l($library['vendor url'], $library['vendor url'])); + } + if ($library['download url']) { + $rows['download_url'] = array('' . t('Download URL') . '', l($library['download url'], $library['download url'])); + } + $rows['provider'] = array('' . t('Provider') . '', libraries_admin_get_provider_with_type($library)); + $rows['library_path'] = array('' . t('Library path') . '', $library['library path']); + $rows['version'] = array('' . t('Version') . '', $library['version']); + if (!empty($library['variants'])) { + $rows['variants'] = array('' . t('Variants') . '', implode(', ', array_keys($library['variants']))); + } + + return array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + ); +} + +/** + * Returns instructions for dealing with a missing library. + * + * @param array $library + * A library information array. + * + * @return array + * A renderable array containing the instructions. + */ +function libraries_admin_instructions_missing(array $library) { + $build = array(); + + $build['instruction']['#markup'] = t('Follow these steps to install the library:'); + + $items = array(); + // 1. Download the library. + // If no supported versions are specified, the latest version is + // recommended. + if (empty($library['versions'])) { + $items[] = t('Download the latest version of the library here.', array( + '@download-url' => $library['download url'], + )); + } + // Otherwise, the latest supported version is recommended. + else { + $versions = array_keys($library['versions']); + usort($versions, 'version_compare'); + $versions = array_reverse($versions); + $version = $versions[0]; + $items[] = t('Download version %version of the library here.', array( + '%version' => $version, + '@download-url' => $library['download url'], + )); + } + // 2. Unpack it. + $items[] = t('If the library is an archive, i.e. if the file ending is for example .tar.gz or .zip, unpack it.'); + // 3. Create the libraries folder. + $items[] = t('In the %library-directory directory of your Drupal installation create a %library directory.', array( + '%library-directory' => 'sites/all/libraries', + '%library' => $library['machine name'], + )); + // 4. Upload it. + // If the library has variant-independent files, give the user the + // location of an example file to check his filesystem against. + if ($directory_layout = libraries_admin_directory_layout($library)) { + $items[] = t('Upload the whole library (which can consist of multiple directories) into the newly created %library-path directory. The following files and directories should be contained in that directory: !directory-layout', array( + '%library-path' => 'sites/all/libraries/' . $library['machine name'], + '!directory-layout' => drupal_render($directory_layout), + )); + } + else { + $items[] = t('Upload the whole library (which can consist of multiple directories) into the newly created %library-path directory.', array( + '%library-path' => 'sites/all/libraries/' . $library['machine name'], + )); + } + // 5. Reload. + $items[] = t('Reload the page. If successful, you should see status information about this library.'); + + $build['steps'] = array( + '#theme' => 'item_list', + '#items' => $items, + '#type' => 'ol' + ); + + return $build; +} + + +/** + * Returns instructions for dealing with an undetected library. + * + * @param array $library + * A library information array. + * + * @return array + * A renderable array containing the instructions. + */ +function libraries_admin_instructions_undetected($library) { + $build = array(); + // Re-check location. + // @todo Avoid usage of
      elements. + $build['instruction']['#markup'] = t('Check that the whole library is located at %library-path.', array( + '%library-path' => $library['library path'], + )) . '
      '; + // If the library has variant-independent files, give the user the + // exact location of the files to check against. + // @todo It should be possible to display even variant-specific files + // in case the variant is installed, but libraries_detect() does not + // detect variants if the library version cannot be detected. + if ($directory_layout = libraries_admin_directory_layout($library)) { + $build['directory_layout'] = $directory_layout; + $build['directory_layout']['#prefix'] = t('The following files and directories should be contained in that directory:'); + } + + // If the library is placed correctly the library information is + // incorrect. + // This switch could be avoided by using $library['info type'], but that would + // hinder properly translating these strings. + $build['reload']['#markup'] = t('If you have moved any files, reload the page. If successful, you should see status information about this library.') . '
      '; + $build['notice']['#markup'] = t('If the files are placed correctly and the version can still not be detected, the library information is incorrect.') . '
      '; + + $provider = libraries_admin_get_provider($library); + switch ($library['info type']) { + case 'module': + $build['contact']['#markup'] = t('Contact the maintainer of the %module module to correct this.', array( + '%module' => $provider, + )) . '
      '; + break; + + case 'theme': + $build['contact']['#markup'] = t('Contact the maintainer of the %theme theme to correct this.', array( + '%theme' => $provider, + )) . '
      '; + break; + + case 'info file': + $build['contact']['#markup'] = t('Contact the maintainer of the %info-file info file to correct this.', array( + '%info-file' => $provider, + )) . '
      '; + break; + } + return $build; +} + + +/** + * Returns instructions for dealing with an unsupported library. + * + * @param array $library + * A library information array. + * + * @return array + * A renderable array containing the instructions. + */ +function libraries_admin_instructions_unsupported($library) { + $build = array(); + $items = array(); + + // Either download a different version of the library... + $versions = array_keys($library['versions']); + usort($versions, 'version_compare'); + $versions = array_reverse($versions); + $version = $versions[0]; + $build['instruction']['#markup'] = t('Please install version %version of the library by following the following steps:', + array( + '%version' => $version, + )); + // 1. Delete the old library. + $items[] = t('Delete the entire contents of the %library-path directory.', + array( + '%library-path' => $library['library path'], + )); + // 2. Download the new library. + $items[] = t('Download version %version of the library here.', + array( + '%version' => $version, + '@download-url' => $library['download url'], + )); + // 3. Unpack it. + $items[] = t('If the library is an archive, i.e. if the file ending is for example .tar.gz or .zip, unpack it.'); + // 4. Upload the new library. + // If the library has variant-independent files, give the user the + // location of an example file to check his filesystem against. + if ($directory_layout = libraries_admin_directory_layout($library)) { + $items[] = t('Upload the new files into the %library-path directory. The following files and directories should be contained in that directory: !directory-layout', + array( + '%library-path' => $library['library path'], + '!directory-layout' => drupal_render($directory_layout), + )); + } + else { + $items[] = t('Upload the new files into the %library-path directory.', + array( + '%library-path' => $library['library path'], + )); + } + // 5. Reload. + $items[] = t('Reload the page. If successful, you should see status information about this library.'); + $build['steps'] = array( + '#theme' => 'item_list', + '#items' => $items, + '#type' => 'ol', + ); + // ...or contact the maintainer of the library information. + $provider = libraries_admin_get_provider($library); + switch ($library['info type']) { + case 'module': + $build['contact']['#markup'] = t('If you are bound to version @version of the library, ask the maintainer of the %module module to provide support for it.', array( + '@version' => $library['version'], + '%module' => $provider, + )) . '
      '; + break; + + case 'theme': + $build['contact']['#markup'] = t('If you are bound to version @version of the library, ask the maintainer of the %theme theme to provide support for it.', array( + '@version' => $library['version'], + '%theme' => $provider, + )) . '
      '; + break; + + case 'info file': + $build['contact']['#markup'] = t('If you are bound to version @version of the library, ask the maintainer of the %info-file info file to provide support for it.', array( + '@version' => $library['version'], + '%info-file' => $provider, + )) . '
      '; + break; + } + return $build; +} + +/** + * Returns the directory layout of the library, if possible. + * + * The result of this function can help users to verify that they have uploaded + * the library to the correct location. + * + * @param array $library + * A library information array. + * + * @return array|false + * A renderable array containing the directory layout of the library or FALSE + * if a directory layout could not be generated. + */ +function libraries_admin_directory_layout(array $library) { + $build = array( + '#theme' => 'item_list', + '#type' => 'ul', + '#items' => array(), + ); + + $items = &$build['#items']; + if ($library['path']) { + $items = &libraries_admin_path_to_tree($items, $library['path']); + } + foreach (array('js', 'css', 'php') as $type) { + if (!empty($library['files'][$type])) { + $files = array_keys($library['files'][$type]); + foreach ($files as $file) { + // Skip JavaScript settings. + if (is_int($file)) { + continue; + } + + $children = &$items; + libraries_admin_path_to_tree($children, $file); + } + } + } + return $build['#items'] ? $build : FALSE; +} + +/** + * Converts a file path into a tree structure for use in an item list. + * + * For example, the path 'foo/bar/baz' will be converted into the tree structure + * represented by the following list: + * - foo + * - bar + * - baz + * + * The $items array that is modified by reference or returned (see below) can + * be used as the 'items' variable for theme_item_list(). + * + * This function modifies passed-in $items array, so that multiple paths can + * be placed into the same tree structure easily. + * + * @code + * $items = array(); + * foreach ($paths as $path) { + * libraries_admin_path_to_tree($items, $path); + * } + * @endcode + * + * It also returns the last item by reference, so that it can also be used to + * traverse into a sub-structure and add further children there. + * + * @code + * $items = array(); + * $children = &libraries_admin_path_to_tree($items, $path); + * foreach ($sub_paths as $sub_path) { + * libraries_admin_path_to_tree($children, $sub_path); + * } + * @endcode + * + * @param array $items + * @param string $path + * + * @return array + */ +function &libraries_admin_path_to_tree(array &$items, $path) { + $part = strtok($path, '/'); + while ($part) { + if (!isset($items[$part])) { + $items[$part] = array( + 'data' => $part, + 'children' => array(), + ); + } + $items = &$items[$part]['children']; + $part = strtok('/'); + } + + return $items; +} + +/** + * Sorts libraries by name. + * + * This function can be used as a callback for usort() or uasort(). + * + * @param array $a + * The first library information array. + * @param array $b + * The second library information array. + * + * @return int + * Returns -1 if $a is considered smaller than $b, 1 if $a considered greater + * than $b and 0 if $a and $b are considered equal. + * + * @see strnatcasecmp() + * @see usort() + * @see uasort() + */ +function libraries_admin_sort_title(array $a, array $b) { + return strnatcasecmp($a['name'], $b['name']); +} + +/** + * Returns the library's provider. + * + * The provider can be a module, a theme, or an info file. + * + * @param array $library + * A library information array. + * + * @return string + * The provider. + */ +function libraries_admin_get_provider($library) { + $provider = ''; + + switch ($library['info type']) { + case 'module': + case 'theme': + $info = system_get_info($library['info type'], $library[$library['info type']]); + $provider = $info['name']; + break; + + case 'info file': + $provider = basename($library['info file']); + break; + } + + return $provider; +} + +/** + * Returns the library's provider and provider type. + * + * The provider type is either 'module', 'theme', or 'info file'. + * + * @param array $library + * A library information array. + * + * @return string + * The provider and provider type. + */ +function libraries_admin_get_provider_with_type($library) { + $provider = libraries_admin_get_provider($library); + $provider_with_type = ''; + + // This switch could be avoided by using $library['info type'], but that would + // hinder properly translating these strings. + switch ($library['info type']) { + case 'module': + $provider_with_type = t('%module module', array('%module' => $provider)); + break; + + case 'theme': + $provider_with_type = t('%theme theme', array('%theme' => $provider)); + break; + + case 'info file': + $provider_with_type = t('%info-file info file', array('%info-file' => $provider)); + break; + } + + return $provider_with_type; +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.api.php b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.api.php index 8ac31115..71e90e0c 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.api.php +++ b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.api.php @@ -16,6 +16,10 @@ * - name: The official, human-readable name of the library. * - vendor url: The URL of the homepage of the library. * - download url: The URL of a web page on which the library can be obtained. + * - download file url: (optional) The URL where the latest version of the + * library can be downloaded. In case such a static URL exists the library + * can be downloaded automatically via Drush. Run + * 'drush help libraries-download' in the command-line for more information. * - path: (optional) A relative path from the directory of the library to the * actual library. Only required if the extracted download package contains * the actual library files in a sub-directory. @@ -211,6 +215,9 @@ function hook_libraries_info() { 'name' => 'Example library', 'vendor url' => 'http://example.com', 'download url' => 'http://example.com/download', + // It is important that this URL does not include the actual version to + // download. Not all libraries provide such a static URL. + 'download file url' => 'http://example.com/latest.tar.gz', // Optional: If, after extraction, the actual library files are contained in // 'sites/all/libraries/example/lib', specify the relative path here. 'path' => 'lib', @@ -343,6 +350,9 @@ function hook_libraries_info() { 'name' => 'Simple library', 'vendor url' => 'http://example.com/simple', 'download url' => 'http://example.com/simple', + // The download file URL can also point to a single file (instead of an + // archive). + 'download file url' => 'http://example.com/latest/simple.js', 'version arguments' => array( 'file' => 'readme.txt', // Best practice: Document the actual version strings for later reference. @@ -467,7 +477,7 @@ function hook_libraries_info_alter(&$libraries) { * @return * An array of paths. */ -function hook_libraries_paths() { +function hook_libraries_info_file_paths() { // Taken from the Libraries test module, which needs to specify the path to // the test library. return array(drupal_get_path('module', 'libraries_test') . '/example'); diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.drush.inc b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.drush.inc index 26b35eb8..32587135 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.drush.inc +++ b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.drush.inc @@ -1,5 +1,4 @@ 'libraries_drush_list', - 'description' => dt('Lists registered library information.'), + 'description' => dt('Show a list of registered libraries.'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('lls', 'lib-list'), ); - /**$items['libraries-download'] = array( - 'callback' => 'libraries_drush_download', - 'description' => dt('Downloads a registered library into the libraries directory for the active site.'), + + $items['libraries-download'] = array( + 'description' => dt('Download library files of registered libraries.'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('ldl', 'lib-download'), 'arguments' => array( - 'name' => dt('The internal name of the registered library.'), + 'libraries' => 'A comma delimited list of library machine names.', ), - );*/ + 'required-arguments' => TRUE, + ); + return $items; } /** - * Implements hook_drush_help(). + * Implements hook_drush_cache_clear(). + * + * @see drush_cache_clear_types() */ -function libraries_drush_help($section) { - switch ($section) { - case 'drush:libraries-list': - return dt('Lists registered library information.'); - - case 'drush:libraries-download': - return dt('Downloads a registered library into the libraries directory for the active site. +function libraries_drush_cache_clear(array &$types) { + $types['libraries'] = 'libraries_drush_invalidate_cache'; +} -See libraries-list for a list of registered libraries.'); +/** + * Clears the library cache. + */ +function libraries_drush_invalidate_cache() { + // @see drupal_flush_all_caches() + foreach (libraries_flush_caches() as $table) { + cache_clear_all('*', $table, TRUE); } } /** - * Lists registered library information. + * Command callback. Show a list of registered libraries. */ -function libraries_drush_list() { - $libraries = array(); - foreach (libraries_info() as $name => $info) { - $libraries[$name] = libraries_detect($name); - } +function drush_libraries_list() { + $libraries = libraries_detect(); ksort($libraries); if (empty($libraries)) { drush_print('There are no registered libraries.'); } - else { + module_load_include('inc', 'libraries', 'libraries.admin'); + $rows = array(); // drush_print_table() automatically treats the first row as the header, if // $header is TRUE. - $rows[] = array(dt('Name'), dt('Status'), dt('Version'), dt('Variants'), dt('Dependencies')); + $rows[] = array( + dt('Name'), + dt('Status'), + dt('Version'), + dt('Variants'), + dt('Dependencies'), + dt('Provider'), + ); foreach ($libraries as $name => $library) { - $status = ($library['installed'] ? dt('OK') : drupal_ucfirst($library['error'])); - $version = (($library['installed'] && !empty($library['version'])) ? $library['version'] : '-'); - // Only list installed variants. $variants = array(); foreach ($library['variants'] as $variant_name => $variant) { @@ -69,83 +80,150 @@ function libraries_drush_list() { $variants[] = $variant_name; } } - $variants = (empty($variants) ? '-' : implode(', ', $variants)); - $dependencies = (!empty($library['dependencies']) ? implode(', ', $library['dependencies']) : '-'); - - $rows[] = array($name, $status, $version, $variants, $dependencies); + $rows[] = array( + $name, + $library['installed'] ? dt('OK') : drupal_ucfirst($library['error']), + ($library['installed'] && $library['version']) ? '-' : $library['version'], + $variants ? implode(', ', $variants) : '-', + $library['dependencies'] ? implode(', ', $library['dependencies']) : '-', + libraries_admin_get_provider($library), + ); } + // Make the possible values for the 'Status' column and the 'Version' header // wrap nicely. - $widths = array(0, 12, 7, 0, 0); + $widths = array(0, 12, 7, 0, 0, 0); drush_print_table($rows, TRUE, $widths); } } /** - * Downloads a library. + * Command callback. Downloads a library. + * + * Only libraries that provide a download file URL can be downloaded. * - * @param $name - * The internal name of the library to download. + * @see hook_libraries_info() + * @see drush_pm_download() */ -function libraries_drush_download($name) { - return; +function drush_libraries_download() { + drush_command_include('pm-download'); + + $libraries = libraries_info(); + + // @todo Consider supporting downloading all downloadable libraries. + // @todo Consider offering a selection if no library is specified. + foreach (pm_parse_arguments(func_get_args(), FALSE) as $machine_name) { + if (!isset($libraries[$machine_name])) { + $message = dt("The !library library is not registered with Libraries API.\n", array('!library' => $machine_name)); + $message .= dt("Provide an info file for it or implement hook_libraries_info().\n"); + $message .= dt("See hook_libraries_info() for more information.\n"); + drush_set_error('DRUSH_LIBRARY_UKNOWN', $message); + continue; + } + $library = $libraries[$machine_name]; + + if (empty($library['download file url'])) { + $message = dt("The !library library cannot be downloaded.\n", array('!library' => $machine_name)); + $message .= dt("Libraries need to specify a download file URL to support being downloaded via Drush.\n"); + $message .= dt("See hook_libraries_info() for more information.\n"); + drush_set_error('DRUSH_LIBRARY_NOT_DOWNLOADABLE', $message); + continue; + } + $download_url = $library['download file url']; + + drush_log(dt('Downloading library !name ...', array('!name' => $machine_name))); + + // @see package_handler_download_project() in wget.inc + // It cannot be used directly because it will always try to extract the + // archive which fails when downloading a single file. + // @todo Modify upstream to be able to use + // package_handler_download_project() directly. + // Prepare download path. On Windows file name cannot contain '?'. + // See http://drupal.org/node/1782444 + $filename = str_replace('?', '_', basename($download_url)); + $download_path = drush_tempdir() . '/' . $filename; + + // Download the tarball. + // Never cache the downloaded file. The downloading relies on the fact that + // different versions of the library are available under the same URL as new + // versions are released. + $download_path = drush_download_file($download_url, $download_path, 0); + if ($download_path || drush_get_context('DRUSH_SIMULATE')) { + drush_log(dt('Downloading !filename was successful.', array('!filename' => $filename))); + } + else { + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Unable to download !project to !path from !url.', array('!project' => $machine_name, '!path' => $download_path, '!url' => $download_url))); + drush_log(dt('Error downloading !name', array('!name' => $machine_name)), 'error'); + continue; + } - // @todo Looks wonky? - if (!drush_shell_exec('type unzip')) { - return drush_set_error(dt('Missing dependency: unzip. Install it before using this command.')); - } + // @todo Suport MD5 file hashing. - // @todo Simply use current drush site. - $args = func_get_args(); - if ($args[0]) { - $path = $args[0]; - } - else { - $path = 'sites/all/libraries'; - } + // Extract the tarball in place and return the full path to the untarred directory. + $download_base = dirname($download_path); + if (drush_file_is_tarball($download_path)) { + if (!$tar_file_list = drush_tarball_extract($download_path, $download_base, TRUE)) { + // An error has been logged. + return FALSE; + } + $tar_directory = drush_trim_path($tar_file_list[0]); + $download_path = $download_base . '/' . $tar_directory; + } + else { + $download_path = $download_base; + } - // Create the path if it does not exist. - if (!is_dir($path)) { - drush_op('mkdir', $path); - drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice'); - } + // Determine the install location for the project. User provided + // --destination has preference. + $destination = drush_get_option('destination'); + if (!empty($destination)) { + if (!file_exists($destination)) { + drush_mkdir($destination); + } + $install_location = realpath($destination); + } + else { + /** @see _pm_download_destination_lookup() */ + // _pm_download_destination_lookup() pluralizes the passed type by + // appending an s. + // This relies on the fact that there is no library named 'contrib'. + // @todo Request that this be turned into a proper API upstream. + $install_location = _pm_download_destination('librarie'); + } - // Set the directory to the download location. - $olddir = getcwd(); - chdir($path); + // @todo Consider invoking a hook similar to + // hook_drush_pm_download_destination_alter(). - $filename = basename(COLORBOX_DOWNLOAD_URI); - $dirname = basename(COLORBOX_DOWNLOAD_URI, '.zip'); + // @todo Consider adding version-control support similar to pm-download. - // Remove any existing Colorbox plugin directory - if (is_dir($dirname)) { - drush_log(dt('A existing Colorbox plugin was overwritten at @path', array('@path' => $path)), 'notice'); - } - // Remove any existing Colorbox plugin zip archive - if (is_file($filename)) { - drush_op('unlink', $filename); - } + $install_location .= '/' . $machine_name; - // Download the zip archive - if (!drush_shell_exec('wget '. COLORBOX_DOWNLOAD_URI)) { - drush_shell_exec('curl -O '. COLORBOX_DOWNLOAD_URI); - } + // Check if install location already exists. + if (is_dir($install_location)) { + if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $install_location)))) { + drush_delete_dir($install_location, TRUE); + } + else { + drush_log(dt("Skip installation of !project to !dest.", array('!project' => $library['machine name'], '!dest' => $install_location)), 'warning'); + continue; + } + } - if (is_file($filename)) { - // Decompress the zip archive - drush_shell_exec('unzip -qq -o '. $filename); - // Remove the zip archive - drush_op('unlink', $filename); - } + // Copy the project to the install location. + if (drush_op('_drush_recursive_copy', $download_path, $install_location)) { + drush_log(dt("Library !project downloaded to !dest.", array('!project' => $machine_name, '!dest' => $install_location)), 'success'); - // Set working directory back to the previous working directory. - chdir($olddir); + // @todo Consider invoking a hook similar to + // hook_drush_pm_post_download(). - if (is_dir($path .'/'. $dirname)) { - drush_log(dt('Colorbox plugin has been downloaded to @path', array('@path' => $path)), 'success'); - } - else { - drush_log(dt('Drush was unable to download the Colorbox plugin to @path', array('@path' => $path)), 'error'); + // @todo Support printing release notes. + } + else { + // We don't `return` here in order to proceed with downloading additional projects. + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project could not be downloaded to !dest.", array('!project' => $machine_name, '!dest' => $install_location))); + } + + // @todo Consider adding notify support. } } diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.info b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.info index 136d323b..dcd2d486 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.info +++ b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.info @@ -3,11 +3,14 @@ description = Allows version-dependent and shared usage of external libraries. core = 7.x ; We use hook_system_theme_info() which was added in Drupal 7.11 dependencies[] = system (>=7.11) -files[] = tests/libraries.test +files[] = tests/LibrariesAdminWebTest.test +files[] = tests/LibrariesLoadWebTest.test +files[] = tests/LibrariesUnitTest.test +files[] = tests/LibrariesWebTestBase.test -; Information added by Drupal.org packaging script on 2014-02-09 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2016-05-12 +version = "7.x-2.3" core = "7.x" project = "libraries" -datestamp = "1391965716" +datestamp = "1463077450" diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.install b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.install index b210b98c..ebc4087b 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.install +++ b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.install @@ -25,3 +25,12 @@ function libraries_update_7200() { db_create_table('cache_libraries', $specs['cache_libraries']); } } + +/** + * Rebuild the class registry. + */ +function libraries_update_7201() { + // The tests were split from a single libraries.test file into multiple files + // during the 7.x-2.x cycle. + registry_rebuild(); +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.module b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.module index 0448476d..48275250 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/libraries.module +++ b/profiles/commerce_kickstart/modules/contrib/libraries/libraries.module @@ -33,7 +33,7 @@ function libraries_flush_caches() { * @param $base_path * Whether to prefix the resulting path with base_path(). * - * @return + * @return string * The path to the specified library or FALSE if the library wasn't found. * * @ingroup libraries @@ -67,7 +67,7 @@ function libraries_get_path($name, $base_path = FALSE) { * in both the site-wide directory and site-specific directory, only the * site-specific version will be listed. * - * @return + * @return array * A list of library directories. * * @ingroup libraries @@ -122,7 +122,7 @@ function libraries_get_libraries() { * - sites/$sitename/libraries * - any directories specified via hook_libraries_info_file_paths() * - * @return + * @return array * An array of info files, keyed by library name. The values are the paths of * the files. */ @@ -429,17 +429,21 @@ function &libraries_info($name = NULL) { /** * Applies default properties to a library definition. * - * @library + * @param array $library * An array of library information, passed by reference. - * @name + * @param string $name * The machine name of the passed-in library. + * + * @return array + * The library information array with defaults populated. */ -function libraries_info_defaults(&$library, $name) { +function libraries_info_defaults(array &$library, $name) { $library += array( 'machine name' => $name, 'name' => $name, 'vendor url' => '', 'download url' => '', + 'download file url' => '', 'path' => '', 'library path' => NULL, 'version callback' => 'libraries_get_version', @@ -472,12 +476,15 @@ function libraries_info_defaults(&$library, $name) { /** * Tries to detect a library and its installed version. * - * @param $name - * The machine name of a library to return registered information for. + * @param string $name + * (optional) The machine name of a library to detect and return registered + * information for. If omitted, information about all registered libraries is + * returned. * * @return array|false - * An associative array containing registered information for the library - * specified by $name, or FALSE if the library $name is not registered. + * An associative array containing registered information for all libraries, + * the registered information for the library specified by $name, or FALSE if + * the library $name is not registered. * In addition to the keys returned by libraries_info(), the following keys * are contained: * - installed: A boolean indicating whether the library is installed. Note @@ -491,7 +498,15 @@ function libraries_info_defaults(&$library, $name) { * * @see libraries_info() */ -function libraries_detect($name) { +function libraries_detect($name = NULL) { + if (!isset($name)) { + $libraries = &libraries_info(); + foreach ($libraries as $name => $library) { + libraries_detect($name); + } + return $libraries; + } + // Re-use the statically cached value of libraries_info() to save memory. $library = &libraries_info($name); @@ -531,7 +546,7 @@ function libraries_detect($name) { $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments'])); } else { - $library['version'] = $library['version callback']($library, $library['version arguments']); + $library['version'] = call_user_func($library['version callback'], $library, $library['version arguments']); } if (empty($library['version'])) { $library['error'] = 'not detected'; @@ -693,6 +708,10 @@ function libraries_load($name, $variant = NULL) { * The number of loaded files. */ function libraries_load_files($library) { + // As this key was added after 7.x-2.1 cached library structures might not + // have it. + $library += array('post-load integration files' => FALSE); + // Load integration files. if (!$library['post-load integration files'] && !empty($library['integration files'])) { $enabled_themes = array(); @@ -867,3 +886,61 @@ function libraries_get_version($library, $options) { } fclose($file); } + +/** + * Implements hook_help(). + */ +function libraries_help($path, $arg) { + switch ($path) { + case 'admin/reports/libraries': + return t('Click on a library for a status report or detailed installation instructions in case the library is not installed correctly.'); + } +} + +/** + * Implements hook_menu(). + */ +function libraries_menu() { + $items = array(); + $items['admin/reports/libraries'] = array( + 'title' => 'Libraries', + 'description' => 'An overview of libraries installed on this site.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('libraries_admin_overview'), + 'access arguments' => array('access site reports'), + 'file' => 'libraries.admin.inc' + ); + $items['admin/reports/libraries/%libraries_ui'] = array( + 'title' => 'Library status report', + 'description' => 'Status overview for a single library', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('libraries_admin_library_status_form', 3), + 'access arguments' => array('access site reports'), + 'file' => 'libraries.admin.inc' + ); + return $items; +} + +/** + * Loads library information for display in the user interface. + * + * This can be used as a menu loader function by specifying a '%libraries_ui' + * parameter in a path. + * + * We do not use libraries_load() (and, thus, a '%libraries' parameter) directly + * for displaying library information in the user interface as we do not want + * the library files to be loaded. + * + * @param string $name + * The machine name of a library to return registered information for. + * + * @return array|false + * An associative array containing registered information for the library + * specified by $name, or FALSE if the library $name is not registered. + * + * @see libraries_detect() + * @see libraries_menu() + */ +function libraries_ui_load($name) { + return libraries_detect($name); +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesAdminWebTest.test b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesAdminWebTest.test new file mode 100644 index 00000000..a4d2188b --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesAdminWebTest.test @@ -0,0 +1,119 @@ + 'Libraries administration', + 'description' => 'Tests the administrative interface for libraries.', + 'group' => 'Libraries API', + ); + } + + /** + * Tests the libraries report at /admin/reports/libraries. + */ + public function testLibrariesReportOverview() { + $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries'); + $this->assertRaw('Libraries'); + + // Make sure that all the libraries are listed. + $libraries = libraries_info(); + $this->assertTrue($libraries); + foreach ($libraries as $library) { + $this->assertText($library['name']); + $this->assertLinkByHref('admin/reports/libraries/' . $library['machine name']); + } + + // Make sure that all possible statuses are displayed. + $this->assertText('OK'); + $this->assertText('Not found'); + $this->assertText('Not detected'); + $this->assertText('Not supported'); + $this->assertText('Missing dependency'); + $this->assertText('Incompatible dependency'); + + // Make sure that the providers are displayed. + $this->assertRaw('Libraries test module module'); + $this->assertRaw('Libraries test theme theme'); + $this->assertRaw('example_info_file.libraries.info info file'); + + // Make sure that homepage and download links are displayed. + $this->assertLinkByHref('http://example.com'); + $this->assertLinkByHref('http://example.com/download'); + } + + /** + * Tests the libraries report for an installed library. + */ + public function testLibrariesReportInstalled() { + $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_files'); + $this->assertRaw('Status report for library Example files'); + $this->assertRaw('The Example files library is installed correctly.'); + // Check that the information in the status report is displayed. + $this->assertText('Example files'); + $this->assertText('example_files'); + $this->assertRaw('Libraries test module module'); + $this->assertText(drupal_get_path('module', 'libraries') . '/tests/libraries/example'); + $this->assertText('1'); + } + + /** + * Tests the libraries report for a missing library. + */ + public function testLibrariesReportMissing() { + $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_missing'); + $this->assertRaw('Status report for library Example missing'); + $this->assertRaw('The Example missing library could not be found.'); + // Check that the download link is being displayed. + $this->assertLinkByHref('http://example.com/download'); + } + + + /** + * Tests the libraries report for a missing library. + */ + public function testLibrariesReportNotDetected() { + $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_undetected_version'); + $this->assertRaw('Status report for library Example undetected version'); + $this->assertRaw('The version of the Example undetected version library could not be detected.'); + } + + /** + * Tests the libraries report for a missing library. + */ + public function testLibrariesReportNotSupported() { + $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_unsupported_version'); + $this->assertRaw('Status report for library Example unsupported version'); + $this->assertRaw('The installed version 1 of the Example unsupported version library is not supported.'); + // Check that the download link is being displayed. + $this->assertLinkByHref('http://example.com/download'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries.test b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesLoadWebTest.test similarity index 92% rename from profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries.test rename to profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesLoadWebTest.test index 41926318..7c4fe51d 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries.test +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesLoadWebTest.test @@ -2,67 +2,29 @@ /** * @file - * Tests for Libraries API. + * Contains LibrariesLoadWebTest. + * + * Simpletest automatically discovers test files using PSR-4. We cannot, + * however, declare a namespace for this class, as that would require PHP 5.3. + * To prepare the PHP 5.3 requirement and the usage of PSR-4 in 7.x-3.x, we + * place the test files in the correct directory already, but for now register + * them explicitly in libraries.info */ /** - * Tests basic Libraries API functions. + * Tests basic detection and loading of libraries. */ -class LibrariesUnitTestCase extends DrupalUnitTestCase { - public static function getInfo() { - return array( - 'name' => 'Libraries API unit tests', - 'description' => 'Tests basic functions provided by Libraries API.', - 'group' => 'Libraries API', - ); - } - - function setUp() { - drupal_load('module', 'libraries'); - parent::setUp(); - } - - /** - * Tests libraries_get_path(). - */ - function testLibrariesGetPath() { - // Note that, even though libraries_get_path() doesn't find the 'example' - // library, we are able to make it 'installed' by specifying the 'library - // path' up-front. This is only used for testing purposed and is strongly - // discouraged as it defeats the purpose of Libraries API in the first - // place. - $this->assertEqual(libraries_get_path('example'), FALSE, 'libraries_get_path() returns FALSE for a missing library.'); - } +class LibrariesLoadWebTest extends LibrariesWebTestBase { /** - * Tests libraries_prepare_files(). + * Provides metadata about this test. + * + * @return array + * An array of test metadata with the following keys: + * - name: The name of the test. + * - description: The description of the test. + * - group: The group of the test. */ - function testLibrariesPrepareFiles() { - $expected = array( - 'files' => array( - 'js' => array('example.js' => array()), - 'css' => array('example.css' => array()), - 'php' => array('example.php' => array()), - ), - ); - $library = array( - 'files' => array( - 'js' => array('example.js'), - 'css' => array('example.css'), - 'php' => array('example.php'), - ), - ); - libraries_prepare_files($library, NULL, NULL); - $this->assertEqual($expected, $library, 'libraries_prepare_files() works correctly.'); - } -} - -/** - * Tests basic detection and loading of libraries. - */ -class LibrariesTestCase extends DrupalWebTestCase { - protected $profile = 'testing'; - public static function getInfo() { return array( 'name' => 'Libraries detection and loading', @@ -71,15 +33,10 @@ class LibrariesTestCase extends DrupalWebTestCase { ); } - function setUp() { - parent::setUp('libraries', 'libraries_test_module'); - theme_enable(array('libraries_test_theme')); - } - /** * Tests libraries_detect_dependencies(). */ - function testLibrariesDetectDependencies() { + public function testLibrariesDetectDependencies() { $library = array( 'name' => 'Example', 'dependencies' => array('example_missing'), @@ -158,7 +115,7 @@ class LibrariesTestCase extends DrupalWebTestCase { /** * Tests libraries_scan_info_files(). */ - function testLibrariesScanInfoFiles() { + public function testLibrariesScanInfoFiles() { $expected = array('example_info_file' => (object) array( 'uri' => drupal_get_path('module', 'libraries') . '/tests/libraries/example_info_file.libraries.info', 'filename' => 'example_info_file.libraries.info', @@ -171,7 +128,7 @@ class LibrariesTestCase extends DrupalWebTestCase { /** * Tests libraries_info(). */ - function testLibrariesInfo() { + public function testLibrariesInfo() { // Test that modules can provide and alter library information. $info = libraries_info(); $this->assertTrue(isset($info['example_module'])); @@ -226,7 +183,7 @@ class LibrariesTestCase extends DrupalWebTestCase { /** * Tests libraries_detect(). */ - function testLibrariesDetect() { + public function testLibrariesDetect() { // Test missing library. $library = libraries_detect('example_missing'); $this->verbose('
      ' . var_export($library, TRUE) . '
      '); @@ -306,10 +263,24 @@ class LibrariesTestCase extends DrupalWebTestCase { $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.'); } + /** + * Tests libraries_detect() without a $name parameter. + */ + public function testLibrariesDetectAll() { + // Test that an array with all library information is returned and that the + // libraries are properly detected. + $libraries = libraries_detect(); + $this->verbose('
      ' . var_export($libraries, TRUE) . '
      '); + $this->assertEqual($libraries['example_missing']['error'], 'not found'); + $this->assertEqual($libraries['example_undetected_version']['error'], 'not detected'); + $this->assertEqual($libraries['example_unsupported_version']['error'], 'not supported'); + $this->assertEqual($libraries['example_supported_version']['installed'], TRUE); + } + /** * Tests libraries_load(). */ - function testLibrariesLoad() { + public function testLibrariesLoad() { // Test dependencies. $library = libraries_load('example_dependency_missing'); $this->verbose('
      ' . var_export($library, TRUE) . '
      '); @@ -334,7 +305,7 @@ class LibrariesTestCase extends DrupalWebTestCase { /** * Tests the applying of callbacks. */ - function testCallbacks() { + public function testCallbacks() { $expected = array( 'name' => 'Example callback', 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', @@ -453,7 +424,7 @@ class LibrariesTestCase extends DrupalWebTestCase { * * @see _libraries_test_module_load() */ - function testLibrariesOutput() { + public function testLibrariesOutput() { // Test loading of a simple library with a top-level files property. $this->drupalGet('libraries-test-module/files'); $this->assertLibraryFiles('example_1', 'File loading'); @@ -527,7 +498,7 @@ class LibrariesTestCase extends DrupalWebTestCase { * (optional) The expected file extensions of $name. Defaults to * array('js', 'css', 'php'). */ - function assertLibraryFiles($name, $label = '', $extensions = array('js', 'css', 'php')) { + public function assertLibraryFiles($name, $label = '', $extensions = array('js', 'css', 'php')) { $label = ($label !== '' ? "$label: " : ''); // Test that the wrong files are not loaded... @@ -570,4 +541,3 @@ class LibrariesTestCase extends DrupalWebTestCase { } } - diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesUnitTest.test b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesUnitTest.test new file mode 100644 index 00000000..cd5d30a2 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesUnitTest.test @@ -0,0 +1,78 @@ + 'Libraries API unit tests', + 'description' => 'Tests basic functions provided by Libraries API.', + 'group' => 'Libraries API', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + drupal_load('module', 'libraries'); + parent::setUp(); + } + + /** + * Tests libraries_get_path(). + */ + public function testLibrariesGetPath() { + // Note that, even though libraries_get_path() doesn't find the 'example' + // library, we are able to make it 'installed' by specifying the 'library + // path' up-front. This is only used for testing purposed and is strongly + // discouraged as it defeats the purpose of Libraries API in the first + // place. + $this->assertEqual(libraries_get_path('example'), FALSE, 'libraries_get_path() returns FALSE for a missing library.'); + } + + /** + * Tests libraries_prepare_files(). + */ + public function testLibrariesPrepareFiles() { + $expected = array( + 'files' => array( + 'js' => array('example.js' => array()), + 'css' => array('example.css' => array()), + 'php' => array('example.php' => array()), + ), + ); + $library = array( + 'files' => array( + 'js' => array('example.js'), + 'css' => array('example.css'), + 'php' => array('example.php'), + ), + ); + libraries_prepare_files($library, NULL, NULL); + $this->assertEqual($expected, $library, 'libraries_prepare_files() works correctly.'); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesWebTestBase.test b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesWebTestBase.test new file mode 100644 index 00000000..094534ce --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/LibrariesWebTestBase.test @@ -0,0 +1,64 @@ +drupalGetContent(). + * + * @see \DrupalWebTestCase::drupalGet() + * @see \DrupalWebTestCase::drupalCreateUser() + */ + protected function getWithPermissions(array $permissions, $path, array $options = array(), array $headers = array()) { + $this->drupalGet($path, $options, $headers); + $this->assertResponse(403); + + $this->drupalLogin($this->drupalCreateUser($permissions)); + $this->drupalGet($path, $options, $headers); + $this->assertResponse(200); + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info b/profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info index 59475b4e..11152731 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info @@ -2,9 +2,9 @@ name = Example info file -; Information added by Drupal.org packaging script on 2014-02-09 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2016-05-12 +version = "7.x-2.3" core = "7.x" project = "libraries" -datestamp = "1391965716" +datestamp = "1463077450" diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info b/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info index 450a0a20..7ad319a2 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info @@ -5,9 +5,9 @@ package = Testing dependencies[] = libraries hidden = TRUE -; Information added by Drupal.org packaging script on 2014-02-09 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2016-05-12 +version = "7.x-2.3" core = "7.x" project = "libraries" -datestamp = "1391965716" +datestamp = "1463077450" diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.module b/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.module index 65f412ec..51d07c98 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.module +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.module @@ -18,6 +18,9 @@ function libraries_test_module_libraries_info() { // Test library detection. $libraries['example_missing'] = array( 'name' => 'Example missing', + // Provide a vendor and download URL to test that the UI links to it. + 'vendor url' => 'http://example.com', + 'download url' => 'http://example.com/download', 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/missing', ); $libraries['example_undetected_version'] = array( @@ -28,6 +31,8 @@ function libraries_test_module_libraries_info() { ); $libraries['example_unsupported_version'] = array( 'name' => 'Example unsupported version', + // Provide a download URL to test that the UI links to it. + 'download url' => 'http://example.com/download', 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version callback' => '_libraries_test_module_return_version', 'version arguments' => array('1'), diff --git a/profiles/commerce_kickstart/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info b/profiles/commerce_kickstart/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info index 7528416b..255d1040 100644 --- a/profiles/commerce_kickstart/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info +++ b/profiles/commerce_kickstart/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info @@ -3,9 +3,9 @@ description = Tests that themes can provide and alter library information. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2014-02-09 -version = "7.x-2.2" +; Information added by Drupal.org packaging script on 2016-05-12 +version = "7.x-2.3" core = "7.x" project = "libraries" -datestamp = "1391965716" +datestamp = "1463077450" diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/README.txt b/profiles/commerce_kickstart/modules/contrib/lingotek/README.txt index 451d834c..9abcf6c9 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/README.txt +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/README.txt @@ -41,4 +41,4 @@ http://www.drupaltranslation.com/drupal_translation_module_installation. CONFIGURATION -------------- -There is setup wizard that helps you to get started, go to admin/config/lingotek/setup. \ No newline at end of file +There is setup wizard that helps you to get started, go to admin/config/regional/lingotek/setup. diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/fprm/okf_html@drupal_subfilter.fprm b/profiles/commerce_kickstart/modules/contrib/lingotek/fprm/okf_html@drupal_subfilter.fprm index 29e765f0..4683d4a5 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/fprm/okf_html@drupal_subfilter.fprm +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/fprm/okf_html@drupal_subfilter.fprm @@ -48,7 +48,7 @@ elements: ruleTypes: [TEXTUNIT] idAttributes: [id] drupalvar: - ruleTypes: [INLINE, EXCLUDE] + ruleTypes: [INLINE] dt: ruleTypes: [TEXTUNIT] idAttributes: [id] diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/includes/lingotek.ajax.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/includes/lingotek.ajax.inc index 906cb9eb..9fbbe167 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/includes/lingotek.ajax.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/includes/lingotek.ajax.inc @@ -26,6 +26,7 @@ function lingotek_page_mark_phases_complete($node) { $targets = LingotekDocument::load($lingotek_document_id)->translationTargets(); $api = LingotekApi::instance(); foreach ($_POST['targets'] as $target) { + $target = filter_xss($target); if ($remote_target = $api->getTranslationTarget($targets[$target]->id)) { $current_phase_id = lingotek_current_phase($remote_target->phases); if ($api->markPhaseComplete($current_phase_id)) { diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.admin.js b/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.admin.js index aa8f5826..bc8d3bed 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.admin.js +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.admin.js @@ -24,31 +24,6 @@ Drupal.behaviors.lingotekAdminForm = { } }); - //when a content type checkbox is clicked - $('.form-select', context).change( function() { - isEnabled = $(this).val() != 'DISABLED'; - var totalChecked = $(this).parents('tr').find('.form-checkbox:checked').length; - - $(this).parents('tr').find('.form-checkbox').each( function() { - if(isEnabled && totalChecked === 0) { - $(this).attr('checked', isEnabled); - } else if (!isEnabled) { - $(this).removeAttr('checked'); - } - }) - }); - - // default all fields to be checked when profile is not disabled (and no fields are currently checked) - $('.lingotek-content-settings-table').find('tr').each(function() { - var val = $(this).find('.form-select').val(); - var count = 0; - if (val != 'DISABLED') { - count = $(this).find('.form-checkbox:checked').size(); - if (count == 0) { - $(this).find('.form-checkbox').attr('checked', true); - } - } - }); //when a field checkbox is clicked var exemptions = [ "lingotek_prepare_config_blocks", @@ -65,12 +40,12 @@ Drupal.behaviors.lingotekAdminForm = { if($(this).attr('checked')) { row.find('td:first-child .form-checkbox').each( function() { $(this).attr('checked', true); - }) + }); } else { count = 0; row.find('.field.form-checkbox').each( function() { count += $(this).attr('checked') ? 1 : 0; - }) + }); if(count == 0) { row.find('td:first-child .form-checkbox').attr('checked',false); row.find('.form-select').val('DISABLED'); @@ -84,6 +59,12 @@ Drupal.behaviors.lingotekAdminForm = { if ($(this).children().first().is(':checked')) { $('.field.form-checkbox').removeAttr('disabled').attr('checked',true); } + else { + $('.field.form-checkbox').attr('checked',false); + $('#lingotek_prepare_config_blocks').attr('disabled',true); + $('#lingotek_prepare_config_menus').attr('disabled',true); + $('#lingotek_prepare_config_taxonomies').attr('disabled',true); + } }); //uncheck dependent-function boxes when primary is not checked @@ -101,6 +82,13 @@ Drupal.behaviors.lingotekAdminForm = { $('#lingotek_prepare_config_menus').removeAttr('checked').attr('disabled',true); } }); + $('#edit-config-lingotek-translate-config-taxonomies').change( function () { + if ($('#edit-config-lingotek-translate-config-taxonomies').is(':checked')) { + $('#lingotek_prepare_config_taxonomies').removeAttr('disabled').attr('checked',true); + } else { + $('#lingotek_prepare_config_taxonomies').removeAttr('checked').attr('disabled',true); + } + }); // set prep functions to disabled/enabled on initial page load $( function () { @@ -121,10 +109,12 @@ Drupal.behaviors.lingotekAdminForm = { } else { $('#lingotek_prepare_config_menus').attr('disabled',true); } - if ($('.form-item-config-lingotek-translate-config-views').parent().siblings().last().children().last().val() != 1) { $('#edit-config-lingotek-translate-config-views').attr('disabled',true); } + if ($('.form-item-config-lingotek-translate-config-fields').parent().siblings().last().children().last().val() != 1) { + $('#edit-config-lingotek-translate-config-fields').attr('disabled',true); + } }); //ensure that there is a vertical tab set @@ -143,7 +133,7 @@ Drupal.behaviors.lingotekAdminForm = { $(context).find('select').each(function( index ) { var $this = $(this); var name = $this.attr('name'); - if(name && name.substring(0, 7) == 'profile') { + if(name && name.substring(0, 7) == 'profile' && name.indexOf('__') < 0) { if($this.val() != 'DISABLED') { $list.push($this.val()); } @@ -161,7 +151,7 @@ Drupal.behaviors.lingotekAdminForm = { $('.ltk-entity').each(function(index) { var $entity_utility_options = $(this).find('.js-utility-options'); - var $entity_profile_selects = $(this).find('select'); + var $entity_profile_selects = $(this).find('select').filter(function() { return !this.id.match(/__/); }); function turn_on() { $entity_utility_options.find('input[type="checkbox"]').attr('checked', true); @@ -193,7 +183,7 @@ Drupal.behaviors.lingotekAdminForm = { // config summary $('fieldset#ltk-config', context).drupalSetSummary(function (context) { $list = []; - max = 7; + max = 8; extra_text = ""; $(context).find('input').each(function( index ) { @@ -230,13 +220,22 @@ Drupal.behaviors.lingotekAdminForm = { } }; +$(document).ready(function(){ + $('.description').css('width','auto'); + $('.description').css('border','0px'); + $('.description').css('background','transparent'); + $('.description').css('position','static'); + $('.description').css('display','block'); + $('.description').css('padding-left','0'); + $('.form-type-checkbox').find('.description').css('margin-left','1.5em'); +}); + })(jQuery); -function lingotek_set_all(sel, val) { +function lingotek_set_all(sel, target) { fieldset = jQuery(sel); - console.log(jQuery(sel)); jQuery(sel).find('.form-select').each( function() { - jQuery(this).val(val); + jQuery(this).val(target.value); jQuery(this).trigger('change'); }); } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.bulk_grid.js b/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.bulk_grid.js index 670c7c02..fd830de0 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.bulk_grid.js +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.bulk_grid.js @@ -5,8 +5,8 @@ function lingotek_perform_action(nid, action) { jQuery('#edit-grid-container .form-checkbox').removeAttr('checked'); jQuery('#edit-the-grid-' + nid).attr('checked', 'checked'); - jQuery('#edit-actions-select').val(action); - jQuery('#edit-actions-select').trigger('change'); + jQuery('#edit-select-actions').val(action); + jQuery('#edit-select-actions').trigger('change'); } (function ($) { @@ -14,22 +14,21 @@ function lingotek_perform_action(nid, action) { var $self = $(self); url = $self.attr('href'); var entity_ids = []; - $('#edit-grid-container .form-checkbox').each(function() { - if($(this).attr('checked')) { + $('#edit-grid-container .form-checkbox').each(function () { + if ($(this).attr('checked')) { val = $(this).val(); - if(val != 'on') { + if (val != 'on') { entity_ids.push(val); } } }); - console.log(entity_ids); - if(entity_ids.length > 0) { - $('#edit-actions-select').val('select'); + if (entity_ids.length > 0) { + $('#edit-select-actions').val('select'); ob = Drupal.ajax[url]; ob.element_settings.url = ob.options.url = ob.url = url + '/' + entity_ids.join(','); $self.trigger('click'); $self.attr('href', url); - $('.modal-header .close').click( function() { + $('.modal-header .close').click(function () { location.reload(); }); } else { @@ -39,10 +38,10 @@ function lingotek_perform_action(nid, action) { } var message_already_shown = false; - +//causes all config content in a matching set to be selected together Drupal.behaviors.lingotekBulkGrid = { attach: function (context) { - $('.form-checkbox').change(function() { + $('.form-checkbox').change(function () { var cells_of_selected_row = $(this).parents("tr").children(); var selected_set_name = cells_of_selected_row.children('.set_name').text(); @@ -69,25 +68,456 @@ function lingotek_perform_action(nid, action) { } }); - $('input#edit-actions-submit.form-submit').hide(); - $('#edit-actions-select').change(function() { - val = $(this).val(); + $('input#edit-submit-actions.form-submit').hide(); + $('#edit-select-actions').once('lingotek_once_id', function () { + $('#edit-select-actions').change(function () { + val = $(this).val(); - if(val == 'reset' || val == 'delete') { - lingotek_trigger_modal($('#'+val+'-link')); - } else if(val == 'edit') { - lingotek_trigger_modal($('#edit-settings-link')); - } else if(val == 'workflow') { - lingotek_trigger_modal($('#change-workflow-link')); - } else { - $('input#edit-actions-submit.form-submit').trigger('click'); - } + if (val == 'reset' || val == 'delete') { + lingotek_trigger_modal($('#' + val + '-link')); + } else if (val == 'edit') { + lingotek_trigger_modal($('#edit-settings-link')); + } else if (val == 'workflow') { + lingotek_trigger_modal($('#change-workflow-link')); + } else if (val == 'delete_translations') { + lingotek_trigger_modal($('#delete-translations-link')); + } else { + $('input#edit-submit-actions.form-submit').trigger('click'); + } + }); }); - $('#edit-limit-select').change(function() { + $('#edit-limit-select').change(function () { $('#edit-search-submit.form-submit').trigger('click'); }); + } + }; + + function addClickToDownloadReady() { + original_download_ready_URL = $('#download-ready').attr('href'); + $('#download-ready').click(function () { + modifyActionButtonURL('#download-ready', original_download_ready_URL); + }); + } + + function addClickToUploadButton() { + original_upload_edited_URL = $('#upload-edited').attr('href'); + $('#upload-edited').click(function () { + modifyActionButtonURL('#upload-edited', original_upload_edited_URL); + }); + } + + this.check_box_count = 0; + function addClickToCheckboxes() { + $('#edit-grid-container .form-checkbox').each(function () { + $(this).change(function (event) { + clarifyButtonsForCheckboxes(event); + }); + }); + } + + //changes the href associated with the download/upload buttons after they are clicked + //but before the links are actually followed. Also checks to see if the results are + //filtered. + function modifyActionButtonURL(element_id, original_URL) { + var new_URL = original_URL.valueOf();//clones the original + var entity_ids = getIDArray(); + var id_string = entity_ids.join(","); + new_URL += entity_ids.length !== 0 ? "/" + entity_ids.join(",") : ""; + new_URL = entity_ids.length === 0 ? original_URL : new_URL; + $(element_id).attr('href', new_URL); + } + + //looks at every currently displayed row and pushes the entity_id of each + //row with a checked checkbox into the return variable + function getIDArray(visible_check) { + var entity_ids = []; + var visible = visible_check === true; + $('#edit-grid-container .form-checkbox').each(function () { + var val = $(this).val(); + if ($(this).attr('checked') || visible) { + if (val !== 'on') {//'on' represents the 'select all' checkbox + entity_ids.push(val); + } + } + }); + return entity_ids; + } + + function clarifyButtonsForFilter() { + $('.notify-checked-action').hide(); + $('#upload-edited').attr('title', 'Re-upload all edited source content'); + $('#download-ready').attr('title', 'Download Ready translations'); + var text = $('#clear-filters').text(); + + if (text === undefined || text === "") { + $('.notify-filtered-action').hide(); + } + else { + $('.notify-filtered-action').show(); + $('#upload-edited').attr('title', 'Upload filtered results'); + $('#download-ready').attr('title', 'Download filtered results'); + } + } + + function clarifyButtonsForCheckboxes(event) { + var box_checked = $(event.target).attr('checked'); + //accounts for the select all box + if ($(event.target).val() === 'on' && box_checked) { + this.check_box_count = $('#edit-grid-container .form-checkbox').length - 2; + } + else if ($(event.target).val() === 'on' && !box_checked) { + this.check_box_count = 0; + } + else if (box_checked === true) { + this.check_box_count++; + } + else { + this.check_box_count--; + } + if (this.check_box_count > 0) { + $('.notify-filtered-action').hide(); + $('.notify-checked-action').show(); + $('#upload-edited').attr('title', 'Upload selected results'); + $('#download-ready').attr('title', 'Download selected results'); + return false; + } + else { + clarifyButtonsForFilter(); + } + } + + //guarantees that search and actions fields will match in width. Looks nicer, + //can't do this simply with css, because the actions dropdown's width may change + //based on its content + function alignFields() { + var common_width = $('#edit-select-actions').width(); + var padding_top = $('#edit-select-actions').css('padding-top'); + var padding_bottom = $('#edit-select-actions').css('padding-bottom'); + var height = $('#edit-select-actions').height(); + $('#edit-search').width(common_width); + $('#edit-search').css('paddingBottom', padding_bottom); + $('#edit-search').css('paddingTop', padding_top); + $('#edit-search').css('min-height', height); + } + + function setupToggleMarked() { + $('.ltk-marked-checkbox').bind('click',function(){ + var $self = $(this); + var url = $self.attr('href'); + var marked = url.substring(url.length - 1, url.length); + var elements = $self.attr('id').split("-"); + var entityType = elements[1]; + var entityId = elements[2]; + var newMarkedValue = marked == 1 ? 0 : 1; + var title = newMarkedValue == 1 ? 'Unmark content' : 'Mark content'; + var markedClass0 = 'fa-square-o'; + var markedClass1 = 'fa-check-square'; + var newMarkedClass = newMarkedValue ? markedClass1 : markedClass0; + var newUrl = url.substring(0, url.length - 1) + newMarkedValue; + $.ajax({ + url: url, + method: 'GET', + success: function (data) { + $self.attr('href',newUrl); + $self.attr('title',title); + $self.removeClass(markedClass0 + ' ' + markedClass1); + $self.addClass(newMarkedClass); + } + }); + }); + } + + //update_empty_cells allows cells with no translations statuses to display them + //when they are available + function update_empty_cells(data, parent, entity_id) { + if(data[entity_id].length !== undefined) { + return; + } + var used_keys = {}; + var entity_type = document.getElementById('entity-type').getAttribute('value'); + if (entity_type === 'config') { + return; + } + for(var key in data[entity_id]){ + if(entity_type !== 'config' && !data[entity_id][key].hasOwnProperty('status')){ + continue; + } + var lang_code = key.valueOf(); + //this keeps the displayed language code consistent with what is retrieved + //on page load + lang_code = lang_code.toLowerCase(); + lang_code = lang_code.replace('_','-'); + var url = window.location.href; + url = url.substr(0,url.indexOf('admin')); + var href = url + 'lingotek/workbench/' + data.entity_type + '/' + entity_id + '/' + key; + var link_text = key.substring(0,2); + //accounts for multiple dialects, current format is to shorten the first language + //and give the full language for all subsequent dialects of that language + if(used_keys.hasOwnProperty(link_text)){ + link_text = lang_code; + } + else { + used_keys[link_text] = link_text; + } + //Create the appropriate title + var title; + var status = entity_type !== 'config' ? data[entity_id][key].status : data[entity_id][key].toUpperCase(); + switch(status) { + case "READY": + title = 'Ready to download'; + break; + case "CURRENT": + title = 'Current'; + break; + case "READY_INTERIM": + title = 'Ready to Download Interim Translations'; + break; + case "INTERIM": + title = 'Interim translation downloaded'; + case "EDITED": + title = 'Needs to be Uploaded'; + break; + case "PENDING": + title = 'In progress'; + break; + case "ERROR": + title = 'Error'; + break; + case "DELETED": + continue; + } + //create the link + var status_link = $(''); + status_link.attr('href', href); + status_link.attr('target','_blank'); + status_link.attr('title',title); + status_link.addClass('language-icon target-' + status.toLowerCase()); + status_link.text(link_text); + + $('.emptyTD', parent).each(function(){ + var index = $('td',parent).index($(this)); + var translation_header = $('th').eq(index); + if($('a',translation_header).text().toLowerCase() === 'translations'){ + $(this).append(status_link); + } + }); + } + //remove the identifying class + $('.emptyTD',parent).removeClass(); } -}; + function updateRowStatus(data, row, entity_id) { + //if the row does not yet have status indicators + if($('.emptyTD',row).length > 0){ + update_empty_cells(data, row, entity_id); + return; + } + //content is disabled and should not be updated + if($(row).find('.fa-minus-square').length > 0){ + return; + } + if(data[entity_id].hasOwnProperty('last_modified')){ + var main_table = document.getElementsByClassName('table-select-processed'); + var table_headers = main_table[0].getElementsByTagName('th'); + var last_modified_index = null; + for(var i = 0; i < table_headers.length; i++){ + if(table_headers[i].textContent.toLowerCase().indexOf('modified') !== -1) { + last_modified_index = i; + break; + } + } + if(last_modified_index !== null) { + var tds = row[0].getElementsByTagName('td'); + tds[last_modified_index].textContent = data[entity_id]['last_modified']; + } + } + var entity_type = document.getElementById('entity-type').getAttribute('value'); + if(data[entity_id].length !== undefined && entity_type === 'config') { + $('.language-icon', row).parent().empty().addClass('emptyTD'); + $('.fa-check-square', row).removeClass().addClass('fa fa-square-o').attr('title', 'Needs to be Uploaded'); + return; + } + + // Find and update the source icon + var source_status = data[entity_id]['source_status']; + var source_icon = $(row).find('.ltk-source-icon'); + var entity_profile = data[entity_id]['profile']; + switch (source_status) { + case "NONE" : + source_icon.removeClass().addClass('ltk-source-icon source-none'); + source_icon.removeAttr('title').attr('title', 'Upload'); + break; + case "EDITED": + source_icon.removeClass().addClass('ltk-source-icon source-edited'); + source_icon.removeAttr('target'); + source_icon.removeAttr('title').attr('title', 'Re-upload (content has changed since last upload'); + source_icon.removeAttr('href').attr('href', '#'); + source_icon.click(function(){ + lingotek_perform_action(entity_id,'upload'); + }); + break; + case "CURRENT": + source_icon.removeClass().addClass('ltk-source-icon source-current'); + source_icon.removeAttr('title').attr('title', 'Source Uploaded'); + break; + case "ERROR": + source_icon.removeClass().addClass('ltk-source-icon source-error'); + error_title = data[entity_id]['last_upload_error']; + source_icon.removeAttr('title').attr('title', error_title); + break; + } + if (entity_profile === 'DISABLED') { + source_icon.removeClass().addClass('ltk-source-icon source-disabled'); + source_icon.attr('title', 'Disabled, cannot request translation'); + } + + //iterate through each target icon and update them + $(row).find('a.language-icon').each(function () { + var icon_href = $(this).attr('href'); + //retrieve the language code from the href + icon_href = icon_href.split("#")[0]; + + var language_code = icon_href.substring(icon_href.length - 'xx_XX'.length);//normal locale code + if(data[entity_id][language_code] === undefined){ + var language_code = icon_href.substring(icon_href.length - 'xx'.length);//language code case + } + var title = $(this).attr('title'); + var cutoff = title.indexOf('-'); + title = title.substring(0, cutoff + 1); + var target_status = entity_type !== 'config' ? data[entity_id][language_code] + : data[entity_id][language_code].toUpperCase(); + switch (target_status) { + case "NONE": + var attrs = { + class:'ltk-target-none', + title:'No Translation', + }; + $(this).replaceWith(function () { + var new_element = $("", attrs).append($(this).contents()); + return new_element; + }); + case "READY": + $(this).removeClass().addClass('language-icon target-ready'); + $(this).attr('title', 'Ready for Download'); + break; + case "CURRENT": + $(this).removeClass().addClass('language-icon target-current'); + $(this).attr('title', 'Current'); + break; + case "READY_INTERIM": + $(this).removeClass().addClass('language-icon target-ready_interim'); + $(this).attr('title', 'Ready for Interim Download'); + break; + case "INTERIM": + $(this).removeClass().addClass('language-icon target-interim'); + $(this).attr('title', 'In-progress (interim translation downloaded)'); + break; + case "EDITED": + $(this).removeClass().addClass('language-icon target-edited'); + $(this).attr('title', 'Source Edited'); + break; + case "PENDING": + $(this).removeClass().addClass('language-icon target-pending'); + $(this).attr('title', 'In-Progress'); + break; + case "UNTRACKED": + $(this).removeClass().addClass('language-icon target-untracked') + $(this).attr('title', 'Translation exists, but it is not being tracked by Lingotek'); + break; + case "ERROR": + $(this).removeClass().addClass('language-icon target-error'); + $(this).attr('title', 'Error'); + break; + } + if (entity_profile === 'DISABLED') { + var attrs = { + class:'ltk-target-disabled', + title:'Disabled, cannot request translation', + }; + $(this).replaceWith(function () { + var new_element = $("", attrs).append($(this).contents()); + return new_element; + }); + } + }); + } + + function updateStatusIndicators(data) { + //the checkboxes always have the row's entity id + $('#edit-grid-container .form-checkbox').each(function () { + var entity_id = $(this).val(); + if (data.hasOwnProperty(entity_id)) { + var parent = $(this).closest('tr'); + //this creates the random fill in effect, not sure if its a keeper + var i = Math.floor((Math.random() * 7) + 1); + setTimeout(updateRowStatus,300 * i,data,parent,entity_id); + } + }); + } + + function pollTranslationStatus(){ + // Prevent jumping to top of page when source icons are clicked. + $('.ltk-source-icon.source-none').click(function(e) { + e.preventDefault(); + }); + $('.ltk-source-icon.source-edited').click(function(e){ + e.preventDefault(); + }); + //makes it easy to find empty cells, the only empty ones will be in the status + //column if the row hasn't been uploaded yet. + $('td:empty').addClass('emptyTD'); + var ids_to_poll = ''; + //get all the entity_ids currently displayed + $('#edit-grid-container .form-checkbox').each(function () { + var entity_id = $(this).val(); + if(entity_id !== 'on') { + ids_to_poll += $(this).val() + ','; + } + }); + //start the poller on 30 sec interval (30000) + setInterval(function () { + $.ajax({ + url: $('#async-update').attr('href') + '/' + ids_to_poll.substr(0,ids_to_poll.length-1), + dataType: 'json', + success: function (data) { + if (data !== null) { + updateStatusIndicators(data); + } + } + }); + }, 10000); + } + function pollAutomaticDownloads(){ + //config section does not have profiles, so automatic downloads should not + //happen + if($('#entity-type').val() === 'config'){ + return; + } + setInterval(function () { + $.ajax({ + url: $('#auto-download').attr('href'), + dataType: 'json' + }); + }, 30000); + } + function configShowMoreOptions(){ + $('#more-options').toggleClass('more-options-flip'); + $('#force-down').toggle(); + } + function setupConfigMoreOptions() { + $('#force-down').hide(); + $('#more-options').click(configShowMoreOptions); + } + $(document).ready(function () { + setupConfigMoreOptions(); + alignFields(); + setupToggleMarked(); + pollTranslationStatus(); +// pollAutomaticDownloads(); + addClickToDownloadReady(); + addClickToUploadButton(); + addClickToCheckboxes(); + clarifyButtonsForFilter(); + }); })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.form.js b/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.form.js index 0e266584..ecec6c87 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.form.js +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/js/lingotek.form.js @@ -9,6 +9,7 @@ lingotek.forms = lingotek.forms || {}; // Page setup for node add/edit forms. lingotek.forms.init = function() { + $("#edit-language").change(updateProfileSelectorDefault); $("#edit-language").change(updateVerticalTabSummary); $("#edit-language").change(toggleMenuSelector); $('#ltk-enable-from-et').bind('click',lingotek.forms.enableLtkFunc); @@ -17,6 +18,7 @@ lingotek.forms = lingotek.forms || {}; $('#edit-lingotek-profile').change(updateVerticalTabSummary); $('#edit-lingotek-profile').change(checkForEnablement); $('#edit-lingotek-allow-source-overwriting').change(updateVerticalTabSummary); + updateProfileSelectorDefault(); updateVerticalTabSummary(); checkForEnablement(); toggleMenuSelector(); @@ -49,6 +51,29 @@ lingotek.forms = lingotek.forms || {}; } } + var updateProfileSelectorDefault = function() { + // if the node already exists, then it must have a profile already: don't change it! + if ($('#lingotek-preserve-profile').val() == "1") { + return; + } + if ($('#lingotek-bundle-profiles').length > 0) { + // get the language map for the current bundle + var profiles_by_langcode = JSON.parse($('#lingotek-bundle-profiles').val()); + // set the #edit-lingotek-profile select box + var langcode = $("#edit-language").val(); + if ($('#lingotek-language-specific-profiles').val() == '0') { + $('#edit-lingotek-profile').val(profiles_by_langcode['DEFAULT']); + } + else if (langcode in profiles_by_langcode) { + $('#edit-lingotek-profile').val(profiles_by_langcode[langcode]); + } + else { + // The language must not be enabled for Lingotek at all. + $('#edit-lingotek-profile').val('DISABLED'); + } + } + } + var checkForEnablement = function() { if ($('#edit-lingotek-profile').val() != 'DISABLED') { $('#edit-lingotek-overwrite-warning').show(); @@ -61,8 +86,6 @@ lingotek.forms = lingotek.forms || {}; var updateVerticalTabSummary = function() { var isPushedToLingotek = !isNaN(parseInt($('#edit-lingotek-document-id').val())); var isEntityTranslationNode = $('#ltk-entity-translation-node').val(); - //console.log('pushedToLingotek: '+isPushedToLingotek); - //console.log('entityTranslationNode: '+isEntityTranslationNode); var summaryMessages = []; if(!lingotek.forms.enableLtkFromET && isEntityTranslationNode && !isPushedToLingotek) { @@ -87,7 +110,6 @@ lingotek.forms = lingotek.forms || {}; var sourceLanguageSet = language != 'und'; var autoUpload = $('#edit-lingotek-create-lingotek-document').is(":checked"); var autoDownload = $('#edit-lingotek-sync-method').is(":checked"); - var custom = $('#edit-lingotek-profile').val() == 'CUSTOM'; // Source language set or not if (sourceLanguageSet) { @@ -107,17 +129,7 @@ lingotek.forms = lingotek.forms || {}; $('.form-item-lingotek-profile').hide(); } - if($('#edit-lingotek-profile').val() != 'CUSTOM') { - $('#edit-lingotek-content').hide(); - $('#edit-lingotek-advanced').hide(); - } - - if(custom) { - summaryMessages.push( autoUpload ? Drupal.t("auto-upload") : Drupal.t("manual upload")); - summaryMessages.push( autoDownload ? Drupal.t("auto-download") : Drupal.t("manual download")); - } else { - summaryMessages.push($('#edit-lingotek-profile option:selected').text()); - } + summaryMessages.push($('#edit-lingotek-profile option:selected').text()); if($('#edit-lingotek-allow-source-overwriting').is(":visible")) { if($('#edit-lingotek-allow-source-overwriting').is(":checked")) { diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/Lingotek.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/Lingotek.php index a03ad1f3..f979a740 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/Lingotek.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/Lingotek.php @@ -208,7 +208,7 @@ class Lingotek { ); public static $language_mapping_l2d_exceptions = array( 'ar' => 'ar', - 'zh_CN' => 'zh', + 'zh_CN' => 'zh-hans', 'zh_TW' => 'zh-hant' ); public static $language_mapping_d2l_exceptions = array( @@ -229,13 +229,12 @@ class Lingotek { * FALSE otherwise. */ public static function convertDrupal2Lingotek($drupal_language_code, $enabled_check = TRUE) { - $lingotek_locale = FALSE; // standard conversion if (!$enabled_check) { - // If the code contains a dash then, keep it specific - $exceptions = self::$language_mapping_d2l_exceptions; + // If the code contains a dash then, keep it specific + $exceptions = variable_get('lingotek_mapping_d2l_exceptions', self::$language_mapping_d2l_exceptions); if (array_key_exists($drupal_language_code, $exceptions)) { $lingotek_locale = $exceptions[$drupal_language_code]; } @@ -277,21 +276,25 @@ public static function convertDrupal2Lingotek($drupal_language_code, $enabled_ch * FALSE otherwise. */ public static function convertLingotek2Drupal($lingotek_locale, $enabled_check = TRUE) { - $drupal_language_code = strtolower(str_replace("_", "-", $lingotek_locale)); // standard conversion $drupal_general_code = substr($drupal_language_code, 0, strpos($drupal_language_code, '-')); + $languages = language_list(); if (!$enabled_check) { - $exceptions = self::$language_mapping_l2d_exceptions; + $exceptions = variable_get('lingotek_mapping_l2d_exceptions', self::$language_mapping_l2d_exceptions); if (array_key_exists($lingotek_locale, $exceptions)) { return $exceptions[$lingotek_locale]; } + foreach ($languages as $target) { + if ($target->language == $drupal_language_code) { + return $target->language; + } + } return $drupal_general_code; } $ret = FALSE; // check to see if the lingotek_locale is set in the drupal languages table - $languages = language_list(); foreach ($languages as $target) { if (isset($target->lingotek_locale) && strcmp($target->lingotek_locale, $lingotek_locale) == 0) { return $target->language; @@ -310,7 +313,6 @@ public static function convertLingotek2Drupal($lingotek_locale, $enabled_check = $ret = $drupal_language_code; } } - //echo "\n\n convertLingotek2Drupal: ".$lingotek_locale." => ".$ret." \n\n"; return $ret; } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekAccount.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekAccount.php index 290bf1e5..5860890f 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekAccount.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekAccount.php @@ -85,7 +85,10 @@ public function getPlan() { } public function setPlanType($type = 'unknown') { - variable_set('lingotek_account_plan_type', $type); + // Set plan type variable only if it's needed. + if ($type != variable_get('lingotek_account_plan_type', 'standard')) { + variable_set('lingotek_account_plan_type', $type); + } $standard_types = array('cosmopolitan_monthly', 'cosmopolitan_yearly'); // if in this list, then set to 'standard' $type = in_array($type, $standard_types) ? 'standard' : $type; $this->planType = $type; diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekApi.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekApi.php index 8a691a7e..afa500c3 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekApi.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekApi.php @@ -64,6 +64,8 @@ public static function instance() { * * @param object $translatable_object * A Drupal node object or lingotek ConfigChunk object + * @param mixed $with_targets + * An optional array of locales to include for translation, or TRUE for all enabled languages. */ public function addContentDocument(LingotekTranslatableEntity $translatable_object, $with_targets = FALSE) { $success = FALSE; @@ -86,17 +88,48 @@ public function addContentDocument(LingotekTranslatableEntity $translatable_obje ); $parameters['documentName'] = $translatable_object->getDocumentName(); $parameters['documentDesc'] = $translatable_object->getDescription(); - $parameters['content'] = $translatable_object->documentLingotekXML(); + $parameters['content'] = $this->check_url_alias_translation($translatable_object, $translatable_object->documentLingotekXML()); $parameters['url'] = $translatable_object->getUrl(); $parameters['workflowId'] = $translatable_object->getWorkflowId(); $this->addAdvancedParameters($parameters, $translatable_object); + $has_language_specific_targets = FALSE; - if ($with_targets) { - $parameters['targetAsJSON'] = Lingotek::getLanguagesWithoutSourceAsJSON($source_language); + // If the document has invalid characters, return without uploading + $invalid_xml = lingotek_keystore($translatable_object->getEntityType(), $translatable_object->getId(), 'invalid_xml'); + if ($invalid_xml == LingotekSync::INVALID_XML_PRESENT) { + drupal_set_message(t('Unable to upload to Lingotek because entity contains invalid XML characters.'), 'warning'); + return FALSE; + } - $parameters['applyWorkflow'] = 'true'; // API expects a 'true' string - $result = $this->request('addContentDocumentWithTargetsAsync', $parameters); + if ($with_targets) { + if (is_array($with_targets)) { + // Assumes language-specific profiles are enabled, so handle adding + // target locales with *custom workflows* separately, and include + // all the other target locales here. + $default_targets = array(); + foreach ($with_targets as $l => $v) { + if (empty($v['workflow_id'])) { + $default_targets[] = $l; + } + else { + $has_language_specific_targets = TRUE; + } + } + if (!empty($default_targets)) { + $parameters['targetAsJSON'] = json_encode($default_targets); + $parameters['applyWorkflow'] = 'true'; // API expects a 'true' string + $result = $this->request('addContentDocumentWithTargetsAsync', $parameters); + } + else { + $result = $this->request('addContentDocumentAsync', $parameters); + } + } + else { + $parameters['targetAsJSON'] = Lingotek::getLanguagesWithoutSourceAsJSON($source_language); + $parameters['applyWorkflow'] = 'true'; // API expects a 'true' string + $result = $this->request('addContentDocumentWithTargetsAsync', $parameters); + } } else { $result = $this->request('addContentDocumentAsync', $parameters); @@ -105,7 +138,7 @@ public function addContentDocument(LingotekTranslatableEntity $translatable_obje if ($result) { if (isset($result->errors) && $result->errors) { LingotekLog::error(t('Request to send document to Lingotek failed: ') . print_r($result->errors, TRUE), array()); - $translatable_object->setStatus(LingotekSync::STATUS_FAILED); + $translatable_object->setStatus(LingotekSync::STATUS_ERROR); $translatable_object->setLastError(is_array($result->errors) ? array_shift($result->errors) : $result->errors); return FALSE; } @@ -113,7 +146,7 @@ public function addContentDocument(LingotekTranslatableEntity $translatable_obje $translatable_object->setDocumentId($result->id); $translatable_object->setProjectId($project_id); $translatable_object->setStatus(LingotekSync::STATUS_CURRENT); - $translatable_object->setTargetsStatus(LingotekSync::STATUS_PENDING); + $translatable_object->setTargetsStatus(LingotekSync::STATUS_PENDING, $with_targets); // WTD: there is a race condition here where a user could modify a locales- // source entry between the time the dirty segments are pulled and the time @@ -122,18 +155,81 @@ public function addContentDocument(LingotekTranslatableEntity $translatable_obje LingotekConfigSet::setSegmentStatusToCurrentById($translatable_object->getId()); } else { - // node assumed (based on two functions below... + // Add targets with custom workflows after the fact, if language-specific profiles are detected + if ($has_language_specific_targets) { + $this->upload_language_specific_targets($result->id, $with_targets); + } $entity_type = $translatable_object->getEntityType(); lingotek_keystore($entity_type, $translatable_object->getId(), 'document_id', $result->id); lingotek_keystore($entity_type, $translatable_object->getId(), 'last_uploaded', time()); } - $success = TRUE; } } return $success; } - + + /** + * Waits until document import status is complete, then adds targets, or logs an error + * @param string $doc_id + * @param string $target_locale + * @param string $workflow_id + */ + private function upload_language_specific_targets($doc_id, $targets) { + $i = 0; + $sleep_intervals = array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); + $done_processing = FALSE; + $params = array( + 'id' => $doc_id + ); + + while(!$done_processing) { + $response = $this->request('getDocumentImportStatus', $params); + if ($response->status === 'COMPLETE') { + $done_processing = TRUE; + } + elseif($i > count($sleep_intervals) - 1) { + drupal_set_message(t('Uploading some language-specific targets failed because of network timeout. Please retry.'), 'warning', FALSE); + LingotekLog::error(t('Adding language-specific targets failed: ') . print_r('Document id: ' . $doc_id, TRUE), array()); + return; + } + else { + sleep($sleep_intervals[$i]); + $i++; + } + } + + // The document has been imported successfully, now add the targets + if (is_array($targets)) { + foreach ($targets as $target_locale => $target_attribs) { + if (!empty($doc_id) && !empty($target_attribs['workflow_id'])) { + $tl_result = $this->addTranslationTarget($doc_id, NULL, $target_locale, $target_attribs['workflow_id']); + } + } + } + } + + /** + * Checks if this content needs to have it's url translated, strips it out if it doesn't. + * Best place for this function as the function that puts the url alias into the content + * is used in other places. + * @param type $translatable_object + * @param string $content + * @return string + */ + private function check_url_alias_translation($translatable_object, $content) { + if($translatable_object->lingotek['url_alias_translation'] != 1){ + $openTagPosition = strpos($content, ''); + if($openTagPosition !== FALSE){ + $closeTagPosition = strpos($content, '') + strlen(''); + $firstChunk = substr($content, 0, $openTagPosition); + $lastChunk = substr($content, $closeTagPosition); + $content = $firstChunk . $lastChunk; + } + } + return $content; + } + /** * Updates the content of an existing Lingotek document with the current object contents. * @@ -148,24 +244,37 @@ public function updateContentDocument($translatable_object) { $parameters['documentId'] = $translatable_object->getMetadataValue('document_id'); $parameters['documentName'] = $translatable_object->getDocumentName(); $parameters['documentDesc'] = $translatable_object->getDescription(); - $parameters['content'] = $translatable_object->documentLingotekXML(); + $parameters['content'] = $this->check_url_alias_translation($translatable_object, $translatable_object->documentLingotekXML()); $parameters['url'] = $translatable_object->getUrl(); $parameters['format'] = $this->xmlFormat(); - $this->addAdvancedParameters($parameters, $translatable_object); + // If the document has invalid characters, return without uploading + $invalid_xml = lingotek_keystore($translatable_object->getEntityType(), $translatable_object->getId(), 'invalid_xml'); + if ($invalid_xml == LingotekSync::INVALID_XML_PRESENT) { + drupal_set_message(t('Unable to upload to Lingotek because entity contains invalid XML characters.'), 'warning'); + return FALSE; + } + $this->addAdvancedParameters($parameters, $translatable_object); $result = $this->request('updateContentDocumentAsync', $parameters); if ($result) { if (get_class($translatable_object) == 'LingotekConfigSet') { - $translatable_object->setStatus(LingotekSync::STATUS_CURRENT); - $translatable_object->setTargetsStatus(LingotekSync::STATUS_PENDING); - - // WTD: there is a race condition here where a user could modify a locales- - // source entry between the time the dirty segments are pulled and the time - // they are set to current at this point. This same race condition exists - // for nodes as well; however, the odds may be lower due to number of entries. - LingotekConfigSet::setSegmentStatusToCurrentById($translatable_object->getId()); + if ($result->results === 'success') { + $translatable_object->setStatus(LingotekSync::STATUS_CURRENT); + $translatable_object->setTargetsStatus(LingotekSync::STATUS_PENDING); + $translatable_object->deleteUploadError(); + // WTD: there is a race condition here where a user could modify a locales- + // source entry between the time the dirty segments are pulled and the time + // they are set to current at this point. This same race condition exists + // for nodes as well; however, the odds may be lower due to number of entries. + LingotekConfigSet::setSegmentStatusToCurrentById($translatable_object->getId()); + } + else { + $translatable_object->setStatus(LingotekSync::STATUS_ERROR); + $translatable_object->setTargetsStatus(LingotekSync::STATUS_EDITED); + $translatable_object->setUploadError('Failed to update config item.'); + } } } @@ -203,7 +312,6 @@ public function addTranslationTarget($lingotek_document_id, $lingotek_project_id global $_lingotek_client, $_lingotek_locale; $parameters = array( - 'applyWorkflow' => 'true', // Ensure that as translation targets are added, the associated project's Workflow template is applied. 'targetLanguage' => $lingotek_locale ); @@ -217,6 +325,10 @@ public function addTranslationTarget($lingotek_document_id, $lingotek_project_id if ($workflow_id) { $parameters['workflowId'] = $workflow_id; } + else { + // The associated project's Workflow template should be applied. + $parameters['applyWorkflow'] = 'true'; + } if ($new_translation_target = $this->request('addTranslationTarget', $parameters)) { // If the request went through, there was no OAuth error and we should enable. @@ -229,9 +341,9 @@ public function addTranslationTarget($lingotek_document_id, $lingotek_project_id * Removes a target language to an existing Lingotek Document or Project. * * @param int $lingotek_document_id - * The document to which the new translation target should be added. Or null if the target will be added to the project. + * The document from which the translation target should be removed. Or null if the target will be removed from the project. * @param int $lingotek_project_id - * The project to which the new translation target should be added. Or null if the target will be added to a document instead. + * The project from which the translation target should be removed. Or null if the target will be removed from a document instead. * @param string $lingotek_locale * The two letter code representing the language which should be added as a translation target. * @param string $workflow_id @@ -241,10 +353,9 @@ public function addTranslationTarget($lingotek_document_id, $lingotek_project_id * @return bool * TRUE on success, or FALSE on error. */ - public function removeTranslationTarget($lingotek_document_id, $lingotek_project_id, $lingotek_locale, $workflow_id = '') { + public function removeTranslationTarget($lingotek_document_id, $lingotek_project_id, $lingotek_locale) { $parameters = array( - 'applyWorkflow' => 'true', // Ensure that as translation targets are added, the associated project's Workflow template is applied. 'targetLanguage' => $lingotek_locale ); @@ -255,10 +366,6 @@ public function removeTranslationTarget($lingotek_document_id, $lingotek_project $parameters['projectId'] = $lingotek_project_id; } - if ($workflow_id) { - $parameters['workflowId'] = $workflow_id; - } - if ($old_translation_target = $this->request('removeTranslationTarget', $parameters)) { return ( $old_translation_target->results == 'success' ) ? TRUE : FALSE; } @@ -309,7 +416,7 @@ public function currentPhase($translation_target_id) { * A Lingotek language/locale code. * * @return mixed - * On success, a SimpleXMLElement object representing the translated document. FALSE on failure. + * On success, a LingotekXMLElement object representing the translated document. FALSE on failure. * */ public function downloadDocument($document_id, $lingotek_locale) { @@ -350,7 +457,7 @@ public function downloadDocument($document_id, $lingotek_locale) { unlink($tmpFile); - $document = new SimpleXMLElement($text); + $document = new LingotekXMLElement($text); } catch (Exception $e) { LingotekLog::error('Unable to parse downloaded document. Error: @error. Text: !xml.', array('!xml' => $text, '@error' => $e->getMessage())); } @@ -411,7 +518,7 @@ public function getDocumentProgress($document_id) { return $document; } - /** + /** * Gets the workflow progress of the specified documents. * * @param int $document_id @@ -429,11 +536,26 @@ public function listDocumentProgress($document_ids) { return $documents; } + /** + * Gets the Target Tranlsations of the specified document. + * + * @param int $document_id + * The ID of the Lingotek Document to retrieve. + * + * @return mixed + * The API response object with Lingotek Document data, or FALSE on error. + */ + public function listTranslationTargets($document_id) { + $params['documentId'] = $document_id; + $targetTranslations = $this->request('listTranslationTargets', $params); + return $targetTranslations; + } + /** * Gets the workflow progress of the specified project (or list of document ids). * * @param int $project_id - * + * * @param array $document_ids * An array of document IDs of the Lingotek Document to retrieve. * @@ -637,7 +759,7 @@ public function getWorkbenchLink($document_id, $phase_id) { $links = &drupal_static(__FUNCTION__); global $user; - $externalId = isset($user->name) ? $user->name : ''; // send a blank string for anonymous users (community translation) + $externalId = isset($user->mail) ? $user->mail : ''; // send a blank string for anonymous users (community translation) self::checkUserWorkbenchLinkPermissions($externalId); $static_id = $document_id . '-' . $phase_id; @@ -664,10 +786,10 @@ public function getWorkbenchLink($document_id, $phase_id) { /** * Gets available Lingotek projects. - * + * * @param $reset * A boolean value to determin whether we need to query the API - * + * * @return array * An array of available projects with project IDs as keys, project labels as values. */ @@ -677,7 +799,7 @@ public function listProjects($reset = FALSE) { if (!empty($projects) && $reset == FALSE) { return $projects; } - + if ($projects_raw = $this->request('listProjects')) { $projects = array(); foreach ($projects_raw->projects as $project) { @@ -691,12 +813,12 @@ public function listProjects($reset = FALSE) { /** * Gets available Lingotek Workflows. - * + * * @param $reset * A boolean value to determine whether we need to query the API * @param $include_public * A boolean value to determine whether to show public workflows - * + * * @return array * An array of available Workflows with workflow IDs as keys, workflow labels as values. */ @@ -709,7 +831,7 @@ public function listWorkflows($reset = FALSE, $include_public = FALSE) { if ($workflows_raw = $this->request('listWorkflows')) { $workflows = array(); foreach ($workflows_raw->workflows as $workflow) { - if ($include_public || (!$workflow->is_public + if ($include_public || (!$workflow->is_public && $workflow->owner != LINGOTEK_DEFAULT_WORKFLOW_TEMPLATE)) $workflows[$workflow->id] = $workflow->name; } @@ -721,12 +843,12 @@ public function listWorkflows($reset = FALSE, $include_public = FALSE) { /** * Gets Lingotek Workflow by ID - * + * * @param $id * a string containing the workflow ID * @param $reset * A boolean value to determine whether we need to query the API - * + * * @return array * An array of workflow details */ @@ -745,10 +867,10 @@ public function getWorkflow($id, $reset = FALSE) { /** * Gets available Lingotek Translation Memory Vaults. - * + * * @param $reset * A boolean value to determin whether we need to query the API - * + * * @return array * An array of available vaults. */ @@ -786,14 +908,14 @@ public function listVaults($reset = FALSE, $show_public_vaults = FALSE) { /** * Updates one or more nids to belong to a given workflow - * + * * @param array $document_ids * An array of document IDs * @param string $workflow_id * A string containing the desired workflow_id * @param string $prefillPhase * An optional parameter specifying the prefill phase - * + * * @return bool * TRUE on success, FALSE on failure. */ @@ -901,7 +1023,7 @@ public function request($method, $parameters = array(), $request_method = 'POST' $result = $request->doRequest(0, array(CURLOPT_SSL_VERIFYPEER => FALSE)); $response = json_decode($result['body']); } catch (OAuthException2 $e) { - LingotekLog::error('Failed OAuth request.
      Method: @method
      Message: @message + LingotekLog::error('Failed OAuth request.
      Method: @method
      Message: @message
      API URL: @url
      Parameters: !params.
      Response: !response', array( @@ -913,7 +1035,7 @@ public function request($method, $parameters = array(), $request_method = 'POST' } $timer_results = timer_stop($timer_name); - + // cleanup parameters so that the logs aren't too long if(isset($parameters['fprmFileContents'])) { $parameters['fprmFileContents'] = 'removed for brevity'; @@ -963,7 +1085,7 @@ public function request($method, $parameters = array(), $request_method = 'POST' public function createCommunity($parameters = array(), $callback_url = NULL) { $credentials = array('consumer_key' => LINGOTEK_AP_OAUTH_KEY, 'consumer_secret' => LINGOTEK_AP_OAUTH_SECRET); if (isset($callback_url)) { - $parameters['projectName'] = lingotek_get_site_name(); + $parameters['projectName'] = lingotek_get_site_name(); $parameters['callbackUrl'] = $callback_url; } $response = $this->request('autoProvisionCommunity', $parameters, 'POST', $credentials); @@ -1022,7 +1144,7 @@ private function __construct() { * Private clone implementation. */ private function __clone() { - + } } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekConfigSet.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekConfigSet.php index 4fa7b68f..acece03f 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekConfigSet.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekConfigSet.php @@ -47,7 +47,7 @@ class LingotekConfigSet implements LingotekTranslatableEntity { * A static flag for content updates. */ protected static $content_update_in_progress = FALSE; - + protected $workflow_id = null; /** * Constructor. * @@ -62,9 +62,10 @@ private function __construct($set_id = NULL) { $this->source_data = self::getAllSegments($this->sid); $this->source_meta = self::getSetMeta($this->sid); $this->language = language_default(); - if (!isset($this->language->lingotek_locale)) { // if Drupal variable 'language_default' does not exist - $this->language->lingotek_locale = Lingotek::convertDrupal2Lingotek($this->language->language); - } + // INT-791 Respecting the i18n_string_source_language setting + $i18n_language = variable_get('i18n_string_source_language', language_default()->language); + $this->language->language = $i18n_language; + $this->language->lingotek_locale = Lingotek::convertDrupal2Lingotek($this->language->language); $this->language_targets = Lingotek::getLanguagesWithoutSource($this->language->lingotek_locale); } @@ -193,6 +194,43 @@ public static function getSetId($lid, $assign = TRUE) { return $existing_sid; } + public static function bulkGetSetId($lid_map){ + $set_ids = array(); + foreach($lid_map as $textgroup => $lids){ + $open_set_id = self::getOpenSet($textgroup); + if ($open_set_id === FALSE) { + $open_set_id = self::createSet($textgroup); + } + + $query = db_select('lingotek_config_map', 'lcm'); + $query->addField('lcm', 'set_id'); + $query->addField('lcm', 'lid'); + $query->condition('lid', $lids, "IN"); + $result = $query->execute()->fetchAllAssoc('lid'); + + $insert = db_insert('lingotek_config_map'); + $insert->fields(array('lid','set_id')); + + $count = 0; + foreach($lids as $lid){ + if($count === LINGOTEK_CONFIG_SET_SIZE){ + $open_set_id = self::createSet($textgroup); + $count = 0; + } + if(!isset($result[$lid])){ + $insert->values(array('lid'=>$lid,'set_id'=>$open_set_id)); + $set_ids[$open_set_id] = $open_set_id; + } + else { + $set_ids[$result[$lid]->set_id] = $result[$lid]->set_id; + } + $count++; + } + $insert->execute(); + } + return $set_ids; + } + protected static function assignSetId($lid) { // get the $lid's textgroup $textgroup = db_select('locales_source', 'l') @@ -279,9 +317,9 @@ protected static function createSet($textgroup) { } protected static function getNextSetId() { - $query = db_select('lingotek_config_metadata', 'lcm'); - $query->addExpression('MAX(id)'); - $max_set_id = $query->execute()->fetchField(); + $query = db_query('SELECT max(id) FROM lingotek_config_metadata WHERE id < :max_size', array(':max_size' => LingotekSync::MARKED_OFFSET)); + $max_set_id = $query->fetchField(); + if ($max_set_id) { return (int) $max_set_id + 1; } @@ -307,6 +345,25 @@ public static function getAllConfigDocIds() { return $doc_ids; } + public static function getAllUnsetWorkflowConfigDocIds() { + $setWorkflowSetIds = db_select('lingotek_config_metadata', 'lcm') + ->fields('lcm', array('id')) + ->condition('config_key', 'workflow_id'); + $doc_ids = db_select('lingotek_config_metadata', 'l') + ->fields('l', array('value')) + ->condition('config_key', 'document_id') + ->condition('id', $setWorkflowSetIds, 'NOT IN') + ->execute() + ->fetchCol(); + return $doc_ids; + } + + public static function deleteConfigSetWorkflowIds(){ + db_delete('lingotek_config_metadata') + ->condition('config_key', 'workflow_id', '=') + ->execute(); + } + public function getSourceLocale() { return $this->language->lingotek_locale; } @@ -437,7 +494,7 @@ protected static function getAllSegments($set_id) { $response[$r['lid']] = $r['source']; } else { - LingotekLog::warning("Config item @id was not sent to Lingotek for translation because it exceeds the max length of 4096 characters.", array('@id' => $r['lid'])); + LingotekLog::warning("Config item @id was not sent to Lingotek for translation because it exceeds the max length of @max_length characters.", array('@id' => $r['lid'], '@max_length' => $max_length)); // Remove it from the set in the config_map table so it doesn't get marked as uploaded or translated. self::disassociateSegments($r['lid']); } @@ -481,6 +538,21 @@ public static function getLidsByStatus($status) { $lids = self::getLidsFromSets($set_ids); return $lids; } + + public static function getSetIdsByStatus($status, $lids = null) { + $query = db_select('lingotek_config_metadata', 'l'); + if($lids !== null) { + $query->join('lingotek_config_map', 'lc', 'l.id = lc.set_id'); + $query->condition('lc.lid', $lids, 'IN'); + } + $query->fields('l', array('id')); + $query->condition('l.config_key', 'target_sync_status_%', 'LIKE'); + $query->condition('l.value', $status); + $query->distinct(); + $result = $query->execute(); + $set_ids = $result->fetchCol(); + return $set_ids; + } /** * Return any metadata for the given set ID, if it exists @@ -530,65 +602,8 @@ public static function loadByLingotekDocumentId($lingotek_document_id) { if (isset($set_id)) { $set = self::loadById($set_id); } - - return $set; - } - - /** - * Event handler for updates to the config set's data. - */ - public function contentUpdated() { - - $metadata = $this->metadata(); - if (empty($metadata['document_id'])) { - $this->createLingotekDocument(); - } - else { - $update_result = $this->updateLingotekDocument(); - } - - // Synchronize the local content with the translations from Lingotek. - // We instruct the users to configure config set translation with a - // single-phase machine translation-only Workflow, so the updated content - // should be available right after our create/update document calls from above. - // If it isn't, Lingotek will call us back via LINGOTEK_NOTIFICATIONS_URL - // when machine translation for the item has finished. - if (!self::$content_update_in_progress) { - // Only update the local content if the Document is 100% complete - // according to Lingotek. - - $document_id = $this->getMetadataValue('document_id'); - if ($document_id) { - $document = $this->api->getDocument($document_id); - if (!empty($document->percentComplete) && $document->percentComplete == 100) { - $this->updateLocalContent(); - } - } - else { - LingotekLog::error('Unable to retrieve Lingotek Document ID for config set @id', array('@id' => $this->sid)); - } - } - } - /** - * Creates a Lingotek Document for this config set. - * - * @return bool - * TRUE if the document create operation was successful, FALSE on error. - */ - protected function createLingotekDocument() { - return ($this->api->addContentDocumentWithTargets($this)) ? TRUE : FALSE; - } - - /** - * Updates the existing Lingotek Documemnt for this config set. - * - * @return bool - * TRUE if the document create operation was successful, FALSE on error. - */ - protected function updateLingotekDocument() { - $result = $this->api->updateContentDocument($this); - return ($result) ? TRUE : FALSE; + return $set; } /** @@ -620,7 +635,7 @@ protected function metadata() { public function hasLingotekDocId() { $has_id = array_key_exists('document_id', $this->source_meta); if ($has_id && (strlen($this->source_meta['document_id']) > 0)) { - return TRUE; + return $this->source_meta['document_id']; } return FALSE; } @@ -662,18 +677,47 @@ public function setStatus($status) { } /** - * Assign the set's last error in the config metadata table + * Assign the set's last sync error in the config metadata table */ public function setLastError($errors) { $this->setMetadataValue('last_sync_error', substr($errors, 0, 255)); return $this; } + /** + * Assign the set's last upload_error in the config metadata table + */ + public function setUploadError($errors) { + $this->setMetadataValue('upload_error', substr($errors, 0, 255)); + return $this; + } + + /** + * Delete the set's last error in the config metadata table + */ + public function deleteLastError() { + $this->deleteMetadataValue('last_sync_error'); + return $this; + } + + /** + * Delete the set's last upload_error in the config metadata table + */ + public function deleteUploadError() { + $this->deleteMetadataValue('upload_error'); + return $this; + } + /** * Assign the set's target status(es) in the config metadata table */ - public function setTargetsStatus($status, $lingotek_locale = 'all') { - if ($lingotek_locale != 'all') { + public function setTargetsStatus($status, $lingotek_locale = NULL) { + if (is_array($lingotek_locale)) { + foreach ($lingotek_locale as $ll) { + $this->setMetadataValue('target_sync_status_' . $ll, $status); + } + } + elseif (is_string($lingotek_locale) && !empty($lingotek_locale)) { $this->setMetadataValue('target_sync_status_' . $lingotek_locale, $status); } else { // set status for all available targets @@ -768,10 +812,10 @@ public function setMetadataValue($key, $value) { ->execute(); } } - + /** * Deletes a Lingotek metadata value for this item - * + * * @param string $key * The key for a name/value pair */ @@ -962,6 +1006,7 @@ public static function getLidsToUpdate($current = 0, $lids = 'all') { $query->condition('lcm.lid', $lids, 'IN'); } $query->join('locales_source', 'ls', "lcm.lid = ls.lid"); + $query->addField('ls', 'textgroup'); $query->condition('ls.textgroup', $textgroups, 'IN'); $query->join('locales_target', 'lt', "lcm.lid = lt.lid"); @@ -969,11 +1014,37 @@ public static function getLidsToUpdate($current = 0, $lids = 'all') { $or->condition('lcm.current', $current); $or->condition('lt.i18n_status', 1); $query->condition($or); - - $lids = $query->execute()->fetchCol(); - return array_unique($lids); + + $results = $query->execute(); + $lids = array(); + foreach($results as $result){ + $lids[$result->textgroup][$result->lid] = $result->lid; + } + return $lids; + } + /** + * Check a given list for lids that have never been uploaded + * @param type $control_list + * The list of lids to search through + * @return type + */ + public static function findNeverUploadedLids($control_list = NULL){ + if($control_list !== NULL && !empty($control_list)){ + $query = db_select('locales_source','ls'); + $query->leftJoin('locales_target','lt','ls.lid = lt.lid'); + $query->isNull("lt.lid"); + $query->addField('ls', 'lid'); + $query->addField('ls',"textgroup"); + $query->condition('ls.lid',$control_list, 'IN'); + $never_uploaded_lids = $query->execute(); + $textgroup_lid = array(); + foreach($never_uploaded_lids as $lid){ + $textgroup_lid[$lid->textgroup][$lid->lid] = $lid->lid; + } + return $textgroup_lid; + } + return array(); } - /** * Delete all target segments for a given set * @@ -1009,6 +1080,34 @@ public static function disassociateSegments($lids) { ->execute(); } + public static function deleteConfigSetMetadataBySetId($set_ids) { + db_delete('lingotek_config_metadata') + ->condition('id', $set_ids) + ->execute(); + } + + public static function deleteConfigSetMapDataBySetId($set_ids) { + db_delete('lingotek_config_map') + ->condition('set_id', $set_ids) + ->execute(); + } + + public static function removeEmptyConfigSets($set_ids) { + foreach($set_ids as $set_id) { + $set_members = db_select('lingotek_config_map', 'lcm') + ->fields('lcm', array('lid')) + ->condition('set_id', $set_ids, 'IN') + ->execute() + ->fetchAll(); + + if (empty($set_members)) { // The set is empty, so delete it. + db_delete('lingotek_config_metadata') + ->condition('id', $set_id) + ->execute(); + } + } + } + /** * Get lingotek translation agent ID */ @@ -1041,7 +1140,7 @@ protected static function getNonLingotekLocalesTargets($document_xml, $target_la * Save segment target translations for the given language * * @param obj - * the SimpleXMLElement object containing the translations to be saved + * the LingotekXMLElement object containing the translations to be saved * @param string * the language code under which to save the translations */ @@ -1097,7 +1196,7 @@ public function lingotekDocumentId() { public function getEntityType() { return self::DRUPAL_ENTITY_TYPE; } - + /** * Magic get for access to set and set properties. */ @@ -1123,12 +1222,19 @@ protected static function getLidFromTag($tag) { } public function getWorkflowId() { - $profiles = variable_get('lingotek_profiles'); - $config_profile = $profiles[LingotekSync::PROFILE_CONFIG]; - $workflow_id = array_key_exists('workflow_id', $config_profile) ? $config_profile['workflow_id'] : variable_get('lingotek_translate_config_workflow_id', ''); + if($this->workflow_id !== null){ + $workflow_id = $this->workflow_id; + } + else { + $profiles = variable_get('lingotek_profiles'); + $config_profile = $profiles[LingotekSync::PROFILE_CONFIG]; + $workflow_id = array_key_exists('workflow_id', $config_profile) ? $config_profile['workflow_id'] : variable_get('lingotek_translate_config_workflow_id', ''); + } return $workflow_id; } - + public function setWorkflowId($workflow_id) { + $this->workflow_id = $workflow_id; + } public function getProjectId() { $profiles = variable_get('lingotek_profiles'); $config_profile = $profiles[LingotekSync::PROFILE_CONFIG]; @@ -1166,12 +1272,15 @@ public static function getTextgroupsForTranslation() { if (variable_get('lingotek_translate_config_fields', 0)) { $textgroups[] = 'field'; } + if (variable_get('lingotek_translate_config_webform', 0)) { + $textgroups[] = 'webform'; + } if (variable_get('lingotek_translate_config_misc', 0)) { $textgroups[] = 'misc'; } return $textgroups; } - + public static function getLidBySource($source_string) { return db_select('locales_source', 's') ->fields('s', array('lid')) @@ -1200,7 +1309,7 @@ public function getDocumentName() { public function getUrl() { return ''; } - + public function getNote() { return ''; } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekDocument.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekDocument.php index d11f798f..3a6d7178 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekDocument.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekDocument.php @@ -145,7 +145,7 @@ public function getImportStatus($include_details = TRUE) { $response = $this->api->request('getDocumentImportStatus', array('id' => $this->document_id)); $status = self::IMPORT_STATUS__UNKNOWN; $status_message = $status; - if ($response->results == 'success') { + if ($response && $response->results == 'success') { $status = $response->status; if ($status === self::IMPORT_STATUS__PROCESSING) { $status_message = $status . " (" . $response->process->percentComplete . "% complete)"; @@ -157,7 +157,7 @@ public function getImportStatus($include_details = TRUE) { $status_message = $status; } } - elseif ($response->results == 'fail') { + elseif ($response && $response->results == 'fail') { $status = self::IMPORT_STATUS__ERROR; $status_message = $status . " (" . $response->error . ")"; } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekEntity.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekEntity.php index fedb6b1f..b5fe4888 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekEntity.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekEntity.php @@ -21,6 +21,11 @@ class LingotekEntity implements LingotekTranslatableEntity { */ protected $entity_type; + /** + * The Drupal entity id + */ + protected $entity_id; + /** * The title of the document */ @@ -46,6 +51,7 @@ class LingotekEntity implements LingotekTranslatableEntity { private function __construct($entity, $entity_type) { $this->entity = $entity; $this->entity_type = $entity_type; + $this->entity_id = $this->getId(); $this->info = entity_get_info($this->entity_type); if (!empty($entity->language_override)) { $this->setLanguage($entity->language_override); @@ -203,27 +209,29 @@ public function getMetadataValue($key) { */ public function setMetadataValue($key, $value) { $metadata = $this->metadata(); + $entity_type = $this->getEntityType(); + $entity_id = $this->getId(); if (!isset($metadata[$key])) { db_insert('lingotek_entity_metadata') ->fields(array( - 'entity_id' => $this->getId(), - 'entity_type' => $this->getEntityType(), + 'entity_id' => $entity_id, + 'entity_type' => $entity_type, 'entity_key' => $key, 'value' => $value, )) ->execute(); - } else { db_update('lingotek_entity_metadata') ->fields(array( 'value' => $value )) - ->condition('entity_id', $this->getId()) - ->condition('entity_type', $this->getEntityType()) + ->condition('entity_id', $entity_id) + ->condition('entity_type', $entity_type) ->condition('entity_key', $key) ->execute(); } + lingotek_cache_clear($entity_type, $entity_id); } /** @@ -235,11 +243,14 @@ public function setMetadataValue($key, $value) { public function deleteMetadataValue($key) { $metadata = $this->metadata(); if (isset($metadata[$key])) { + $entity_type = $this->getEntityType(); + $entity_id = $this->getId(); db_delete('lingotek_entity_metadata') - ->condition('entity_id', $this->getId()) - ->condition('entity_type', $this->getEntityType()) - ->condition('entity_key', $key, 'LIKE') - ->execute(); + ->condition('entity_id', $entity_id) + ->condition('entity_type', $entity_type) + ->condition('entity_key', $key, 'LIKE') + ->execute(); + lingotek_cache_clear($entity_type, $entity_id); } } @@ -295,6 +306,7 @@ public function getTitle() { array('@entity_type' => $this->entity_type, '@entity_id' => $this->entity_id)); $this->title = $this->entity_type . " #" . $this->entity_id; } + return $this->title; } @@ -339,6 +351,22 @@ public function getSourceLocale() { return 'en_US'; } } + if ($this->entity_type == 'bean') { + // Assume all block entities are created in the site's default language. + return Lingotek::convertDrupal2Lingotek(language_default()->language); + } + if ($this->entity_type == 'group') { + $group_language = lingotek_get_group_source($this->entity->gid); + return Lingotek::convertDrupal2Lingotek($group_language); + } + if ($this->entity_type == 'paragraphs_item') { + $paragraphs_language = lingotek_get_paragraphs_item_source($this->entity->item_id); + return Lingotek::convertDrupal2Lingotek($paragraphs_language); + } + if ($this->entity_type == 'file') { + $file_language = lingotek_get_file_source($this->entity->fid); + return Lingotek::convertDrupal2Lingotek($file_language); + } return Lingotek::convertDrupal2Lingotek($this->language); } @@ -353,17 +381,25 @@ public function getNote() { public function getUrl() { global $base_url; - if ($this->entity_type == 'node' || $this->entity_type == 'comment') { + $path = entity_uri($this->entity_type, $this->entity); + $url = ''; + + if ($path) { $hack = (object) array('language' => ''); // this causes the url function to not prefix the url with the current language the user is viewing the site in - return $base_url . "/lingotek/view/" . $this->getEntityType() . '/' . $this->getId() . '/{locale}'; + $url = $base_url . "/lingotek/view/" . $this->getEntityType() . '/' . $this->getId() . '/{locale}'; } - return ''; + + drupal_alter('lingotek_source_URL', $url); + return $url; } public function preDownload($lingotek_locale, $completed) { if ($completed) { lingotek_keystore($this->getEntityType(), $this->getId(), 'target_sync_status_' . $lingotek_locale, LingotekSync::STATUS_READY); } + else{ + lingotek_keystore($this->getEntityType(), $this->getId(), 'target_sync_status_' . $lingotek_locale, LingotekSync::STATUS_READY_INTERIM); + } } public function postDownload($lingotek_locale, $completed) { @@ -392,8 +428,16 @@ public function setLastError($errors) { return $this; } - public function setTargetsStatus($status, $lingotek_locale = 'all') { - if ($lingotek_locale != 'all') { + /** + * Assign the entity's target status(es) in the config metadata table + */ + public function setTargetsStatus($status, $lingotek_locale = NULL) { + if (is_array($lingotek_locale)) { + foreach ($lingotek_locale as $ll) { + $this->setMetadataValue('target_sync_status_' . $ll, $status); + } + } + elseif (is_string($lingotek_locale) && !empty($lingotek_locale)) { $this->setMetadataValue('target_sync_status_' . $lingotek_locale, $status); } else { // set status for all available targets @@ -410,12 +454,26 @@ public function setTargetsStatus($status, $lingotek_locale = 'all') { */ public function setLanguage($language = NULL) { if (empty($language)) { - $drupal_locale = Lingotek::convertDrupal2Lingotek($this->entity->language); - if (!empty($this->entity->lingotek['allow_source_overwriting']) && !empty($this->entity->lingotek['source_language_' . $drupal_locale])) { - $language = $this->entity->lingotek['source_language_' . $drupal_locale]; + if ($this->entity_type == 'bean') { + $language = language_default()->language; + } + elseif ($this->entity_type == 'group') { + $language = lingotek_get_group_source($this->entity->gid); + } + elseif ($this->entity_type == 'paragraphs_item') { + $language = lingotek_get_paragraphs_item_source($this->entity->item_id); + } + elseif ($this->entity_type == 'file') { + $language = lingotek_get_file_source($this->entity->fid); } else { - $language = $this->entity->language; + $drupal_locale = Lingotek::convertDrupal2Lingotek($this->entity->language); + if (!empty($this->entity->lingotek['allow_source_overwriting']) && !empty($this->entity->lingotek['source_language_' . $drupal_locale])) { + $language = $this->entity->lingotek['source_language_' . $drupal_locale]; + } + else { + $language = $this->entity->language; + } } } $this->language = $language; diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekException.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekException.php new file mode 100644 index 00000000..0e362199 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekException.php @@ -0,0 +1,8 @@ +setId($profile_id); + $this->setInherit(TRUE); + $this->refresh(); + + if (empty(self::$global_profile)) { + self::$global_profile = lingotek_get_global_profile(); + } + + if ($profile_id === LingotekSync::PROFILE_DISABLED || $profile_id === LingotekSync::PROFILE_ENABLED) { + $this->setName($profile_id); + return $this; + } + if ($profile_id === LingotekSync::PROFILE_INHERIT) { + $this->setName($profile_id); + return LingotekSync::PROFILE_INHERIT; + } + if (empty(self::$profiles[$profile_id]) && !empty($profile_attributes)) { + // create one on the fly + $unique_attributes = array(); + foreach ($profile_attributes as $key => $value) { + if (empty(self::$global_profile[$key]) || self::$global_profile[$key] !== $value) { + $unique_attributes[$key] = $value; + } + } + self::$profiles[$profile_id] = $unique_attributes; + $this->save(); + } + // A convenience reference to the current profile. + $this->profile = &self::$profiles[$profile_id]; + } + + public static function create($profile_id, array $profile_attributes) { + if (isset(self::$profiles[$profile_id])) { + throw new LingotekException('Unable to create profile "' . $profile_id . '": profile already exists.'); + } + return new LingotekProfile($profile_id, $profile_attributes); + } + + public static function update($profile_id, array $profile_attributes) { + if (!isset(self::$profiles[$profile_id])) { + throw new LingotekException('Unable to update profile "' . $profile_id . '": profile does not exist.'); + } + $profile = self::loadById($profile_id); + foreach ($profile_attributes as $key => $value) { + if (!empty(self::$global_profile[$key]) && self::$global_profile[$key] === $value) { + // remove any attributes that are the same as the global ones + $profile->deleteAttribute($key); + } + else { + // keep any custom attributes + $profile->setAttribute($key, $value); + } + } + } + + public static function loadById($profile_id) { + return new LingotekProfile($profile_id); + } + + public static function loadByName($profile_name) { + $this->refresh(); + foreach (self::$profiles as $profile_id => $profile) { + if ($profile['name'] == $profile_name) { + return new LingotekProfile($profile_id); + } + } + throw new LingotekException('Unknown profile name: ' . $profile_name); + } + + public static function loadByBundle($entity_type, $bundle, $source_locale = NULL) { + $entity_profiles = variable_get('lingotek_entity_profiles', array()); + if (!empty($source_locale) && isset($entity_profiles[$entity_type][$bundle . '__' . $source_locale])) { + try { + $profile = new LingotekProfile($entity_profiles[$entity_type][$bundle . '__' . $source_locale]); + if ($profile->getName() == LingotekSync::PROFILE_INHERIT) { + $profile = new LingotekProfile($entity_profiles[$entity_type][$bundle]); + } + return $profile; + } + catch (Exception $e) { + // TODO: a debug statement perhaps saying there are no customizations for the given source locale? + } + } + if (isset($entity_profiles[$entity_type][$bundle])) { + return new LingotekProfile($entity_profiles[$entity_type][$bundle]); + } + return self::loadById(LingotekSync::PROFILE_DISABLED); + } + + public static function loadByEntity($entity_type, $entity) { + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + $result = db_select('lingotek_entity_metadata', 'l') + ->fields('l', array('value')) + ->condition('l.entity_id', $id) + ->condition('l.entity_type', $entity_type) + ->condition('l.entity_key', 'profile') + ->execute(); + if ($result) { + $profile_id = $result->fetchfield(); + if ($profile_id !== FALSE) { + return self::loadById($profile_id); + } + } + $source_locale = lingotek_entity_locale($entity_type, $entity); + return self::loadByBundle($entity_type, $bundle, $source_locale); + } + + // Return the profile ID for a given entity. + public static function getIdByEntity($entity_type, $entity) { + $profile = self::loadByEntity($entity_type, $entity); + return $profile->getId(); + } + + // @params TRUE or FALSE, depending on whether the profile should look for inherited attributes + // from the global profile + public function setInherit($inherit) { + $this->inherit = (bool) $inherit; + } + + public function lookForInherited() { + return $this->inherit; + } + + public function getId() { + return $this->profile_id; + } + + // IDs should either be auto-generated or special-case IDs, not configurable by the user + protected function setId($profile_id) { + $this->profile_id = $profile_id; + } + + // replaces lingotek_admin_profile_usage() in lingotek.admin.inc + // replaces lingotek_admin_profile_usage_by_types() in lingotek.admin.inc + public function getUsage($by_bundle = FALSE) { + if ($by_bundle) { + $bundles_using_profile = lingotek_get_bundles_by_profile_id($this->getId()); + $count_types = 0; + foreach ($bundles_using_profile as $bup) { + $count_types += count($bup); + } + return $count_types; + } + else { + /** + *This is a representation of the query and subquery we are building to get + *the usage for each profile. + *@author t.murphy, smithworx, jbhovik, clarticus + * + * + *SELECT count(*) as COUNT, entity_type as ENTITY_TYPE + *FROM lingotek_entity_metadata + *WHERE entity_key = 'profile' + *AND value = '' + *AND entity_id NOT IN + * (SELECT entity_id + * FROM lingotek_entity_metadata + * WHERE entity_key = 'upload_status' + * AND value = 'TARGET') + *GROUP BY entity_type; + * + */ + + + $subquery = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('entity_id')) + ->condition('lem.entity_key', 'upload_status') + ->condition('lem.value', 'TARGET'); + $entity_ids = $subquery->execute()->fetchCol(); + + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('entity_type')) + ->condition('lem.entity_key', 'profile') + ->condition('lem.value', $this->getId()); + + if (!empty($entity_ids)) { + $query->condition('lem.entity_id', $entity_ids, 'NOT IN'); + } + + + $query->groupBy('lem.entity_type'); + $query->addExpression('count(lem.entity_id)', 'COUNT'); + $entities = $query->execute()->fetchAll(); + + $entity_counts = array(); + foreach ($entities as $e) { + $entity_counts[$e->entity_type] = $e->COUNT; + } + return $entity_counts; + } + } + + public function getDocumentIds() { + $metadata_table = $this->getId() === LingotekSync::PROFILE_CONFIG ? 'lingotek_config_metadata' : 'lingotek_entity_metadata'; + $metadata_key_col = $this->getId() === LingotekSync::PROFILE_CONFIG ? 'config_key' : 'entity_key'; + $query = db_select($metadata_table, 't') + ->fields('t', array('value')) + ->condition('t.' . $metadata_key_col, 'document_id'); + return $query->execute()->fetchcol(); + } + + public function getBundles() { + $entities = entity_get_info(); + $lentities = variable_get('lingotek_entity_profiles'); + $bundles = array(); + foreach ($entities as $entity_name => $entity) { + if (!isset($lentities[$entity_name])) { + unset($entities[$entity_name]); + } + foreach ($entity['bundles'] as $bundle_name => $bundle) { + if (isset($lentities[$entity_name][$bundle_name]) && $lentities[$entity_name][$bundle_name] === (string)$this->getId()) { + if (!isset($bundles[$entity_name])) { + $bundles[$entity_name] = array(); + } + $bundles[$entity_name][$bundle_name] = TRUE; + } + } + } + return $bundles; + } + + public function getEntities($entity_type = NULL) { + if (!empty($entity_type)) { + // get all bundles that belong to the given profile + $all_bundles = $this->getBundles(); + $bundles = array(); + $entities = array(); + + if (isset($all_bundles[$entity_type])) { + $bundles = array($entity_type => $all_bundles[$entity_type]); + } + + // get all entities that belond to those bundles + foreach ($bundles as $entity_type => $entity_bundles) { + if ($entity_type == 'comment') { + $ref_tables = array(); + foreach (array_keys($entity_bundles) as $key) { + $tmp_array = explode('_', $key); + $key = implode('_', array_slice($tmp_array, 2)); + $ref_tables[] = $key; + } + $query = db_select('' . $entity_type . '', 'e') + ->fields('e', array('cid')); + $query->join('node', 'n', "n.nid = e.nid AND n.type IN ('" . implode("','", $ref_tables) . "')"); + $results = $query->execute()->fetchCol(); + foreach ($results as $id) { + $entities[] = array('id' => $id, 'type' => $entity_type); + } + } + else { + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $entity_type) + ->entityCondition('bundle', array_keys($entity_bundles), 'IN'); + $result = $query->execute(); + unset($query); + if (isset($result[$entity_type])) { + foreach ($result[$entity_type] as $id => $entity_data) { + $entities[] = array('id' => $id, 'type' => $entity_type); + } + } + } + // END OPTIMIZED WAY + } + + // subtract all entities specifically *not* set to the given profile + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('entity_id', 'entity_type')) + ->condition('lem.entity_key', 'profile') + ->condition('lem.value', $this->getId(), '!=') + ->condition('lem.entity_type', $entity_type); + $result = $query->execute(); + $subtract_entity_ids = $result->fetchAll(); + + $doc_ids = lingotek_get_document_id_tree(); + $subtractions = array(); + foreach ($subtract_entity_ids as $sei) { + if (!isset($subtractions[$sei->entity_type])) { + $subtractions[$sei->entity_type] = array(); + } + $subtractions[$sei->entity_type][$sei->entity_id] = TRUE; + } + $filtered_entities = array(); + foreach ($entities as $e) { + if (!isset($subtractions[$e['type']][$e['id']])) { + if (isset($doc_ids[$e['type']][$e['id']])) { + $e['document_id'] = $doc_ids[$e['type']][$e['id']]; + } + $filtered_entities[$e['id']] = $e; + } + } + + // add all entities specifically set to the given profile + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('entity_id', 'entity_type')) + ->condition('lem.entity_key', 'profile') + ->condition('lem.value', $this->getId()); + if ($entity_type != 'all') { + $query->condition('lem.entity_type', $entity_type); + } + $result = $query->execute(); + $add_entity_ids = $result->fetchAll(); + foreach ($add_entity_ids as $aei) { + $addition = array('id' => $aei->entity_id, 'type' => $aei->entity_type); + if (isset($doc_ids[$aei->entity_type][$aei->entity_id])) { + $addition['document_id'] = $doc_ids[$aei->entity_type][$aei->entity_id]; + } + $filtered_entities[$aei->entity_id] = $addition; + } + return $filtered_entities; + } + else { // GATHER LIST OF ENTITIES AND RECURSE + + // gather all bundles for searching, as some entities may be one-offs + // even though Lingotek is not enabled for the entire bundle. + self::$profiles[LingotekSync::PROFILE_DISABLED] = TRUE; + // TODO: CREATE SOME KIND OF FOREACH LOOP + $all_bundles = lingotek_get_bundles_by_profile_id(array_keys(self::$profiles)); + $all_entities = array(); + // aggregate all entity-type-specific results into a single numbered array + foreach (array_keys($all_bundles) as $entity_type) { + $entities = $this->getEntities($entity_type); + foreach ($entities as $e) { + $all_entities[] = $e; + } + } + return $all_entities; + } + } + + public function save() { + if ($this->getId() !== LingotekSync::PROFILE_DISABLED && $this->getId() !== LingotekSync::PROFILE_ENABLED) { + variable_set('lingotek_profiles', self::$profiles); + } + } + + public function refresh() { + self::$profiles = variable_get('lingotek_profiles', array()); + } + + public function delete() { + if (!$this->isProtected()) { + if ($this->getEntities()) { + throw new LingotekException('Unable to delete profile "@name": profile not empty.', array('@name' => $this->getName())); + } + unset(self::$profiles[$this->getId()]); + variable_set('lingotek_profiles', self::$profiles); + } + } + + public function getName() { + return $this->getAttribute('name'); + } + + public function setName($profile_name) { + $this->setAttribute('name', $profile_name); + } + + public function isAutoDownload($target_locale = NULL) { + return $this->getAttribute('auto_download', $target_locale); + } + + public function setAutoDownload($active, $target_locale = NULL) { + // Make sure only TRUE or FALSE is saved. + $active = (bool) $active; + $this->setAttribute('auto_download', $active, $target_locale); + } + + public function isNodeBased() { + $node_based = $this->getAttribute('lingotek_nodes_translation_method'); + if ($node_based == 'node') { + return TRUE; + } + return FALSE; + } + + public function setNodeBased($active) { + if ($active) { + $this->setAttribute('lingotek_nodes_translation_method', 'node'); + } + else { + $this->deleteAttribute('lingotek_nodes_translation_method'); + } + } + + public function getWorkflow($target_locale = NULL) { + return $this->getAttribute('workflow_id', $target_locale); + } + + public function setWorkflow($workflow_id, $target_locale = NULL) { + return $this->setAttribute('workflow_id', $workflow_id, $target_locale); + } + + public function getProjectId() { + return $this->getAttribute('project_id'); + } + + public function setProjectId($project_id) { + $this->setAttribute('project_id', $project_id); + } + + public function disableTargetLocale($target_locale) { + $this->deleteTargetLocaleOverrides($target_locale); + $this->setAttribute('disabled', TRUE, $target_locale); + } + + public function isTargetLocaleDisabled($target_locale) { + // don't check for disabled attributes in the parent profiles + $this->setInherit(FALSE); + $disabled = FALSE; + if ($this->getAttribute('disabled', $target_locale)) { + $disabled = TRUE; + } + $this->setInherit(TRUE); + return $disabled; + } + + public function isTargetLocaleCustom($target_locale) { + return !$this->isTargetLocaleDisabled($target_locale) && $this->getTargetLocaleOverrides($target_locale); + } + + public function toArray() { + return array_merge(self::$global_profile, $this->profile); + } + + protected function initTargetLocaleOverride($target_locale) { + if (!isset($this->profile['target_language_overrides'][$target_locale])) { + $this->profile['target_language_overrides'][$target_locale] = array(); + } + } + + public function getAttribute($attrib_name, $target_locale = NULL) { + if (!empty($target_locale) && isset($this->profile['target_language_overrides'][$target_locale][$attrib_name])) { + return $this->profile['target_language_overrides'][$target_locale][$attrib_name]; + } + elseif ($this->lookForInherited()) { + if (!empty($this->profile[$attrib_name])) { + return $this->profile[$attrib_name]; + } + elseif (!empty(self::$global_profile[$attrib_name])) { + return self::$global_profile[$attrib_name]; + } + } + return NULL; + } + + public function setAttribute($attrib_name, $value, $target_locale = NULL) { + + if ($this->isProtectedAttribute($attrib_name)) { + return; + } + + $original_value = $this->getAttribute($attrib_name, $value); + + if ($target_locale) { + // Set the language-specific attribute if different from the base attribute + if ($value !== $original_value) { + $this->initTargetLocaleOverride($target_locale); + $this->profile['target_language_overrides'][$target_locale][$attrib_name] = $value; + } + else { + $this->deleteAttribute($attrib_name, $target_locale); + // Clean up any empty language overrides + if (empty($this->profile['target_language_overrides'][$target_locale])) { + unset($this->profile['target_language_overrides'][$target_locale]); + } + } + } + else { + // Set the base attribute if different from the global attribute + if (isset(self::$global_profile[$attrib_name])) { + $original_value = self::$global_profile[$attrib_name]; + } + if ($value !== $original_value) { + $this->profile[$attrib_name] = $value; + } + else { + $this->deleteAttribute($attrib_name); + } + } + // Clean up target language attribute if empty + if (empty($this->profile['target_language_overrides'])) { + unset($this->profile['target_language_overrides']); + } + $this->save(); + } + + public function deleteAttribute($attrib_name, $target_locale = NULL) { + if ($target_locale) { + if (isset($this->profile['target_language_overrides'][$target_locale][$attrib_name])) { + unset($this->profile['target_language_overrides'][$target_locale][$attrib_name]); + $this->save(); + } + } + else { + if (isset($this->profile[$attrib_name])) { + unset($this->profile[$attrib_name]); + $this->save(); + } + } + } + + public function deleteTargetLocaleOverrides($target_locale) { + unset($this->profile['target_language_overrides'][$target_locale]); + $this->save(); + } + + public function getTargetLocaleOverrides($target_locale) { + if (!empty($this->profile['target_language_overrides'][$target_locale])) { + return $this->profile['target_language_overrides'][$target_locale]; + } + return array(); + } + + public function getAttributes($target_locale = NULL) { + if ($this->getId() == LingotekSync::PROFILE_DISABLED) { + return array( + 'name' => LingotekSync::PROFILE_DISABLED, + 'profile' => LingotekSync::PROFILE_DISABLED, + ); + } + if ($this->getId() == LingotekSync::PROFILE_ENABLED) { + return array( + 'name' => LingotekSync::PROFILE_ENABLED, + 'profile' => LingotekSync::PROFILE_ENABLED, + ); + } + if (empty(self::$profiles[$this->getId()])) { + drupal_set_message(t('Lingotek profile ID @profile_id not found.', array('@profile_id' => $this->getId())), 'error', FALSE); + + //throw new LingotekException('Profile ID "' . $this->getId() . '" not found.'); + } + if ($target_locale) { + $attributes = $this->getTargetLocaleOverrides($target_locale); + if ($this->lookForInherited()) { + $attributes = array_merge(self::$profiles[$this->getId()], $attributes); + } + } + elseif (!empty(self::$profiles[$this->getId()])) { + $attributes = self::$profiles[$this->getId()]; + } + else { + $attributes = array(); + } + return array_merge(self::$global_profile, $attributes); + } + + public function filterTargetLocales($available_locales) { + $filtered_locales = array(); + $default_workflow = $this->getWorkflow(); + // foreach locale, get the overrides + foreach ($available_locales as $locale) { + if ($this->isTargetLocaleDisabled($locale)) { + // filter this out. + } + elseif ($this->isTargetLocaleCustom($locale)) { + $filtered_locales[$locale] = $this->getTargetLocaleOverrides($locale); + } + else { + $filtered_locales[$locale] = TRUE; + } + } + return $filtered_locales; + } + + protected function isProtected() { + $locked_profiles = array( + LingotekSync::PROFILE_DISABLED, + LingotekSync::PROFILE_AUTOMATIC, + LingotekSync::PROFILE_MANUAL + ); + if (in_array($this->getId(), $locked_profiles)) { + return TRUE; + } + return FALSE; + } + + protected function isProtectedAttribute($attrib_name) { + $locked_attribs = array( + 0 => array('auto_upload', 'auto_download'), + 1 => array('auto_upload', 'auto_download'), + ); + if (array_key_exists($this->getId(), $locked_attribs) && in_array($attrib_name, $locked_attribs[$this->getId()])) { + return TRUE; + } + return FALSE; + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekProfileManager.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekProfileManager.php new file mode 100644 index 00000000..8fbc8360 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekProfileManager.php @@ -0,0 +1,37 @@ + 'Automatic', + 'auto_upload' => 1, + 'auto_download' => 1, + ); + self::$profiles[] = array( + 'name' => 'Manual', + 'auto_upload' => 0, + 'auto_download' => 0, + ); + variable_set('lingotek_profiles', self::$profiles); + } + return self::$profiles; + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekSync.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekSync.php index 962c88a2..0d8d618d 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekSync.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekSync.php @@ -10,22 +10,35 @@ */ class LingotekSync { - const STATUS_CURRENT = 'CURRENT'; // The node or target translation is current + const STATUS_NONE = 'NONE'; const STATUS_EDITED = 'EDITED'; // The node has been edited, but has not been uploaded to Lingotek - const STATUS_FAILED = 'FAILED'; // The node or target translation has failed during processing + const STATUS_CURRENT = 'CURRENT'; // The node or target translation is current + const STATUS_ERROR = 'ERROR'; const STATUS_PENDING = 'PENDING'; // The target translation is awaiting to receive updated content from Lingotek const STATUS_READY = 'READY'; // The target translation is complete and ready for download + const STATUS_INTERIM = 'INTERIM'; // Part of the target translation is done and ready for download what has been done + const STATUS_READY_INTERIM = 'READY_INTERIM'; const STATUS_TARGET = 'TARGET'; // A target node is being used to store a translation (ignored for upload by Lingotek) const STATUS_UNTRACKED = 'UNTRACKED'; // A translation was discovered that is not currently managed by Lingotek const STATUS_TARGET_LOCALIZE = 'TARGET_LOCALIZE'; // A localization must be made of the source before uploading to Lingotek const STATUS_TARGET_EDITED = 'TARGET_EDITED'; // A localized version is ready for uploading to Lingotek const STATUS_NON_LINGOTEK = 'NON_LINGOTEK'; // Translations exist but the translation_agent_id != 3 (Not owned by Lingotek) + const STATUS_DELETED = 'DELETED'; // Entity was translated but was afterward deleted const PROFILE_CUSTOM = 'CUSTOM'; const PROFILE_DISABLED = 'DISABLED'; + const PROFILE_ENABLED = 'ENABLED'; const PROFILE_CONFIG = 'CONFIG'; const PROFILE_AUTOMATIC = 0; const PROFILE_MANUAL = 1; + const PROFILE_INHERIT = 'INHERIT'; + const MARKED = 1; + const NOT_MARKED = 0; + const INVALID_XML_PRESENT = 1; + const TRANSLATION_AGENT_ID_UNKNOWN = 1; + const TRANSLATION_AGENT_ID_DRUPAL = 2; + const TRANSLATION_AGENT_ID_LINGOTEK = 3; + const MARKED_OFFSET = 1000000000; public static function getTargetStatus($doc_id, $lingotek_locale) { $key = 'target_sync_status_' . $lingotek_locale; @@ -39,14 +52,39 @@ public static function getTargetStatus($doc_id, $lingotek_locale) { LingotekLog::error('Did not find any local info for Lingotek Doc ID "@id"', array('@id' => $doc_id)); return FALSE; } + public static function getAllTargetStatusForEntity($entity_type, $entity_id, $lingotek_locale = NULL) { + $dbkey = 'target_sync_status_'; + $query = db_select('lingotek_entity_metadata', 'l') + ->fields('l', array('entity_key', 'value')) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id); + if ($lingotek_locale !== NULL) { + $query->condition('entity_key', $dbkey . $lingotek_locale); + } + else { + $query->condition('entity_key', $dbkey . '%', 'LIKE'); + } + $result = $query->execute()->fetchAll(); + $targets = array(); + foreach ($result as $r_obj) { + // get the locale of each result + $locale = substr($r_obj->entity_key, strlen($dbkey)); + // assign the status for that locale + $targets[$locale] = $r_obj->value; + } + return $targets; + } public static function getTargetStatusOptions() { return array( + 'STATUS_NONE' => self::STATUS_NONE, 'STATUS_CURRENT' => self::STATUS_CURRENT, 'STATUS_EDITED' => self::STATUS_EDITED, - 'STATUS_FAILED' => self::STATUS_FAILED, + 'STATUS_ERROR' => self::STATUS_ERROR, 'STATUS_PENDING' => self::STATUS_PENDING, 'STATUS_READY' => self::STATUS_READY, + 'STATUS_READY_INTERIM' => self::STATUS_READY_INTERIM, + 'STATUS_INTERIM' => self::STATUS_INTERIM, 'STATUS_TARGET' => self::STATUS_TARGET, 'STATUS_UNTRACKED' => self::STATUS_UNTRACKED, 'STATUS_TARGET_LOCALIZE' => self::STATUS_TARGET_LOCALIZE, @@ -69,18 +107,75 @@ public static function setTargetStatus($entity_type, $entity_id, $lingotek_local $key = 'target_sync_status_' . $lingotek_locale; return lingotek_keystore($entity_type, $entity_id, $key, $status, $update_on_dup); } - + public static function setAllTargetStatus($entity_type, $entity_id, $status) { + if($entity_type === 'config'){ + $query = db_update('lingotek_config_metadata') + ->condition('id', $entity_id, "=") + ->condition('config_key', 'target_sync_status%', 'LIKE') + ->fields(array('value' => $status, 'modified' => time())) + ->execute(); + return; + } + $query = db_update('lingotek_entity_metadata') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('entity_key', 'target_sync_status%', 'LIKE') + ->fields(array('value' => $status, 'modified' => time())) + ->execute(); + lingotek_cache_clear($entity_type, $entity_id); + } + + public static function bulkSetAllTargetStatus($entity_type, $entity_ids, $status){ + if($entity_type === 'config'){ + $query = db_update('lingotek_config_metadata') + ->condition('id', $entity_ids, "IN") + ->condition('config_key', 'target_sync_status%', 'LIKE') + ->fields(array('value' => $status, 'modified' => time())) + ->execute(); + return; + } $query = db_update('lingotek_entity_metadata') ->condition('entity_type', $entity_type) - ->condition('entity_id', $entity_id) - ->condition('entity_key', 'target_sync_status%', 'LIKE') - ->fields(array('value' => $status, 'modified' => time())) + ->condition('entity_id', $entity_ids, "IN") + ->condition('entity_key', 'target_sync_status%', 'LIKE') + ->fields(array('value' => $status, 'modified' => time())) ->execute(); + foreach ($entity_ids as $eid) { + lingotek_cache_clear($entity_type, $eid); + } + } + + public static function setUploadStatus($entity_type, $entity_id, $status) { + return lingotek_keystore($entity_type, $entity_id, 'upload_status', $status); } - public static function setNodeStatus($node_id, $status) { - return lingotek_keystore('node', $node_id, 'upload_status', $status); + public static function setAllUploadStatuses($entity_type, $entity_ids, $status) { + foreach ($entity_ids as $entity_id) { + $query = db_update('lingotek_entity_metadata') + ->condition('entity_id', $entity_id) + ->condition('entity_type', $entity_type) + ->condition('entity_key', 'upload_status') + ->fields(array('value' => $status, 'modified' => time())) + ->execute(); + } + } + + public static function setUploadStatuses($entity_type, $entity_ids, $status) { + foreach($entity_ids as $entity_id) { + return lingotek_keystore($entity_type, $entity_id, 'upload_status', $status); + } + } + + public static function getUploadStatus($entity_type, $entity_id) { + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('value')) + ->condition('lem.entity_id', $entity_id) + ->condition('lem.entity_type', $entity_type) + ->condition('lem.entity_key', 'upload_status'); + $upload_status = $query->execute()->fetchField(); + + return $upload_status; } public static function getSyncProjects() { @@ -114,10 +209,53 @@ public static function insertTargetEntriesForAllSets($lingotek_locale) { } public static function insertTargetEntriesForAllEntities($lingotek_locale) { + //this prevents a target from being added to the same source + $locale = strtolower(str_replace("_", "-", $lingotek_locale)); + $node_source_check = db_select('node', 'n'); + $node_source_check->addField('n', 'nid'); + $or = db_or()->condition('n.tnid','0')->where('n.tnid = n.nid'); + $node_source_check->condition($or); + $node_source_check->condition('n.language', $locale); + + $taxonomy_source_check = db_select('taxonomy_term_data', 't'); + $taxonomy_source_check->addField('t', 'tid'); + $taxonomy_source_check->condition('t.language', $locale); + // insert/update a target language for all entities $query = db_select('lingotek_entity_metadata', 'meta') ->fields('meta', array('entity_id', 'entity_type')) - ->condition('meta.entity_key', 'document_id'); + ->condition('meta.entity_key', 'document_id') + ->condition('entity_id', $node_source_check, "NOT IN") + ->condition('entity_id', $taxonomy_source_check, "NOT IN"); + + if (module_exists('comment')) { + $comment_source_check = db_select('comment', 'c'); + $comment_source_check->addField('c', 'cid'); + $comment_source_check->condition('c.language', $locale); + $query->condition('entity_id', $comment_source_check, "NOT IN"); + } + + if (module_exists('bean') && variable_get('lingotek_translate_beans')) { + $bean_source_check = db_select('bean', 'b'); + $bean_source_check->addField('b', 'bid'); + $bean_source_check->condition('b.language', $locale); + $query->condition('entity_id', $bean_source_check, "NOT IN"); + } + + if (module_exists('group') && variable_get('lingotek_translate_groups')) { + $group_source_check = db_select('groups', 'g'); + $group_source_check->addField('g', 'gid'); + $group_source_check->condition('g.language', $locale); + $query->condition('entity_id', $group_source_check, "NOT IN"); + } + + if (module_exists('file_entity') && variable_get('lingotek_translate_files')) { + $file_source_check = db_select('file_managed', 'fm'); + $file_source_check->addField('fm', 'fid'); + $file_source_check->condition('fm.language', $locale); + $query->condition('entity_id', $file_source_check, "NOT IN"); + } + $entities = $query->execute()->fetchAll(); foreach ($entities as $e) { @@ -125,6 +263,19 @@ public static function insertTargetEntriesForAllEntities($lingotek_locale) { } } + public static function delete_entity_from_metadata($entity_type, $entity_id) { + db_delete('lingotek_entity_metadata') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute(); + lingotek_cache_clear($entity_type, $entity_id); + } + + public static function deleteTargetStatus($entity_type, $entity_id, $lingotek_locale) { + $key = 'target_sync_status_' . $lingotek_locale; + return lingotek_keystore_delete($entity_type, $entity_id, $key); + } + // Remove the node sync target language entries from the lingotek table lingotek_delete_target_sync_status_for_all_nodes public static function deleteTargetEntriesForAllEntities($lingotek_locale) { $keys = array( @@ -141,23 +292,28 @@ public static function deleteTargetEntriesForAllChunks($lingotek_locale) { public static function deleteTargetEntriesForAllDocs($lingotek_locale) { self::deleteTargetEntriesForAllEntities($lingotek_locale); self::deleteTargetEntriesForAllChunks($lingotek_locale); + lingotek_cache_clear(); } - - + + /** * getDocIdTargetsByStatus - * + * * @param status (e.g., LingotekSync::READY) - * + * * @return an array of associate arrays. Each associate array will have a 'nid' (e.g., 5), 'locale' (e.g., 'de_DE'), and optionally 'doc_id' (e.g., 46677222-b5ec-47d5-880e-24632feffaf5) */ - public static function getTargetsByStatus($entity_type, $status, $include_doc_ids = FALSE) { + public static function getTargetsByStatuses($entity_type, $statuses=array(), $include_doc_ids = FALSE) { $target_language_search = '%'; $query = db_select('lingotek_entity_metadata', 'l'); $query->fields('l', array('entity_id', 'entity_key', 'value')); $query->condition('entity_type', $entity_type); $query->condition('entity_key', 'target_sync_status_' . $target_language_search, 'LIKE'); - $query->condition('value', $status); + $or = db_or(); + foreach($statuses as $status){ + $or->condition('value', $status); + } + $query->condition($or); $result = $query->execute(); $records = $result->fetchAll(); //$result->fetchAllAssoc('nid'); @@ -294,8 +450,24 @@ public static function getEntitySourceCount($lingotek_locale, $entity_type = NUL $query->condition($tnid_query); } + // Exclude translation sets for menu_links + if ($entity_base_table == 'menu_links') { + $min_query = db_select('menu_links', 'ml') + ->condition('ml.i18n_tsid', 0, '!=') + ->groupBy('i18n_tsid'); + $min_query->addExpression('MIN(mlid)', 'minimum'); + + $ml_or = db_or(); + $ml_or->condition('t.i18n_tsid', 0); + $ml_or->condition('t.mlid', $min_query, 'IN'); + + $query->condition('t.language', LANGUAGE_NONE, '!='); + $query->condition($ml_or); + } + // exclude disabled entities (including those that have disabled bundles) - $disabled_entities = lingotek_get_entities_by_profile_and_entity_type(LingotekSync::PROFILE_DISABLED, $entity_type); + $disabled_profile = LingotekProfile::loadById(LingotekSync::PROFILE_DISABLED); + $disabled_entities = $disabled_profile->getEntities($entity_type); if (count($disabled_entities)) { $disabled_entity_ids = array(); array_walk($disabled_entities, function($a) use (&$disabled_entity_ids) { @@ -371,7 +543,8 @@ public static function getEntityTargetCountByStatus($status, $lingotek_locale, $ } // exclude disabled nodes (including those that have disabled bundles) - $disabled_entities = lingotek_get_entities_by_profile_and_entity_type(LingotekSync::PROFILE_DISABLED, $entity_base_table); + $disabled_profile = LingotekProfile::loadById(LingotekSync::PROFILE_DISABLED); + $disabled_entities = $disabled_profile->getEntities($entity_base_table); if (!empty($disabled_entities)) { $disabled_entity_ids = array(); array_walk($disabled_entities, function($a) use (&$disabled_entity_ids) { @@ -381,6 +554,12 @@ public static function getEntityTargetCountByStatus($status, $lingotek_locale, $ } $query->condition('l.value', $status, 'IN'); + + // exclude orphaned targets (targets whose source language has been deleted) + if (db_field_exists($entity_base_table, 'language')) { + $query->condition('t.language', '', '!='); + } + $count = $query->countQuery()->execute()->fetchField(); $total_count += $count; } @@ -476,7 +655,7 @@ public static function getTargetCountByDocumentIds($document_ids) { } public static function getETNodeIds() { // get nids for entity_translation nodes that are not lingotek pushed - $types = lingotek_translatable_node_types(); // get all translatable node types + $types = lingotek_translatable_node_types(); // get all translatable node types $et_content_types = array(); foreach ($types as $type) { if (lingotek_managed_by_entity_translation($type)) { // test if lingotek_managed_by_entity_translation @@ -641,13 +820,22 @@ public static function getNodeIdsByStatus($status, $source) { return $nids; } - public static function getEntityIdsToUpload($entity_type) { + public static function getEntityIdsToUpload($entity_type, $entity_ids = null) { $info = entity_get_info($entity_type); $id_key = $info['entity keys']['id']; $query = db_select($info['base table'], 'base'); $query->addField('base', $id_key); $query->leftJoin('lingotek_entity_metadata', 'upload', 'upload.entity_id = base.' . $id_key . ' and upload.entity_type =\'' . $entity_type . '\' and upload.entity_key = \'upload_status\''); + $query2 = db_select('lingotek_entity_metadata', 'lem'); + $query2->distinct(); + $query2->addField('lem', 'entity_id'); + $query2->condition('entity_key', 'profile'); + $query2->condition('entity_type', $entity_type); + $query2->condition('value', 'DISABLED'); + + $query->condition('upload.entity_id', $query2, 'NOT IN'); + if ($entity_type == 'node') { // Exclude any target nodes created using node-based translation. $tnid_query = db_or(); @@ -660,7 +848,9 @@ public static function getEntityIdsToUpload($entity_type) { $or->condition('upload.value', LingotekSync::STATUS_EDITED); $or->isNull('upload.value'); $query->condition($or); - + if($entity_ids !== null){ + $query->condition('upload.entity_id', $entity_ids, 'IN'); + } $result = $query->execute()->fetchCol(); return $result; } @@ -682,7 +872,7 @@ public static function getConfigSetIdsToUpload() { * a string containing the desired status * @param $locales * (optional) an array containing the locales to check - * + * * @return an array of IDs */ public static function getEntityIdsByTargetStatus($entity_type, $status, $locales = NULL) { @@ -707,6 +897,33 @@ public static function getEntityIdsByTargetStatus($entity_type, $status, $locale return $result; } + public static function getEntityIdsByStatuses($entity_type, $statuses = array(), $locales = NULL) { + if ($locales === NULL) { + $verb = 'LIKE'; + $target_locales = 'target_sync_status_%'; + } + else { + $verb = 'IN'; + $target_locales = array(); + foreach ($locales as $l) { + $target_locales[] = 'target_sync_status_' . $l; + } + } + $or = db_or(); + foreach($statuses as $status){ + $or->condition('value', $status); + } + $query = db_select('lingotek_entity_metadata', 'l') + ->distinct() + ->condition('entity_type', $entity_type) + ->condition('entity_key', $target_locales, $verb) + ->condition($or) + ->condition('value', LingotekSync::PROFILE_DISABLED, '<>'); + $query->addField('l', 'entity_id', 'nid'); + $result = $query->execute()->fetchCol(); + return $result; + } + public static function getEntityIdsByProfileStatus($entity_type, $status) { $query = db_select('lingotek_entity_metadata', 'l') ->distinct() @@ -718,6 +935,17 @@ public static function getEntityIdsByProfileStatus($entity_type, $status) { return $result; } + public static function getProfileByEntityId($entity_type, $entity_id) { + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('value')) + ->condition('lem.entity_id', $entity_id) + ->condition('lem.entity_type', $entity_type) + ->condition('lem.entity_key', 'profile'); + $profile = $query->execute()->fetchField(); + + return $profile; + } + public static function getConfigDocIdsByStatus($status) { $doc_ids = array(); @@ -750,6 +978,9 @@ public static function getSetIdsFromLids($lids) { } public static function getConfigDocIdsFromSetIds($sids) { + if (empty($sids)) { + return $sids; + } $query = db_select('lingotek_config_metadata', 'l'); $query->addField('l', 'value'); $query->condition('id', $sids, 'IN'); @@ -759,6 +990,44 @@ public static function getConfigDocIdsFromSetIds($sids) { return $doc_ids; } + public static function updateConfigSetWorkflow($set_id, $workflow_id){ + $insertRecord = array( + "value" => $workflow_id, + "created" => time(), + "modified" => time(), + "id" => $set_id, + "config_key" => 'workflow_id' + ); + $updateRecord = array( + "value" => $workflow_id, + "modified" => time() + ); + db_merge('lingotek_config_metadata') + ->key(array('id' => $set_id, 'config_key' => 'workflow_id')) + ->insertFields($insertRecord) + ->updateFields($updateRecord) + ->execute(); +// drupal_write_record('lingotek_config_metadata', $record); + } + + public static function getWorkflowIdFromConfigSet($sid){ + $query = db_select('lingotek_config_metadata', 'lcm'); + $query->addField('lcm', 'value'); + $query->condition('id', $sid, '='); + return $query->execute()->fetchCol(); + } + + public static function getWorkflowIdFromEntityId($id) { + $query = db_select('lingotek_entity_metadata', 'lem'); + $query->addField('lem', 'value'); + $query->condition('entity_key', 'workflow_id', '='); + $query->condition('entity_id', $id, '='); + + $result = $query->execute()->fetchCol(); + + return $result[0]; + } + public static function getChunkIdsByStatus($status) { $query = db_select('lingotek_config_metadata', 'meta'); $query->fields('meta', array('id')); @@ -772,6 +1041,7 @@ public static function getChunkIdsByStatus($status) { public static function disassociateAllEntities() { db_truncate('lingotek_entity_metadata')->execute(); + lingotek_cache_clear(); } public static function disassociateAllSets() { @@ -782,8 +1052,9 @@ public static function disassociateEntities($document_ids = array()) { $eids = self::getNodeIdsFromDocIds($document_ids); db_delete('lingotek_entity_metadata') ->condition('entity_type', 'node') - ->condition('entity_id', $eids, 'IN') - ->execute(); + ->condition('entity_id', $eids, 'IN') + ->execute(); + lingotek_cache_clear('node'); } public static function getAllLocalDocIds() { @@ -827,10 +1098,10 @@ public static function getEntityIdFromDocId($lingotek_document_id, $entity_type $query->condition('entity_key', $key); $query->condition('value', $lingotek_document_id); $result = $query->execute(); - + $found = FALSE; $type = FALSE; - + if ($record = $result->fetchAssoc()) { $found = $record['entity_id']; $type = $record['entity_type']; @@ -838,7 +1109,7 @@ public static function getEntityIdFromDocId($lingotek_document_id, $entity_type return array($found, $type); } - + public static function getNodeIdFromDocId($lingotek_document_id) { list($id, $type) = LingotekSync::getEntityIdFromDocId($lingotek_document_id); return array($id, $type); @@ -858,7 +1129,7 @@ public static function getNodeIdsFromDocIds($lingotek_document_ids) { public static function getDocIdFromNodeId($drupal_node_id) { return getDocIdFromEntityId('node', $drupal_node_id); } - + public static function getDocIdFromEntityId($entity_type, $entity_id) { $found = FALSE; @@ -942,10 +1213,108 @@ public static function updateNotifyUrl() { if ($success) { variable_set('lingotek_notify_url', $new_url); variable_set('lingotek_notify_security_token', $security_token); + if (strpos($new_url,'localhost') !== false) { + $success = 'localhost_url'; + } } return $success; } -} + public static function getMenuLinkTargetStatus($mlid, $lingotek_locale) { + $key = 'target_sync_status_' . $lingotek_locale; + $status = lingotek_keystore('menu_link', $mlid, $key); + + if ($status) { + return $status; + } + else { + LingotekLog::error('Did not find any targets for menu link "@id"', array('@id' => $mlid)); + return FALSE; + } + } -?> + public static function getDocumentId($entity_type, $entity_id) { + $doc_id = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('value')) + ->condition('lem.entity_type', $entity_type) + ->condition('lem.entity_id', $entity_id) + ->condition('lem.entity_key', 'document_id') + ->execute() + ->fetch(PDO::FETCH_ASSOC); + + if ($doc_id) { + return $doc_id; + } + else { + return FALSE; + } + } + + public static function setLastSyncError($entity_type, $entity_id, $error) { + lingotek_keystore($entity_type, $entity_id, 'last_sync_error', $error); + } + + public static function getLastSyncError($entity_type, $entity_id) { + $error = ''; + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('value')) + ->condition('lem.entity_id', $entity_id) + ->condition('lem.entity_type', $entity_type) + ->condition('lem.entity_key', 'last_sync_error'); + $last_sync_error = $query->execute()->fetchField(); + + return $last_sync_error; + } + + public static function deleteLastSyncError($entity_type, $entity_id) { + lingotek_keystore_delete($entity_type, $entity_id, 'last_sync_error'); + } + + public static function updateLingotekTranslationAgentId($lids, $agent_id) { + $query = db_update('locales_target') + ->condition('lid', $lids, "IN") + ->fields(array('translation_agent_id' => $agent_id)) + ->execute(); + } + + /** + * Sets a Lingotek metadata value for this item. + * @param int $lid + * The lid for the config item. + * @param int $marked_offset + * Integer space reserved for marked value + */ + public static function setConfigMarkedValue($lid, $marked_offset) { + $insertRecord = array( + "value" => $lid, + "created" => time(), + "modified" => time(), + "id" => $marked_offset, + "config_key" => 'marked' + ); + $updateRecord = array( + "value" => $lid, + "modified" => time() + ); + db_merge('lingotek_config_metadata') + ->key(array('id' => $marked_offset, 'config_key' => 'marked')) + ->insertFields($insertRecord) + ->updateFields($updateRecord) + ->execute(); + } + + /** + * Deletes a Lingotek metadata value for this item. + * @param int $lid + * The lid for the config item. + * @param string $marked_offset + * Integer space reserved for marked value. + */ + public static function deleteConfigMarkedValue($lid, $marked_offset) { + db_delete('lingotek_config_metadata') + ->condition('id', $marked_offset) + ->condition('config_key', 'marked') + ->condition('value', $lid) + ->execute(); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekTranslatableEntity.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekTranslatableEntity.php index 34bd8baa..af3aa4a6 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekTranslatableEntity.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekTranslatableEntity.php @@ -98,7 +98,7 @@ public function setTitle($title); public function setStatus($status); - public function setTargetsStatus($status); + public function setTargetsStatus($status, $lingotek_locale = NULL); /* * Returns the source locale for the translatable entity */ diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekXMLElement.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekXMLElement.php new file mode 100644 index 00000000..32159535 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/Drupal/lingotek/LingotekXMLElement.php @@ -0,0 +1,46 @@ +ownerDocument; + $xml->appendChild($doc->createCDATASection($text)); + } + + /** + * Add a SimpleXMLElement object as a child + * @param SimpleXMLElement $child + * @param type $element_name + */ + public function addChildXML(SimpleXMLElement $child, $element_name = NULL) { + if (!$element_name) { + $element_name = $child->getName(); + } + $this_child = $this->addChild($element_name); + $to_dom = dom_import_simplexml($this_child); + $from_dom = dom_import_simplexml($child); + foreach ($from_dom->childNodes as $child_element) { + $to_dom->appendChild($to_dom->ownerDocument->importNode($child_element, TRUE)); + } + } + + /** + * Add a SimpleXMLElement object to this object + * @param SimpleXMLElement $xml + */ + public function addXML(SimpleXMLElement $xml) { + $to_dom = dom_import_simplexml($this); + $from_dom = dom_import_simplexml($xml); + foreach ($from_dom->childNodes as $child_element) { + $to_dom->appendChild($to_dom->ownerDocument->importNode($child_element, TRUE)); + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/client/googledocs.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/client/googledocs.php index 8abac8f0..41c48ca6 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/client/googledocs.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/client/googledocs.php @@ -73,13 +73,17 @@ } else { // STEP 2: Get an access token - $oauthToken = $_GET["oauth_token"]; + $oauthToken = check_plain($_GET["oauth_token"]); // echo "oauth_verifier = '" . $oauthVerifier . "'
      "; - $tokenResultParams = $_GET; + $params = array(); + foreach($_GET as $key => $value){ + $params[$key] = filter_xss($value); + } + $tokenResultParams = $params; try { - LingotekOAuthRequester::requestAccessToken(GOOGLE_CONSUMER_KEY, $oauthToken, 0, 'POST', $_GET); + LingotekOAuthRequester::requestAccessToken(GOOGLE_CONSUMER_KEY, $oauthToken, 0, 'POST', $params); } catch (OAuthException2 $e) { diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/core/init.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/core/init.php index 602058d8..4841cc81 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/core/init.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/core/init.php @@ -112,10 +112,11 @@ function assert_request_vars() function assert_request_vars_all() { foreach($_REQUEST as $row) - { + { foreach(func_get_args() as $a) { - if (!isset($row[$a])) + $filtered_row = filter_xss($row[$a]); + if ($filtered_row == NULL) { header('HTTP/1.1 400 Bad Request'); echo 'Bad request.'; diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/logon.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/logon.php index 5c937b71..e22d22b9 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/logon.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/logon.php @@ -40,7 +40,7 @@ $_SESSION['authorized'] = true; if (!empty($_REQUEST['goto'])) { - header('Location: ' . $_REQUEST['goto']); + header('Location: ' . filter_xss($_REQUEST['goto'])); die; } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/register.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/register.php index 0a74297b..7d5602ff 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/register.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/example/server/www/register.php @@ -10,8 +10,12 @@ { $store = OAuthStore::instance(); $user_id = 1; // this should not be hardcoded, of course - $key = $store->updateConsumer($_POST, $user_id, true); - + $params = array(); + foreach ($_POST as $key => $value){ + $params[$key] = filter_xss($value); + } + $key = $store->updateConsumer($params, $user_id, true); + $c = $store->getConsumer($key, $user_id); echo 'Your consumer key is: ' . $c['consumer_key'] . '
      '; echo 'Your consumer secret is: ' . $c['consumer_secret'] . '
      '; diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/library/LingotekOAuthRequest.php b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/library/LingotekOAuthRequest.php index ae222e04..0ba25ca7 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/library/LingotekOAuthRequest.php +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lib/oauth-php/library/LingotekOAuthRequest.php @@ -105,45 +105,14 @@ function __construct ( $uri = null, $method = null, $parameters = '', $headers = } $headers = LingotekOAuthRequestLogger::getAllHeaders(); $this->method = strtoupper($method); - - // If this is a post then also check the posted variables - if (strcasecmp($method, 'POST') == 0) - { - // TODO: what to do with 'multipart/form-data'? - if ($this->getRequestContentType() == 'multipart/form-data') - { - // Get the posted body (when available) - if (!isset($headers['X-OAuth-Test'])) - { - $parameters .= $this->getRequestBodyOfMultipart(); - } - } - if ($this->getRequestContentType() == 'application/x-www-form-urlencoded') - { - // Get the posted body (when available) - if (!isset($headers['X-OAuth-Test'])) - { - $parameters .= $this->getRequestBody(); - } - } - else - { - $body = $this->getRequestBody(); - } - } - else if (strcasecmp($method, 'PUT') == 0) - { - $body = $this->getRequestBody(); - } - + $body = $this->getRequestBody(); $this->method = strtoupper($method); $this->headers = $headers; // Store the values, prepare for oauth $this->uri = $uri; $this->body = $body; - $this->parseUri($parameters); + $this->parseUri(); $this->parseHeaders(); - $this->transcodeParams(); } @@ -454,31 +423,6 @@ function setParam ( $name, $value, $encoded = false ) } } - - /** - * Re-encode all parameters so that they are encoded using RFC3986. - * Updates the $this->param attribute. - */ - protected function transcodeParams () - { - $params = $this->param; - $this->param = array(); - - foreach ($params as $name=>$value) - { - if (is_array($value)) - { - $this->param[$this->urltranscode($name)] = array_map(array($this,'urltranscode'), $value); - } - else - { - $this->param[$this->urltranscode($name)] = $this->urltranscode($value); - } - } - } - - - /** * Return the body of the OAuth request. * @@ -506,7 +450,7 @@ function setBody ( $body ) * * @param string $parameters optional extra parameters (from eg the http post) */ - protected function parseUri ( $parameters ) + protected function parseUri () { $ps = @parse_url($this->uri); @@ -551,34 +495,6 @@ protected function parseUri ( $parameters ) $ps['fragment'] = ''; } - // Now all is complete - parse all parameters - foreach (array($ps['query'], $parameters) as $params) - { - if (strlen($params) > 0) - { - $params = explode('&', $params); - foreach ($params as $p) - { - @list($name, $value) = explode('=', $p, 2); - if (!strlen($name)) - { - continue; - } - - if (array_key_exists($name, $this->param)) - { - if (is_array($this->param[$name])) - $this->param[$name][] = $value; - else - $this->param[$name] = array($this->param[$name], $value); - } - else - { - $this->param[$name] = $value; - } - } - } - } $this->uri_parts = $ps; } @@ -780,7 +696,7 @@ private function getRequestBodyOfMultipart() if (is_array($_POST) && count($_POST) > 1) { foreach ($_POST AS $k => $v) { - $body .= $k . '=' . $this->urlencode($v) . '&'; + $body .= $k . '=' . url_check($this->urlencode($v)) . '&'; } #end foreach if(substr($body,-1) == '&') { diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.admin.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.admin.inc index c655463f..748bf4de 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.admin.inc @@ -80,6 +80,8 @@ function lingotek_label_compare($a, $b) { */ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity_type, $show_fieldset = FALSE) { + drupal_add_js('misc/collapse.js'); + $setup_complete = (!lingotek_is_config_missing()); $entity_type_info = entity_get_info($entity_type); $bundles = $entity_type_info['bundles']; @@ -108,6 +110,8 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity $entity_type_info['label'] = 'Field Collection'; } + drupal_alter('lingotek_entity_profiles_options', $entity_type, $profiles_options); + $entity_profiles = variable_get('lingotek_entity_profiles'); $form['entity_type'] = array( @@ -116,12 +120,7 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity ); $selector = '.lingotek-content-settings-table.' . $entity_type; - $enable_all = l(t('(set all to automatic)'), '#', array('attributes' => array('onclick' => "lingotek_set_all('" . $selector . "', '0'); return false;"))); - $enable_all_manual = l(t('(set all to manual)'), '#', array('attributes' => array('onclick' => "lingotek_set_all('" . $selector . "', '1'); return false;"))); - $disable_all = l(t('(disable all)'), '#', array('attributes' => array('onclick' => "lingotek_set_all('" . $selector . "', 'DISABLED'); return false;"))); - $title_str = ($setup_complete ? t('Translate @types', array('@type' => $entity_type_info['label'])) : t('Which content types do you want translated?')); - $links_str = t('') . ' ' . $enable_all . ' ' . $enable_all_manual . ' ' . $disable_all; $form['translation_' . $entity_type] = array( '#type' => $show_fieldset ? 'fieldset' : 'item', @@ -134,10 +133,16 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity ), '#submit' => array('lingotek_admin_entity_bundle_profiles_form_submit'), ); - + $changeAllSelect = 'Set all to: '; $form['translation_' . $entity_type][] = array( '#type' => 'item', - '#description' => $links_str, + '#markup' => $changeAllSelect ); $translatable_field_types = lingotek_get_translatable_field_types(); @@ -148,12 +153,12 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity foreach ($bundles as $bundle_name => $bundle) { $original_fields = field_info_instances($entity_type, $bundle_name); $fields = lingotek_node_settings_row_fields($entity_type, $bundle_name, $setup_complete, $translatable_field_types, $translate); - $oneoffs = ($entity_type == 'node') ? lingotek_node_settings_row_oneoffs($bundle_name) : NULL; $translation_mode = ($entity_type == 'taxonomy_term') ? lingotek_get_translation_mode($bundle_name) : NULL; + $config_handled = $entity_type == 'taxonomy_term' && empty($original_fields) && $translation_mode['#markup'] !== 'Translate'; $translation_handling = $config_handled ? array('#markup' => 'Config') : array('#markup' => 'Bundle'); $profiles = lingotek_node_settings_row_profiles($bundle_name, $entity_profiles, $entity_type, $profiles_options, $setup_complete, $config_handled); - $entity_specifics = array('oneoffs' => $oneoffs, 'translation_mode' => $translation_mode, 'translation_handling' => $translation_handling); + $entity_specifics = array('translation_mode' => $translation_mode, 'translation_handling' => $translation_handling); if ($config_handled) { $fields = array( @@ -163,22 +168,25 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity ); } + $languages = lingotek_get_target_locales(FALSE); + $bundle['collapsible_label'] = lingotek_get_language_specific_labels($entity_type, $bundle, $languages); + if ($translation_mode['#markup'] == 'Fixed' || $translation_mode['#markup'] == 'No') { continue; } $rows[$bundle_name] = lingotek_node_settings_row($entity_type, $bundle, $profiles, $fields, $entity_specifics); } - $header = array(t('Content Type'), t('Translation Profile *'), t('Fields')); + $header = array(t('Content Type'), t('Translation Profile *'), t('Fields')); if ($entity_type == 'node') { - $header = array(t('Content Type'), t('Translation Profile *'), t('Exceptions'), t('Fields')); + $header = array(t('Content Type'), t('Translation Profile *'), t('Fields')); } elseif ($entity_type == 'taxonomy_term') { - $header = array(t('Content Type'), t('Translation Profile *'), t('Translation Mode'), t('Translation Handling'), t('Fields')); + $header = array(t('Content Type'), t('Translation Profile *'), t('Translation Mode'), t('Translation Handling'), t('Fields')); } elseif ($entity_type == 'field_collection_item') { - $header = array(t('Collection Name'), t('Status *'), t('Fields')); + $header = array(t('Collection Name'), t('Status *'), t('Fields')); } $variables = array( @@ -193,7 +201,11 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity ); if ($setup_complete) { - $form['translation_' . $entity_type]['types']['#suffix'] = t('Note: changing the profile will update all settings for existing @entity_types except for the project, workflow, vault, and storage method (e.g. node/field)', array('@entity_type' => strtolower($entity_type_info['label']))); + $node_message = ''; + if (variable_get('lingotek_translate_original_node_titles', FALSE) && $entity_type === 'node') { + $node_message = "
      Note: If \"Node Title\" and \"Title\" are both translated, \"Title\" will display within nodes."; + } + $form['translation_' . $entity_type]['types']['#suffix'] = t('Note: Changing the default profile will affect only future content. Existing content will keep current profile settings. (To switch the profile for existing content, use the \'Edit translation settings\' action on the Manage tab.)' . $node_message, array('@entity_type' => strtolower($entity_type_info['label']))); $form['#action'] = url('admin/settings/lingotek/settings', array('fragment' => lingotek_get_tab_id($entity_type))); } @@ -203,16 +215,17 @@ function lingotek_admin_entity_bundle_profiles_form($form, &$form_state, $entity '#type' => 'submit', '#value' => t('Save'), ); - return $form; } function lingotek_node_settings_row_profiles($bundle_name, $entity_profiles, $entity_type, $profiles_options, $setup_complete, $config_handled) { $default_profile = $setup_complete ? LingotekSync::PROFILE_DISABLED : 0; + $inherit_profile = LingotekSync::PROFILE_INHERIT; // Disable automatic profile as the default for comments during setup if ($entity_type == 'comment') { $default_profile = LingotekSync::PROFILE_DISABLED; } + $languages = lingotek_get_target_locales(FALSE); $profiles = array(); if ($config_handled) { $profiles['profile_' . $bundle_name] = array( @@ -228,6 +241,7 @@ function lingotek_node_settings_row_profiles($bundle_name, $entity_profiles, $en ); } else { + $profiles['profile_' . $bundle_name] = array( '#type' => 'select', '#options' => $profiles_options, @@ -238,18 +252,67 @@ function lingotek_node_settings_row_profiles($bundle_name, $entity_profiles, $en 'class' => array('field'), ), ); + + if (variable_get('lingotek_enable_language_specific_profiles', FALSE)) { + + // add a title to the main profile for the bundle + $profiles['profile_' . $bundle_name]['#title'] = 'Default Profile'; + + // fieldset wrapper to indent the language overrides + $profiles['profile_overrides'] = array( + '#type' => 'fieldset', + '#id' => 'profile_override__' . $bundle_name, + '#states' => array( + 'invisible' => array(':input[name="' . $entity_type . '_language"]' => array('visible' => FALSE)), + ), + '#attributes' => array( + 'style' => 'border: none; padding: 0 0 0 0; background-color: inherit;', + ) + ); + + // add the 'inherit' option after writing the main profile box + $profiles_options[LingotekSync::PROFILE_INHERIT] = '(use default profile for ' . $bundle_name . ')'; + + foreach ($languages as $language) { + $profiles['profile_overrides'][$bundle_name . '_' . $language->lingotek_locale] = array( + '#title' => 'Content created in ' . $language->name, + '#type' => 'select', + '#options' => $profiles_options, + '#value' => isset($entity_profiles[$entity_type][$bundle_name . '__' . $language->lingotek_locale]) ? $entity_profiles[$entity_type][$bundle_name . '__' . $language->lingotek_locale] : LingotekSync::PROFILE_INHERIT, + '#attributes' => array( + 'id' => array('edit-form-item-profile-' . $bundle_name . '__' . $language->lingotek_locale), + 'name' => 'profile_' . $bundle_name . '__' . $language->lingotek_locale, + 'class' => array('field'), + ), + ); + } + } } return $profiles; } function lingotek_node_settings_row_fields($entity_type, $bundle_name, $setup_complete, $translatable_field_types, $translate) { + $field_types = field_info_fields(); + $field_info_instances = field_info_instances($entity_type, $bundle_name); + + if ($entity_type == 'menu_link') { + $field_info_instances = lingotek_get_menu_link_fields(); + } + $fields = array(); - foreach (field_info_instances($entity_type, $bundle_name) as $field) { + foreach ($field_info_instances as $field) { $field_label = $field['label']; $field_machine_name = $field['field_name']; - $field_type = $field['widget']['type']; - if (array_search($field_type, $translatable_field_types)) { + + if ($entity_type == 'menu_link') { + $field_type = 'text'; + } + else { + $field_type = $field_types[$field['field_name']]['type']; + } + + if (in_array($field_type, $translatable_field_types)) { $fields[$field_machine_name] = array( '#type' => 'checkbox', '#title' => check_plain($field_label), @@ -274,6 +337,8 @@ function lingotek_node_settings_row_fields($entity_type, $bundle_name, $setup_co } $missing_fields = array(); + $title_id = "edit-form-item-$bundle_name-separator-title"; + if (!isset($fields['title_field']) && $entity_type == 'node') { $missing_fields[] = 'title'; } @@ -288,17 +353,19 @@ function lingotek_node_settings_row_fields($entity_type, $bundle_name, $setup_co } if (!empty($missing_fields)) { - foreach ($missing_fields as $missing_field) { - $message = t(ucfirst($missing_field) . ' (Note: field will be created.)'); + foreach ($missing_fields as $key => $missing_field) { + $message = t('@field (Note: field will be created)', array( + '@field' => ucfirst($missing_field), + )); $fields[$missing_field . '_field'] = array( '#type' => 'checkbox', '#title' => check_plain($message), '#attributes' => array( - 'id' => array('edit-form-item-' . $bundle_name . '-seperator-title'), + 'id' => array("$title_id-$key"), 'name' => 'title_swap_' . $bundle_name . '_' . $missing_field, 'class' => array('field'), ), - '#id' => 'edit-form-item-' . $bundle_name . '-seperator-title', + '#id' => "$title_id-$key", '#states' => array( 'invisible' => array( ':input[name="profile_' . $bundle_name . '"]' => array('value' => 'DISABLED'), @@ -310,7 +377,31 @@ function lingotek_node_settings_row_fields($entity_type, $bundle_name, $setup_co } } } + + // Enable translation of original title if preference is checked + if (variable_get('lingotek_translate_original_node_titles', FALSE) && $entity_type == 'node') { + $fields['title'] = array( + '#type' => 'checkbox', + '#title' => t('Node Title*'), + '#id' => $title_id, + '#attributes' => array( + 'id' => array($title_id), + 'name' => $bundle_name . '_SEPERATOR_title', + 'class' => array('field'), + ), + '#states' => array( + 'invisible' => array( + ':input[name="profile_' . $bundle_name . '"]' => array('value' => 'DISABLED'), + ), + ), + ); + if (!$setup_complete || !empty($translate[$bundle_name]) && array_search('title', $translate[$bundle_name]) !== FALSE) { + $fields['title']['#attributes']['checked'] = 'checked'; + } + } + uasort($fields, 'lingotek_title_compare'); + return $fields; } @@ -321,60 +412,6 @@ function lingotek_title_compare($a, $b) { return strcasecmp($a['#title'], $b['#title']); } -function lingotek_node_settings_row_oneoffs($bundle_name) { - $exception_num = lingotek_oneoff_count($bundle_name); - - $options = array( - 'attributes' => array('class' => array('ctools-use-modal ctools-modal-lingotek-small lingotek-center'), 'title' => 'Remove all profile exceptions for this content type'), - ); - - drupal_add_js(array( - 'lingotek-small' => array( - 'modalSize' => array( - 'type' => 'fixed', - 'width' => 450, - 'height' => 400, - ), - 'closeImage' => theme('image', array('path' => drupal_get_path('module', 'lingotek') . '/images/close.png', 'alt' => t('Close window'), 'title' => t('Close window'))), - 'closeText' => '', - 'animation' => 'fadeIn', - ), - ), 'setting'); - - $title = $exception_num > 0 ? l($exception_num, LINGOTEK_MENU_MAIN_BASE_URL . '/clearexceptions/' . $bundle_name, $options) : $exception_num; - - $oneoffs = array( - '#type' => 'item', - '#markup' => $title, - ); - - return $oneoffs; -} - -function lingotek_oneoff_count($bundle_name) { - $target_ids = db_select('lingotek_entity_metadata', 'lem') - ->fields('lem', array('entity_id')) - ->condition('entity_key', 'upload_status') - ->condition('entity_type', 'node') - ->condition('value', 'TARGET') - ->execute() - ->fetchCol(); - $query = db_select('lingotek_entity_metadata', 'lem') - ->fields('lem') - ->condition('lem.entity_key', 'profile') - ->condition('lem.entity_type', 'node'); - if (!empty($target_ids)) { - $query->condition('lem.entity_id', $target_ids, 'NOT IN'); - } - - $query->join('node', 'n', 'lem.entity_id = n.nid AND n.type = \'' . $bundle_name . '\''); - - $exception_num = $query->execute() - ->rowCount(); - - return $exception_num; -} - function lingotek_get_translation_mode($bundle) { $query = db_select('taxonomy_vocabulary', 't') ->fields('t', array('i18n_mode')) @@ -397,17 +434,14 @@ function lingotek_get_translation_mode($bundle) { } function lingotek_node_settings_row($entity_type, $bundle, $profiles, $fields, $entity_specifics) { - if ($entity_type == 'node') { - $row = array( - array('data' => $bundle['label'], 'width' => '20%'), - array('data' => drupal_render($profiles)), - array('data' => drupal_render($entity_specifics['oneoffs']), 'style' => 'text-align:center'), - array('data' => drupal_render($fields), 'width' => '65%'), - ); - } - elseif ($entity_type == 'taxonomy_term') { + + // REMOVED TEMPORARILY UNTIL REFACTOR OF LANGUAGE-SPECIFIC PROFILE UI IMPROVEMENTS. + //$label = variable_get('lingotek_enable_language_specific_profiles') ? $bundle['collapsible_label'] : $bundle['label']; + $label = '' . $bundle['label'] . ''; + + if ($entity_type == 'taxonomy_term') { $row = array( - array('data' => $bundle['label'], 'width' => '20%'), + array('data' => $label, 'width' => '20%'), array('data' => drupal_render($profiles)), array('data' => drupal_render($entity_specifics['translation_mode']), 'width' => '15%'), array('data' => drupal_render($entity_specifics['translation_handling'])), @@ -416,12 +450,11 @@ function lingotek_node_settings_row($entity_type, $bundle, $profiles, $fields, $ } else { $row = array( - array('data' => $bundle['label'], 'width' => '20%'), + array('data' => $label, 'width' => '20%'), array('data' => drupal_render($profiles)), array('data' => drupal_render($fields), 'width' => '65%'), ); } - return $row; } @@ -479,7 +512,6 @@ function lingotek_admin_add_entity_specific_changes(&$form, &$form_state, $entit '#description' => t('Set all language neutral comments (and underlying fields) for enabled comment types to be @language.', array('@language' => language_default('name'))), '#default_value' => 1, // default to enable (for use in setup) --- js will disable on settings by default ); - //$form['translation_' . $entity_type]['types']['#suffix'] = t('Note: changing the profile will update all settings for existing comments except for the project, workflow, vault, and storage method (e.g. node/field).'); break; case 'field_collection_item': // add note about field collections being coupled with their parent entities @@ -500,6 +532,7 @@ function lingotek_admin_entity_bundle_profiles_form_submit($form, &$form_state) } $enabled_fields = variable_get('lingotek_enabled_fields', array()); + // Reset fields for the entity type but leave its existence there $enabled_fields[$entity_type] = array(); $operations = array(); @@ -518,9 +551,12 @@ function lingotek_admin_entity_bundle_profiles_form_submit($form, &$form_state) $content_type = $parts[0]; $content_field = $parts[1]; + // Save the enabled fields, even if disabled, as one-offs could be + // enabled and will need to know which fields to translate. + $enabled_fields[$entity_type][$content_type][] = $content_field; + //check to make sure that the content type is enabled if ($form_state['input']['profile_' . $content_type] != LingotekSync::PROFILE_DISABLED) { - $enabled_fields[$entity_type][$content_type][] = $content_field; // Set this content type to be translation-enabled only if currently disabled. $currently_enabled_content_type = variable_get('language_content_type_' . $content_type, '0'); @@ -531,9 +567,14 @@ function lingotek_admin_entity_bundle_profiles_form_submit($form, &$form_state) if ($field_based_translation) { // Update the field via the Field API (Instead of the direct db_update) $field = field_info_field($content_field); + if (!$field) { + continue; + } $is_field_collection = key_exists($content_field, $field_collection_field_types); $field['translatable'] = $is_field_collection ? 0 : 1; - field_update_field($field); + if ($entity_type != 'menu_link') { + field_update_field($field); + } } } } @@ -547,7 +588,7 @@ function lingotek_admin_entity_bundle_profiles_form_submit($form, &$form_state) $legacy_field = array_pop($field_array); $content_type = implode('_', $field_array); -//check to make sure that the content type is enabled + //check to make sure that the content type is enabled if ($form_state['input']['profile_' . $content_type] != LingotekSync::PROFILE_DISABLED) { // Do the actual title replacement $bundle = $content_type; @@ -583,6 +624,7 @@ function lingotek_admin_entity_bundle_profiles_form_submit($form, &$form_state) } } $_SESSION['lingotek_setup_path'][] = lingotek_get_entity_setup_path($entity_type); + variable_set('lingotek_enabled_fields', $enabled_fields); drupal_set_message(t('Your content types have been updated.')); @@ -619,27 +661,13 @@ function lingotek_admin_entity_bundle_profiles_form_submit($form, &$form_state) * Return an array of entity types with the number of each using the given profile */ function lingotek_admin_profile_usage($profile_id) { - $entity_counts = array(); - $entities = lingotek_get_all_entities_by_profile($profile_id); - // count up the entities by type - foreach ($entities as $e) { - if (isset($entity_counts[$e['type']])) { - $entity_counts[$e['type']]++; - } - else { - $entity_counts[$e['type']] = 1; - } - } - return $entity_counts; + $profile = LingotekProfile::loadById($profile_id); + return $profile->getUsage(); } function lingotek_admin_profile_usage_by_types($profile_id) { - $bundles_using_profile = lingotek_get_bundles_by_profile_id($profile_id); - $count_types = 0; - foreach ($bundles_using_profile as $bup) { - $count_types += count($bup); - } - return $count_types; + $profile = LingotekProfile::loadById($profile_id); + return $profile->getUsage($by_bundle = TRUE); } /** @@ -718,16 +746,26 @@ function lingotek_admin_additional_translation_settings_form($form, &$form_state '#value' => module_exists('i18nviews'), ); + $hidden_i18n_field_value = array( + '#id' => 'lingotek_i18n_field_enabled', + '#type' => 'hidden', + '#value' => module_exists('i18n_field'), + ); + $options = array( 'lingotek_translate_config_blocks' => array(t('Blocks'), t('Include translation for all translatable blocks.') . drupal_render($prep_blocks_chbx)), 'lingotek_translate_config_taxonomies' => array(t('Taxonomy'), t('Include translation for all translatable taxonomy vocabularies and terms.') . drupal_render($prep_taxonomy_chbx)), 'lingotek_translate_config_menus' => array(t('Menus'), t('Include translation for all menus set to \'Translate and Localize\'.') . drupal_render($prep_menus_chbx)), 'lingotek_translate_config_views' => array(t('Views'), t('Include translation of all translatable strings within views. This will not generally include translation of all results generated by each view, which is usually configurable by selecting localizable fields within the view itself. (Note: You must have the i18nviews module enabled in order to use this.)') . drupal_render($hidden_i18nviews_value)), - 'lingotek_translate_config_fields' => array(t('Field Labels'), t('Include translation of all translatable field labels. (Note: You must have the i18n_field sub-module enabled in order to use this.)')), + 'lingotek_translate_config_fields' => array(t('Field Labels'), t('Include translation of all translatable field labels. (Note: You must have the i18n_field sub-module enabled in order to use this.)') . drupal_render($hidden_i18n_field_value)), 'lingotek_translate_config_builtins' => array(t('Built-in Interface'), t('Include translation of built-in strings. (Note: Indexing all of these may take several minutes or longer.)') . drupal_render($fr)), 'lingotek_translate_config_misc' => array(t('Miscellaneous Strings'), t('Include translation of any other miscellaneous strings found by the Internationalization (i18n) module.')), ); + if (module_exists('webform') && module_exists('webform_localization')) { + $options['lingotek_translate_config_webform'] = array(t('Webform'), t('Include translation of Webform components. (Note: Requires Webform and Webform Localization modules.)')); + } + $defaults = array(); foreach (array_keys($options) as $config_type) { @@ -742,117 +780,128 @@ function lingotek_admin_additional_translation_settings_form($form, &$form_state ); $profiles = variable_get('lingotek_profiles'); - $config_profile = isset($profiles[LingotekSync::PROFILE_CONFIG]) ? $profiles[LingotekSync::PROFILE_CONFIG] : NULL; - $workflows = $api->listWorkflows(); - if ($config_profile) { - if (is_array($workflows) && count($workflows) > 1 && $setup_complete) { - $stored_profile = lingotek_get_profile_settings(LingotekSync::PROFILE_CONFIG); - $stored_workflow_id = (isset($stored_profile['workflow_id']) ? $stored_profile['workflow_id'] : ''); - $selected_workflow_id = (isset($form_state['values']['addtl_workflow_id']) ? $form_state['values']['addtl_workflow_id'] : $stored_workflow_id); - $curr_workflow = $api->getWorkflow($stored_workflow_id); - $num_config_docs = count(LingotekConfigSet::getAllDocumentIds()); - if (!empty($curr_workflow)) { - // append the current workflow to the top of the workflow list, if it's not already there - $workflows = array($stored_workflow_id => $curr_workflow->name) + $workflows; - } + $config_profile = LingotekProfile::loadById(LingotekSync::PROFILE_CONFIG); - $form['additional_translation']['addtl_workflow_id'] = array( - '#type' => 'select', - '#title' => t('Workflow'), - '#description' => t('This workflow will be used for handling these additional translation items.'), - '#default_value' => $stored_workflow_id, - '#options' => $workflows, - '#ajax' => array( - 'callback' => 'lingotek_config_default_workflow_form_callback', - 'wrapper' => 'addtl-prefill-phases-div', - 'method' => 'replace', - 'effect' => 'fade', - ), - '#prefix' => '
      ', - ); + if (is_array($workflows) && count($workflows) > 1 && $setup_complete) { + $stored_workflow_id = $config_profile->getWorkflow(); + $selected_workflow_id = (isset($form_state['values']['addtl_workflow_id']) ? $form_state['values']['addtl_workflow_id'] : $stored_workflow_id); + $curr_workflow = $api->getWorkflow($stored_workflow_id); + if (!empty($curr_workflow)) { + // append the current workflow to the top of the workflow list, if it's not already there + $workflows = array($stored_workflow_id => $curr_workflow->name) + $workflows; + } - $form['additional_translation']['addtl_prefill_phases_checkbox'] = array( - '#type' => 'checkbox', - '#title' => t('Change all current config content (@total documents in total) to use the new workflow', array('@total' => $num_config_docs)), - '#default_value' => FALSE, - '#description' => t('All current translations will be removed and recreated using the new workflow, with translations pulled from the previous workflow'), - '#states' => array( - 'invisible' => array(':input[name="addtl_workflow_id"]' => array('value' => $stored_workflow_id)), - ) - ); + $form['additional_translation']['addtl_workflow_id'] = array( + '#type' => 'select', + '#title' => t('Workflow'), + '#description' => t('This workflow will be used for handling these additional translation items.'), + '#default_value' => $stored_workflow_id, + '#options' => $workflows, + '#ajax' => array( + 'callback' => 'lingotek_config_default_workflow_form_callback', + 'wrapper' => 'addtl-prefill-phases-div', + 'method' => 'replace', + 'effect' => 'fade', + ), + '#prefix' => '
      ', + ); - $form['additional_translation']['addtl_prefill_phase_select'] = array( - '#title' => t("Desired Prefill Phase"), - '#description' => t('Please select the highest phase which should be prefilled for the new workflow'), - '#type' => 'select', - '#states' => array( - 'visible' => array(':input[name="addtl_workflow_id"]' => array('!value' => $stored_workflow_id), - ':input[name="addtl_prefill_phases_checkbox"]' => array('checked' => TRUE) - ), + $form['additional_translation']['addtl_prefill_phases_checkbox'] = array( + '#type' => 'checkbox', +// '#title' => t('Change all current content in this profile (@total in total) to use the new workflow', array('@total' => array_sum(lingotek_admin_profile_usage(LingotekSync::PROFILE_CONFIG)))), + '#title' => t('Change all current content in this profile to use the new workflow'), + '#default_value' => FALSE, + '#description' => t('All current translations will be removed and recreated using the new workflow, with translations pulled from the previous workflow'), + '#states' => array( + 'invisible' => array(':input[name="addtl_workflow_id"]' => array('value' => $stored_workflow_id)), + ) + ); + + $form['additional_translation']['overwrite_config_set_workflow'] = array( + '#type' => 'checkbox', + '#title' => t('Include config sets that have been explicitly changed to another workflow.'), + '#default_value' => FALSE, + '#description' => t('All config sets will be changed, regardless of individual changes made'), + '#states' => array( + 'visible' => array(':input[name="addtl_workflow_id"]' => array('!value' => $stored_workflow_id), + ':input[name="addtl_prefill_phases_checkbox"]' => array('checked' => TRUE)) + ) + ); + + $form['additional_translation']['addtl_prefill_phase_select'] = array( + '#title' => t("Desired Prefill Phase"), + '#description' => t('Please select the highest phase which should be prefilled for the new workflow'), + '#type' => 'select', + '#states' => array( + 'visible' => array(':input[name="addtl_workflow_id"]' => array('!value' => $stored_workflow_id), + ':input[name="addtl_prefill_phases_checkbox"]' => array('checked' => TRUE) ), - '#suffix' => '
      ', - ); - $form['additional_translation']['addtl_prefill_phase_select']['#options'] = lingotek_get_phases_by_workflow_id($selected_workflow_id); - } + ), + '#suffix' => '
      ', + ); + $form['additional_translation']['addtl_prefill_phase_select']['#options'] = lingotek_get_phases_by_workflow_id($selected_workflow_id); + } - // Projects - $projects = (class_exists('LingotekApi')) ? $api->listProjects() : array(); - $id = variable_get('lingotek_project', ''); + // Projects + $projects = (class_exists('LingotekApi')) ? $api->listProjects() : array(); + $project_id = variable_get('lingotek_project', ''); - if ($id == '' || !array_key_exists($id, $projects)) { //No project id set, project deleted, or community changed to one without that project. Try to find the Drupal project - $id = array_search($site, $projects); - if ($id === False) { //Setup a default Drupal project - $id = lingotek_add_project($site); - $projects = (class_exists('LingotekApi')) ? $api->listProjects() : array(); - } - else { //Assign to an existing Drupal project - variable_set('lingotek_project', $id); - } + if ($project_id == '' || !array_key_exists($project_id, $projects)) { //No project id set, project deleted, or community changed to one without that project. Try to find the Drupal project + $project_id = array_search($site, $projects); + if ($project_id === False) { //Setup a default Drupal project + $project_id = lingotek_add_project($site); + $projects = (class_exists('LingotekApi')) ? $api->listProjects() : array(); } - - if ($projects && count($projects) > 0 && $setup_complete) { - $sorted = asort($projects); - $form['additional_translation']['addtl_project_id'] = array( - '#type' => 'select', - '#title' => t('Default Project'), - '#options' => $projects, - '#description' => t('The default Lingotek Project with which translations will be associated.'), - '#default_value' => isset($config_profile['project_id']) ? $config_profile['project_id'] : $id, - ); + else { //Assign to an existing Drupal project + variable_set('lingotek_project', $project_id); } + } - // Vaults - $vaults = $api->listVaults(); - $current_vault_id = variable_get('lingotek_vault', ''); - $personal_vault_count = ( isset($vaults['Personal Vaults']) ) ? count($vaults['Personal Vaults']) : 0; - $community_vault_count = ( isset($vaults['Community Vaults']) ) ? count($vaults['Community Vaults']) : 0; - - // If no vault id is set, and we don't have any personal vaults, then create one and add it to our project. - if (( $current_vault_id == '' ) && ( $personal_vault_count == 0 ) && ( $community_vault_count == 0 )) { - $current_project_id = variable_get('lingotek_project', ''); - // But only if we have a ProjectID. - if ($current_project_id != '') { - $current_vault_id = lingotek_add_vault($site); - lingotek_add_vault_to_project(); - } - } + if ($projects && count($projects) > 0 && $setup_complete) { + $sorted = asort($projects); + $default_project_id = $config_profile->getAttribute('project_id'); + $form['additional_translation']['addtl_project_id'] = array( + '#type' => 'select', + '#title' => t('Default Project'), + '#options' => $projects, + '#description' => t('The default Lingotek Project with which translations will be associated.'), + '#default_value' => $default_project_id ? $default_project_id : $project_id, + ); + } - if ($personal_vault_count + $community_vault_count > 0 && $setup_complete) { - $form['additional_translation']['addtl_vault_id'] = array( - '#type' => 'select', - '#title' => t('Default Vault'), - '#options' => $vaults, - '#description' => t('The default Translation Memory Vault where translations are saved.'), - '#default_value' => isset($config_profile['vault_id']) ? $config_profile['vault_id'] : $current_vault_id, - ); + // Vaults + $vaults = $api->listVaults(); + $current_vault_id = variable_get('lingotek_vault', ''); + $personal_vault_count = ( isset($vaults['Personal Vaults']) ) ? count($vaults['Personal Vaults']) : 0; + $community_vault_count = ( isset($vaults['Community Vaults']) ) ? count($vaults['Community Vaults']) : 0; + + // If no vault id is set, and we don't have any personal vaults, then create one and add it to our project. + if (( $current_vault_id == '' ) && ( $personal_vault_count == 0 ) && ( $community_vault_count == 0 )) { + $current_project_id = variable_get('lingotek_project', ''); + // But only if we have a ProjectID. + if ($current_project_id != '') { + $current_vault_id = lingotek_add_vault($site); + lingotek_add_vault_to_project(); } + } - $form['additional_translation']['lingotek_translate_config_options']['view_status'] = array( - '#type' => 'item', - '#description' => t('You can view the progress of the configuration translations on the Translate Interface page', array('@link' => url('admin/settings/lingotek/manage/config'))), + if ($personal_vault_count + $community_vault_count > 0 && $setup_complete) { + $default_vault_id = $config_profile->getAttribute('vault_id'); + $form['additional_translation']['addtl_vault_id'] = array( + '#type' => 'select', + '#title' => t('Default Vault'), + '#options' => $vaults, + '#description' => t('The default Translation Memory Vault where translations are saved.'), + '#default_value' => $default_vault_id ? $default_vault_id : $current_vault_id, ); } + + $form['additional_translation']['lingotek_translate_config_options']['view_status'] = array( + '#type' => 'item', + '#description' => t('You can view the progress of the configuration translations on the Translate Interface page', array('@link' => url('admin/settings/lingotek/manage/config'))), + ); + $setup_complete = (!lingotek_is_config_missing()); if ($setup_complete) { $form['#action'] = url('admin/settings/lingotek/settings', array('fragment' => 'ltk-config')); @@ -860,10 +909,6 @@ function lingotek_admin_additional_translation_settings_form($form, &$form_state return $form; } -/* - * This function is not used. Should we remove it? - */ - function lingotek_verify_modules_enabled($module_list) { $missing_modules = FALSE; foreach ($module_list as $mod) { @@ -919,18 +964,7 @@ function lingotek_admin_additional_translation_settings_form_submit($form, &$for lingotek_admin_prepare_menus(); $config_groups['menu'] = 'menu'; } - if (variable_get('lingotek_translate_config_views', 0) && isset($form_state['input']['lingotek_prepare_config_views'])) { - lingotek_admin_prepare_views(); - $config_groups['views'] = 'views'; - } - if (variable_get('lingotek_translate_config_fields', 0) && isset($form_state['input']['lingotek_prepare_config_fields'])) { - lingotek_admin_prepare_fields(); - $config_groups['fields'] = 'fields'; - } - if (variable_get('lingotek_translate_config_misc', 0) && isset($form_state['input']['lingotek_prepare_config_misc'])) { - lingotek_admin_prepare_misc(); - $config_groups['misc'] = 'misc'; - } + // refresh all strings for each config type if (count($config_groups)) { // combine string refresh operations with other additional operations @@ -954,7 +988,7 @@ function lingotek_admin_additional_translation_settings_form_submit($form, &$for $final_destination = 'admin/settings/lingotek/settings'; if ( isset($_SESSION['lingotek_setup_path']) && is_array($_SESSION['lingotek_setup_path']) - && end($_SESSION['lingotek_setup_path']) == 'admin/config/lingotek/additional-translation-settings') { + && end($_SESSION['lingotek_setup_path']) == LINGOTEK_MENU_LANG_BASE_URL . '/additional-translation-settings') { // Remove the setup path so future checks will not think setup just completed. unset($_SESSION['lingotek_setup_path']); $final_destination = 'admin/settings/lingotek'; @@ -973,22 +1007,30 @@ function lingotek_save_config_profile($form_state) { $workflow_id = isset($form_state['values']['addtl_workflow_id']) ? $form_state['values']['addtl_workflow_id'] : variable_get('lingotek_translate_config_workflow_id', ''); $prefill_phase = isset($form_state['values']['addtl_prefill_phase_select']) ? $form_state['values']['addtl_prefill_phase_select'] : NULL; $config_checkbox = isset($form_state['values']['addtl_prefill_phases_checkbox']) ? $form_state['values']['addtl_prefill_phases_checkbox'] : FALSE; - + $overwrite_config_set_workflow = isset($form_state['values']['overwrite_config_set_workflow']) ? $form_state['values']['overwrite_config_set_workflow'] : TRUE; $profile = array( 'project_id' => $project_id, 'vault_id' => $vault_id, 'name' => 'lingotek_config', 'workflow_id' => $workflow_id, - 'create_lingotek_document' => 0, - 'sync_method' => 1, + 'auto_upload' => 0, + 'auto_download' => 1, ); - $profile_index = LingotekSync::PROFILE_CONFIG; - lingotek_set_profile_settings($profile_index, $profile); + try { + LingotekProfile::create(LingotekSync::PROFILE_CONFIG, $profile); + } + catch (LingotekException $e) { + LingotekProfile::update(LingotekSync::PROFILE_CONFIG, $profile); + } if ($config_checkbox == TRUE) { + $document_ids = $overwrite_config_set_workflow == TRUE ? LingotekConfigSet::getAllConfigDocIds() + : LingotekConfigSet::getAllUnsetWorkflowConfigDocIds(); // Change all other workflows retroactively - $document_ids = LingotekConfigSet::getAllConfigDocIds(); + if($overwrite_config_set_workflow == TRUE){ + LingotekConfigSet::deleteConfigSetWorkflowIds();//removes workflows set on config sets. Defaults to the config default. + } if (!empty($document_ids)) { $api = LingotekApi::instance(); $api->changeWorkflow($document_ids, $workflow_id, $prefill_phase); @@ -1162,17 +1204,20 @@ function lingotek_admin_prepare_menus() { // find all menu links that are not language-neutral and not part of a // translation set and update them to be language-neutral (so they will - // be added to the locales_source table as a string to be translated) - $result = db_update('menu_links') - ->fields(array('language' => LANGUAGE_NONE)) - ->condition('language', LANGUAGE_NONE, '!=') - ->condition('i18n_tsid', 0) - ->execute(); - if ($result) { - drupal_set_message(format_plural($result, t('Set 1 menu link to language neutral.'), t('Set @num menu links to Language neutral.', array('@num' => (int) $result)))); - } - else { - drupal_set_message(t('All menu links already set to language neutral.')); + // be added to the locales_source table as a string to be translated), + // only if advanced handling of menu items is off + if (!variable_get('lingotek_advanced_menu_links', FALSE)) { + $result = db_update('menu_links') + ->fields(array('language' => LANGUAGE_NONE)) + ->condition('language', LANGUAGE_NONE, '!=') + ->condition('i18n_tsid', 0) + ->execute(); + if ($result) { + drupal_set_message(format_plural($result, t('Set 1 menu link to language neutral.'), t('Set @num menu links to Language neutral.', array('@num' => (int) $result)))); + } + else { + drupal_set_message(t('All menu links already set to language neutral.')); + } } // make sure all menu links have the "alter" attribute, so that @@ -1199,21 +1244,6 @@ function lingotek_admin_prepare_menus() { return TRUE; } -function lingotek_admin_prepare_views() { - // Stub for future preparations if necessary - return TRUE; -} - -function lingotek_admin_prepare_fields() { - // Stub for future preparations if necessary - return TRUE; -} - -function lingotek_admin_prepare_misc() { - // Stub for future preparations if necessary - return TRUE; -} - /** * Advanced Parsing - XML Configuration */ @@ -1375,6 +1405,67 @@ function lingotek_admin_prefs_form($form, &$form_state, $show_fieldset = FALSE) '#default_value' => variable_get('lingotek_advanced_taxonomy_terms', FALSE), ); + $form['prefs']['lingotek_advanced_menu_links'] = array( + '#type' => 'checkbox', + '#title' => t('Enable advanced handling of menu links'), + '#description' => t('This option is used to handle translation of menu links as entities. Requires Entity Menu Links module.'), + '#default_value' => variable_get('lingotek_advanced_menu_links', FALSE), + ); + + $form['prefs']['lingotek_disassociate_delete_tms'] = array( + '#type' => 'checkbox', + '#title' => t('Delete documents from Lingotek TMS when disassociating'), + '#description' => t('Your documents will remain in your Drupal site but will be deleted from the Lingotek TMS if this option is checked.'), + '#default_value' => variable_get('lingotek_disassociate_delete_tms', FALSE), + ); + + if (module_exists('bean')) { + $form[$fkey]['lingotek_translate_beans'] = array( + '#type' => 'checkbox', + '#title' => t('Enable bean translation'), + '#default_value' => variable_get('lingotek_translate_beans', FALSE), + '#description' => t('If checked, translatable bean fields will be managed by Lingotek.'), + ); + } + + if (module_exists('group')) { + $form[$fkey]['lingotek_translate_groups'] = array( + '#type' => 'checkbox', + '#title' => t('Enable group translation'), + '#default_value' => variable_get('lingotek_translate_groups', FALSE), + '#description' => t('If checked, translatable group fields will be managed by Lingotek.'), + ); + } + + if (module_exists('paragraphs')) { + $form[$fkey]['lingotek_translate_paragraphs'] = array( + '#type' => 'checkbox', + '#title' => t('Enable paragraphs translation'), + '#default_value' => variable_get('lingotek_translate_paragraphs', FALSE), + '#description' => t('If checked, paragraphs fields will be managed by Lingotek.'), + ); + } + + if (module_exists('file_entity')) { + $form[$fkey]['lingotek_translate_files'] = array( + '#type' => 'checkbox', + '#title' => t('Enable file entity translation'), + '#default_value' => variable_get('lingotek_translate_files', FALSE), + '#description' => t('If checked, file entity fields will be managed by Lingotek.'), + ); + } + + $node_based_translation = variable_get('lingotek_nodes_translation_method') == 'node' ? TRUE : FALSE; + + if ($node_based_translation) { + $form['prefs']['lingotek_translate_original_node_titles'] = array( + '#type' => 'checkbox', + '#title' => t('Enable translation of node titles'), + '#description' => t('This option is used to translate non-field node titles.'), + '#default_value' => variable_get('lingotek_translate_original_node_titles', FALSE), + ); + } + $form['prefs']['lingotek_hide_tlmi'] = array( '#type' => 'checkbox', '#title' => t('Hide top-level menu item'), @@ -1413,11 +1504,43 @@ function lingotek_admin_prefs_form($form, &$form_state, $show_fieldset = FALSE) ), ); + if (module_exists('metatag')) { + $form[$fkey]['lingotek_translate_metatags'] = array( + '#type' => 'checkbox', + '#title' => t('Enable metatag translation'), + '#default_value' => (variable_get('lingotek_translate_metatags') ? 1 : 0), + '#description' => t('If checked, content from metatag fields will be included with other uploaded content for each entity that supports metatags.'), + ); + } + + $lang_specific_profiles_enabled = variable_get('lingotek_enable_language_specific_profiles', 0); + $form[$fkey]['lingotek_enable_language_specific_profiles'] = array( + '#type' => 'checkbox', + '#title' => t('Enable language-specific profiles'), + '#default_value' => $lang_specific_profiles_enabled ? 1 : 0, + '#description' => t('If checked, languages enabled for Lingotek translation will not automatically be queued for all content. Instead, languages enabled for Lingotek will be added to the available languages for profiles but will be disabled by default on profiles that have existing content. (Note: this cannot be unchecked if language-specific settings are in use.)'), + '#disabled' => $lang_specific_profiles_enabled && lingotek_using_language_specific_profiles() ? TRUE : FALSE, + ); + + $form[$fkey]['lingotek_remove_target_from_tms'] = array( + '#prefix' => '
      ', + '#suffix' => '
      ', + '#type' => 'checkbox', + '#title' => t('Remove languages from TMS when changing profiles'), + '#default_value' => variable_get('lingotek_remove_target_from_tms', FALSE), + '#description' => t('If checked, changing the translation profile to a profile with fewer languages will cause the non-applicable languages to be removed from the TMS.'), + '#states' => array( + 'invisible' => array( + ':input[name="lingotek_enable_language_specific_profiles"]' => array('checked' => FALSE), + ), + ), + ); + $form[$fkey]['lingotek_account_plan_type'] = array( '#type' => 'checkbox', - '#title' => t('Enable Advanced Features'), + '#title' => t('Enable advanced features'), '#default_value' => (variable_get('lingotek_account_plan_type') == 'advanced' ? 1 : 0), - '#description' => t('Some features may not be available without an') . ' Enterprise license ' . t('for the Lingotek TMS. Call 801.331.7777 for details.'), + '#description' => t('Some features may not be available without an Enterprise license for the Lingotek TMS. Call 801.331.7777 for details.'), ); return $form; @@ -1427,6 +1550,75 @@ function lingotek_admin_prefs_form_submit($form, &$form_state) { lingotek_admin_language_switcher_form_submit($form, $form_state); $form_state['values']['lingotek_account_plan_type'] = ($form['prefs']['lingotek_account_plan_type']['#checked'] ? 'advanced' : 'standard'); system_settings_form_submit($form, $form_state); + if (module_exists('bean') && variable_get('lingotek_translate_beans')) { + if (!db_field_exists('bean', 'language')) { + $schema['bean']['fields']['language'] = array( + 'type' => 'char', + 'length' => 8, + 'not null' => TRUE, + 'default' => language_default()->language, + 'description' => 'Language used for third party translation', + ); + + db_add_field('bean', 'language', $schema['bean']['fields']['language']); + LingotekLog::warning('Language column has been added to the @module_name by Lingotek.', array('@module_name' => ('Bean'))); + } + } + if (module_exists('group') && variable_get('lingotek_translate_groups')) { + if (!db_field_exists('groups', 'language')) { + $schema['groups']['fields']['language'] = array( + 'type' => 'char', + 'length' => 8, + 'not null' => TRUE, + 'default' => language_default()->language, + 'description' => 'Language used for third party translation', + ); + + db_add_field('groups', 'language', $schema['groups']['fields']['language']); + LingotekLog::warning('Language column has been added to the @module_name by Lingotek.', array('@module_name' => ('Group'))); + } + + if (!db_table_exists('entity_translation')) { + drupal_set_message(t('For Group translation, make sure you\'ve installed the Entity Translation module.'), 'warning', FALSE); + } + } + if (module_exists('paragraphs') && variable_get('lingotek_translate_paragraphs', FALSE)) { + if (!db_field_exists('paragraphs_item', 'language')) { + $schema['paragraphs_item']['fields']['language'] = array( + 'type' => 'char', + 'length' => 8, + 'not null' => TRUE, + 'default' => language_default()->language, + 'description' => 'Language used for third party translation', + ); + db_add_field('paragraphs_item', 'language', $schema['paragraphs_item']['fields']['language']); + LingotekLog::warning('Language column has been added to the @module_name by Lingotek.', array('@module_name' => ('Paragraph'))); + } + + if (!db_table_exists('entity_translation')) { + drupal_set_message(t('For Paragraphs translation, make sure you\'ve installed the Entity Translation module.'), 'warning', FALSE); + } + } + if (module_exists('file_entity') && variable_get('lingotek_translate_files', FALSE)) { + if (!db_field_exists('file_managed', 'language')) { + $schema['file_managed']['fields']['language'] = array( + 'type' => 'char', + 'length' => 8, + 'not null' => TRUE, + 'default' => language_default()->language, + 'description' => 'Language used for third party translation', + ); + db_add_field('file_managed', 'language', $schema['file_managed']['fields']['language']); + LingotekLog::warning('Language column has been added to the @module_name by Lingotek.', array('@module_name' => ('File Entity'))); + } + + if (!db_table_exists('entity_translation')) { + drupal_set_message(t('For File Entity translation, make sure you\'ve installed the Entity Translation module.'), 'warning', FALSE); + } + } + if (!module_exists('entity_menu_links') && $form_state['values']['lingotek_advanced_menu_links'] == TRUE) { + drupal_set_message(t('For advanced menu link translation, make sure you\'ve installed the Entity Menu Links module.'), 'warning', FALSE); + } menu_rebuild(); } @@ -1534,7 +1726,14 @@ function lingotek_admin_connection_form_submit($form, &$form_state) { } function lingotek_admin_profiles_form($form, &$form_state, $show_fieldset = FALSE) { - $profiles = lingotek_get_profiles_by_name(TRUE); // these will already be translated using the t-function + + $flag_profile_changed = variable_get('lingotek_profile_changed', FALSE); + if ($flag_profile_changed !== FALSE) { + variable_delete('lingotek_profile_changed'); + lingotek_batch_update_entity_languages_by_profile($flag_profile_changed); + } + + $profiles = lingotek_get_profiles_by_name(); // these will already be translated using the t-function $form['header'] = array( '#type' => 'item', @@ -1550,7 +1749,7 @@ function lingotek_admin_profiles_form($form, &$form_state, $show_fieldset = FALS $total_count_with_profiles = 0; $rows = array(); - $profiles_excluded_from_editing = array('CUSTOM', 'DISABLED'); + $profiles_excluded_from_editing = array('DISABLED'); $profiles_excluded_from_showing = array(LingotekSync::PROFILE_CONFIG); foreach ($profiles as $key => $profile_name) { @@ -1589,13 +1788,7 @@ function lingotek_admin_profiles_form($form, &$form_state, $show_fieldset = FALS $entities_str = strlen(trim($entities_str)) ? " ($entities_str)" : ""; $entities_str = $total_entities . ' ' . format_plural($total_entities, "entity", "entities") . $entities_str; - // The custom profile line should not be shown unless there are entities assigned as 'custom' - if ($key !== 'CUSTOM') { - $rows[] = array($profile_name, $entities_str . ', ' . $count_types . ' ' . format_plural($count_types, 'content type', 'content types'), $edit_link); - } - elseif ($count_by_entity_type) { - $rows[] = array($profile_name, $entities_str, $edit_link); - } + $rows[] = array($profile_name, $entities_str . ', ' . $count_types . ' ' . format_plural($count_types, 'content type', 'content types'), $edit_link); } $variables = array( @@ -1616,19 +1809,9 @@ function lingotek_admin_profiles_form($form, &$form_state, $show_fieldset = FALS } function lingotek_admin_profile_manage($profile_id) { - $profiles = lingotek_get_profiles(); - + $profiles = lingotek_get_profiles(FALSE); + $profile = lingotek_get_global_profile(); if ($profile_id == 'add') { - $profile = (object) array( - 'name' => '', - 'lingotek_nodes_translation_method' => 'field', - 'create_lingotek_document' => 1, - 'sync_method' => 1, - 'allow_target_localization' => 0, - 'allow_source_overwriting' => 0, - 'allow_community_translation' => 0, - 'url_alias_translation' => 0, - ); end($profiles); // Set ID to one more than the current count, and then increment as needed. $profile_id = count($profiles) + 1; @@ -1637,7 +1820,6 @@ function lingotek_admin_profile_manage($profile_id) { } } else { - $profile = lingotek_get_global_profile(); $profile = array_merge($profile, $profiles[$profile_id]); } @@ -1666,7 +1848,6 @@ function lingotek_admin_profile_manage($profile_id) { print ajax_render($commands); drupal_exit(); } - print ajax_render($output); } @@ -1674,19 +1855,21 @@ function lingotek_admin_profile_manage($profile_id) { * Content defaults Form */ function lingotek_admin_profile_form($form, &$form_state, $show_fieldset = FALSE) { - $account = LingotekAccount::instance(); $api = LingotekApi::instance(); $site = variable_get('site_name', 'Drupal Site'); $profile_id = (isset($form_state['values']['profile_id']) ? $form_state['values']['profile_id']: -1); - $stored_profile = lingotek_get_profile_settings($profile_id); + $stored_profile = LingotekProfile::loadById($profile_id); $first_custom_id = 2; $is_custom_profile_id = $profile_id >= $first_custom_id; $show_advanced = $account->showAdvanced(); + $download_translations_title = t('Download Translations Automatically'); + $download_translations_desc = t('When enabled, completed translations will automatically be downloaded from Lingotek.
      When disabled, you are required to manually download translations by clicking the "Download" button on the Translations tab.'); + //$form['#action'] = url('admin/settings/lingotek/settings', array('fragment' => 'ltk-profiles')); $form['defaults']['profile_id'] = array( '#type' => 'value', @@ -1698,6 +1881,7 @@ function lingotek_admin_profile_form($form, &$form_state, $show_fieldset = FALSE '#title' => t('Profile Name'), '#default_value' => $form_state['values']['name'], '#disabled' => $is_custom_profile_id ? FALSE : TRUE, + '#required' => TRUE ); $form['defaults']['current_future_note'] = array( @@ -1705,20 +1889,20 @@ function lingotek_admin_profile_form($form, &$form_state, $show_fieldset = FALSE '#markup' => '

      ' . t('Profile settings impacting all entities (new and existing)') . '


      ', ); // Upload - $form['defaults']['create_lingotek_document'] = array( + $form['defaults']['auto_upload'] = array( '#type' => 'checkbox', '#title' => t('Upload Content Automatically'), - '#default_value' => $form_state['values']['create_lingotek_document'], + '#default_value' => $form_state['values']['auto_upload'], '#description' => t('When enabled, your Drupal content (including saved edits) will automatically be uploaded to Lingotek for translation.
      When disabled, you are required to manually upload your content by clicking the "Upload" button on the Translations tab.'), '#disabled' => $is_custom_profile_id ? FALSE : TRUE, ); // Download - $form['defaults']['sync_method'] = array( + $form['defaults']['auto_download'] = array( '#type' => 'checkbox', - '#title' => t('Download Translations Automatically'), - '#default_value' => $form_state['values']['sync_method'], - '#description' => t('When enabled, completed translations will automatically be downloaded from Lingotek.
      When disabled, you are required to manually download translations by clicking the "Download" button on the Translations tab.'), + '#title' => $download_translations_title, //xss checks not necessary here. Text only comes from php + '#default_value' => $form_state['values']['auto_download'], + '#description' => $download_translations_desc, //xss checks not necessary here. Text only comes from php and relies on
      tag '#disabled' => $is_custom_profile_id ? FALSE : TRUE, ); @@ -1761,8 +1945,8 @@ function lingotek_admin_profile_form($form, &$form_state, $show_fieldset = FALSE // Workflows $workflows = $api->listWorkflows(); - if ($workflows && count($workflows) > 1) { - $stored_workflow_id = (isset($stored_profile['workflow_id']) ? $stored_profile['workflow_id'] : ''); + if (!empty($workflows) && count($workflows) > 1) { + $stored_workflow_id = $stored_profile->getWorkflow(); $selected_workflow_id = (isset($form_state['values']['workflow_id']) ? $form_state['values']['workflow_id'] : $stored_workflow_id); $curr_workflow = $api->getWorkflow($stored_workflow_id); if (!empty($curr_workflow)) { @@ -1888,6 +2072,82 @@ function lingotek_admin_profile_form($form, &$form_state, $show_fieldset = FALSE ); } + // TARGET-LANGUAGE OVERRIDES + if (variable_get('lingotek_enable_language_specific_profiles', FALSE)) { + + $form['defaults']['language_overrides_label'] = array( + '#type' => 'markup', + '#markup' => '

      ' . t('Target Languages') . '


      ', + ); + $languages = lingotek_get_target_locales(FALSE); + usort($languages, "lingotek_language_specific_profile_cmp"); + $form['defaults']['language_overrides'] = array(); + $settings_options = array( + LingotekSync::PROFILE_INHERIT => 'Inherit default settings', + LingotekSync::PROFILE_CUSTOM => 'Use custom settings', + LingotekSync::PROFILE_DISABLED => 'Disabled', + ); + foreach ($languages as $language) { + + if ($stored_profile->isTargetLocaleDisabled($language->lingotek_locale)) { + $default_language_override_value = LingotekSync::PROFILE_DISABLED; + } + elseif ($stored_profile->isTargetLocaleCustom($language->lingotek_locale)) { + $default_language_override_value = LingotekSync::PROFILE_CUSTOM; + } + else { + $default_language_override_value = LingotekSync::PROFILE_INHERIT; + } + + $form['defaults']['language_override'][$language->lingotek_locale] = array( + '#title' => $language->name . ' (' . $default_language_override_value . ')',//xss checks not necessary here. Text only comes from php + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#id' => 'language_override__' . $language->lingotek_locale, + ); + + $form['defaults']['language_override'][$language->lingotek_locale]['settings_' . $language->lingotek_locale] = array( + '#type' => 'select', + '#options' => $settings_options, + '#default_value' => $default_language_override_value, + ); + + $is_automatic_or_manual_profile = in_array($stored_profile->getId(), array(LingotekSync::PROFILE_AUTOMATIC, LingotekSync::PROFILE_MANUAL)); + $auto_download = $stored_profile->isAutoDownload($language->lingotek_locale); + $form['defaults']['language_override'][$language->lingotek_locale]['auto_download_' . $language->lingotek_locale] = array( + '#type' => 'checkbox', + '#title' => $download_translations_title, //xss checks not necessary here. Text only comes from php + '#default_value' => $auto_download, + '#disabled' => $is_automatic_or_manual_profile, + '#states' => array( + 'invisible' => array( + ':input[name="settings_' . $language->lingotek_locale . '"]' => array('!value' => LingotekSync::PROFILE_CUSTOM), + ), + ), + ); + if ($is_automatic_or_manual_profile) { + $form['defaults']['language_override'][$language->lingotek_locale]['auto_download_' . $language->lingotek_locale]['#description'] = t('(disabled because this profile is @profile_name)', array('@profile_name' => $stored_profile->getName())); + } + + if (!empty($workflows) && count($workflows) > 1) { + $curr_workflow = $stored_profile->getWorkflow($language->lingotek_locale); + $form['defaults']['language_override'][$language->lingotek_locale]['workflow_id_' . $language->lingotek_locale] = array( + '#type' => 'select', + '#title' => t('Workflow'), + '#options' => $workflows, + '#default_value' => $curr_workflow ? $curr_workflow : $stored_workflow_id, + '#empty_option' => '(select one)', + '#states' => array( + 'invisible' => array( + ':input[name="settings_' . $language->lingotek_locale . '"]' => array('!value' => LingotekSync::PROFILE_CUSTOM), + ), + ), + ); + } + } + } + $form['defaults']['save'] = array( '#type' => 'submit', '#value' => t('Save'), @@ -1904,17 +2164,31 @@ function lingotek_admin_profile_form($form, &$form_state, $show_fieldset = FALSE else { $markup = $is_custom_profile_id ? t('You can only delete this profile when there are no entities or bundles using it.') : t('This profile cannot be deleted.'); $form['delete'] = array( - '#markup' => '' . $markup . '', + '#markup' => '' . $markup . '', ); } - $form['defaults']['cancel'] = array( - '#type' => 'submit', - '#value' => t('Cancel'), + + $form['actions']['cancel'] = array( + '#markup' => l(t('Cancel'), '#', + array( + 'attributes' => array( + 'class' => 'ctools-close-modal' + ), + 'external' => TRUE + ) + ) ); return $form; } +function lingotek_language_specific_profile_cmp($a, $b) { + if (!isset($a->name) || !isset($b->name)) { + return 0; + } + return strcmp($a->name, $b->name); +} + function lingotek_profile_default_workflow_form_callback($form, $form_state) { return array( $form['defaults']['workflow_id'], @@ -1927,60 +2201,161 @@ function lingotek_config_default_workflow_form_callback($form, $form_state) { return array( $form['additional_translation']['addtl_workflow_id'], $form['additional_translation']['addtl_prefill_phases_checkbox'], + $form['additional_translation']['overwrite_config_set_workflow'], $form['additional_translation']['addtl_prefill_phase_select'], ); } function lingotek_admin_profile_form_submit($form, &$form_state) { - if ($form_state['values']['op'] == 'Save') { - $profiles = variable_get('lingotek_profiles'); - $profile = array(); + $profile_obj = LingotekProfile::loadById($form_state['values']['profile_id']); - $profile['name'] = $form_state['values']['name']; + if ($form_state['values']['op'] == 'Save') { + $profile_obj->setName($form_state['values']['name']); foreach (lingotek_get_profile_fields(TRUE, TRUE) as $key) { if (isset($form_state['values'][$key])) { - $profile[$key] = $form_state['values'][$key]; + $profile_obj->setAttribute($key, $form_state['values'][$key]); + } + } + + // Set all source-language-specific overrides + $languages = lingotek_get_target_locales(); + foreach ($languages as $l) { + if (!empty($form_state['values']['settings_' . $l])) { + $override_settings = $form_state['values']['settings_' . $l]; + if ($override_settings == LingotekSync::PROFILE_DISABLED) { + $profile_obj->deleteTargetLocaleOverrides($l); + $profile_obj->setAttribute('disabled', TRUE, $l); + } + elseif ($override_settings === LingotekSync::PROFILE_CUSTOM) { + // reset for new settings + $profile_obj->deleteTargetLocaleOverrides($l); + // set auto-download + $auto_download = !empty($form_state['values']['auto_download_' . $l]) ? $form_state['values']['auto_download_' . $l] : FALSE; + $profile_obj->setAutoDownload($auto_download, $l); + + // set workflow + $workflow_id = !empty($form_state['values']['workflow_id_' . $l]) ? $form_state['values']['workflow_id_' . $l] : $profile['workflow_id']; + $profile_obj->setWorkflow($workflow_id, $l); + } + elseif ($override_settings === LingotekSync::PROFILE_INHERIT) { + $profile_obj->deleteTargetLocaleOverrides($l); + } + else { + throw new LingotekException('Unknown profile selection'); + } } } // If the workflow has changed and if current translations should be changed, // then pull all nodes associated with this profile and update the workflow // on TMS, including the correct phase. - if (isset($form_state['values']['profile_id'])) { - $profile_id = $form_state['values']['profile_id']; - lingotek_set_profile_settings($profile_id, $profile); + // + // TODO: ACCOUNT FOR THE LANGUAGE-SPECIFIC OVERRIDES BEFORE CHANGING THIS! + if (isset($form_state['values']['prefill_phases_checkbox']) + && $form_state['values']['prefill_phases_checkbox']) { + $workflow_id = $form_state['values']['workflow_id']; + $prefill_phase = $form_state['values']['prefill_phase_select']; - if (isset($form_state['values']['prefill_phases_checkbox']) - && $form_state['values']['prefill_phases_checkbox']) { - $workflow_id = $form_state['values']['workflow_id']; - $prefill_phase = $form_state['values']['prefill_phase_select']; + $entities = lingotek_get_all_entities_by_profile($profile_obj->getId()); - $entities = lingotek_get_all_entities_by_profile($profile_id); - - $api = LingotekApi::instance(); - $document_ids = array(); + $api = LingotekApi::instance(); + $document_ids = array(); - // gather doc IDs for bulk submission - foreach ($entities as $entity) { - if (isset($entity['document_id']) && $entity['document_id']) { - $document_ids[] = $entity['document_id']; - lingotek_keystore($entity['type'], $entity['id'], 'workflow_id', $workflow_id); - } - } - if (!empty($document_ids)) { - $api->changeWorkflow($document_ids, $workflow_id, $prefill_phase); + // gather doc IDs for bulk submission + foreach ($entities as $entity) { + if (isset($entity['document_id']) && $entity['document_id']) { + $document_ids[] = $entity['document_id']; + lingotek_keystore($entity['type'], $entity['id'], 'workflow_id', $workflow_id); } } + if (!empty($document_ids)) { + $api->changeWorkflow($document_ids, $workflow_id, $prefill_phase); + } } + variable_set('lingotek_profile_changed', $profile_obj->getId()); } elseif ($form_state['values']['op'] == 'Delete') { - $profiles = variable_get('lingotek_profiles'); - unset($profiles[$form_state['values']['profile_id']]); - variable_set('lingotek_profiles', $profiles); + $profile_obj->delete(); + } +} + +/** + * Field Types and Settings Form + */ +function lingotek_admin_field_settings_form($form, &$form_state) { + $field_types = field_info_field_types(); + $fields = field_info_fields(); + $enabled_types = lingotek_get_translatable_field_types(); + $all_column_names = lingotek_get_all_column_names($fields); + $enabled_column_names = lingotek_get_translatable_field_columns(); + + $form['#action'] = url('admin/settings/lingotek/settings', array('fragment' => 'ltk-fields')); + $form['field_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Field Types and Settings'), + 'actions' => array( + '#type' => 'actions', + 'submit' => array( + '#type' => 'submit', + '#value' => t('Save') + ) + ), + '#submit' => array('lingotek_admin_field_settings_form_submit') + ); + + // FIELD TYPES + $form['field_settings']['field_types'] = array( + '#type' => 'fieldset', + '#title' => t('Field Types'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + foreach ($field_types as $type_name => $type_info) { + $form['field_settings']['field_types']['fld__' . $type_name] = array( + '#type' => 'checkbox', + '#title' => $type_info['label'], //xss checks not necessary here. Text only comes from php + '#default_value' => in_array($type_name, $enabled_types), + ); } - elseif ($form_state['values']['op'] == 'Cancel') { - //do nothing + + // FIELD COLUMN DEFAULTS + $form['field_settings']['field_column_defaults'] = array( + '#type' => 'fieldset', + '#title' => t('Field Column Defaults'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + foreach ($all_column_names as $column_name => $fields) { + $form['field_settings']['field_column_defaults']['col__' . $column_name] = array( + '#type' => 'checkbox', + '#title' => $column_name . ' (' . implode(', ', $fields) . ')',//xss checks not necessary here. Text only comes from php + '#default_value' => in_array($column_name, $enabled_column_names), + ); + } + + return $form; +} + +function lingotek_admin_field_settings_form_submit($form, &$form_state) { + + // Set the translatable field types and column defaults. + $fields = array(); + $columns = array(); + + foreach ($form_state['values'] as $type_name => $val) { + if ($val !== 1) { + continue; + } + list($type, $name) = explode('__', $type_name, 2); + if ($type == 'fld') { + $fields[] = $name; + } + elseif ($type == 'col') { + $columns[] = $name; + } } + variable_set('lingotek_translatable_field_types', $fields); + variable_set('lingotek_translatable_column_defaults', $columns); } /** @@ -2214,10 +2589,17 @@ function lingotek_admin_logging_form($form, &$form_state, $show_fieldset = FALSE '#default_value' => variable_get('lingotek_trace_log', 0), ); + $form['logging']['i18n_string_debug'] = array( + '#type' => 'checkbox', + '#title' => t('i18n String Logging'), + '#description' => t('This appends string context information to each string for debugging purposes. (This should not be enabled on a production web site!)'), + '#default_value' => variable_get('i18n_string_debug', 0), + ); + $form['logging']['lingotek_flush_cache'] = array( '#type' => 'checkbox', '#title' => t('Never cache'), - '#description' => t('Skips caching so you can test easier. This avoids frequent polling of fresh data from Lingotek. Only those with Developer permissions will have caching disabled.'), + '#description' => t('Skips caching to ensure the module is always using current information. Performance may be slower while caching is disabled, but testing may be easier. This will only affect those with Developer permissions.'), '#default_value' => variable_get('lingotek_flush_cache', 0), ); @@ -2303,6 +2685,27 @@ function lingotek_admin_cleanup_form($form, &$form_state, $show_fieldset = FALSE ); } + if (module_exists('bean') && variable_get('lingotek_translate_beans', FALSE)) { + $cleanup_functions['lingotek_cleanup_field_languages_for_beans'] = array( + 'title' => t('Prepare bean fields'), + 'desc' => t('Sets all language neutral bean fields to be @lang.', array('@lang' => language_default('name'))), + ); + } + + if (module_exists('group') && variable_get('lingotek_translate_groups', FALSE)) { + $cleanup_functions['lingotek_cleanup_field_languages_for_groups'] = array( + 'title' => t('Prepare group fields'), + 'desc' => t('Sets all language neutral group fields to be @lang.', array('@lang' => language_default('name'))), + ); + } + + if (module_exists('paragraphs') && variable_get('lingotek_translate_paragraphs', FALSE)) { + $cleanup_functions['lingotek_cleanup_field_languages_for_paragraphs'] = array( + 'title' => t('Prepare paragraph fields'), + 'desc' => t('Sets all language neutral paragraph fields to be @lang.', array('@lang' => language_default('name'))), + ); + } + $cleanup_functions['lingotek_admin_prepare_blocks'] = array( 'title' => t('Prepare blocks'), 'desc' => t('Update all blocks to be translatable in the Languages settings.'), @@ -2315,7 +2718,7 @@ function lingotek_admin_cleanup_form($form, &$form_state, $show_fieldset = FALSE $cleanup_functions['lingotek_admin_prepare_menus'] = array( 'title' => t('Prepare menus'), - 'desc' => t('Update all menus to use Translate and Localize in the Multilingual Options, and update all menu links to be Language neutral.'), + 'desc' => t('Update all menus to use Translate and Localize in the Multilingual Options, and update all menu links to be Language neutral only if advanced handling of menu links is off.'), ); if (module_exists('field_collection')) { @@ -2396,7 +2799,6 @@ function lingotek_admin_configuration_view($form_short_id = NULL, $show_fieldset $account = LingotekAccount::instance(); $api = LingotekApi::instance(); - $site = variable_get('site_name', 'Drupal Site'); $show_advanced = $account->showAdvanced(); //$form_short_id values: config, logging, utilities, language_switcher @@ -2431,13 +2833,14 @@ function lingotek_admin_configuration_view($form_short_id = NULL, $show_fieldset $output['lingotek'][] = lingotek_wrap_in_fieldset($account_summary, t('Account'), array('id' => 'ltk-account')); - foreach (lingotek_managed_entity_types($include_all = TRUE) as $machine_name => $entity_type) { + foreach (lingotek_managed_entity_types(TRUE) as $machine_name => $entity_type) { $entity_settings = drupal_get_form('lingotek_admin_entity_bundle_profiles_form', $machine_name, $show_fieldset); $output['lingotek'][] = lingotek_wrap_in_fieldset($entity_settings, t('Translate @type', array('@type' => $entity_type['label'])), array('id' => lingotek_get_tab_id($machine_name), 'class' => array('ltk-entity'))); } $output['lingotek'][] = lingotek_wrap_in_fieldset(drupal_get_form('lingotek_admin_additional_translation_settings_form', $show_fieldset), t('Translate Configuration'), array('id' => 'ltk-config')); $output['lingotek'][] = lingotek_wrap_in_fieldset(drupal_get_form('lingotek_admin_profiles_form', $show_fieldset), t('Translation Profiles'), array('id' => 'ltk-profiles')); if ($show_advanced) { + $output['lingotek'][] = lingotek_wrap_in_fieldset(drupal_get_form('lingotek_admin_field_settings_form', $show_fieldset), t('Advanced Field Settings'), array('id' => 'ltk-fields')); $output['lingotek'][] = lingotek_wrap_in_fieldset(drupal_get_form('lingotek_admin_advanced_parsing_form', TRUE), t('Advanced Content Parsing'), array('id' => 'ltk-advanced-content-parsing')); } @@ -2541,3 +2944,69 @@ function lingotek_handle_advanced_xml_upgrade($form, $form_state) { function lingotek_get_tab_id($entity_type) { return 'ltk-' . str_replace(' ', '-', $entity_type); } + +function lingotek_get_language_specific_labels($entity_type, $bundle, $languages) { + $label = array( + '#type' => 'fieldset', + '#title' => $bundle['label'], + '#tree' => TRUE, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#attributes' => array( + 'class' => array( + 'collapsible', + 'collapsed', + ), + 'name' => $entity_type . '_languages', + 'style' => 'border: none;', + ), + ); + foreach ($languages as $k => $v) { + $label[$k] = array( + '#type' => 'markup', + '#markup' => '
      ' . $v->native . ' (source)
      ', + ); + } + return $label; +} + +function lingotek_using_language_specific_profiles() { + + // If any of the source languages are using a non-default profile, return true. + $entity_types = variable_get('lingotek_entity_profiles'); + foreach ($entity_types as $type) { + foreach ($type as $bundle => $profile_id) { + if (strpos('__', $bundle) && $profile_id != LingotekSync::PROFILE_INHERIT) { + return TRUE; + } + } + } + + // If any of the profiles have custom or disabled targets, return true. + $profiles = variable_get('lingotek_profiles'); + foreach ($profiles as $profile) { + if (!empty($profile['target_language_overrides'])) { + return TRUE; + } + } + + // No sign of language-specific settings + return FALSE; +} + +function lingotek_get_all_column_names($fields) { + // return a list of all column names along with the fields that use them. + $column_names = array(); + foreach ($fields as $field_name => $field) { + if (empty($field['columns'])) { + continue; + } + foreach (array_keys($field['columns']) as $col) { + if (!isset($column_names[$col])) { + $column_names[$col] = array(); + } + $column_names[$col][] = $field_name; + } + } + return $column_names; +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.api.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.api.inc index 8cff8464..7895d935 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.api.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.api.inc @@ -28,3 +28,59 @@ function hook_lingotek_entity_pre_upload(LingotekTranslatableEntity $translatabl function hook_lingotek_protect_variables_alter(array &$patterns, $protect_vars) { } +/** + * Alter the xml content just before sending to Lingotek for translation. + * + * @param array $params + * An associative array containing the following: + * - entity_type: a string naming the entity type + * - entity: a reference to the entity object for which content is being sent + * - xml: a LingotekXMLElement object containing the content to be translated. + */ +function hook_lingotek_entity_upload_alter(array &$params) { +} + +/** + * Alter the xml content just after receiving a translated entity from Lingotek. + * + * @param array $params + * An associative array containing the following: + * - entity_type: a string naming the entity type + * - entity: a reference to the entity object for which content is received. + * - xml: a LingotekXMLElement object containing the translated content. + * - langcode: the Drupal language code associated with the content. + */ +function hook_lingotek_entity_download_alter(array &$params) { +} + +/** + * Alter the support for entity types within the Lingotek module. + * + * @param array $enabled_types + * An associative array containing the entities currently Lingotek-enabled. + * @param bool $include_all + * Whether all translatable entity types should be shown, or just the ones + * that should appear on the bulk-manage grid. + */ +function hook_lingotek_managed_entity_types_alter(array &$enabled_types, $include_all) { +} + +/** + * Alter the profile options available for a given entity type. + * + * @param string $entity_type + * The entity type for which profile options should be modified. + * @param array $options + * An associative array containing the options for the given entity type. + */ +function hook_lingotek_entity_profiles_options_alter($entity_type, array &$options) { +} + +/** +* Alter the source URL of an entity before upload to TMS. +* +* @param string $source_url +* The entity's url. +*/ +function hook_lingotek_source_URL_alter(&$source_url) { +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.batch.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.batch.inc index d197fc3f..9091b050 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.batch.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.batch.inc @@ -7,7 +7,7 @@ /** * Field Language Data Cleanup Utility - * + * * Creates a batch to cleanup nodes with data in an 'und' language field. * * @param bool $autoset_batch @@ -45,6 +45,7 @@ function lingotek_field_language_data_cleanup_batch_create($entity_type, $autose $query2 = new EntityFieldQuery(); $entities2 = $query2->entityCondition('entity_type', $entity_type) ->execute(); + unset($query2); if (isset($entities2[$entity_type])) { foreach ($entities2[$entity_type] as $entity) { @@ -144,7 +145,6 @@ function lingotek_sync_batch_finished($success, $results, $operations) { * Sync - Upload Batch Elements: Creates the batch elements for nodes/documents that need to be uploaded. */ function lingotek_get_sync_upload_batch_elements($entity_type, $upload_nids = array()) { - $operations = array(); if (is_array($upload_nids)) { @@ -156,13 +156,43 @@ function lingotek_get_sync_upload_batch_elements($entity_type, $upload_nids = ar return $operations; } +/** + * Update marked entities: Creates the batch elements for marked entities. + */ +function lingotek_get_marked_batch_elements($entity_type, $entity_ids = array()) { + $operations = array(); + + if (is_array($entity_ids)) { + foreach ($entity_ids as $entity_id) { + $operations[] = array('lingotek_update_marked_items', array($entity_type, $entity_id)); + } + } + + return $operations; +} + +/** + * Update unmarked entities: Creates the batch elements for unmarked entities. + */ +function lingotek_get_unmarked_batch_elements($entity_type, $entity_ids = array()) { + $operations = array(); + + if (is_array($entity_ids)) { + foreach ($entity_ids as $entity_id) { + $operations[] = array('lingotek_update_unmarked_items', array($entity_type, $entity_id)); + } + } + + return $operations; +} + /** * Sync - Upload Config Batch Elements: Creates the batch elements for config (ie. menus, taxonomies, * etc.), that need to be uploaded. */ function lingotek_get_sync_upload_config_batch_elements($set_ids = array()) { - $operations = array(); + if (is_array($set_ids)) { foreach ($set_ids as $sid) { $operations[] = array('lingotek_sync_upload_config_set', array($sid)); @@ -172,8 +202,8 @@ function lingotek_get_sync_upload_config_batch_elements($set_ids = array()) { } /** * Sync - Download Batch Elements: Creates the batch elements for nodes/documents that need to be downloaded. - * - * @param download_targets + * + * @param download_targets * list of objects (document_id, lingotek_locale) //json_decode([ {"document_id": "191", "locale": "fr_FR" }, ... ]); */ function lingotek_get_sync_download_batch_elements($download_targets = NULL, $sync_success_target = LingotekSync::STATUS_CURRENT) { @@ -229,10 +259,6 @@ function lingotek_get_sync_download_batch_elements($download_targets = NULL, $sy } $doc_ids = array_unique($doc_ids); } - if (count($operations)) { - $operations[] = array('drupal_flush_all_caches', array()); - } - return $operations; } @@ -293,6 +319,40 @@ function lingotek_sync_upload_config_finished($success, $results, $operations) { lingotek_sync_upload_node_finished($success, $results, $operations, TRUE); } +function lingotek_update_marked_items_finished($success, $results) { + if (isset($results['entity_type']) && $results['entity_type'] === 'config') { + $message = format_plural($results['marked'], t('1 config item has been marked.'), t('@num config items have been marked.', array('@num' => (int) $results['marked']))); + } + else { + $message = format_plural($results['marked'], t('1 entity has been marked.'), t('@num entities have been marked.', array('@num' => (int) $results['marked']))); + } + + if ($success) { + drupal_set_message(filter_xss($message), 'status'); + } + else { + $message = format_plural($results['marked'], t('Marking 1 entity has failed.'), t('Marking @num entities has failed.', array('@num' => (int) $results['marked']))); + drupal_set_message(filter_xss($message), 'error'); + } +} + +function lingotek_update_unmarked_items_finished($success, $results) { + if (isset($results['entity_type']) && $results['entity_type'] === 'config') { + $message = format_plural($results['unmarked'], t('1 config item has been unmarked.'), t('@num config items have been unmarked.', array('@num' => (int) $results['unmarked']))); + } + else { + $message = format_plural($results['unmarked'], t('1 entity has been unmarked.'), t('@num entities have been unmarked.', array('@num' => (int) $results['unmarked']))); + } + + if ($success) { + drupal_set_message(filter_xss($message), 'status'); + } + else { + $message = format_plural($results['unmarked'], t('Unmarking 1 entity has failed.'), t('Unmarking @num entities has failed.', array('@num' => (int) $results['unmarked']))); + drupal_set_message(filter_xss($message), 'error'); + } +} + /** * Upload Batch Worker Function: Upload Config Set for Translation */ @@ -302,10 +362,37 @@ function lingotek_sync_upload_config_set($set_id, &$context) { } $api = LingotekApi::instance(); $set = LingotekConfigSet::loadById($set_id); + $query = db_select('lingotek_config_metadata', 'lcm'); + $query->addField('lcm', 'value'); + $query->condition('id', $set_id, '='); + $query->condition('config_key', 'workflow_id', '='); + $workflow_id = $query->execute()->fetchField(); + if($workflow_id !== NULL){ + $set->setWorkflowId($workflow_id); + } + module_invoke_all('lingotek_pre_upload', $set); - if ($existing_document = $set->hasLingotekDocId()) { - LingotekLog::trace('existing document: @existing', array('@existing' => $existing_document)); - $result = $api->updateContentDocument($set); + $existing_document_id = $set->hasLingotekDocId(); + + if ($existing_document_id) { + $params = array( + 'id' => $existing_document_id + ); + LingotekLog::trace('existing document: @existing', array('@existing' => $existing_document_id)); + // There is a race condition here that depends on whether the config document + // has been uploaded to the TMS before other members of the config set try + // to update the document. This loop waits for up to 30 seconds for the document + // to be uploaded before updating. + for ($i = 0; $i < 10; $i++) { + $import_response = $api->request('getDocumentImportStatus', $params); + if ($import_response->status === 'COMPLETE') { + $result = $api->updateContentDocument($set); + break; + } + else { + sleep(3); + } + } } else { $result = $api->addContentDocument($set, TRUE); @@ -379,12 +466,15 @@ function lingotek_source_language_cleanup_batch_worker($entity_type, $entity_id, $loaded_entity = lingotek_entity_load_single($entity_type, $entity_id); $info = entity_get_info($entity_type); + // Default to string 'language' if no language-related entity key is found. $language_field = !empty($info['entity keys']['language']) ? $info['entity keys']['language'] : 'language'; + if (isset($loaded_entity->$language_field) && $loaded_entity->$language_field != $source_language) { $loaded_entity->$language_field = $source_language; - $loaded_entity->lingotek_upload_override = FALSE; // Set 0 : Ensure that uploading does not occur. Set 1 : Force uploading to occur + $loaded_entity->lingotek_upload_override = FALSE; // Set 0 : Prevent upload. Set 1 : Force upload. entity_save($entity_type, $loaded_entity); + if (!isset($context['results']['entity_cleanup'])) { $context['results']['entity_cleanup'] = 0; } @@ -438,8 +528,8 @@ function lingotek_field_language_data_cleanup_batch_worker($entity_type, $entity /** * Ensures correct language-specific field data for the specified item. - * - * Logic: Look at each translatable_field (Any field marked for lingotek + * + * Logic: Look at each translatable_field (Any field marked for lingotek * translation management) for the given node. If the field has data in the * language 'und' area, and is empty in the language area that this node is, * copy the data over. So if this node is marked as English, but there is no @@ -453,9 +543,7 @@ function lingotek_field_language_data_cleanup_batch_worker($entity_type, $entity * TRUE if specified node's field data was updated. FALSE if no changes made. */ function lingotek_field_language_data_cleanup_update_entity($entity_type, $entity_id, &$context) { - $edited = FALSE; - $entity = lingotek_entity_load_single($entity_type, $entity_id); if (!$entity) { @@ -463,23 +551,41 @@ function lingotek_field_language_data_cleanup_update_entity($entity_type, $entit return $edited; } $info = entity_get_info($entity_type); - $label_field = $info['entity keys']['label']; + + if ($entity_type === 'paragraphs_item') { + $label_field = $info['entity keys']['field_name']; + } else { + $label_field = $info['entity keys']['label']; + } + // Use 'language' as a fallback in case the entity doesn't give lang field. $language_field = isset($info['entity keys']['language']) ? $info['entity keys']['language'] : 'language'; $context['message'] = t('Preparing translatable content for @entity_type: @entity_title', array('@entity_type' => $entity_type, '@entity_title' => $entity->$label_field)); if ($entity->$language_field != LANGUAGE_NONE && ($entity_type != 'node' || !lingotek_uses_node_translation($entity))) { $translatable_fields = lingotek_translatable_fields(); foreach ($translatable_fields as $field_name) { - if (!empty($entity->{$field_name}[LANGUAGE_NONE]) && empty($entity->{$field_name}[$entity->{$language_field}])) { - $entity->{$field_name}[$entity->{$language_field}] = $entity->{$field_name}[LANGUAGE_NONE]; - $edited = TRUE; + if ($entity_type == 'bean' || $entity_type == 'group') { + if (!empty($entity->{$field_name}[LANGUAGE_NONE]) && empty($entity->{$field_name}[language_default()->language])) { + $entity->{$field_name}[$entity->{$language_field}] = $entity->{$field_name}[LANGUAGE_NONE]; + $edited = TRUE; + } + } + else { + if (!empty($entity->{$field_name}[LANGUAGE_NONE]) && empty($entity->{$field_name}[$entity->{$language_field}])) { + $entity->{$field_name}[$entity->{$language_field}] = $entity->{$field_name}[LANGUAGE_NONE]; + $edited = TRUE; + } } } } if ($edited) { $entity->lingotek_upload_override = FALSE; - entity_save($entity_type, $entity); + if ($entity_type === 'paragraphs_item') { + $entity->save(TRUE); + } else { + entity_save($entity_type, $entity); + } } return $edited; @@ -490,21 +596,25 @@ function lingotek_field_language_data_cleanup_update_entity($entity_type, $entit */ function lingotek_field_language_data_cleanup_batch_finished($success, $results, $operations) { if ($success) { + $did_something = FALSE; $def_lang = language_default('name'); if (isset($results['entity_cleanup'])) { $num_nodes = (int) $results['entity_cleanup']; drupal_set_message(format_plural($num_nodes, t('Converted @count entities from language neutral to @language', array('@count' => $num_nodes, '@language' => $def_lang)), t('Converted @count entities from language neutral to @language', array('@count' => $num_nodes, '@language' => $def_lang)))); + $did_something = TRUE; } if (isset($results['url_alias_cleanup'])) { $searched = (int) $results['url_alias_cleanup']['searched']; $added = (int) $results['url_alias_cleanup']['added']; drupal_set_message(format_plural($searched, t('Searched @search_count entity for url aliases, added @add_count in @language.', array('@search_count' => $searched, '@add_count' => $added, '@language' => $def_lang)), t('Searched @search_count entities for url aliases, added @add_count in @language.', array('@search_count' => $searched, '@add_count' => $added, '@language' => $def_lang)))); + $did_something = TRUE; } if (isset($results['field_cleanup'])) { $num_nodes = (int) $results['field_cleanup']; drupal_set_message(format_plural($num_nodes, t('Added language-specific fields for @count entity with language-neutral fields.', array('@count' => $num_nodes)), t('Added language-specific fields for @count entities with language-neutral fields.', array('@count' => $num_nodes)))); + $did_something = TRUE; } - else { + if (!$did_something) { drupal_set_message(t('No requested entities currently require any preparation.'), 'status', FALSE); } } @@ -579,3 +689,137 @@ function lingotek_config_update_selected($lids) { function lingotek_config_progress_update_finished() { drupal_set_message(t('Finished updating statuses. The statuses for all previously selected configuration items are now up to date.')); } + +function lingotek_batch_update_entity_languages_by_profile($profile_id) { + + // don't make any changes if the new profile is the Disabled profile. + if ($profile_id == LingotekSync::PROFILE_DISABLED) { + return; + } + + $profile = LingotekProfile::loadById($profile_id); + + // get the target languages for the given profile + $available_locales = lingotek_get_target_locales(); + $target_locales_hash = $profile->filterTargetLocales($available_locales); + $target_locales = array_keys($target_locales_hash); + + // get the current entities assigned to the given profile + $entities = $profile->getEntities(); + + $operations = array(); + + // for each entity with a document ID: + foreach ($entities as $e) { + if (!empty($e['document_id'])) { + // compare the target languages set with the profile's target languages + $entity_locales = lingotek_get_current_locales($e['type'], $e['id']); + + // for each target language in the profile that is not set, add it + $locales_to_add = array_diff($target_locales, $entity_locales); + $operations[] = array('lingotek_add_target_locales', array($e, $locales_to_add, $profile)); + + // for each target language set that is not in the profile anymore, remove it + $locales_to_remove = array_diff($entity_locales, $target_locales); + $operations[] = array('lingotek_remove_target_locales', array($e, $locales_to_remove)); + } + } + // run the batch operation, return the result + if (!empty($operations)) { + $batch = array( + 'title' => t('Updating target locales for entities in profile @profile_name', array('@profile_name' => $profile->getName())), + 'operations' => $operations, + 'finished' => 'lingotek_batch_update_entity_languages_by_profile_finished', + 'file' => 'lingotek.batch.inc' + ); + batch_set($batch); + batch_process('admin/settings/lingotek/settings'); + } +} + +function lingotek_batch_update_entity_languages_by_profile_finished($success, $results, $operations) { + if ($success) { + $added = !empty($results['added']) ? $results['added'] : 0; + $removed = !empty($results['removed']) ? $results['removed'] : 0; + $added_msg = format_plural($added, 'One target locale added.', '@count target locales added.'); + $removed_msg = format_plural($removed, 'One target locale removed.', '@count target locales removed.'); + drupal_set_message(t('Profile update complete')); + drupal_set_message($added_msg); + drupal_set_message($removed_msg); + } + else { + drupal_set_message(t('Finished with an error.')); + $message = t('Finished with an error.'); + drupal_set_message($message);//xss checks not necessary here. Text only comes from php + } +} + +/* + * Add a set of target locales for an entity on the TMS and in Drupal + */ +function lingotek_add_target_locales($entity_params, $locales, LingotekProfile $profile, &$context) { + + $document_id = $entity_params['document_id']; + $entity_type = $entity_params['type']; + $entity_id = $entity_params['id']; + + if (empty($context['results'])) { + $context['results'] = array(); + } + if (empty($context['results']['added'])) { + // initialize the counter of removed targets. + $context['results']['added'] = 0; + } + $api = LingotekApi::instance(); + $document = $api->getDocument($document_id); + foreach ($locales as $l) { + // Don't add the target if it's the same as the source. + if ($document->sourceLanguage == $l) { + continue; + } + $workflow_id = $profile->getWorkflow($l); + $result = $api->addTranslationTarget($document_id, NULL, $l, $workflow_id); + if ($result) { + LingotekSync::setTargetStatus($entity_type, $entity_id, $l, LingotekSync::STATUS_PENDING); + $context['results']['added']++; + } + } +} + +/* + * Remove a set of target locale for an entity on the TMS and in Drupal + */ +function lingotek_remove_target_locales($entity_params, $locales, &$context) { + + $document_id = $entity_params['document_id']; + $entity_type = $entity_params['type']; + $entity_id = $entity_params['id']; + + if (empty($context['results'])) { + $context['results'] = array(); + } + if (empty($context['results']['removed'])) { + // initialize the counter of removed targets. + $context['results']['removed'] = 0; + } + $api = LingotekApi::instance(); + $document = $api->getDocument($document_id); + if ($document) { + $doc_translation_targets = !empty($document->translationTargets) ? $document->translationTargets : array(); + $doc_current_locales = array_map(function($target) { + return $target->language; + }, $doc_translation_targets); + foreach ($locales as $l) { + if (in_array($l, $doc_current_locales)) { + $result = $api->removeTranslationTarget($document_id, NULL, $l); + } + else { + $result = TRUE; // get rid of orphaned target locales of the document. + } + if ($result) { + LingotekSync::deleteTargetStatus($entity_type, $entity_id, $l); + $context['results']['removed']++; + } + } + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.bulk_grid.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.bulk_grid.inc old mode 100644 new mode 100755 index adb9f1f3..31dce79d --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.bulk_grid.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.bulk_grid.inc @@ -6,21 +6,23 @@ */ include_once('lingotek.config.inc'); + function lingotek_manage_callback() { drupal_goto('admin/settings/lingotek/manage/node'); } function lingotek_bulk_grid_form($form, $form_state) { $entity_type = arg(4); - $entity_type = empty($entity_type)? 'node': $entity_type; + if(empty($entity_type)){ + $entity_type = 'node'; + } global $language; - if (isset($_SESSION['grid_entity_type']) && $_SESSION['grid_entity_type'] != $entity_type) { + if (!isset($_SESSION['grid_entity_type'])) { $_SESSION['grid_entity_type'] = $entity_type; - lingotek_grid_clear_filters(); } - elseif (!isset($_SESSION['grid_entity_type'])) { + elseif ($_SESSION['grid_entity_type'] != $entity_type) { $_SESSION['grid_entity_type'] = $entity_type; } @@ -28,14 +30,21 @@ function lingotek_bulk_grid_form($form, $form_state) { * Here we store or retrieve the GET parameters so that the state of the table is maintained when leaving and coming back * Also makes it so the state is not lost when performing bulk actions */ - if (count($_GET) == 1 && isset($_SESSION['grid_custom_parameters']) && !empty($_SESSION['grid_custom_parameters'])) { - $_SESSION['grid_custom_parameters']['preventloop'] = TRUE; - drupal_goto('admin/settings/lingotek/manage/' . $entity_type, array('query' => $_SESSION['grid_custom_parameters'])); + if (count($_GET) == 1 && isset($_SESSION['grid_custom_parameters'][$entity_type]) && !empty($_SESSION['grid_custom_parameters'][$entity_type])) { + $_SESSION['grid_custom_parameters'][$entity_type]['preventloop'] = TRUE; + drupal_goto('admin/settings/lingotek/manage/' . $entity_type, array('query' => $_SESSION['grid_custom_parameters'][$entity_type])); } else { - $_SESSION['grid_custom_parameters'] = $_GET; - if ($_SESSION['grid_custom_parameters']['q']) { - unset($_SESSION['grid_custom_parameters']['q']); + if (!isset($_SESSION['grid_custom_parameters'])) { + $_SESSION['grid_custom_parameters'] = array(); + } + $temp = array(); + foreach ($_GET as $key => $value){ + $temp[$key] = filter_xss($value); + } + $_SESSION['grid_custom_parameters'][$entity_type] = $temp; + if ($_SESSION['grid_custom_parameters'][$entity_type]['q']) { + unset($_SESSION['grid_custom_parameters'][$entity_type]['q']); } } @@ -67,73 +76,80 @@ function lingotek_bulk_grid_form($form, $form_state) { $form['entity_type'] = array( '#type' => 'hidden', '#value' => $entity_type, + '#attributes' => array('id' => 'entity-type') + ); + $form['async_update'] = array( + '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/async-update/' . $entity_type, + array('html' => TRUE, 'attributes' => array('id' => 'async-update', 'type' => 'hidden'))) + ); + $form['auto_download'] = array( + '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/auto-download/' . $entity_type, + array('html' => TRUE, 'attributes' => array('id' => 'auto-download', 'type' => 'hidden'))) ); $page = pager_find_page() + 1; // Get current page from url $limit_select = (isset($_SESSION['limit_select']) ? (int)$_SESSION['limit_select'] : 0); - if ($entity_type == 'config') { - $total_entity_rows = lingotek_config_get_rows($entity_type, $form, $form_state, TRUE); - } - else { - $total_entity_rows = lingotek_grid_get_rows($entity_type, $form, $form_state, TRUE); - } + // Run query to get table rows + $table_data = $entity_type == 'config' ? lingotek_config_get_rows($entity_type, $form, $form_state) : lingotek_grid_get_rows($entity_type, $form, $form_state); - if ((int)($page - 1) * $limit_select > $total_entity_rows) { + $total_entity_rows = count($table_data); + if ((int)($limit_select) < $total_entity_rows || $total_entity_rows == 0) { // reset the page to be the last set of results $page = 1; - $_SESSION['grid_custom_parameters']['page'] = 0; + $_SESSION['grid_custom_parameters'][$entity_type]['page'] = 0; if (isset($_GET['page'])) { $_GET['page'] = 0; // used by PagerDefault class to get page number } + + /** + *Run the query again with the new pages. If not then this code is not worth + *having. + *@author Unknown + * + */ + $table_data = $entity_type == 'config' ? lingotek_config_get_rows($entity_type, $form, $form_state) : lingotek_grid_get_rows($entity_type, $form, $form_state); } - $filter_set = FALSE; - if (isset($_SESSION['grid_filters'])) { - foreach ($_SESSION['grid_filters'] as $key => $value) { - if (is_array($value)) { - $keys = array_keys($value); - if (count($keys) == 1) { - if ($keys[0] !== '' && $keys[0] !== 'all') { - $filter_set = TRUE; - } - } - elseif (count($keys) > 1) { - $filter_set = TRUE; - } - } - else { - // $value === '0' accounts for the case of the automatic profile - if ((!empty($value) && $value !== 'all') || $value === '0') { - $filter_set = TRUE; - } - } - } - } + $filter_set = lingotek_filter_set_check($entity_type); + + - // Run query to get table rows - $table_data = $entity_type == 'config' ? lingotek_config_get_rows($entity_type, $form, $form_state) : lingotek_grid_get_rows($entity_type, $form, $form_state); $results_first = (($page - 1) * $limit_select) + 1; $results_last = $results_first + count($table_data) - 1; - + if($entity_type === 'config') { + $form['lingotek_options'] = array( + '#markup' => '' + ); + $form['lingotek_force_down_all'] = array( + '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/force-down-all/true', array('html' => TRUE, 'attributes' => array('title' => t('Download all regardless of status'), 'class' => array('lingotek-action ltk-force-down'), 'id' => array('force-down')))), + ); + } + $form['lingotek_legend'] = array( + '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/legend', array('html' => TRUE, 'attributes' => array('title' => t('Icon descriptions'), 'class' => array('ctools-use-modal', 'ctools-modal-lingotek-large','lingotek-action'), 'id' => array('legend')))), + ); $form['customize'] = array( '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/customize/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Customize Table'), 'class' => array('ctools-use-modal', 'ctools-modal-lingotek-large', 'lingotek-action')))), ); $last_updated = variable_get('lingotek_pending_last_updated', NULL); - $message = t('Check status of in-progress translations (Last checked @time)', array('@time' => $last_updated ? lingotek_human_readable_timestamp($last_updated) . ' ' . t('ago') : t('Never'))); + $message = t('Check status of In-Progress translations (Last checked @time)', array('@time' => $last_updated ? lingotek_human_readable_timestamp($last_updated) . ' ' . t('ago') : t('Never'))); $form['lingotek_update'] = array( - '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/download-ready/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Download complete translations'), 'class' => array('lingotek-action')))), + '#markup' => l('' + . '' + . '', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/download-ready/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Download Ready translations'), 'class' => array('lingotek-action'), 'id' => array('download-ready')))), ); $form['refresh'] = array( - '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/update/' . $entity_type, array('html' => TRUE, 'attributes' => array('class' => 'lingotek-action', 'title' => $message))), + '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/update/' . $entity_type, array('html' => TRUE, 'attributes' => array('class' => 'lingotek-action', 'title' => $message, 'id' => array('refresh')))), ); $form['lingotek_upload'] = array( - '#markup' => l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/upload-edited/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Upload all pending source content'), 'class' => array('lingotek-action')))), + '#markup' => l('' + . '' + . '', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/upload-edited/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Re-upload all edited source content'), 'class' => array('lingotek-action'), 'id' => array('upload-edited')))), ); $form['edit_settings'] = array( @@ -147,6 +163,9 @@ function lingotek_bulk_grid_form($form, $form_state) { $form['delete'] = array( '#markup' => l(t('Delete selected content'), LINGOTEK_MENU_MAIN_BASE_URL . '/manage/delete/' . $entity_type, array('attributes' => array('id' => 'delete-link', 'class' => array('ctools-use-modal', 'ctools-modal-lingotek-small', 'ltk-hidden-modal-trigger')))), ); + $form['delete_translations'] = array( + '#markup' => l(t('Delete local translations'), LINGOTEK_MENU_MAIN_BASE_URL . '/manage/delete-translations/' . $entity_type, array('attributes' => array('id' => 'delete-translations-link', 'class' => array('ctools-use-modal', 'ctools-modal-lingotek-small', 'ltk-hidden-modal-trigger')))), + ); $form['pop_up_link'] = array( '#markup' => l(t('Hidden pop-up'), '', array('attributes' => array('id' => 'popup-link', 'class' => array('ctools-use-modal', 'ctools-modal-lingotek-large', 'ltk-hidden-modal-trigger')))), ); @@ -161,25 +180,30 @@ function lingotek_bulk_grid_form($form, $form_state) { if ($entity_type == 'node') { $form['node/add'] = array( - '#markup' => l('' . t('Add content') . '
      ', 'node/add', array('html' => TRUE, 'attributes' => array('title' => t('Add content')))) + '#markup' => '
      ' + . l('' + . t('Add content') + . '
      ', 'node/add', + array('html' => TRUE, 'attributes' => array('title' => t('Add content')))) + . '
      ' ); } $modal_size = $entity_type == 'config' ? 'ctools-modal-lingotek-small' : 'ctools-modal-lingotek-large'; $modal_classes = array('ctools-use-modal', $modal_size, 'ltk-action'); - $search_title = l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/filters/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Advanced Search'), 'class' => $modal_classes))) . ' ' . t('Search') . ': '; + $search_title = l('', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/filters/' . $entity_type, array('html' => TRUE, 'attributes' => array('title' => t('Advanced Search'), 'class' => $modal_classes))); $form['search'] = array( '#type' => 'textfield', - '#default_value' => isset($_SESSION['grid_filters']['search']) ? $_SESSION['grid_filters']['search'] : '', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['search']) ? $_SESSION['grid_filters'][$entity_type]['search'] : '', '#title' => filter_xss($search_title), '#size' => 30, + '#attributes' => array('placeholder' => t('Search')) ); + $search_options = $entity_type == 'config' ? lingotek_config_search_options() : lingotek_grid_search_options($entity_type); - $search_options = $entity_type == 'config' ? lingotek_config_search_options() : lingotek_grid_search_options(); - - $grid_term = isset($_SESSION['grid_filters']['search_type']) ? $_SESSION['grid_filters']['search_type'] : 'all'; - $config_term = isset($_SESSION['grid_filters']['textgroup']) ? $_SESSION['grid_filters']['textgroup'] : 'all'; + $grid_term = isset($_SESSION['grid_filters'][$entity_type]['search_type']) ? $_SESSION['grid_filters'][$entity_type]['search_type'] : 'all'; + $config_term = isset($_SESSION['grid_filters'][$entity_type]['textgroup']) ? $_SESSION['grid_filters'][$entity_type]['textgroup'] : 'all'; $search_term = $entity_type == 'config' ? $config_term : $grid_term; $form['search_type'] = array( @@ -194,7 +218,8 @@ function lingotek_bulk_grid_form($form, $form_state) { $form['search_submit'] = array( '#type' => 'submit', - '#value' => t('Go'), + '#value' => decode_entities(''), + '#attributes' => array('title' => 'Search'), '#submit' => $search_submit, ); @@ -204,24 +229,23 @@ function lingotek_bulk_grid_form($form, $form_state) { if ($filter_set) { $form['filter_message'] = array( - '#markup' => '' . l('' . t('Clear Filters') . '', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/clear/filters/' . $entity_type, array('html' => TRUE)) . '', + '#markup' => '' . l('' . t('Clear Filters') . '', LINGOTEK_MENU_MAIN_BASE_URL . '/manage/clear/filters/' . $entity_type, array('html' => TRUE, 'attributes' => array('id' => array('clear-filters')))) . '', ); } // Build actions selector - $form['actions_select'] = array( + $form['select_actions'] = array( '#type' => 'select', '#options' => $action_options, - '#title' => '' . ' ' . t('Actions') . ': ', ); $actions_submit_function = $entity_type == 'config' ? array('lingotek_config_action_submit') : array('lingotek_grid_action_submit'); // Actions submit button - $form['actions_submit'] = array( + $form['submit_actions'] = array( '#type' => 'submit', '#value' => t('Submit Action'), - '#name' => 'actions_submit', + '#name' => 'submit_actions', '#submit' => $actions_submit_function, ); @@ -321,28 +345,59 @@ function lingotek_bulk_grid_form($form, $form_state) { return $form; } +function lingotek_filter_set_check($entity_type) { + $filter_set = FALSE; + if (isset($_SESSION['grid_filters'][$entity_type])) { + foreach ($_SESSION['grid_filters'][$entity_type] as $key => $value) { + if($key === 'filtered_ids' || $key === 'filtered_config_lids') {//these keys are filter metadata + continue; + } + if (is_array($value)) { + $keys = array_keys($value); + if (count($keys) == 1) { + if ($keys[0] !== '' && $keys[0] !== 'all') { + $filter_set = TRUE; + } + } + elseif (count($keys) > 1) { + $filter_set = TRUE; + } + } + else { + // $value === '0' accounts for the case of the automatic profile + if ((!empty($value) && $value !== 'all') || $value === '0') { + $filter_set = TRUE; + } + } + } + } + + return $filter_set; +} + function lingotek_grid_action_options($entity_type) { $delete_text = $entity_type == 'config' ? t('Delete selected translations') : t('Delete selected content'); $action_options = array( - 'select' => t('Select an action'), + 'select' => t('Actions...'), 'upload' => t('Upload source for translation'), 'sync' => t('Check progress of translations'), 'reset' => t('Disassociate translations'), 'delete' => $delete_text, + 'marked' => t('Mark content'), + 'unmarked' => t('Unmark content') ); - + $api = LingotekApi::instance(); + $workflows = $api->listWorkflows(); + if (is_array($workflows) && count($workflows) > 1) { + $action_options['workflow'] = t('Change workflow'); + } if ($entity_type == 'config') { $action_options[t('Download')] = array('download_all' => t('Download All Translations')); + } else { + $action_options['delete_translations'] = t('Delete local translations'); $action_options['edit'] = t('Edit translation settings'); - - $api = LingotekApi::instance(); - $workflows = $api->listWorkflows(); - if (is_array($workflows) && count($workflows) > 1) { - $action_options['workflow'] = t('Change workflow'); - } - $action_options[t('Download')] = array('download_all' => t('Download All Translations')); } @@ -354,13 +409,16 @@ function lingotek_grid_action_options($entity_type) { return $action_options; } -function lingotek_grid_search_options() { +function lingotek_grid_search_options($entity_type) { $search_options = array( - 'all' => t('All'), 'title' => t('Title'), - 'body' => t('Body'), ); + if ($entity_type != 'fieldable_panels_pane') { + $search_options['all'] = t('All'); + $search_options['body'] = t('Body'); + } + return $search_options; } @@ -376,6 +434,11 @@ function lingotek_config_search_options() { 'views' => t('Views'), ); + if (variable_get('lingotek_translate_config_webform', 0)) { + $search_options['webform'] = t('Webforms'); + } + + return $search_options; } @@ -404,7 +467,7 @@ function lingotek_filters_popup_form($form = array(), $form_state = array()) { '#submit' => array('lingotek_grid_clear_filters'), ); - $form_state['values']['filters'] = lingotek_grid_get_filters(TRUE); + $form_state['values']['filters'] = lingotek_grid_get_filters($form_state['entity_type'], TRUE); $form['filter_fieldset']['filters'] += $form_state['entity_type'] == 'config' ? lingotek_config_build_filters($form_state) : lingotek_grid_build_filters($form_state); @@ -490,7 +553,7 @@ function lingotek_grid_customize_form($form, $form_state) { '#submit' => array('lingotek_grid_reset_columns'), ); - $form_state['values']['columns'] = $form_state['entity_type'] == 'config' ? lingotek_grid_get_columns('config') : lingotek_grid_get_columns(); + $form_state['values']['columns'] = $form_state['entity_type'] == 'config' ? lingotek_grid_get_columns('config') : lingotek_grid_get_columns($_SESSION['grid_entity_type']); $form['customize_table_fieldset']['custom_columns'] += $form_state['entity_type'] == 'config' ? lingotek_config_build_column_checkboxes($form_state) : lingotek_grid_build_column_checkboxes($form_state); return $form; @@ -521,22 +584,152 @@ function lingotek_grid_customize($entity_type) { print ajax_render($output); } +function lingotek_grid_legend_form($form, $form_state) { + $css_style = 'background-color:#80a49e; margin-right: 3px; padding:1px; padding-left:4px; padding-right:4px; color:#fff; white-space:nowrap; border:1px solid #fff; text-decoration:none; font-family: \'Roboto Mono\', monospace;'; + + $form['lingotek_legend_header'] = array( + '#markup' => '

      ' . t('Icon descriptions') . '

      ' + ); + $legend_table = "" + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '') + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "" + . '' + . '' + . "" + + . "
      '. t('Upload content to Lingotek for translation.') .'
      ' . t('Check the status of In-Progress translations.') . '
      ' . t('Download all translations with status Ready for Download.') . '
      ' . t('Choose which table columns to show or hide.
      ' . t('(Config page only) Download all content regardless of current status.') . '
      ' . t('Edit the content associated with this row.') . '
      ' . t('View current translation progress for content.') . '
      ' . t('Adjust Lingotek module settings.') . '
      ' . t('This content has No Translation.') . '
      ' . t('This content is current; no translations are In-Progress or Ready for Download.') . '
      ' . t('This content has been edited and is ready for Re-upload to Lingotek.') . '
      ' . t('This content has been uploaded to Lingotek and the translations are In-Progress.') . '
      ' . t('This content has been translated and is Ready for Download.') . '
      ' . t('This content is Ready for Interim Download.') . '
      ' . t('This content has an interim In-Progress translation downloaded.') . '
      ' . t('This content has received an Error.') . '
      ' . t('This content is Disabled, cannot request translation.') . '
      ' . t('This content has translations, but are not being tracked by Lingotek.') . '
      "; + $form['lingotek_legend_table'] = array( + '#markup' => $legend_table + ); + + return $form; +} + +function lingotek_grid_legend() { + ctools_include('node.pages', 'node', ''); + ctools_include('modal'); + ctools_include('ajax'); + + $form_state = array( + 'ajax' => TRUE, + ); + $output = ctools_modal_form_wrapper('lingotek_grid_legend_form', $form_state); + + if (!empty($form_state['executed'])) { + + $f = array(); + form_execute_handlers('submit', $f, $form_state); + $commands = array(); + $commands[] = ctools_modal_command_dismiss(); + $commands[] = ctools_ajax_command_reload(); + print ajax_render($commands); + exit; + } + + print ajax_render($output); +} + function lingotek_grid_filter_inline_submit($form, $form_state) { - $_SESSION['grid_filters']['search_type'] = $form_state['values']['search_type']; + $entity_type = $_SESSION['grid_entity_type']; + $_SESSION['grid_filters'][$entity_type]['search_type'] = $form_state['values']['search_type']; if (!empty($form_state['values']['search'])) { - $_SESSION['grid_filters']['search'] = $form_state['values']['search']; + $_SESSION['grid_filters'][$entity_type]['search'] = $form_state['values']['search']; } else { - unset($_SESSION['grid_filters']['search']); - unset($_SESSION['grid_filters']['search_type']); + unset($_SESSION['grid_filters'][$entity_type]['search']); + unset($_SESSION['grid_filters'][$entity_type]['search_type']); } - unset($_SESSION['grid_filters']['body']); - unset($_SESSION['grid_filters']['title']); + unset($_SESSION['grid_filters'][$entity_type]['body']); + unset($_SESSION['grid_filters'][$entity_type]['title']); if ($form_state['values']['search_type'] == 'title') { - $_SESSION['grid_filters']['title'] = $form_state['values']['search']; + $_SESSION['grid_filters'][$entity_type]['title'] = $form_state['values']['search']; } elseif ($form_state['values']['search_type'] == 'body') { - $_SESSION['grid_filters']['body'] = $form_state['values']['search']; + $_SESSION['grid_filters'][$entity_type]['body'] = $form_state['values']['search']; } if (isset($form_state['values']['limit_select'])) { @@ -561,11 +754,11 @@ function lingotek_grid_filter_submit($form, $form_state) { $nest = NULL; if (strpos($key, '__filter')) { $add_key_to_session = TRUE; - $nest = 'grid_filters'; //stored in $_SESSION['grid_filters'][$key] + $nest = 'grid_filters'; //stored in $_SESSION['grid_filters'][$entity_type][$key] } elseif (strpos($key, '__custom')) { $add_key_to_session = TRUE; - $nest = $form_state['entity_type'] . '_custom'; //stored in $_SESSION['grid_custom'][$key] + $nest = 'grid_custom'; //stored in $_SESSION['grid_custom'][$entity_type][$key] } // if we want this key, add it to the session if ($add_key_to_session) { @@ -573,7 +766,13 @@ function lingotek_grid_filter_submit($form, $form_state) { $_SESSION[$key] = $value; } else { - $_SESSION[$nest][str_replace('__filter', '', $key)] = $value; + if (!isset($_SESSION[$nest])) { + $_SESSION[$nest] = array(); + } + if (!isset($_SESSION[$nest][$form_state['entity_type']])) { + $_SESSION[$nest][$form_state['entity_type']] = array(); + } + $_SESSION[$nest][$form_state['entity_type']][str_replace('__filter', '', $key)] = $value; } } } @@ -588,15 +787,15 @@ function lingotek_grid_action_submit($form, $form_state) { $entity_ids = array(); $entity_type = isset($form_state['entity_type']) ? $form_state['entity_type'] : (isset($form_state['values']['entity_type']) ? $form_state['values']['entity_type'] : NULL); - if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == 'actions_submit') { // If submitting an action + if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == 'submit_actions') { // If submitting an action foreach ($form_state['values']['the_grid'] as $value) { if ($value != 0) { $entity_ids[] = $value; } } - if (isset($form_state['values']['actions_select'])) { // If an action was selected (which it would be, I don't know if this could ever NOT occur with normal use) - $action = $form_state['values']['actions_select']; // Get the action + if (isset($form_state['values']['select_actions'])) { // If an action was selected (which it would be, I don't know if this could ever NOT occur with normal use) + $action = $form_state['values']['select_actions']; // Get the action if (count($entity_ids) <= 0) { // Select a node drupal_set_message(t('You must select at least one node to @action.', array('@action' => $action)), 'warning'); // Or pay the price } @@ -617,7 +816,7 @@ function lingotek_grid_action_submit($form, $form_state) { $target_locales = ($locale == 'all') ? lingotek_get_target_locales() : array($locale); lingotek_grid_download_selected($entity_type, $entity_ids, $target_locales); } - elseif ($action == 'delete' || $action == 'reset') { + elseif ($action == 'delete' || $action == 'reset' || $action == 'delete-translations') { // ajax ctools modal employed (see lingotek_bulk_grid_form() and lingotek.bulk_grid.js) } elseif ($action == 'edit') { // If editing node settings @@ -629,10 +828,105 @@ function lingotek_grid_action_submit($form, $form_state) { elseif ($action == 'sync') { // If syncing the progress lingotek_update_target_progress_batch_create($entity_type, $entity_ids); // Run batch operations to get the progress report from Lingotek } + elseif ($action == 'marked') { + $batch = array( + 'title' => t('Updating Marked Entities'), + 'finished' => 'lingotek_update_marked_items_finished' + ); + + $operations = lingotek_get_marked_batch_elements($entity_type, $entity_ids); + $batch['operations'] = $operations; + $redirect = current_path(); + + batch_set($batch); + batch_process($redirect); + } + elseif ($action == 'unmarked') { + $batch = array( + 'title' => t('Updating Unmarked Entities'), + 'finished' => 'lingotek_update_unmarked_items_finished' + ); + + $operations = lingotek_get_unmarked_batch_elements($entity_type, $entity_ids); + $batch['operations'] = $operations; + $redirect = current_path(); + + batch_set($batch); + batch_process($redirect); + } } } } +/** +* Updates marked value in the lingotek_entity_metadata table for one row +*/ +function lingotek_update_marked_item_single($entity_id, $entity_type, $marked_value) { + if ($entity_type === 'config') { + $marked_offset = $entity_id + LingotekSync::MARKED_OFFSET; + if ($marked_value == LingotekSync::MARKED) { + LingotekSync::deleteConfigMarkedValue($entity_id, $marked_offset, 'marked'); + } + else { + LingotekSync::setConfigMarkedValue($entity_id, $marked_offset); + } + return; + } + + if ($marked_value == LingotekSync::MARKED) { + lingotek_keystore_delete($entity_type, $entity_id, 'marked'); + } + else { + lingotek_keystore($entity_type, $entity_id, 'marked', LingotekSync::MARKED); + } +} + +/** +* Updates marked value in the lingotek_entity_metadata table +*/ +function lingotek_update_marked_items($entity_type, $entity_id, &$context) { + if(empty($context['results'])) { + $context['results']['marked'] = 1; + if ($entity_type === 'config') { + $context['results']['entity_type'] = 'config'; + } + } + else { + $context['results']['marked'] += 1; + } + + if ($entity_type === 'config') { + $marked_offset = $entity_id + LingotekSync::MARKED_OFFSET; + LingotekSync::setConfigMarkedValue($entity_id, $marked_offset, LingotekSync::MARKED); + } + else { + lingotek_keystore($entity_type, $entity_id, 'marked', LingotekSync::MARKED); + } +} + +/** +* Updates unmarked value in the lingotek_entity_metadata table +*/ +function lingotek_update_unmarked_items($entity_type, $entity_id, &$context) { + if(empty($context['results'])) { + $context['results']['unmarked'] = 1; + if ($entity_type === 'config') { + $context['results']['entity_type'] = 'config'; + } + } + else { + $context['results']['unmarked'] += 1; + } + + if ($entity_type === 'config') { + $marked_offset = $entity_id + LingotekSync::MARKED_OFFSET; + LingotekSync::deleteConfigMarkedValue($entity_id, $marked_offset, 'marked'); + } + else { + lingotek_keystore_delete($entity_type, $entity_id, 'marked'); + } +} + /** * Builds the checkbox elements for customizing The Grid's columns * Uses predefined defaults specified in 'lingotek_grid_define_columns' @@ -642,88 +936,108 @@ function lingotek_grid_build_column_checkboxes($form_state) { $suffix = '__custom'; // Suffix specified because the filter submit function differentiates based on this tag and puts the keys into the session variable as such $entity_type = $form_state['entity_type']; $columns = lingotek_grid_define_columns($entity_type); // Allowed columns and defaults for source and target grids are defined here + $e_grid_custom = isset($_SESSION['grid_custom'][$entity_type]) ? $_SESSION['grid_custom'][$entity_type] : array(); + $column_elements = array( + 'marked' => array( + '#type' => 'checkbox', + '#title' => t('Marked'), + '#default_value' => isset($e_grid_custom[$prefix . 'marked' . $suffix]) ? $e_grid_custom[$prefix . 'marked' . $suffix] : in_array('marked', $columns['defaults']), + ), 'nid' => array( '#type' => 'checkbox', '#title' => t('ID'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'nid' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'nid' . $suffix] : in_array('nid', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'nid' . $suffix]) ? $e_grid_custom[$prefix . 'nid' . $suffix] : in_array('nid', $columns['defaults']), ), 'content_type' => array( '#type' => 'checkbox', '#title' => t('Content Type'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'content_type' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'content_type' . $suffix] : in_array('content_type', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'content_type' . $suffix]) ? $e_grid_custom[$prefix . 'content_type' . $suffix] : in_array('content_type', $columns['defaults']), ), 'title' => array( '#type' => 'checkbox', '#title' => t('Title'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'title' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'title' . $suffix] : in_array('title', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'title' . $suffix]) ? $e_grid_custom[$prefix . 'title' . $suffix] : in_array('title', $columns['defaults']), + ), + 'label' => array( + '#type' => 'checkbox', + '#title' => t('Label'), + '#default_value' => isset($e_grid_custom[$prefix . 'label' . $suffix]) ? $e_grid_custom[$prefix . 'label' . $suffix] : in_array('label', $columns['defaults']), ), 'description' => array( '#type' => 'checkbox', '#disabled' => 'true', '#title' => t('Description'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'nid' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'description' . $suffix] : in_array('description', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'nid' . $suffix]) ? $e_grid_custom[$prefix . 'description' . $suffix] : in_array('description', $columns['defaults']), ), 'language' => array( '#type' => 'checkbox', - '#title' => t('Source Uploaded'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'language' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'language' . $suffix] : in_array('language', $columns['defaults']), + '#title' => t('Source'), + '#default_value' => isset($e_grid_custom[$prefix . 'language' . $suffix]) ? $e_grid_custom[$prefix . 'language' . $suffix] : in_array('language', $columns['defaults']), ), 'translations' => array( '#type' => 'checkbox', '#title' => t('Translations'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'translations' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'translations' . $suffix] : in_array('translations', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'translations' . $suffix]) ? $e_grid_custom[$prefix . 'translations' . $suffix] : in_array('translations', $columns['defaults']), ), 'configuration' => array( '#type' => 'checkbox', '#title' => t('Profile'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'configuration' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'configuration' . $suffix] : in_array('configuration', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'configuration' . $suffix]) ? $e_grid_custom[$prefix . 'configuration' . $suffix] : in_array('configuration', $columns['defaults']), ), 'document_id' => array( '#type' => 'checkbox', '#title' => t('Doc ID'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'document_id' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'document_id' . $suffix] : in_array('document_id', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'document_id' . $suffix]) ? $e_grid_custom[$prefix . 'document_id' . $suffix] : in_array('document_id', $columns['defaults']), ), - 'workflow' => array( + 'workflow_id' => array( '#type' => 'checkbox', - '#title' => t('Workflow'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'workflow' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'workflow' . $suffix] : in_array('workflow', $columns['defaults']), + '#title' => t('Workflow ID'), + '#default_value' => isset($e_grid_custom[$prefix . 'workflow_id' . $suffix]) ? $e_grid_custom[$prefix . 'workflow_id' . $suffix] : in_array('workflow_id', $columns['defaults']), + ), + 'workflow_name' => array( + '#type' => 'checkbox', + '#title' => t('Workflow Name'), + '#default_value' => isset($e_grid_custom[$prefix . 'workflow_name' . $suffix]) ? $e_grid_custom[$prefix . 'workflow_name' . $suffix] : in_array('workflow_name', $columns['defaults']), ), 'changed' => array( '#type' => 'checkbox', '#title' => t('Last Modified'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'changed' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'changed' . $suffix] : in_array('changed', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'changed' . $suffix]) ? $e_grid_custom[$prefix . 'changed' . $suffix] : in_array('changed', $columns['defaults']), ), 'last_uploaded' => array( '#type' => 'checkbox', '#title' => t('Last Uploaded'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'last_uploaded' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'last_uploaded' . $suffix] : in_array('last_uploaded', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'last_uploaded' . $suffix]) ? $e_grid_custom[$prefix . 'last_uploaded' . $suffix] : in_array('last_uploaded', $columns['defaults']), ), 'locale_progress_percent' => array( '#type' => 'checkbox', '#title' => t('Target Progress Percentage'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'locale_progress_percent' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'locale_progress_percent' . $suffix] : in_array('locale_progress_percent', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'locale_progress_percent' . $suffix]) ? $e_grid_custom[$prefix . 'locale_progress_percent' . $suffix] : in_array('locale_progress_percent', $columns['defaults']), ), 'progress_updated' => array( '#type' => 'checkbox', '#title' => t('Progress Last Updated'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'progress_updated' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'progress_updated' . $suffix] : in_array('progress_updated', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'progress_updated' . $suffix]) ? $e_grid_custom[$prefix . 'progress_updated' . $suffix] : in_array('progress_updated', $columns['defaults']), ), 'last_downloaded' => array( '#type' => 'checkbox', '#title' => t('Time Last Downloaded'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'last_downloaded' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'last_downloaded' . $suffix] : in_array('last_downloaded', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'last_downloaded' . $suffix]) ? $e_grid_custom[$prefix . 'last_downloaded' . $suffix] : in_array('last_downloaded', $columns['defaults']), ), 'actions' => array( '#type' => 'checkbox', '#title' => t('Actions'), - '#default_value' => isset($_SESSION[$entity_type . '_custom'][$prefix . 'actions' . $suffix]) ? $_SESSION[$entity_type . '_custom'][$prefix . 'actions' . $suffix] : in_array('actions', $columns['defaults']), + '#default_value' => isset($e_grid_custom[$prefix . 'actions' . $suffix]) ? $e_grid_custom[$prefix . 'actions' . $suffix] : in_array('actions', $columns['defaults']), ), ); if ($form_state['entity_type'] == 'taxonomy_term') { $column_elements['description']['#disabled'] = 'true'; $column_elements['title']['#disabled'] = 'true'; } + if ($entity_type == 'paragraphs_item') { + unset($column_elements['changed']); + } $column_elements = array_intersect_key($column_elements, $columns['columns']); // Reduces the output columns to the defaults specified in 'lingotek_grid_define_columns' return lingotek_grid_process_elements($column_elements, $prefix, $suffix); // adds prefixes and suffixes to the elements @@ -733,53 +1047,70 @@ function lingotek_config_build_column_checkboxes($form_state) { $prefix = ''; $suffix = '__custom'; // Suffix specified because the filter submit function differentiates based on this tag and puts the keys into the session variable as such $columns = lingotek_config_define_columns(); // Allowed columns and defaults for source and target grids are defined here + $c_grid_custom = isset($_SESSION['grid_custom']['config']) ? $_SESSION['grid_custom']['config'] : array(); $column_elements = array( + 'marked' => array( + '#type' => 'checkbox', + '#title' => t('Marked'), + '#default_value' => isset($c_grid_custom[$prefix . 'marked' . $suffix]) ? $c_grid_custom[$prefix . 'marked' . $suffix] : in_array('marked', $columns['defaults']), + ), 'lid' => array( '#type' => 'checkbox', '#title' => t('ID'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'lid' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'lid' . $suffix] : in_array('lid', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'lid' . $suffix]) ? $c_grid_custom[$prefix . 'lid' . $suffix] : in_array('lid', $columns['defaults']), + ), + 'config_set_id' => array( + '#type' => 'checkbox', + '#title' => t('Config Set ID'), + '#default_value' => isset($c_grid_custom[$prefix . 'config_set_id' . $suffix]) ? $c_grid_custom[$prefix . 'config_set_id' . $suffix] : in_array('config_set_id', $columns['defaults']), ), 'set_name' => array( '#type' => 'checkbox', '#title' => t('Config Set Name'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'set_name' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'set_name' . $suffix] : in_array('set_name', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'set_name' . $suffix]) ? $c_grid_custom[$prefix . 'set_name' . $suffix] : in_array('set_name', $columns['defaults']), ), 'source' => array( '#type' => 'checkbox', '#title' => t('Source'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'source' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'source' . $suffix] : in_array('source', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'source' . $suffix]) ? $c_grid_custom[$prefix . 'source' . $suffix] : in_array('source', $columns['defaults']), ), 'source_uploaded' => array( '#type' => 'checkbox', '#title' => t('Source Uploaded'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'source_uploaded' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'source_uploaded' . $suffix] : in_array('source_uploaded', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'source_uploaded' . $suffix]) ? $c_grid_custom[$prefix . 'source_uploaded' . $suffix] : in_array('source_uploaded', $columns['defaults']), ), 'translations' => array( '#type' => 'checkbox', '#title' => t('Translations'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'translations' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'translations' . $suffix] : in_array('translations', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'translations' . $suffix]) ? $c_grid_custom[$prefix . 'translations' . $suffix] : in_array('translations', $columns['defaults']), ), 'location' => array( '#type' => 'checkbox', '#title' => t('Location'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'location' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'location' . $suffix] : in_array('location', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'location' . $suffix]) ? $c_grid_custom[$prefix . 'location' . $suffix] : in_array('location', $columns['defaults']), ), 'context' => array( '#type' => 'checkbox', '#title' => t('Context'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'context' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'context' . $suffix] : in_array('context', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'context' . $suffix]) ? $c_grid_custom[$prefix . 'context' . $suffix] : in_array('context', $columns['defaults']), ), 'doc_id' => array( '#type' => 'checkbox', '#title' => t('Document ID'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'doc_id' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'doc_id' . $suffix] : in_array('doc_id', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'doc_id' . $suffix]) ? $c_grid_custom[$prefix . 'doc_id' . $suffix] : in_array('doc_id', $columns['defaults']), ), 'textgroup' => array( '#type' => 'checkbox', '#title' => t('Textgroup'), - '#default_value' => isset($_SESSION['config_custom'][$prefix . 'textgroup' . $suffix]) ? $_SESSION['config_custom'][$prefix . 'textgroup' . $suffix] : in_array('textgroup', $columns['defaults']), + '#default_value' => isset($c_grid_custom[$prefix . 'textgroup' . $suffix]) ? $c_grid_custom[$prefix . 'textgroup' . $suffix] : in_array('textgroup', $columns['defaults']), + ), + 'workflow' => array( + '#type' => 'checkbox', + '#title' => t('Workflow'), + '#default_value' =>isset($c_grid_custom[$prefix . 'textgroup' . $suffix]) ? $c_grid_custom[$prefix . 'workflow' . $suffix] : in_array('workflow', $columns['defaults']), ), ); + $column_elements = array_intersect_key($column_elements, $columns['columns']); // Reduces the output columns to the defaults specified in 'lingotek_grid_define_columns' return lingotek_grid_process_elements($column_elements, $prefix, $suffix); // adds prefixes and suffixes to the elements @@ -789,31 +1120,95 @@ function lingotek_grid_update($entity_type) { if ($entity_type == 'config') { $lids = LingotekConfigSet::getLidsByStatus(LingotekSync::STATUS_PENDING); if (empty($lids)) { - drupal_set_message(t('There are no in-progress translations to check.')); + drupal_set_message(t('There are no In-Progress translations to check.')); drupal_goto('admin/settings/lingotek/manage/' . $entity_type); } lingotek_config_update_selected($lids); return; } + $active_locales = lingotek_get_target_locales(); - $pending_nids = LingotekSync::getEntityIdsByTargetStatus($entity_type, LingotekSync::STATUS_PENDING, $active_locales); - $disabled_nids = LingotekSync::getEntityIdsByProfileStatus($entity_type, LingotekSync::PROFILE_DISABLED); - $nids = array_diff($pending_nids, $disabled_nids); + $entity_ids = LingotekSync::getEntityIdsByStatuses($entity_type, array(LingotekSync::STATUS_PENDING, LingotekSync::STATUS_READY_INTERIM, LingotekSync::STATUS_INTERIM), $active_locales); variable_set('lingotek_pending_last_updated', time()); - if (count($nids) > 0) { - $_SESSION['lingotek_sync_nodes'] = count($nids); - lingotek_update_target_progress_batch_create($entity_type, $nids); // Run batch operations to sync all 'In Progress' nodes + if (count($entity_ids) > 0) { + $_SESSION['lingotek_sync_nodes'] = count($entity_ids); + lingotek_update_target_progress_batch_create($entity_type, $entity_ids); // Run batch operations to sync all 'In Progress' nodes } else { - drupal_set_message(t('There are no in-progress translations to check.')); + drupal_set_message(t('There are no In-Progress translations to check.')); drupal_goto('admin/settings/lingotek/manage/' . $entity_type); } } +function lingotek_grid_query_status($entity_type, $comma_separated_ids = ''){ + if (strlen($comma_separated_ids) == 0) { + return; + } + $ids = explode(',', $comma_separated_ids); + $information = array(); + foreach($ids as $id) { + $source_status = LingotekSync::getUploadStatus($entity_type, $id); + $target_statuses = LingotekSync::getAllTargetStatusForEntity($entity_type, $id); + $profile = LingotekSync::getProfileByEntityId($entity_type, $id); + $last_sync_error = LingotekSync::getLastSyncError($entity_type, $id); + + $information[$id] = $target_statuses; + $information[$id]['source_status'] = $source_status; + $information[$id]['profile'] = $profile; + if ($last_sync_error) { + $information[$id]['last_upload_error'] = $last_sync_error; + } + + if ($entity_type === 'node') { + $query = db_select('node', 'n') + ->fields('n', array('changed')) + ->condition('n.nid', $id); + $timestamp = $query->execute()->fetch(); + $information[$id]['last_modified'] = lingotek_human_readable_timestamp($timestamp->changed) . ' ago'; + } + } + echo json_encode($information); +} + +function lingotek_grid_automatic_download($entity_type){ + $or = db_or()->condition('lem.value','READY')->condition('lem.value','READY_INTERIM'); + $query = db_select('lingotek_entity_metadata', 'lem'); + $query->addField('lem', 'entity_id'); + $query->addField('lem', 'entity_key'); + //$query->condition('lem.value', 'READY'); + $query->condition($or); + $query->condition('lem.entity_type', $entity_type); + $results = $query->execute(); + $ids_to_check = array(); + $translation_status = array(); + foreach($results as $result){ + $entity_id = $result->entity_id; + $ids_to_check[] = $entity_id; + $language_code = substr($result->entity_key,count($result->entity_key) - 6); + $translation_status[$entity_id][$language_code] = $language_code; + } + $entities = entity_load($entity_type, $ids_to_check); + + foreach ($entities as $entity) { + if($entity->lingotek['auto_download'] === 1){ + $id = $entity_type === 'node' ? $entity->nid : $entity->cid; + foreach($translation_status[$id] as $lang_code){ + lingotek_entity_download($entity,$entity_type,$lang_code); + } + } + } +} function lingotek_grid_download_selected($entity_type, $entity_ids, $target_locales) { $operations = array(); $entities = entity_load($entity_type, $entity_ids); + $all_target_locales = $target_locales; foreach ($entities as $entity) { + $target_locales = $all_target_locales; // must reset $target_locales to the original array or else some target languages may be skipped during download + if (variable_get('lingotek_enable_language_specific_profiles')) { + // Filter out the target languages that should not be included for this entity. + $profile = LingotekProfile::loadByEntity($entity_type, $entity); + $target_locales = array_keys($profile->filterTargetLocales($target_locales)); + } foreach ($target_locales as $locale) { // Skip language neutral taxonomy terms because their source is really English. if ($entity_type == 'taxonomy_term' && $entity->language == LANGUAGE_NONE && $locale == 'en_US') { @@ -832,26 +1227,58 @@ function lingotek_grid_download_selected($entity_type, $entity_ids, $target_loca batch_process($redirect); } -function lingotek_grid_download_ready($entity_type) { +function config_download_ready($force_download, $comma_separated_ids = NULL, $entity_type = NULL){ + if ($entity_type === NULL) { + $entity_type = 'node'; + } + if($force_download != NULL){ + $ready_lids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_READY); + $current_lids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_CURRENT); + $interim_current_lids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_INTERIM); + $interim_ready_lids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_READY_INTERIM); + $edited_lids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_EDITED); + $set_ids = array_merge($ready_lids, $current_lids, $edited_lids, $interim_current_lids, $interim_ready_lids); + } + elseif(isset($_SESSION['grid_filters'][$entity_type]) || $comma_separated_ids !== NULL){ + if(isset($_SESSION['grid_filters'][$entity_type])){ + empty_array_check($_SESSION['grid_filters'][$entity_type]['filtered_config_lids'], $entity_type, "There are no translations ready for download for this filter."); + } + $selected_lids = $comma_separated_ids !== NULL ? explode(",", $comma_separated_ids) + : $_SESSION['grid_filters'][$entity_type]['filtered_config_lids']; + $set_ids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_READY, $selected_lids); + } + else { + $set_ids = LingotekConfigSet::getSetIdsByStatus(LingotekSync::STATUS_READY); + } + lingotek_config_download_selected('download_all', $set_ids, true); +} + +function lingotek_grid_download_ready($entity_type, $comma_separated_ids = NULL) { if ($entity_type === 'config') { - // Looks clean but actually gets set_ids, convert those to lids, then the next function converts back to set_ids. - $lids = LingotekConfigSet::getLidsByStatus(LingotekSync::STATUS_READY); - lingotek_config_download_selected('download_all', $lids); + config_download_ready(NULL, $comma_separated_ids, $entity_type); return; } - $targets = LingotekSync::getTargetsByStatus($entity_type, LingotekSync::STATUS_READY); - + $targets = LingotekSync::getTargetsByStatuses($entity_type, array(LingotekSync::STATUS_READY, LingotekSync::STATUS_READY_INTERIM)); if (!empty($targets)) { - - $entity_ids = array(); - foreach ($targets as $target) { - $entity_ids[] = $target['id']; + if($comma_separated_ids !== NULL || (isset($_SESSION['grid_filters'][$entity_type]) && !empty($_SESSION['grid_filters'][$entity_type]))){ + if(isset($_SESSION['grid_filters'][$entity_type])){ + $filtered_ids = isset($_SESSION['grid_filters'][$entity_type]['filtered_ids']) ? isset($_SESSION['grid_filters'][$entity_type]['filtered_ids']) : NULL; + empty_array_check($filtered_ids, $entity_type, "There are no translations ready for download for this filter."); + } + $entity_ids = $comma_separated_ids !== NULL ? explode(",", $comma_separated_ids) + : $_SESSION['grid_filters'][$entity_type]['filtered_ids']; + } + else { + $entity_ids = array(); + foreach ($targets as $target) { + $entity_ids[] = $target['id']; + } } $entities = entity_load($entity_type, $entity_ids); - $operations = array(); foreach ($targets as $target) { - if ($entities[$target['id']]->lingotek['profile'] != LingotekSync::PROFILE_DISABLED) { // exclude nodes with PROFILE_DISABLED + if (isset($entities[$target['id']]) && + $entities[$target['id']]->lingotek['profile'] != LingotekSync::PROFILE_DISABLED) { // exclude nodes with PROFILE_DISABLED $operations[] = array('lingotek_entity_download', array($target['id'], $entity_type, $target['locale'])); } } @@ -865,19 +1292,61 @@ function lingotek_grid_download_ready($entity_type) { batch_process($redirect); } else { - drupal_set_message(t('There are no translations ready for download.')); + drupal_set_message(t('There are no translations with status Ready for Download.')); drupal_goto('admin/settings/lingotek/manage/' . $entity_type); } } -function lingotek_grid_upload_edited($entity_type) { +function config_upload_edited($comma_separated_ids, $entity_type) { + if($comma_separated_ids !== NULL){ + $lids = explode(",", $comma_separated_ids); + lingotek_config_upload_selected($lids); + } + elseif(isset($_SESSION['grid_filters'][$entity_type])){ + empty_array_check($_SESSION['grid_filters'][$entity_type]['filtered_config_lids'], $entity_type, "There are no entities ready for upload for this filter"); + $selected_lids = $_SESSION['grid_filters'][$entity_type]['filtered_config_lids']; + //check lid selection for dirty statuses + $edited_lid_map = LingotekConfigSet::getLidsToUpdate(0, $selected_lids); + $never_lid_map = LingotekConfigSet::findNeverUploadedLids($selected_lids); + $lid_map = array(); + if(!empty($edited_lid_map) && !empty($never_lid_map)){ + foreach($edited_lid_map as $key => $textgroup){ + if(isset($never_lid_map[$key])){ + $lid_map[$key] = array_merge($never_lid_map[$key], $textgroup); + } + } + } + else { + $lid_map = empty($edited_lid_map) ? $never_lid_map + : $edited_lid_map; + } + } + else { + // Get all lids that are ready to upload + $lid_map = LingotekConfigSet::getLidsToUpdate(); + } + lingotek_config_upload_selected($lid_map); +} + +function lingotek_grid_upload_edited($entity_type, $comma_separated_ids = NULL) { if ($entity_type === 'config') { - // Only get the ones that need to be uploaded. - $lids = LingotekConfigSet::getLidsToUpdate(); - lingotek_config_upload_selected($lids); + config_upload_edited($comma_separated_ids, $entity_type); return; } - $entity_ids = LingotekSync::getEntityIdsToUpload($entity_type); + if($comma_separated_ids !== NULL) { + $entity_ids = explode(",", $comma_separated_ids); + } + elseif(isset($_SESSION['grid_filters'][$entity_type])){ + empty_array_check($_SESSION['grid_filters'][$entity_type]['filtered_ids'], $entity_type, "There are no entities ready for upload for this filter"); + $selected_ids = $_SESSION['grid_filters'][$entity_type]['filtered_ids']; + //check lid selection for dirty statuses + $entity_ids = LingotekSync::getEntityIdsToUpload($entity_type, $selected_ids); + empty_array_check($entity_ids, $entity_type, "There are no entities ready for upload for this selection. Note: To upload disassociated content, select it using the checkboxes first."); + } + else { + // Get all lids that are ready to upload + $entity_ids = LingotekSync::getEntityIdsToUpload($entity_type); + } if (!empty($entity_ids)) { $operations = array(); @@ -894,11 +1363,16 @@ function lingotek_grid_upload_edited($entity_type) { batch_process($redirect); } else { - drupal_set_message(t('There are no translations ready for upload.')); + drupal_set_message(t('There are no edited entities ready for Re-upload.')); + drupal_goto('admin/settings/lingotek/manage/' . $entity_type); + } +} +function empty_array_check($array, $entity_type, $message){ + if(empty($array)){ + drupal_set_message(t(check_plain($message))); drupal_goto('admin/settings/lingotek/manage/' . $entity_type); } } - /** * Gets the columns that will be shown from the session variable * @@ -911,12 +1385,11 @@ function lingotek_grid_upload_edited($entity_type) { */ function lingotek_grid_get_columns($grid_name = 'grid') { $filters = array(); - - if (!isset($_SESSION[$grid_name . '_custom'])) { // If the columns do not exist yet in the session variable we get an error, so reset them here. + if (!isset($_SESSION['grid_custom'][$grid_name])) { // If the columns do not exist yet in the session variable we get an error, so reset them here. lingotek_grid_reset_columns(); } - foreach ($_SESSION[$grid_name . '_custom'] as $key => $value) { + foreach ($_SESSION['grid_custom'][$grid_name] as $key => $value) { $columns[str_replace('__custom', '', $key)] = $value; } @@ -928,12 +1401,12 @@ function lingotek_grid_get_columns($grid_name = 'grid') { */ function lingotek_grid_reset_columns() { $grid_name = $_SESSION['grid_entity_type']; - if (isset($_SESSION[$grid_name . '_custom'])) { - unset($_SESSION[$grid_name . '_custom']); + if (isset($_SESSION['grid_custom'][$grid_name])) { + unset($_SESSION['grid_custom'][$grid_name]); } $source_columns = $grid_name == 'config' ? lingotek_config_define_columns() : lingotek_grid_define_columns($grid_name); foreach ($source_columns['columns'] as $column) { - $_SESSION[$grid_name . '_custom'][$column . '__custom'] = in_array($column, $source_columns['defaults']); + $_SESSION['grid_custom'][$grid_name][$column . '__custom'] = in_array($column, $source_columns['defaults']); } } @@ -952,6 +1425,7 @@ function lingotek_grid_reset_columns() { function lingotek_grid_define_columns($entity_type) { $columns = array(); $columns['columns'] = array( + 'marked' => 'marked', 'nid' => 'nid', 'content_type' => 'content_type', 'title' => 'title', @@ -959,7 +1433,8 @@ function lingotek_grid_define_columns($entity_type) { 'translations' => 'translations', 'configuration' => 'configuration', 'document_id' => 'document_id', - 'workflow' => 'workflow', + 'workflow_id' => 'workflow_id', + 'workflow_name' => 'workflow_name', 'changed' => 'changed', 'last_uploaded' => 'last_uploaded', 'actions' => 'actions', @@ -977,13 +1452,19 @@ function lingotek_grid_define_columns($entity_type) { $columns['columns']['description'] = 'description'; $columns['defaults']['description'] = 'description'; } + if ($entity_type == 'bean'){ + $columns['columns']['label'] = 'label'; + } + return $columns; } function lingotek_config_define_columns() { $columns = array( 'columns' => array( + 'marked' => 'marked', 'lid' => 'lid', + 'config_set_id' => 'config_set_id', 'set_name' => 'set_name', 'source' => 'source', 'source_uploaded' => 'source_uploaded', @@ -991,6 +1472,7 @@ function lingotek_config_define_columns() { 'location' => 'location', 'context' => 'context', 'doc_id' => 'doc_id', + 'workflow' => 'workflow', 'textgroup' => 'textgroup', ), 'defaults' => array( @@ -1000,6 +1482,7 @@ function lingotek_config_define_columns() { 'source_uploaded' => 'source_uploaded', 'translations' => 'translations', 'textgroup' => 'textgroup', + 'workflow' => 'workflow', ), ); return $columns; @@ -1020,53 +1503,69 @@ function lingotek_grid_build_filters($form_state) { asort($source_languages); $profiles = array(); - $profiles[LingotekSync::PROFILE_CUSTOM] = t('Custom'); $profiles[LingotekSync::PROFILE_DISABLED] = t('Disabled'); $profile_defaults = lingotek_get_profiles(); foreach ($profile_defaults as $key => $p) { - $profiles[$key] = $p['name']; + $profiles[$key] = $p->getName(); } unset($profiles['CONFIG']); asort($profiles); + $entity_type = $form_state['entity_type']; + $filters = array( 'nid' => array( '#type' => 'textfield', - '#default_value' => isset($_SESSION['grid_filters']['nid']) ? $_SESSION['grid_filters']['nid'] : '', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['nid']) ? $_SESSION['grid_filters'][$entity_type]['nid'] : '', '#title' => t('Entity ID is'), '#size' => 8, ), 'document_id' => array( '#type' => 'textfield', - '#default_value' => isset($_SESSION['grid_filters']['document_id']) ? $_SESSION['grid_filters']['document_id'] : '', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['document_id']) ? $_SESSION['grid_filters'][$entity_type]['document_id'] : '', '#title' => t('Doc ID is'), '#size' => 10, ), 'source_language' => array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['source_language']) ? $_SESSION['grid_filters']['source_language'] : 'all', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['source_language']) ? $_SESSION['grid_filters'][$entity_type]['source_language'] : 'all', '#title' => t('Source Language'), '#options' => array('all' => t('All Languages')) + $source_languages, ), 'profile' => array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['profile']) ? $_SESSION['grid_filters']['profile'] : 'all', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['profile']) ? $_SESSION['grid_filters'][$entity_type]['profile'] : 'all', '#title' => t('Translation Profile'), '#options' => array('all' => 'All') + $profiles, '#multiple' => TRUE ), 'upload_status' => array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['upload_status']) ? $_SESSION['grid_filters']['upload_status'] : 'all', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['upload_status']) ? $_SESSION['grid_filters'][$entity_type]['upload_status'] : 'all', '#title' => t('Upload Status'), '#options' => array( 'all' => t('All'), LingotekSync::STATUS_EDITED => t('Out of Sync'), LingotekSync::STATUS_CURRENT => t('In Sync'), + 'uploaded' => t('Uploaded'), + 'upload_failed' => t('Upload Failed'), + 'never' => t('Never Uploaded'), + 'disassoc' => t('Disassociated'), ), '#multiple' => FALSE, - ) + ), + 'marked_status' => array ( + '#type' => 'select', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['marked_status']) ? $_SESSION['grid_filters'][$entity_type]['marked_status'] : 'all', + '#title' => t('Marked status'), + '#options' => array ( + 'all' => t('All'), + 'marked' => t('Marked'), + 'unmarked' => t('Not marked'), + ), + '#multiple' => FALSE, + ), ); // Include a content-type filter if there are bundles by which to filter. @@ -1078,7 +1577,7 @@ function lingotek_grid_build_filters($form_state) { asort($grid_bundles); $filters['content_type'] = array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['content_type']) ? $_SESSION['grid_filters']['content_type'] : 'all', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['content_type']) ? $_SESSION['grid_filters'][$entity_type]['content_type'] : 'all', '#title' => t('Content Type(s)'), '#options' => array('all' => t('All')) + $grid_bundles, '#multiple' => TRUE @@ -1089,6 +1588,7 @@ function lingotek_grid_build_filters($form_state) { } function lingotek_config_build_filters($form_state) { + $entity_type = $_SESSION['grid_entity_type']; $languages = language_list(); $search_languages = array('all' => t('All Languages')); foreach ($languages as $code => $language) { @@ -1099,25 +1599,31 @@ function lingotek_config_build_filters($form_state) { $filters = array( 'lid' => array( '#type' => 'textfield', - '#default_value' => isset($_SESSION['grid_filters']['lid']) ? $_SESSION['grid_filters']['lid'] : '', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['lid']) ? $_SESSION['grid_filters'][$entity_type]['lid'] : '', '#title' => t('Config Entity ID is'), '#size' => 8, ), + 'config_set_id' => array( + '#type' => 'textfield', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['config_set_id']) ? $_SESSION['grid_filters'][$entity_type]['config_set_id'] : '', + '#title' => t('Config Set ID is'), + '#size' => 10, + ), 'document_id' => array( '#type' => 'textfield', - '#default_value' => isset($_SESSION['grid_filters']['document_id']) ? $_SESSION['grid_filters']['document_id'] : '', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['document_id']) ? $_SESSION['grid_filters'][$entity_type]['document_id'] : '', '#title' => t('Doc ID is'), '#size' => 10, ), 'location' => array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['location']) ? $_SESSION['grid_filters']['location'] : '', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['location']) ? $_SESSION['grid_filters'][$entity_type]['location'] : '', '#title' => t('Module'), '#options' => array('' => '') + $modules, - ), + ), 'upload_status' => array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['upload_status']) ? $_SESSION['grid_filters']['upload_status'] : 'all', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['upload_status']) ? $_SESSION['grid_filters'][$entity_type]['upload_status'] : 'all', '#title' => t('Upload Status'), '#options' => array( 'all' => t('All'), @@ -1127,6 +1633,17 @@ function lingotek_config_build_filters($form_state) { ), '#multiple' => FALSE, ), + 'marked_status' => array ( + '#type' => 'select', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['marked_status']) ? $_SESSION['grid_filters'][$entity_type]['marked_status'] : 'all', + '#title' => t('Marked status'), + '#options' => array ( + 'all' => t('All'), + 'marked' => t('Marked'), + 'unmarked' => t('Not marked'), + ), + '#multiple' => FALSE, + ), ); // Include a content-type filter if there are bundles by which to filter. @@ -1137,7 +1654,7 @@ function lingotek_config_build_filters($form_state) { }, $entity_info['bundles']); $filters['content_type'] = array( '#type' => 'select', - '#default_value' => isset($_SESSION['grid_filters']['content_type']) ? $_SESSION['grid_filters']['content_type'] : 'all', + '#default_value' => isset($_SESSION['grid_filters'][$entity_type]['content_type']) ? $_SESSION['grid_filters'][$entity_type]['content_type'] : 'all', '#title' => t('Content Type(s)'), '#options' => array('all' => t('All')) + $grid_bundles, '#multiple' => TRUE @@ -1157,10 +1674,10 @@ function lingotek_config_build_filters($form_state) { * Associative array keyed by filter name with prefix and suffix removed * Keys point to the values to be filtered on */ -function lingotek_grid_get_filters($source = TRUE) { +function lingotek_grid_get_filters($entity_type, $source = TRUE) { $filters = array(); - if (isset($_SESSION['grid_filters'])) { - foreach ($_SESSION['grid_filters'] as $key => $value) { + if (isset($_SESSION['grid_filters'][$entity_type])) { + foreach ($_SESSION['grid_filters'][$entity_type] as $key => $value) { $new_key = str_replace($source ? 'source_' : 'target_', '', $key, $replaced); if ($replaced > 0) { $filters[str_replace('__filter', '', $new_key)] = $value; @@ -1174,15 +1691,21 @@ function lingotek_grid_get_filters($source = TRUE) { * Completely clears out any filters from the session variable * Filters will automatically revert to their defaults */ -function lingotek_grid_clear_filters() { - if (isset($_SESSION['grid_filters'])) { - unset($_SESSION['grid_filters']); +function lingotek_grid_clear_filters($entity_type = NULL) { + if ($entity_type === NULL || is_array($entity_type)) { + if (empty($_SESSION['grid_entity_type'])) { + throw new LingotekException('Attempted to clear filters on an empty entity type!'); + } + $entity_type = $_SESSION['grid_entity_type']; + } + if (isset($_SESSION['grid_filters'][$entity_type])) { + unset($_SESSION['grid_filters'][$entity_type]); session_write_close(); } } function lingotek_grid_clear_filters_page($entity_type) { - lingotek_grid_clear_filters(); + lingotek_grid_clear_filters($entity_type); drupal_goto('admin/settings/lingotek/manage/' . $entity_type); } @@ -1210,11 +1733,18 @@ function lingotek_grid_get_rows($entity_type, $form, &$form_state, $count_only = $info = entity_get_info($entity_type); $entity_id_key = $info['entity keys']['id']; $eid = 'n.' . $entity_id_key; - $entity_properties = array_flip($info['schema_fields_sql']['base table']); - $label_col = isset($info['entity keys']['label']) && $info['entity keys']['label'] ? $info['entity keys']['label'] : NULL; + + if ($entity_type == 'menu_link') { + $label_col = 'link_title'; + } + if ($entity_type == 'paragraphs_item') { + $label_col = 'field_name'; + } + $bundle_col = isset($info['entity keys']['bundle']) && $info['entity keys']['bundle'] && isset($entity_properties[$info['entity keys']['bundle']]) ? $info['entity keys']['bundle'] : NULL; + // All managed entity types should have a language column. // Field collection entities have one, but it is called 'langcode'. // Message type entities have one, and it is called 'language'; but currently it does not appear in their entity keys. @@ -1229,14 +1759,17 @@ function lingotek_grid_get_rows($entity_type, $form, &$form_state, $count_only = $columns = isset($form_state['values']['columns']) ? $form_state['values']['columns'] : array(); $header = array(// Define the tentative source header + 'marked' => array('data' => t('Marked')), 'nid' => array('data' => t('ID'), 'field' => 'n.' . $entity_id_key), 'content_type' => array('data' => t('Content Type'), 'field' => 'type'), 'title' => array('data' => t('Title'), 'field' => 'n.' . $label_col), + 'label' => array('data' => t('Label'), 'field' => 'n.label'), 'description' => array('data' => t('Description')), - 'language' => array('data' => t('Source Uploaded')), //, 'field' => 'upload_status'), + 'language' => array('data' => t('Source')), //, 'field' => 'upload_status'), 'translations' => array('data' => t('Translations'), 'field' => 't_current_c'), 'configuration' => array('data' => t('Profile')), - 'workflow' => array('data' => t('Workflow'), 'field' => 'workflow'), + 'workflow_id' => array('data' => t('Workflow ID'), 'field' => 'workflow_id'), + 'workflow_name' => array('data' => t('Workflow Name'), 'field' => 'workflow_name'), 'document_id' => array('data' => t('Doc ID'), 'field' => 'document_id'), 'changed' => array('data' => t('Last Modified'), 'field' => 'changed'), 'last_uploaded' => array('data' => t('Last Uploaded'), 'field' => 'last_uploaded'), @@ -1258,11 +1791,16 @@ function lingotek_grid_get_rows($entity_type, $form, &$form_state, $count_only = unset($header['content_type']); } + if ($entity_type != 'bean') { + unset($header['label']); + } + if (isset($entity_properties['changed'])) { $header['changed']['sort'] = 'desc'; } $form_state['values']['grid_header'] = lingotek_bulk_grid_refine_source_header($header, $columns); $query = lingotek_bulk_grid_query($form_state, $count_only, $entity_id_key, $label_col, $entity_type, $limit, $bundle_col, $info, $entity_properties, $eid); + //picked an arbitrarily large number to ensure all results are gotten // Initialize Query and extend paginator and tablesort if necessary lingotek_bulk_grid_filter_query($query, $entity_type, $eid, $label_col, $info); @@ -1274,6 +1812,20 @@ function lingotek_grid_get_rows($entity_type, $form, &$form_state, $count_only = // Execute the query $table_data_raw = $query->distinct()->execute()->fetchAllAssoc($entity_id_key); + + + //save filtered id's to session for group download/upload + if (lingotek_filter_set_check($entity_type)) { + $no_limit_query = lingotek_bulk_grid_query($form_state, $count_only, $entity_id_key, $label_col, $entity_type, 500000, $bundle_col, $info, $entity_properties, $eid); + lingotek_bulk_grid_filter_query($no_limit_query, $entity_type, $eid, $label_col, $info); + $no_limit_query_raw = $no_limit_query->distinct()->execute(); + $all_filtered_entity_ids = array(); + foreach($no_limit_query_raw as $value){ + $all_filtered_entity_ids[$value->$entity_id_key] = $value->$entity_id_key; + } + $_SESSION['grid_filters'][$entity_type]['filtered_ids'] = $all_filtered_entity_ids; + } + lingotek_entity_load($table_data_raw, $entity_type); // Parse returned objects and make them arrays keyed by the Node ID for clean use in The Grid. @@ -1294,7 +1846,6 @@ function lingotek_bulk_grid_refine_source_header($header, $columns) { function lingotek_bulk_grid_query($form_state, $count_only, $entity_id_key, $label_col, $entity_type, $limit, $bundle_col, $info, $entity_properties, $eid) { $base_table = $info['base table']; - if ($count_only) { $query = db_select('' . $base_table . '', 'n'); } @@ -1302,15 +1853,18 @@ function lingotek_bulk_grid_query($form_state, $count_only, $entity_id_key, $lab $query = db_select('' . $base_table . '', 'n') ->extend('PagerDefault') ->extend('TableSort'); - $query->limit($limit) - ->orderByHeader($form_state['values']['grid_header']); + $query->limit($limit); + if($limit <= 5000){//anything greater than 500 is a filtered_id query for the batch filter actions + $query->orderByHeader($form_state['values']['grid_header']); + } } if ($base_table == 'node') { - $query->innerJoin('node', 'node2', '(n.nid = node2.nid) AND (node2.tnid = 0 OR node2.tnid = node2.nid)'); + $query->where('n.tnid = 0 OR n.tnid = n.nid'); } // Entity Title and Name of Content Type (type) $query->fields('n', array($entity_id_key)); + $query->addField('n', $entity_id_key, 'entity_id_key'); if ($label_col) { $query->addField('n', $label_col, 'title'); @@ -1327,6 +1881,7 @@ function lingotek_bulk_grid_query($form_state, $count_only, $entity_id_key, $lab lingotek_bulk_grid_query_add_localized_title($query, $entity_type, $eid, $label_col); lingotek_bulk_grid_query_add_keys($query, $entity_type, $eid); + return $query; } @@ -1337,26 +1892,40 @@ function lingotek_bulk_grid_query_add_entity_specifics($query, $entity_type, $bu $query->addExpression("CONCAT('comment_node_',nn.type)", 'node_type'); } elseif ($entity_type == 'taxonomy_term') { - $query->addField('n', 'description'); + $taxonomy_bundles_with_custom_fields = lingotek_get_advanced_vocabularies(); + if (empty($taxonomy_bundles_with_custom_fields)) { + $taxonomy_bundles_with_custom_fields = array(-1); + } + $query->addField('n', 'description'); $query->innerJoin('taxonomy_vocabulary', 'tv', 'n.vid = tv.vid'); $query->addField('tv', 'name', 'tv_name'); $query->addField('tv', 'machine_name', 'type'); $query->addField('tv', 'i18n_mode', 'translation_mode'); - - // Remove taxonomy terms handled by config manage page. - $query->leftJoin('field_config_instance', 'fields', 'fields.bundle = tv.machine_name'); - $or = db_or(); - // Has no custom fields - $or->isNotNull('fields.bundle'); - $or->condition('tv.i18n_mode', LINGOTEK_TAXONOMY_LOCALIZE_VALUE, '<>'); - $query->condition($or); + $query->condition('tv.machine_name', $taxonomy_bundles_with_custom_fields, 'IN'); } elseif ($bundle_col) { if ($info['entity keys']['bundle'] != 'type') { $query->addField('n', $info['entity keys']['bundle']); } $query->addField('n', $info['entity keys']['bundle'], 'type'); + // beans have the $bundle_col, and also need the label + if ($entity_type == 'bean'){ + $query->addField('n', 'label'); + } + if ($entity_type == 'menu_link') { + $min_query = db_select('menu_links', 'ml') + ->condition('ml.i18n_tsid', 0, '!=') + ->groupBy('i18n_tsid'); + $min_query->addExpression('MIN(mlid)', 'minimum'); + + $ml_or = db_or(); + $ml_or->condition('n.i18n_tsid', 0); + $ml_or->condition('n.mlid', $min_query, 'IN'); + + $query->condition('n.language', LANGUAGE_NONE, '!='); + $query->condition($ml_or); + } } } @@ -1366,10 +1935,14 @@ function lingotek_bulk_grid_query_add_statuses($query, $entity_type, $eid) { $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_PENDING . "')", 't_pending'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_READY . "')", 't_ready'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_CURRENT . "')", 't_current'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_INTERIM . "')", 't_interim'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_READY_INTERIM . "')", 't_ready_interim'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_EDITED . "')", 't_edited'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_UNTRACKED . "')", 't_untracked'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_TARGET_LOCALIZE . "')", 't_target_localize'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_TARGET_EDITED . "')", 't_target_edited'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_ERROR . "')", 't_error'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(entity_key, 20, 10)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'target_sync_status_%' AND value='" . LingotekSync::STATUS_NONE . "')", 't_none'); $query->addExpression("(SELECT GROUP_CONCAT(CONCAT(SUBSTRING(entity_key, 17, 10), ':' , value)) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key LIKE 'source_language_%')", 'lang_override'); } @@ -1377,49 +1950,49 @@ function lingotek_bulk_grid_query_add_localized_title($query, $entity_type, $eid $localized_label_table = 'field_data_' . $label_col . '_field'; if ($label_col && db_table_exists($localized_label_table)) { - $query->leftJoin('' . $localized_label_table . '', 't_title', 't_title.entity_id = ' . $eid . ' and t_title.entity_type = \'' . $entity_type . '\' and t_title.language=\'' . $GLOBALS['language']->language . '\''); - $query->addField('t_title', $label_col . '_field_value', 'localized_title'); + $query->addExpression("(SELECT GROUP_CONCAT(" . $label_col . '_field_value' . ") FROM {" . $localized_label_table . "} WHERE entity_id=" . $eid . " AND entity_type='" . $entity_type . "' AND language = '" . $GLOBALS['language']->language . "')", 'localized_title'); } } function lingotek_bulk_grid_query_add_keys($query, $entity_type, $eid) { - // left joins are necessary here because some lingotek table keys might not exist // Lingotek Document ID - $query->leftJoin('lingotek_entity_metadata', 'lingo_document_id', 'lingo_document_id.entity_type =\'' . $entity_type . '\' AND lingo_document_id.entity_id = ' . $eid . ' and lingo_document_id.entity_key = \'document_id\''); - $query->addField('lingo_document_id', 'value', 'document_id'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'document_id')", 'document_id'); + + //Invalid xml + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'invalid_xml')", 'invalid_xml'); // Entity Upload Status - $query->leftJoin('lingotek_entity_metadata', 'lingo_upload_status', 'lingo_upload_status.entity_type =\'' . $entity_type . '\' AND lingo_upload_status.entity_id = ' . $eid . ' and lingo_upload_status.entity_key = \'upload_status\' and lingo_upload_status.value <> \'' . LingotekSync::STATUS_TARGET . '\''); - $query->addField('lingo_upload_status', 'value', 'upload_status'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'upload_status' AND value <> '". LingotekSync::STATUS_TARGET . "' AND value <> '" . LingotekSync::STATUS_DELETED . "')", 'upload_status'); // Profile Settings - $query->leftJoin('lingotek_entity_metadata', 'lingo_profile', 'lingo_profile.entity_type =\'' . $entity_type . '\' AND lingo_profile.entity_id = ' . $eid . ' and lingo_profile.entity_key = \'profile\''); - $query->addField('lingo_profile', 'value', 'profile'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'profile')", 'profile'); // Last Uploaded Timestamp - $query->leftJoin('lingotek_entity_metadata', 'lingo_last_uploaded', 'lingo_last_uploaded.entity_type =\'' . $entity_type . '\' AND lingo_last_uploaded.entity_id = ' . $eid . ' and lingo_last_uploaded.entity_key = \'last_uploaded\''); - $query->addField('lingo_last_uploaded', 'value', 'last_uploaded'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'last_uploaded')", 'last_uploaded'); // Any Upload Errors - $query->leftJoin('lingotek_entity_metadata', 'lingo_last_sync_error', 'lingo_last_sync_error.entity_type =\'' . $entity_type . '\' AND lingo_last_sync_error.entity_id = ' . $eid . ' and lingo_last_sync_error.entity_key = \'last_sync_error\''); - $query->addField('lingo_last_sync_error', 'value', 'last_sync_error'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'last_sync_error')", 'last_sync_error'); + + // Any specifically defined workflow id + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'workflow_id')", 'workflow_id'); - // Any specifically defined workflow - $query->leftJoin('lingotek_entity_metadata', 'lingo_workflow', '' . $eid . ' = lingo_workflow.entity_id and lingo_workflow.entity_type =\'' . $entity_type . '\' and lingo_workflow.entity_key = \'workflow_id\''); - $query->addField('lingo_workflow', 'value', 'workflow'); + // Any specifically defined workflow name + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'workflow_name')", 'workflow_name'); + + // Marked value + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE entity_type='" . $entity_type . "' AND entity_id=" . $eid . " AND entity_key = 'marked')", 'marked'); // Original source language of the entity, in case of source overwriting option if ($entity_type == 'node') { - $query->leftJoin('lingotek_entity_metadata', 'lingo_orig_lang', $eid . ' = lingo_orig_lang.entity_id and lingo_orig_lang.entity_type =\'' . $entity_type . '\' and lingo_orig_lang.entity_key = \'original_language\''); - $query->addField('lingo_orig_lang', 'value', 'original_lang'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_entity_metadata} WHERE'" . $eid . "' = entity_id and entity_type ='" . $entity_type . "' and entity_key = 'original_language')", 'original_lang'); } } function lingotek_bulk_grid_filter_query($query, $entity_type, $eid, $label_col, $info) { - if (!isset($_SESSION['grid_filters'])) { + if (!isset($_SESSION['grid_filters'][$entity_type])) { return; } - $filters = $_SESSION['grid_filters']; + $filters = $_SESSION['grid_filters'][$entity_type]; lingotek_bulk_grid_filter_search_box($query, $filters, $eid, $label_col, $entity_type); lingotek_bulk_grid_filter_popup_options($query, $filters, $eid, $info, $entity_type); @@ -1501,13 +2074,14 @@ function lingotek_bulk_grid_filter_popup_options($query, $filters, $eid, $info, $array_fix = array('upload_status', 'content_type', 'auto_upload', 'auto_download', 'crowdsourcing', 'url_alias', 'translation_status', - 'locale_progress_percent'); + 'locale_progress_percent', 'marked_status'); foreach ($array_fix as $value) { if (isset($filters[$value]) && !is_array($filters[$value])) { $filters[$value] = array($filters[$value]); } } + // Source Language if (isset($filters['source_language']) && $filters['source_language'] != 'all') { $query->condition('n.' . $info['entity keys']['language'], $filters['source_language']); @@ -1515,7 +2089,69 @@ function lingotek_bulk_grid_filter_popup_options($query, $filters, $eid, $info, // Upload Status if (isset($filters['upload_status']) && !in_array('all', $filters['upload_status'])) { - $query->condition('lingo_upload_status.value', $filters['upload_status'], 'IN'); + //never uploaded case + if(in_array("never", $filters['upload_status'])){ + $notUploadedQuery = db_select('lingotek_entity_metadata','lingo_em'); + $notUploadedQuery->distinct(); + $notUploadedQuery->fields('lingo_em',array('entity_id')); + $notUploadedQuery->condition('entity_key', 'last_uploaded'); + + $query->havingCondition('entity_id_key', $notUploadedQuery, 'NOT IN'); + } + else if(in_array("disassoc", $filters['upload_status'])){ + $disassocQuery = db_select('lingotek_entity_metadata','lingo_em'); + $disassocQuery->distinct(); + $disassocQuery->fields('lingo_em',array('entity_id')); + $disassocQuery->condition('entity_key', 'last_uploaded'); + + $query->havingCondition('entity_id_key', $disassocQuery, 'IN'); + $query->havingCondition('upload_status', 'UNTRACKED'); + } + elseif(in_array("uploaded", $filters['upload_status'])) { + $uploadedQuery = db_select('lingotek_entity_metadata','lingo_em'); + $uploadedQuery->distinct(); + $uploadedQuery->fields('lingo_em', array('entity_id')); + $uploadedQuery->condition('entity_key', 'document_id'); + + $query->havingCondition('entity_id_key', $uploadedQuery, 'IN'); + } + elseif(in_array("upload_failed", $filters['upload_status'])) { + $uploadFailedQuery = db_select('lingotek_entity_metadata','lingo_em'); + $uploadFailedQuery->distinct(); + $uploadFailedQuery->fields('lingo_em', array('entity_id')); + $uploadFailedQuery->condition('entity_key', 'invalid_xml'); + + $query->havingCondition('entity_id_key', $uploadFailedQuery, 'IN'); + $query->havingCondition('invalid_xml', LingotekSync::INVALID_XML_PRESENT); + } + //other cases + else { + $query->havingCondition('upload_status', $filters['upload_status'], 'IN'); + } + } + + // Marked status + if (isset($filters['marked_status']) && !in_array('all', $filters['marked_status'])) { + // Marked + if(in_array("marked", $filters['marked_status'])){ + $markedQuery = db_select('lingotek_entity_metadata','lingo_em'); + $markedQuery->distinct(); + $markedQuery->fields('lingo_em',array('entity_id')); + $markedQuery->condition('entity_key', 'marked'); + $markedQuery->condition('value', LingotekSync::MARKED); + + $query->havingCondition('entity_id_key', $markedQuery, 'IN'); + } + // Unmarked + else if(in_array("unmarked", $filters['marked_status'])){ + $unMarkedQuery = db_select('lingotek_entity_metadata','lingo_em'); + $unMarkedQuery->distinct(); + $unMarkedQuery->fields('lingo_em',array('entity_id')); + $unMarkedQuery->condition('entity_key', 'marked'); + $unMarkedQuery->condition('value', LingotekSync::MARKED); + + $query->havingCondition('entity_id_key', $unMarkedQuery, 'NOT IN'); + } } // Content Type @@ -1538,9 +2174,14 @@ function lingotek_bulk_grid_filter_popup_options($query, $filters, $eid, $info, } if (isset($filters['profile']) && !in_array('all', $filters['profile'])) { - $profiled_entities = lingotek_get_entities_by_profile_and_entity_type($filters['profile'], $entity_type); $profiled_entity_ids = array(-1); - foreach ($profiled_entities as $p) { + + foreach ($filters['profile'] as $p) { + $profile_obj = LingotekProfile::loadById($p); + $entities = $profile_obj->getEntities($entity_type); + $profiled_entity_ids = array_merge($profiled_entity_ids, array_keys($entities)); + } + foreach ($profiled_entity_ids as $p) { $profiled_entity_ids[] = $p['id']; } $query->condition('n.' . $info['entity keys']['id'], $profiled_entity_ids, 'IN'); @@ -1552,10 +2193,10 @@ function lingotek_bulk_grid_filter_popup_options($query, $filters, $eid, $info, function lingotek_filter_by_document_id($query, $filters) { if (isset($filters['document_id']) && $filters['document_id'] != '') { if ($filters['document_id'] == 'None') { - $query->condition('lingo_document_id.value', NULL); + $query->havingCondition('document_id', NULL); } else { - $query->condition('lingo_document_id.value', $filters['document_id']); + $query->havingCondition('document_id', $filters['document_id']); } } } @@ -1563,14 +2204,14 @@ function lingotek_filter_by_document_id($query, $filters) { function lingotek_bulk_grid_filter_last_uploaded($query, $filters) { if (isset($filters['last_uploaded']) && $filters['last_uploaded'] != 'all') { if ($filters['last_uploaded'] == '1 day') { - $query->condition('lingo_last_uploaded.value', strToTime($filters['last_uploaded']), '<'); + $query->havingCondition('last_uploaded', strToTime($filters['last_uploaded']), '<'); } elseif ($filters['last_uploaded'] == 'unknown') { - $query->condition('lingo_last_uploaded.value', NULL); + $query->havingCondition('last_uploaded', NULL); } else { $params = explode(' ', $filters['last_uploaded'], 2); // string formatted like '< 1 week', so explode with a limit of two gives us array(0 => '<', 1 => '1 week') - $query->condition('lingo_last_uploaded.value', strToTime($params[1]), $params[0]); + $query->havingCondition('last_uploaded', strToTime($params[1]), $params[0]); } } } @@ -1591,19 +2232,24 @@ function lingotek_bulk_grid_filter_last_downloaded($query, $filters) { } function lingotek_bulk_grid_parse_table_data($table_data_raw, $entity_properties, $entity_id_key) { + global $language; $languages = language_list(); $profiles = lingotek_get_profiles(); - $api = LingotekApi::instance(); $workflows = $api->listWorkflows(); - $table_data = array(); + $node_based_translations = array(); foreach ($table_data_raw as $row) { + if($row->language !== $language->language){ + $node_based_translations[] = $row->$entity_id_key; + } + $entity_id = $row->{$entity_id_key}; + $entity_type = $entity_properties['type']; $row->title = lingotek_truncate_grid_text($row->title, 55); - $icon = lingotek_source_uploaded_icon($row); + $locales_statuses = lingotek_build_locales_statuses($row); ksort($locales_statuses); @@ -1611,30 +2257,36 @@ function lingotek_bulk_grid_parse_table_data($table_data_raw, $entity_properties $disabled = $row->lingotek['profile'] == LingotekSync::PROFILE_DISABLED; $disabled_class = $disabled ? ' ltk-disabled-icon' : ''; $allow_source_overwriting = !empty($row->lingotek['allow_source_overwriting']); - $allow_target_localization = !empty($profiles[$row->lingotek['profile']]['allow_target_localization']); + $allow_target_localization = !empty($row->lingotek['allow_target_localization']); - $translation_icons = lingotek_lang_icons($entity_properties['type'], $languages, $entity_id, $locales_statuses, $disabled, $row->language, $allow_source_overwriting, $allow_target_localization); // show translation statuses + $translation_icons = lingotek_lang_icons($entity_properties['type'], $languages, $entity_id, $row->lingotek['profile'], $locales_statuses, $disabled, $row->language, $allow_source_overwriting, $allow_target_localization, array()); // show translation statuses if (!empty($translation_icons['source'])) { $row->overridden_source_target_icon = $translation_icons['source']; unset($translation_icons['source']); } $target_icons_str = implode('', array_values($translation_icons)); $configuration = lingotek_render_configuration($row, $profiles, $disabled_class); - $source = lingotek_render_source($entity_properties['type'], $row, $icon, $languages, $entity_properties['language_col']); - $actions = lingotek_render_actions($entity_properties['type'], $entity_id, $disabled_class); - $title = $entity_properties['label_col'] ? lingotek_render_title($row, $entity_properties, $GLOBALS['language']) : ''; + $source = lingotek_render_source($entity_properties['type'], $row, $languages, $entity_properties['language_col'], $entity_properties); + $marked_html = lingotek_render_marked_html($row, $entity_type, $entity_id); + $actions = lingotek_render_actions($entity_properties, $entity_id, $disabled_class); + $title = $entity_properties['label_col'] ? lingotek_render_title($row, $entity_properties) : ''; + $workflow_id = !empty($row->lingotek['workflow_id']) ? $row->lingotek['workflow_id'] : ''; + $workflow_name = array_key_exists($workflow_id, $workflows) ? $workflows[$workflow_id] : ''; // Build the data to be output for this row $data = array( + 'marked' => $marked_html, 'nid' => $row->{$entity_id_key} ? : t('??'), 'title' => $title, + 'label' => isset($row->label) ? $row->label : '', 'language' => $source, 'translations' => $target_icons_str, 'configuration' => $configuration, 'document_id' => $row->document_id ? : t('N/A'), 'content_type' => isset($entity_properties['info']['bundles'][$row->type]['label']) ? $entity_properties['info']['bundles'][$row->type]['label'] : $row->type, 'last_uploaded' => $row->last_uploaded ? t('@time ago', array('@time' => lingotek_human_readable_timestamp($row->last_uploaded))) : t('Never'), - 'workflow' => ($row->lingotek['workflow_id'] ? (isset($workflows[$row->lingotek['workflow_id']]) ? check_plain($workflows[$row->lingotek['workflow_id']]) : $row->lingotek['workflow_id']) : ''), + 'workflow_id' => $workflow_id, + 'workflow_name' => $workflow_name, 'actions' => '' . $actions . '', 'description' => isset($row->description) ? $row->description : '', ); @@ -1642,9 +2294,25 @@ function lingotek_bulk_grid_parse_table_data($table_data_raw, $entity_properties if (isset($entity_properties['changed'])) { $data['changed'] = $row->changed ? t('@time ago', array('@time' => lingotek_human_readable_timestamp($row->changed))) : t('Never'); } - $table_data[$entity_id] = $data; } + $entity_type = $entity_properties['type']; + if(!empty($node_based_translations)){ + if($entity_type === 'node') { + $query = db_select('node', 'n'); + $query->addField('n', 'title'); + $query->addField('n', 'tnid'); + $query->addField('n', 'nid'); + $query->condition('n.tnid', $node_based_translations, 'IN'); + $query->condition('n.language', $language->language); + $results = $query->execute(); + foreach($results as $result) { + if(isset($table_data[$result->tnid])){ + $table_data[$result->tnid]['title'] = l(t($result->title), $entity_properties['type'] . "/$result->nid"); + } + } + } + } return $table_data; } @@ -1657,39 +2325,8 @@ function lingotek_truncate_grid_text($string, $truncate_length) { return $string; } -function lingotek_source_uploaded_icon($row) { - $icon = ''; - if ($row->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { - $icon = ''; - } - elseif (!is_null($row->upload_status)) { - switch ($row->upload_status) { - case LingotekSync::STATUS_EDITED: - $icon = ''; - break; - case LingotekSync::STATUS_CURRENT: - $icon = ''; - break; - case LingotekSync::STATUS_PENDING: - $icon = ''; - break; - case LingotekSync::STATUS_FAILED: - $error = $row->last_sync_error ? $row->last_sync_error : ''; - $icon = ''; - break; - default: - $icon = ''; - break; - } - } - else { - $icon = ''; - } - return $icon; -} - function lingotek_build_locales_statuses($row, $t_prefix = TRUE) { - $list_statuses = array('pending', 'ready', 'current', 'edited', 'untracked'); + $list_statuses = array('none', 'pending', 'ready', 'current', 'ready_interim', 'interim', 'edited', 'untracked', 'error'); $locales_statuses = array(); foreach ($list_statuses as $status) { $key = $t_prefix ? 't_' . $status : $status; @@ -1700,10 +2337,21 @@ function lingotek_build_locales_statuses($row, $t_prefix = TRUE) { } } + if (count($locales_statuses) == 0) { + $source_language = $row->language; + $languages = language_list(); + foreach ($languages as $language => $value) { + if ($language != $source_language) { + $lingotek_locale = Lingotek::convertDrupal2Lingotek($language); + $locales_statuses[$lingotek_locale] = LingotekSync::STATUS_NONE; + } + } + } return $locales_statuses; } -function lingotek_render_title($row, $entity_properties, $language) { +function lingotek_render_title($row, $entity_properties) { + $language = $GLOBALS['language']; $info = $entity_properties['info']; $entity_type = $entity_properties['type']; $no_localized_title = (language_default()->language != $language->language) && (!isset($row->localized_title) or $row->localized_title == ''); @@ -1714,11 +2362,31 @@ function lingotek_render_title($row, $entity_properties, $language) { $uri = isset($info['uri callback']) ? call_user_func($info['uri callback'], $term_obj) : ''; } else { + if ($entity_type == 'bean') { + $row = lingotek_entity_load_single($entity_type, $row->bid); + } + elseif ($entity_type == 'group') { + $row = lingotek_entity_load_single($entity_type, $row->gid); + } + elseif ($entity_type == 'paragraphs_item') { + $row = lingotek_entity_load_single($entity_type, $row->item_id); + } + elseif ($entity_type == 'file') { + $row = lingotek_entity_load_single($entity_type, $row->fid); + } $uri = isset($info['uri callback']) ? call_user_func($info['uri callback'], $row) : ''; } $uri['options']['attributes'] = array('target' => '_blank'); - $title_to_show = (isset($row->localized_title) ? $row->localized_title : $row->title); + if ($entity_type == 'paragraphs_item') { + $title_to_show = lingotek_clean_paragraphs_title($row->field_name); + } + elseif ($entity_type == 'file') { + $title_to_show = $row->filename; + } + else { + $title_to_show = (isset($row->localized_title) ? $row->localized_title : $row->title); + } $title = ($no_localized_title ? '' : ''); if (isset($uri['path'])) { $title .= l($title_to_show, $uri['path'], $uri['options']); @@ -1731,7 +2399,74 @@ function lingotek_render_title($row, $entity_properties, $language) { return $title; } -function lingotek_render_source($entity_type, $row, $icon, $languages, $language_col) { +function lingotek_clean_paragraphs_title($field_name) { + $field_name = ucfirst($field_name); + $field_name = str_replace('_', ' ', $field_name); + + return $field_name; +} + +function lingotek_render_source_current_icon_link($row, $entity_properties, $profile) { + $language = $GLOBALS['language']; + $info = $entity_properties['info']; + $language_col = $entity_properties['language_col']; + $entity_type = $entity_properties['type']; + $no_localized_title = (language_default()->language != $language->language) && (!isset($row->localized_title) or $row->localized_title == ''); + + $icon_class = $profile === LingotekSync::PROFILE_DISABLED ? 'ltk-source-icon source-disabled' : 'ltk-source-icon source-current'; + + $tip = $profile === LingotekSync::PROFILE_DISABLED ? 'Disabled, cannot request translation' : 'Source Uploaded'; + + // special handling of taxonomy terms for URI generation + if ($entity_type == 'taxonomy_term') { + $term_obj = lingotek_entity_load_single($entity_type, $row->tid); + $uri = isset($info['uri callback']) ? call_user_func($info['uri callback'], $term_obj) : ''; + } + else { + if ($entity_type == 'bean') { + $row = lingotek_entity_load_single($entity_type, $row->bid); + } + elseif ($entity_type == 'group') { + $row = lingotek_entity_load_single($entity_type, $row->gid); + } + elseif ($entity_type == 'paragraphs_item') { + // Paragraphs url callback doesn't work currently, so we build the link here + $paragraphs_item = lingotek_entity_load_single($entity_type, $row->item_id); + $bundle = $paragraphs_item->bundle; + $real_path = $entity_properties['info']['bundles'][$bundle]['admin']['real path']; + $edit_link = $real_path . '/edit'; + $paragraphs_language = ($language_col ? $row->language : ''); + $paragraphs_icon = l($paragraphs_language, $edit_link, array('attributes' => array('target' => '_blank', 'title' => $tip, 'class' => $icon_class))); + + return $paragraphs_icon; + } + elseif ($entity_type == 'file') { + $row = lingotek_entity_load_single($entity_type, $row->fid); + } + $uri = isset($info['uri callback']) ? call_user_func($info['uri callback'], $row) : ''; + } + + $uri['options']['attributes'] = array( + 'target' => '_blank', + 'class' => $icon_class, + 'title' => $tip + ); + $title = ''; + $title_to_show = (isset($row->localized_title) ? $row->localized_title : $row->title); + $source_str = ($language_col ? $row->language : ''); + + if (isset($uri['path'])) { + $title .= l($source_str, $uri['path'], $uri['options']); + } + else { + $title .= $title_to_show; + } + + return $title; +} + +function lingotek_render_source($entity_type, $row, $languages, $language_col, $entity_properties = array()) { + $profile = $row->lingotek['name']; if ($entity_type == 'node') { $id = $row->nid; } @@ -1741,47 +2476,107 @@ function lingotek_render_source($entity_type, $row, $icon, $languages, $language elseif ($entity_type == 'config') { $id = $row->lid; } + elseif ($entity_type == 'fieldable_panels_pane'){ + $id = $row->fpid; + } elseif ($entity_type == 'taxonomy_term') { $id = $row->tid; $row->language = isset($row->translation_mode) && $row->translation_mode == '1' ? 'en' : $row->language; } + elseif ($entity_type == 'bean') { + $id = $row->bid; + $row->language = language_default()->language; + } + elseif ($entity_type == 'group') { + $id = $row->gid; + $language = lingotek_get_group_source($row->gid); + $row->language = $language; + } + elseif ($entity_type == 'menu_link') { + $id = $row->mlid; + } + elseif ($entity_type == 'paragraphs_item') { + $id = $row->item_id; + } + elseif ($entity_type == 'file') { + $id = $row->fid; + $row->language = lingotek_get_file_source($row->fid); + } elseif (!empty($row->id)) { $id = $row->id; } - $source = '' . $icon . ' '; - $source .= ($language_col ? lingotek_get_upload_string($row, $languages) : ''); - $source .= ''; - $linked_statuses = array(LingotekSync::STATUS_EDITED, LingotekSync::STATUS_FAILED); - $needs_upload = (in_array($row->upload_status, $linked_statuses) || empty($row->upload_status)) && $row->lingotek['profile'] != LingotekSync::PROFILE_DISABLED; - if ($needs_upload) { // add link for upload - $source = '' . $source . ''; + + $source_str = ($language_col ? $row->language : ''); + + if ($row->upload_status === LingotekSync::STATUS_NONE) { + $source_html = '' . $source_str . ''; + } + elseif ($row->upload_status === LingotekSync::STATUS_EDITED) { + $source_html = '' . $source_str . ''; + } + elseif ($row->upload_status === LingotekSync::STATUS_CURRENT) { + if ($entity_type === 'config') { + $source_html = '' . $source_str . ''; + } + else { + $source_html = lingotek_render_source_current_icon_link($row, $entity_properties, $profile); + } + } + elseif ($row->upload_status === LingotekSync::STATUS_ERROR) { + if ($entity_type === 'config') { + $error_message = (isset($row->upload_error)) ? $row->upload_error : ''; + } + else { + $error_message = (isset($row->last_sync_error)) ? $row->last_sync_error : ''; + } + $source_html = '' . $source_str . ''; + } + else { + $source_html = '' . $source_str . ''; + } + + // Config items aren't added to the lingotek_config_metadata table until uploaded. + if ($entity_type === 'config' && $row->upload_status === NULL) { + $source_html = '' . $source_str . ''; + } + + if ($profile == LingotekSync::PROFILE_DISABLED && $entity_type != 'config') { + $source_html = '' . $source_str . ''; } - return $source; + + return $source_html; +} + +function lingotek_render_marked_html($row, $entity_type, $entity_id) { + global $base_url; + $marked_value = (isset($row->marked) && $row->marked) ? 1 : 0; + $title = $marked_value == 1 ? 'Unmark content' : 'Mark content'; + $marked_icon = $marked_value ? "fa-check-square" : "fa-square-o"; + $marked_html = ''; + + return $marked_html; } function lingotek_render_configuration($row, $profiles, $disabled_class) { $configuration = ''; - if ($row->lingotek['create_lingotek_document']) { + if (!empty($row->lingotek['auto_upload'])) { $configuration .= ' '; } - if ($row->lingotek['sync_method']) { + if (!empty($row->lingotek['auto_download'])) { $configuration .= ' '; } - if ($row->lingotek['allow_community_translation']) { + if (!empty($row->lingotek['allow_community_translation'])) { $configuration .= ' '; } - if ($row->lingotek['profile'] == LingotekSync::PROFILE_CUSTOM) { - $configuration = t('Custom') . ' ' . $configuration . ''; - } - elseif ($row->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { + if ($row->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { $configuration = t('Disabled'); } else { $profile_key = $row->lingotek['profile']; if (array_key_exists($profile_key, $profiles)) { - $configuration = t($profiles[$profile_key]['name']); + $configuration = t($profiles[$profile_key]->getName()); } else { $configuration = t('Unknown');// profile id does not exist in profiles (this state should be unobtainable) @@ -1791,19 +2586,21 @@ function lingotek_render_configuration($row, $profiles, $disabled_class) { return $configuration; } -function lingotek_render_actions($entity_type, $entity_id, $disabled_class) { +function lingotek_render_actions($entity_properties, $entity_id, $disabled_class) { + $entity_type = $entity_properties['type']; $pm_icon = ($entity_type == 'node') ? ' ' : ''; $pm_link = ' ' . l($pm_icon, 'node/' . $entity_id . '/lingotek_pm', array('html' => TRUE, 'attributes' => array('title' => t('View Translations'), 'target' => '_blank'))); $actions = ''; - $actions .= lingotek_get_entity_edit_link($entity_type, $entity_id); + $actions .= lingotek_get_entity_edit_link($entity_properties, $entity_id); $actions .= empty($disabled_class) ? $pm_link : $pm_icon; $actions .= ' ' . l('', '', array('html' => TRUE, 'attributes' => array('onclick' => 'lingotek_perform_action(' . $entity_id . ',"edit"); return false;'))); return $actions; } -function lingotek_get_entity_edit_link($entity_type, $entity_id, $url_only = FALSE) { +function lingotek_get_entity_edit_link($entity_properties, $entity_id, $url_only = FALSE) { + $entity_type = $entity_properties['type']; $edit_link = ""; $query = array(); $query['destination'] = 'admin/settings/lingotek/manage/' . $entity_type; @@ -1816,6 +2613,15 @@ function lingotek_get_entity_edit_link($entity_type, $entity_id, $url_only = FAL break; case 'message_type': return ''; + case 'menu_link': + $edit_link = 'admin/structure/menu/item/' . $entity_id; + break; + case 'paragraphs_item'; + $paragraphs_item = lingotek_entity_load_single($entity_type, $entity_id); + $bundle = $paragraphs_item->bundle; + $real_path = $entity_properties['info']['bundles'][$bundle]['admin']['real path']; + $edit_link = $real_path . '/edit'; + break; default: $edit_link = $entity_type . '/' . $entity_id . '/edit'; break; @@ -1823,19 +2629,29 @@ function lingotek_get_entity_edit_link($entity_type, $entity_id, $url_only = FAL return $url_only ? url($edit_link, array('query' => $query)) : l('', $edit_link, array('html' => TRUE, 'attributes' => array('target' => '_blank'), 'query' => $query)); } -function lingotek_lang_icons($entity_type, $languages, $entity_id, $locale_statuses = array(), $disabled = FALSE, $source_language = NULL, $allow_source_overwriting = FALSE, $allow_target_localization = FALSE, $non_lingotek_config_translations = array()) { - $icons = array(); +function lingotek_lang_icons($entity_type, $languages, $entity_id, $entity_profile = NULL, $locale_statuses = array(), $disabled = FALSE, $source_language = NULL, $allow_source_overwriting = FALSE, $allow_target_localization = FALSE, $non_lingotek_config_translations = array()) { + if ($entity_type == 'group') { + $source_language = lingotek_get_group_source($entity_id); + } + if ($entity_type == 'paragraphs_item') { + $source_language = lingotek_get_paragraphs_item_source($entity_id); + } + + $icons = array(); $legend = array( - LingotekSync::STATUS_PENDING => t('In progress'), - LingotekSync::STATUS_READY => t('Ready to download'), + LingotekSync::STATUS_NONE => t('No Translation'), + LingotekSync::STATUS_PENDING => t('In-Progress'), + LingotekSync::STATUS_READY => t('Ready for Download'), LingotekSync::STATUS_CURRENT => t('Current'), - LingotekSync::STATUS_EDITED => t('Not current'), - LingotekSync::STATUS_UNTRACKED => t('Untracked'), + LingotekSync::STATUS_READY_INTERIM => t('Ready for Interim Download'), + LingotekSync::STATUS_INTERIM => t('In-progress (interim translation downloaded)'), + LingotekSync::STATUS_EDITED => t('Source Edited'), + LingotekSync::STATUS_UNTRACKED => t('Translation exists, but it is not being tracked by Lingotek'), LingotekSync::STATUS_TARGET_LOCALIZE => t('Pending localization'), LingotekSync::STATUS_TARGET_EDITED => t('Needs to be uploaded'), LingotekSync::STATUS_NON_LINGOTEK => t('Non-Lingotek Translation'), - '' + LingotekSync::STATUS_ERROR => t('Error'), ); $default_link = 'lingotek/workbench/' . $entity_type . '/' . $entity_id . '/'; $manual_link = $entity_type . '/' . $entity_id . '/lingotek_pm/'; @@ -1846,15 +2662,25 @@ function lingotek_lang_icons($entity_type, $languages, $entity_id, $locale_statu LingotekSync::STATUS_PENDING => $default_link, LingotekSync::STATUS_READY => $default_link, LingotekSync::STATUS_CURRENT => $default_link, + LingotekSync::STATUS_READY_INTERIM => $default_link, + LingotekSync::STATUS_INTERIM => $default_link, LingotekSync::STATUS_EDITED => $default_link, LingotekSync::STATUS_UNTRACKED => $untracked_link, LingotekSync::STATUS_TARGET_LOCALIZE => $untracked_link, LingotekSync::STATUS_TARGET_EDITED => $untracked_link, LingotekSync::STATUS_NON_LINGOTEK => $untracked_config_link, + LingotekSync::STATUS_ERROR => $default_link, ); $lingotek_languages = Lingotek::getLanguages(); + // Pull the desired target locales for the entity's current profile. + if (variable_get('lingotek_enable_language_specific_profiles', FALSE) && $entity_type != 'config') { + $profile = LingotekProfile::loadById($entity_profile); + $target_locales = $profile->filterTargetLocales(array_keys($locale_statuses)); + $locale_statuses = array_intersect_key($locale_statuses, $target_locales); + } + foreach ($locale_statuses as $locale => $locale_status) { // if it's lingotek enabled if (array_key_exists($locale, $lingotek_languages)) { @@ -1868,29 +2694,33 @@ function lingotek_lang_icons($entity_type, $languages, $entity_id, $locale_statu if ($locale_enabled) { // exclude language translations that are not lingotek_enabled (we may consider showing everything later) $status = strtoupper($locale_status); - $status = $disabled && strtoupper($status) != LingotekSync::STATUS_CURRENT ? LingotekSync::STATUS_UNTRACKED : $status; - // styling for the icons based on status and whether the translation is owned by Lingotek or not if ($entity_type == 'config' && (array_key_exists($entity_id, $non_lingotek_config_translations) && in_array($lang_code, explode(',', $non_lingotek_config_translations[$entity_id]->language_codes)))) { - $css_classes = 'target-non-lingotek'; - $status = 'NON_LINGOTEK'; - $tip = $languages[$lang_code]->name . ' - ' . $legend[$status]; + $css_classes = 'target-untracked'; + $status = LingotekSync::STATUS_UNTRACKED; + $tip = $legend[$status]; } else { //styling for all icons except Config/NON_LINGOTEK items $css_classes = 'target-' . strtolower($status); - $css_classes .= $disabled ? ' target-disabled' : ''; - $tip = $languages[$lang_code]->name . ' - ' . $legend[$status]; - $status = $disabled ? LingotekSync::STATUS_UNTRACKED : $status; // all disabled entities should link to view page (not workbench) + $tip = $legend[$status]; } - // link redirects to the translate interface for the config string rather than the Lingotek workbench if is an untracked config item if ($entity_type == 'config' && $status == LingotekSync::STATUS_UNTRACKED) { $current_icon = l($lang_code, $untracked_config_link . $lingotek_locale, array('attributes' => array('target' => '_blank', 'title' => $tip, 'class' => array('language-icon', $css_classes)))); } + elseif ($status == LingotekSync::STATUS_NONE) { + $none_css_classes = 'ltk-target-none'; + $none_tip = 'No Translation'; + $current_icon = '' . $lang_code . ''; + } else { $current_icon = l($lang_code, $link_map[$status] . $lingotek_locale, array('attributes' => array('target' => '_blank', 'title' => $tip, 'class' => array('language-icon', $css_classes)))); } + if ($disabled) { + $current_icon = '' . $lang_code . ''; + } + if (!is_null($source_language) && $lang_code === $source_language) { $icons['source'] = $current_icon; } @@ -1900,7 +2730,6 @@ function lingotek_lang_icons($entity_type, $languages, $entity_id, $locale_statu } } } - return $icons; } @@ -1998,9 +2827,14 @@ function lingotek_edit_nodes($entity_type, $nids) { * * Returns a fully rendered html form */ -function lingotek_change_workflow($entity_type, $nids) { - $nids = explode(',', $nids); - +function lingotek_change_workflow($entity_type, $entity_ids) { + $entity_ids = explode(',', $entity_ids); + if($entity_type === 'config'){ + foreach($entity_ids as $entity_id) { + LingotekConfigSet::getSetId($entity_id); + } + $entity_ids = LingotekSync::getSetIdsFromLids($entity_ids); + } ctools_include('node.pages', 'node', ''); ctools_include('modal'); ctools_include('ajax'); @@ -2008,7 +2842,7 @@ function lingotek_change_workflow($entity_type, $nids) { $form_state = array( 'ajax' => TRUE, 'entity_type' => $entity_type, - 'nids' => $nids, + 'nids' => $entity_ids, ); $output = ctools_modal_form_wrapper('lingotek_get_change_workflow_form', $form_state); @@ -2045,8 +2879,8 @@ function lingotek_entity_delete_form($form, &$form_state, $nids = array(), $coll $form['confirm'] = array( '#type' => 'fieldset', '#prefix' => format_plural(count($entity_ids), '', '
      ' . t('This action will apply to @count entities.') . '
      '), - '#title' => t('Are you sure you want to delete these entities?'), - '#description' => filter_xss($content_details_string), + '#title' => $entity_type == 'config' ? t('Are you sure you want to delete these translations?') : t('Are you sure you want to delete these entities?'), + '#description' => $entity_type == 'config' ? '
      '.filter_xss($content_details_string) : filter_xss($content_details_string), '#suffix' => '' . t("Note: this action cannot be undone.") . '', '#collapsible' => $collapse_fieldset, '#collapsed' => $collapse_fieldset, @@ -2068,6 +2902,49 @@ function lingotek_entity_delete_form($form, &$form_state, $nids = array(), $coll return $form; } +/** + * Form constructor for the translations delete. + */ +function lingotek_entity_delete_translations_form($form, &$form_state, $nids = array(), $collapse_fieldset = TRUE) { + if (isset($form_state['entity_ids'])) { + $entity_ids = $form_state['entity_ids']; + $collapse_fieldset = FALSE; + } + + $form = array(); + if (!is_array($entity_ids)) { + $entity_ids = array($entity_ids); + } + $entity_type = $form_state['entity_type']; + + $content_details_string = $entity_type . ' ' . t("entity ids") . ': ' . implode(", ", $entity_ids) . "
      "; + + $form['confirm'] = array( + '#type' => 'fieldset', + '#prefix' => format_plural(count($entity_ids), '', '
      ' . t('This action will apply to @count entities.') . '
      '), + '#title' => t('Are you sure you want to delete these translations?'), + '#description' => '
      '.filter_xss($content_details_string), + '#suffix' => '' . t("Note: this action cannot be undone.") . '', + '#collapsible' => $collapse_fieldset, + '#collapsed' => $collapse_fieldset, + ); + + $submit_function = 'lingotek_entity_delete_translations_form_submit'; + + $form['confirm']['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete Translations'), + '#submit' => array($submit_function), + ); + + $form['entity_ids'] = array( + '#type' => 'hidden', + '#value' => json_encode($entity_ids), + ); + + return $form; +} + /** * Submit handler for the lingotek_entity_delete form. */ @@ -2086,7 +2963,12 @@ function lingotek_entity_delete_form_submit($form, &$form_state) { } if (!empty($entity_ids)) { - entity_delete_multiple($entity_type, $entity_ids); + if ($entity_type == 'menu_link') { + lingotek_delete_menu_links($entity_ids); + } + else { + entity_delete_multiple($entity_type, $entity_ids); + } $count = count($entity_ids); watchdog('content', format_plural($count, 'Deleted 1 entity.', 'Deleted @count entities.')); drupal_set_message(format_plural($count, 'Deleted 1 entity.', 'Deleted @count entities.')); @@ -2096,6 +2978,51 @@ function lingotek_entity_delete_form_submit($form, &$form_state) { return lingotek_grid_filter_submit($form, $form_state); } +/** + * Submit handler for the lingotek_entity_delete_translations form. + */ +function lingotek_entity_delete_translations_form_submit($form, &$form_state) { + if (isset($form_state['values']['entity_ids'])) { + $drupal_entity_ids_json = json_decode($form_state['values']['entity_ids']); + } + elseif (isset($form_state['entity_ids'])) { + $drupal_entity_ids_json = json_decode($form_state['entity_ids']); + } + + $entity_type = isset($form_state['entity_type']) ? $form_state['entity_type'] : 'node'; + $entity_ids = array(); + foreach ($drupal_entity_ids_json as $id) { + $entity_ids[] = $id; + } + + if ($entity_type == 'menu_link') { + lingotek_delete_menu_link_translations($entity_ids); + } + else { + // Deletes translations based on whether it is node based translation or field based translation + if (!empty($entity_ids)) { + $entities = entity_load($entity_type, $entity_ids); + $count = 0; + foreach ($entities as $entity) { + $entity->lingotek_upload_override = FALSE; + if ($entity_type == 'node' && lingotek_uses_node_translation($entity)) { + $count += 1; + lingotek_delete_node_translations($entity->nid); + } + if ($entity->tnid == 0) { + $count += 1; + lingotek_delete_field_translations($entity_type, $entity); + } + } + } + } + + watchdog('content', format_plural($count, 'Deleted translations for 1 entity.', 'Deleted translations for @count entities.')); + drupal_set_message(format_plural($count, 'Deleted translations for 1 entity.', 'Deleted translations for @count entities.')); + $form_state['redirect'] = LINGOTEK_MENU_MAIN_BASE_URL . '/manage'; + return lingotek_grid_filter_submit($form, $form_state); +} + function lingotek_config_delete_form_submit($form, &$form_state) { if (isset($form_state['values']['entity_ids'])) { $lids = json_decode($form_state['values']['entity_ids']); @@ -2142,12 +3069,19 @@ function lingotek_entity_disassociate_form($form, $form_state, $entity_ids = arr '#collapsed' => $collapse_fieldset, ); - $form['disassociate_translations']['confirm'] = array( - '#type' => 'checkbox', - '#title' => t('Also remove all document(s) from Lingotek TMS'), - '#default_value' => FALSE, - ); - + if (variable_get('lingotek_disassociate_delete_tms', FALSE)) { + $form['disassociate_translations']['confirm'] = array( + '#type' => 'item', + '#markup' => t('Documents(s) will also be deleted from Lingotek TMS.'), + ); + } + else { + $form['disassociate_translations']['confirm'] = array( + '#type' => 'checkbox', + '#title' => t('Also remove all document(s) from Lingotek TMS'), + '#default_value' => FALSE, + ); + } $submit_function = ($entity_type === 'config') ? 'lingotek_config_disassociate_form_submit' : 'lingotek_entity_disassociate_form_submit'; $form['disassociate_translations']['submit'] = array( @@ -2168,7 +3102,6 @@ function lingotek_entity_disassociate_form($form, $form_state, $entity_ids = arr * Submit handler for the lingotek_entity_disassociate form. */ function lingotek_entity_disassociate_form_submit($form, $form_state) { - if (isset($form_state['values']['entity_ids'])) { $drupal_entity_ids_json = json_decode($form_state['values']['entity_ids']); } @@ -2184,7 +3117,8 @@ function lingotek_entity_disassociate_form_submit($form, $form_state) { $api = LingotekApi::instance(); $remove_from_tms = $form_state['values']['confirm']; - if ($remove_from_tms) { //disassociate on TMS + $global_remove_from_tms = variable_get('lingotek_disassociate_delete_tms', FALSE); + if ($remove_from_tms || $global_remove_from_tms) { //disassociate on TMS $result = lingotek_batch_disassociate_content_worker($api, $doc_ids); if (!$result) { drupal_set_message(t('Failed to remove documents from Lingotek TMS.'), 'warning', FALSE); @@ -2192,14 +3126,40 @@ function lingotek_entity_disassociate_form_submit($form, $form_state) { } } lingotek_keystore_delete_multiple($entity_type, $entity_ids, 'document_id'); - lingotek_keystore_delete_multiple($entity_type, $entity_ids, 'upload_status'); + LingotekSync::setAllUploadStatuses($entity_type, $entity_ids, LingotekSync::STATUS_NONE); lingotek_keystore_delete_multiple($entity_type, $entity_ids, 'original_language'); lingotek_keystore_delete_multiple($entity_type, $entity_ids, 'source_language_%', 'LIKE'); foreach ($entity_ids as $entity_id) { - LingotekSync::setAllTargetStatus($entity_type, $entity_id, LingotekSync::STATUS_UNTRACKED); + // find which translations exist on Drupal for the entity + $existing_translations = lingotek_get_languages($entity_type, $entity_id); + // find which targets have a status within Lingotek + $target_locales = LingotekSync::getAllTargetStatusForEntity($entity_type, $entity_id); + // for each existing translation, set the target status to untracked + foreach ($existing_translations as $language) { + $locale = Lingotek::convertDrupal2Lingotek($language); + if (isset($target_locales[$locale])) { + LingotekSync::setTargetStatus($entity_type, $entity_id, $locale, LingotekSync::STATUS_UNTRACKED); + unset($target_locales[$locale]); + } + } + // for each non-existent translation, remove the target status + foreach (array_keys($target_locales) as $locale) { + LingotekSync::deleteTargetStatus($entity_type, $entity_id, $locale); + } } - drupal_set_message(format_plural(count($entity_ids), 'Translations disassociated for one entity.', 'Translations disassociated for @count entities.')); + $disassociated_entities = '(Entity ID\'s: '; + + for ($i = 0; $i < count($entity_ids); $i++) { + if ($i == count($entity_ids) - 1) { + $disassociated_entities .= $entity_ids[$i] . ')'; + } + else { + $disassociated_entities .= $entity_ids[$i] . ', '; + } + } + + drupal_set_message(format_plural(count($entity_ids), 'Translations disassociated for one entity. ' . $disassociated_entities, 'Translations disassociated for @count entities. ' . $disassociated_entities)); } @@ -2254,21 +3214,20 @@ function lingotek_config_disassociate_form_submit($form, $form_state) { elseif (isset($form_state['lids'])) { $lids = json_decode($form_state['entity_ids']); } - $api = LingotekApi::instance(); $remove_from_tms = $form_state['values']['confirm']; + $global_remove_from_tms = variable_get('lingotek_disassociate_delete_tms', FALSE); $set_ids = LingotekSync::getSetIdsFromLids($lids); $doc_ids = LingotekSync::getConfigDocIdsFromSetIds($set_ids); - if ($remove_from_tms) { //disassociate on TMS + // Update the translation_agent_id in the locales_target table + LingotekSync::updateLingotekTranslationAgentId($lids, LingotekSync::TRANSLATION_AGENT_ID_DRUPAL); + + if (($remove_from_tms || $global_remove_from_tms) && !empty($doc_ids)) { //disassociate on TMS $result = lingotek_batch_disassociate_content_worker($api, $doc_ids); - //Deletes document data from lingotek_config_metadata table - $query1 = db_delete('lingotek_config_metadata') - ->condition('id', $set_ids) - ->execute(); - $query2 = db_delete('lingotek_config_map') - ->condition('set_id', $set_ids) - ->execute(); + //Deletes document data from lingotek_config_metadata table and lingotek_config_metadata table + LingotekConfigSet::deleteConfigSetMetadataBySetId($set_ids); + LingotekConfigSet::deleteConfigSetMapDataBySetId($set_ids); if (!$result) { drupal_set_message(t('Failed to remove documents from Lingotek TMS.'), 'warning', FALSE); @@ -2279,4 +3238,19 @@ function lingotek_config_disassociate_form_submit($form, $form_state) { if (!empty($lids)) { LingotekConfigSet::disassociateSegments($lids); } + + LingotekConfigSet::removeEmptyConfigSets($set_ids); + $disassociated_config = '(Config ID\'s: '; + + for ($i = 0; $i < count($lids); $i++) { + if ($i == count($lids) - 1) { + $disassociated_config .= $lids[$i] . ')'; + } + else { + $disassociated_config .= $lids[$i] . ', '; + } + } + + drupal_set_message(format_plural(count($lids), 'Translations disassociated for one config item. ' . $disassociated_config, 'Translations disassociated for @count config items. ' . $disassociated_config)); + } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.config.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.config.inc index 774c101b..c4d567d3 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.config.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.config.inc @@ -7,13 +7,14 @@ // All the stuff for the new config menu goes in here function lingotek_config_filter_inline_submit($form, $form_state) { - unset($_SESSION['grid_filters']['textgroup']); - $_SESSION['grid_filters']['textgroup'] = $form_state['values']['search_type']; + $entity_type = $_SESSION['grid_entity_type']; + unset($_SESSION['grid_filters'][$entity_type]['textgroup']); + $_SESSION['grid_filters'][$entity_type]['textgroup'] = $form_state['values']['search_type']; if (!empty($form_state['values']['search'])) { - $_SESSION['grid_filters']['search'] = $form_state['values']['search']; + $_SESSION['grid_filters'][$entity_type]['search'] = $form_state['values']['search']; } else { - unset($_SESSION['grid_filters']['search']); + unset($_SESSION['grid_filters'][$entity_type]['search']); } if (isset($form_state['values']['limit_select'])) { @@ -23,7 +24,9 @@ function lingotek_config_filter_inline_submit($form, $form_state) { function lingotek_config_header() { $header = array(// Define the tentative source header + 'marked' => array('data' => 'Marked'), 'lid' => array('data' => t('ID'), 'field' => 'lid', 'width' => '2%'), + 'config_set_id' => array('data' => t('Config Set ID'), 'field' => 'config_set_id', 'width' => '9%'), 'set_name' => array('data' => t('Config Set Name'), 'field' => 'set_id', 'width' => '15%'), 'source' => array('data' => t('Source'), 'field' => 'source', 'width' => '25%'), 'source_uploaded' => array('data' => t('Source Uploaded')), @@ -32,11 +35,31 @@ function lingotek_config_header() { 'context' => array('data' => t('Context'), 'field' => 'context'), 'doc_id' => array('data' => t('Doc ID'), 'field' => 'document_id', 'display' => 'none'), 'textgroup' => array('data' => t('Textgroup'), 'field' => 'textgroup'), + 'workflow' => array('data' => t('Workflow'), 'field' => 'workflow_id'), ); return $header; } +function lingotek_row_status_query() { + $query1 = lingotek_config_start_query(null, 'not taxonomy'); + lingotek_config_add_query_filters($query1); + $query2 = lingotek_config_start_query(null, 'taxonomy name'); + lingotek_config_add_query_filters($query2); + $query3 = lingotek_config_start_query(null, 'taxonomy desc'); + lingotek_config_add_query_filters($query3); + $query1->union($query2, 'UNION'); + $query1->union($query3, 'UNION'); + $query = db_select($query1, 'query_union')->fields('query_union'); + $limit = isset($_SESSION['limit_select']) ? $_SESSION['limit_select'] : 10; + $table_data_raw = $query + ->extend('PagerDefault') + ->extend('TableSort') + ->limit($limit) + ->execute() + ->fetchAllAssoc('lid'); + return lingotek_bulk_grid_parse_config_data($table_data_raw, null, TRUE); +} function lingotek_config_get_rows($entity_type, $form, &$form_state, $count_only = FALSE) { $columns = isset($form_state['values']['columns']) ? $form_state['values']['columns'] : array(); $header = lingotek_config_header(); @@ -64,12 +87,44 @@ function lingotek_config_get_rows($entity_type, $form, &$form_state, $count_only ->orderByHeader($form_state['values']['grid_header']) ->execute() ->fetchAllAssoc('lid'); - $table_data = lingotek_bulk_grid_parse_config_data($table_data_raw, $entity_type); - + if(isset($_SESSION['grid_filters'][$entity_type])){ + $all_filtered_results = $query + ->extend('PagerDefault') + ->limit(500000) + ->execute(); + $all_filtered_lids = array(); + foreach($all_filtered_results as $filtered_result){ + $all_filtered_lids[$filtered_result->lid] = $filtered_result->lid; + } + $_SESSION['grid_filters'][$entity_type]['filtered_config_lids'] = $all_filtered_lids; + } return $table_data; } +function lingotek_config_get_status($lids){ + $metadata_query = db_select('lingotek_config_map', 'lcmap'); + $metadata_query->join('lingotek_config_metadata', 'lcmeta', 'lcmeta.id = lcmap.set_id'); + $metadata_query->condition('lcmeta.config_key','%target_sync_status%', 'LIKE'); + $metadata_query->condition('lcmap.lid', $lids, 'IN'); + $metadata_query->addField('lcmap', 'lid'); + $metadata_query->addField('lcmeta', 'config_key'); + $metadata_query->addField('lcmeta', 'value'); + $metadata_results = $metadata_query->execute(); + + $locales_target_query = db_select('locales_target', 'lt'); + $locales_target_query->condition('lt.i18n_status','1'); + $locales_target_query->condition('lt.lid', $lids, 'IN'); + $locales_target_query->addField('lt', 'lid'); + $locales_target_query->addField('lt', 'i18n_status'); + $locales_target_query->addField('lt', 'language'); + $lt_results = $locales_target_query->execute(); + $lt_array = array(); + foreach($lt_results as $lt_result){ + $lt_array[$lt_result->lid][$lt_result->language] = $lt_result; + } + return array('metadata' => $metadata_results, 'dirty_status' => $lt_array); +} function lingotek_config_add_existing_translations($lids, $lang_codes) { $query = db_select('locales_target', 'lt') ->fields('lt', array('lid', 'language')); @@ -91,26 +146,42 @@ function lingotek_config_start_query($form_state, $union_part_desc) { $query = db_select('locales_source', 's'); // Gets sync statuses for each language. - $query->addExpression("(SELECT COUNT(*) FROM {locales_target} target LEFT JOIN {lingotek_config_map} map ON map.lid = target.lid WHERE map.lid = s.lid AND map.current = 1 AND target.i18n_status = 0)", 'current_target_count'); + $query->addExpression("(SELECT COUNT(*) FROM {locales_target} WHERE i18n_status = 0 AND lid = (SELECT lid FROM {lingotek_config_map} WHERE lid = s.lid AND current = 1))", 'current_target_count'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='PENDING')", 'pending'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='READY')", 'ready'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='CURRENT')", 'current'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='UNTRACKED')", 'ready_interim'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='UNTRACKED')", 'interim'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='EDITED')", 'edited'); $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='UNTRACKED')", 'untracked'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='ERROR')", 'error'); + $query->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(config_key, 20, 10)) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key LIKE 'target_sync_status_%' AND value='NONE')", 'none'); $query->addExpression("(SELECT GROUP_CONCAT(language) FROM {locales_target} WHERE lid = s.lid AND i18n_status = 1)", 'source_edited'); $query->addExpression("(SELECT set_id FROM {lingotek_config_map} lcm WHERE lcm.lid = s.lid)", 'set_id'); + $query->addExpression("(SELECT value FROM {lingotek_config_metadata} lcm WHERE lcm.id = (SELECT set_id FROM lingotek_config_map WHERE lid = s.lid) AND config_key = 'upload_error')", 'upload_error'); + $query->addExpression("(SELECT value FROM {lingotek_config_metadata} lcm WHERE lcm.value = s.lid AND config_key = 'marked')", 'marked'); + $query->addExpression("(SELECT set_id FROM {lingotek_config_map} lcm WHERE lcm.lid = s.lid)", 'config_set_id'); // Lingotek Document ID - $query->leftJoin('lingotek_config_metadata', 'lingo_document_id', 'lingo_document_id.id = (SELECT set_id FROM lingotek_config_map WHERE lid = s.lid) AND lingo_document_id.config_key = \'document_id\''); - $query->addField('lingo_document_id', 'value', 'document_id'); - + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key = 'document_id')", 'document_id'); + //Config Workflow ID + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key = 'workflow_id')", 'workflow_id'); // Entity Upload Status - $query->leftJoin('lingotek_config_metadata', 'lingo_upload_status', 'lingo_upload_status.id = (SELECT set_id FROM lingotek_config_map WHERE lid = s.lid) AND lingo_upload_status.config_key = \'upload_status\' AND lingo_upload_status.value <> \'' . LingotekSync::STATUS_TARGET . '\''); - $query->addField('lingo_upload_status', 'value', 'upload_status'); + $query->addExpression("(SELECT GROUP_CONCAT(value) FROM {lingotek_config_metadata} WHERE id = (SELECT set_id FROM {lingotek_config_map} WHERE lid = s.lid) AND config_key = 'upload_status' AND value <> '" . LingotekSync::STATUS_TARGET . "' AND value <> '" . LingotekSync::STATUS_DELETED . "')", 'upload_status'); $query->fields('s', array('source', 'location', 'context', 'lid', 'textgroup')); // Only show items in textgroups that are selected for translation on Config Settings page. $translatable_textgroups = LingotekConfigSet::getTextgroupsForTranslation() ? : array('no textgroups selected'); - $query->condition('s.textgroup', $translatable_textgroups, 'IN'); + + if (in_array('misc', $translatable_textgroups)) { + $all_textgroup_categories = array('blocks', 'default', 'menu', 'taxonomy', 'views', 'field'); + $qor = db_or(); + $qor->condition('s.textgroup', $translatable_textgroups, 'IN'); + $qor->condition('s.textgroup', $all_textgroup_categories, 'NOT IN'); + $query->condition($qor); + } + else { + $query->condition('s.textgroup', $translatable_textgroups, 'IN'); + } // Hide taxonomy terms that use translate for i18n_mode. $translate_terms_subquery = db_select('taxonomy_vocabulary', 'tv'); @@ -119,21 +190,21 @@ function lingotek_config_start_query($form_state, $union_part_desc) { $translate_ids = $translate_terms_subquery->execute()->fetchCol(); // Hide taxonomy terms that have custom fields. - $custom_fields_subquery = db_select('taxonomy_vocabulary', 'tv'); - $custom_fields_subquery->fields('tv', array('vid')); - $custom_fields_subquery->join('field_config_instance', 'fci', 'tv.machine_name = fci.bundle'); + $advanced_vids = array_keys(lingotek_get_advanced_vocabularies()); if ($union_part_desc == 'taxonomy name') { - $query->leftJoin('taxonomy_term_data', 'term_name', 'term_name.name = s.source AND term_name.vid NOT IN (' . implode(',', array_merge(array(-1), $translate_ids)) . ') AND term_name.vid NOT IN (' . $custom_fields_subquery . ')'); - $query->addField('term_name', 'tid', 'tid_from_name'); + $advanced_vid_expression = empty($advanced_vids) ? '' : ' AND term_name.vid NOT IN (' . implode(',', $advanced_vids) . ')'; + $query->leftJoin('taxonomy_term_data', 'term_name', 'SUBSTRING_INDEX(SUBSTRING_INDEX(s.context , \':\', 2 ),\':\',-1) = term_name.tid AND term_name.name = s.source AND term_name.vid NOT IN (' . implode(',', array_merge(array(-1), $translate_ids)) . ')' . $advanced_vid_expression); + $query->addExpression('term_name.tid', 'tid_from_name');//DO NOT do addField here, it puts the column name in the wrong place and messes with the union } else { $query->addExpression('NULL', 'tid_from_name'); } if ($union_part_desc == 'taxonomy desc') { - $query->leftJoin('taxonomy_term_data', 'term_description', 'term_description.description = s.source AND term_description.vid NOT IN (' . implode(',', array_merge(array(-1), $translate_ids)) . ') AND term_description.vid NOT IN (' . $custom_fields_subquery . ')'); - $query->addField('term_description', 'tid', 'tid_from_description'); + $advanced_vid_expression = empty($advanced_vids) ? '' : ' AND term_description.vid NOT IN (' . implode(',', $advanced_vids) . ')'; + $query->leftJoin('taxonomy_term_data', 'term_description', 'SUBSTRING_INDEX(SUBSTRING_INDEX(s.context , \':\', 2 ),\':\',-1) = term_description.tid AND term_description.description = s.source AND term_description.vid NOT IN (' . implode(',', array_merge(array(-1), $translate_ids)) . ')' . $advanced_vid_expression); + $query->addExpression('term_description.tid', 'tid_from_description');//DO NOT do addField here, it puts the column name in the wrong place and messes with the union } else { $query->addExpression('NULL', 'tid_from_description'); @@ -154,10 +225,11 @@ function lingotek_config_start_query($form_state, $union_part_desc) { } function lingotek_config_add_query_filters($query) { - if (!isset($_SESSION['grid_filters'])) { + $entity_type = $_SESSION['grid_entity_type']; + if (!isset($_SESSION['grid_filters'][$entity_type])) { return; } - $filters = $_SESSION['grid_filters']; + $filters = $_SESSION['grid_filters'][$entity_type]; if (isset($filters['search']) && strlen($filters['search'])) { $query->where("LOWER(CONVERT(source USING utf8)) LIKE :source_string", array(':source_string' => '%' . strtolower($filters['search'] . '%'))); } @@ -181,8 +253,11 @@ function lingotek_config_add_query_filters($query) { elseif ($filters['textgroup'] == 'views') { $query->condition('textgroup', 'views'); } + elseif ($filters['textgroup'] == 'webform') { + $query->condition('textgroup', 'webform'); + } elseif ($filters['textgroup'] == 'misc') { - $query->condition('textgroup', 'misc'); + $query->condition('textgroup', array('default', 'menu', 'taxonomy', 'blocks', 'field', 'views'), 'NOT IN'); } } @@ -195,25 +270,56 @@ function lingotek_config_add_query_filters($query) { // Upload Status if (isset($filters['upload_status']) && $filters['upload_status'] != 'all') { - $query->join('locales_target', 'lt', 'lt.lid = s.lid'); + $query->leftJoin('locales_target', 'lt', 'lt.lid = s.lid'); if ($filters['upload_status'] == 'never') { - $query->isNull('lingo_upload_status.value'); + $query->isNull('lt.lid'); } elseif ($filters['upload_status'] == LingotekSync::STATUS_EDITED) { // The edited status won't show in lingotek_config_metadata table so we need to check the i18n_status (1 == edited). $query->condition('lt.i18n_status', '1'); } else { - $query->condition('lingo_upload_status.value', $filters['upload_status']); + $query->havingCondition('upload_status', $filters['upload_status']); // Do not include items with a source marked as edited in locales_target (1 == edited). $query->condition('lt.i18n_status', '0'); } } + // Marked status + if (isset($filters['marked_status']) && $filters['marked_status'] != 'all') { + // Marked + if($filters['marked_status'] == 'marked') { + $marked_query = db_select('lingotek_config_metadata','lcm'); + $marked_query->distinct(); + $marked_query->fields('lcm',array('value')); + $marked_query->condition('config_key', 'marked'); + + $query->condition('s.lid', $marked_query, 'IN'); + } + // Unmarked + else if($filters['marked_status'] == 'unmarked'){ + $marked_query = db_select('lingotek_config_metadata','lcm'); + $marked_query->distinct(); + $marked_query->fields('lcm',array('value')); + $marked_query->condition('config_key', 'marked'); + + $query->condition('s.lid', $marked_query, 'NOT IN'); + } + } + // Entity ID if (isset($filters['lid']) && $filters['lid'] != '') { $query->condition('s.lid', $filters['lid']); } + + // Config set ID + if (isset($filters['config_set_id']) && $filters['config_set_id'] != '') { + $config_set_query = db_select('lingotek_config_map', 'lcm') + ->fields('lcm', array('lid')) + ->condition('set_id', $filters['config_set_id']); + + $query->condition('s.lid', $config_set_query, 'IN'); + } } /** @@ -232,21 +338,30 @@ function lingotek_get_locked_config_translations($lids = NULL) { return $query->execute()->fetchAllAssoc('lid'); } -function lingotek_bulk_grid_parse_config_data($table_data_raw, $entity_type) { +function lingotek_bulk_grid_parse_config_data($table_data_raw, $entity_type, $status_query = FALSE) { $lingotek_languages = Lingotek::getLanguages(); $lids = array_keys($table_data_raw); $existing_translations = !empty($lids) ? lingotek_config_add_existing_translations($lids, $lingotek_languages) : array(); - $languages = language_list(); $global_profile = lingotek_get_global_profile(); - $non_lingotek_config_translations = lingotek_get_locked_config_translations($lids); + $row_status = array(); $table_data = array(); + if($status_query === FALSE) { + $non_lingotek_config_translations = lingotek_get_locked_config_translations($lids); + $api = LingotekApi::instance(); + $workflows = $api->listWorkflows(); + $languages = language_list(); + } + + $i18n_language = variable_get('i18n_string_source_language', language_default()->language); + foreach ($table_data_raw as $row) { $lid = $row->{'lid'}; + $config_set_id = isset($row->{'config_set_id'}) ? $row->{'config_set_id'} : 'N/A'; $row->lingotek = $global_profile; $row->lingotek['profile'] = 1; - $row->language = 'en'; + $row->language = $i18n_language; $locale_statuses = lingotek_build_locales_statuses($row, FALSE); $item_is_current = LingotekConfigSet::isLidCurrent($lid); @@ -277,18 +392,38 @@ function lingotek_bulk_grid_parse_config_data($table_data_raw, $entity_type) { $locale_statuses[$existing_locale] = 'untracked'; } } + if($status_query === TRUE) { + $row_status[$lid] = $locale_statuses; + if (isset($row->status)) { + $row_status[$lid]['source_status'] = $row->upload_status; + } + continue; + } $escaped_source = htmlentities($row->{'source'}); $source_text = lingotek_truncate_grid_text($escaped_source, 55); - $translation_icons = lingotek_lang_icons($entity_type, $languages, $lid, $locale_statuses, FALSE, $row->language, FALSE, FALSE, $non_lingotek_config_translations); // show translation statuses + $translation_icons = lingotek_lang_icons($entity_type, $languages, $lid, NULL, $locale_statuses, FALSE, $row->language, FALSE, FALSE, $non_lingotek_config_translations); // show translation statuses $target_icons_str = implode('', array_values($translation_icons)); - $icon = lingotek_source_uploaded_icon($row); - $source_uploaded = lingotek_render_source($entity_type, $row, $icon, $languages, 'language'); + $source_uploaded = lingotek_render_source($entity_type, $row, $languages, 'language'); $set_id = LingotekConfigSet::getSetId($lid, FALSE); + $marked_html = lingotek_render_marked_html($row, 'config', $lid); $set_name = $set_id !== FALSE ? LingotekConfigSet::getTitleBySetId($set_id) : t('N/A'); + $config_profile = LingotekProfile::loadById(LingotekSync::PROFILE_CONFIG); + $stored_workflow_id = $config_profile->getWorkflow(); + if(isset($row->{'workflow_id'}) && array_key_exists($row->{'workflow_id'}, $workflows)) { + $workflow = $workflows[$row->{'workflow_id'}]; + } + else if(array_key_exists($stored_workflow_id, $workflows)) { + $workflow = $workflows[$stored_workflow_id]; + } + else { + $workflow = ''; + } $data = array( + 'marked' => $marked_html, + 'config_set_id' => $config_set_id, 'lid' => $lid ? : t('??'), 'source' => '' . $source_text . '' . $escaped_source . '', 'source_uploaded' => $source_uploaded, @@ -297,11 +432,15 @@ function lingotek_bulk_grid_parse_config_data($table_data_raw, $entity_type) { 'textgroup' => $row->{'textgroup'} == 'default' ? 'built-in interface' : $row->{'textgroup'}, 'translations' => $target_icons_str, 'doc_id' => $row->document_id ? $row->document_id : t('N/A'), - 'set_name' => '' . $set_name . '' + 'set_name' => '' . $set_name . '', + 'workflow' => $workflow ); $table_data[$lid] = $data; } + if($status_query === TRUE) { + return $row_status; + } return $table_data; } @@ -313,15 +452,15 @@ function lingotek_bulk_grid_parse_config_data($table_data_raw, $entity_type) { function lingotek_config_action_submit($form, $form_state) { $lids = array(); - if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == 'actions_submit') { // If submitting an action + if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == 'submit_actions') { // If submitting an action foreach ($form_state['values']['the_grid'] as $value) { if ($value != 0) { $lids[] = $value; } } - if (isset($form_state['values']['actions_select'])) { // If an action was selected (which it would be, I don't know if this could ever NOT occur with normal use) - $action = $form_state['values']['actions_select']; // Get the action + if (isset($form_state['values']['select_actions'])) { // If an action was selected (which it would be, I don't know if this could ever NOT occur with normal use) + $action = $form_state['values']['select_actions']; // Get the action if (count($lids) <= 0) { // Select a node drupal_set_message(t('You must select at least one node to @action.', array('@action' => $action)), 'warning'); // Or pay the price } @@ -337,34 +476,81 @@ function lingotek_config_action_submit($form, $form_state) { elseif ($action == 'delete' || $action == 'reset') { // ajax ctools modal employed (see lingotek_bulk_grid_form() and lingotek.bulk_grid.js) } + elseif ($action == 'marked') { + lingotek_config_mark_items($lids); + } + elseif ($action == 'unmarked') { + lingotek_config_unmark_items($lids); + } } } } -function lingotek_config_upload_selected($lids) { - $set_ids = array(); - // You need to iterate through this (with multiple db queries) because this function will also assign the item to a new set and create a new set if necessary. With one query to get all set_ids, it might not do that. - foreach ($lids as $lid) { - $set_id = LingotekConfigSet::getSetId($lid); - $set_ids[] = $set_id; - } - $unique_set_ids = array_unique($set_ids); +function lingotek_config_mark_items($lids) { + $batch = array( + 'title' => t('Updating Marked Config Items'), + 'finished' => 'lingotek_update_marked_items_finished' + ); - $edited_lids = !empty($unique_set_ids) ? LingotekConfigSet::getEditedLidsInSets($unique_set_ids) : array(); - $not_current_lids = array_merge($lids, $edited_lids); + $operations = lingotek_get_marked_batch_elements('config', $lids); + $batch['operations'] = $operations; + $redirect = current_path(); - if (empty($not_current_lids)) { + batch_set($batch); + batch_process($redirect); +} + +function lingotek_config_unmark_items($lids) { + $batch = array( + 'title' => t('Updating Unmarked Config Items'), + 'finished' => 'lingotek_update_unmarked_items_finished' + ); + + $operations = lingotek_get_unmarked_batch_elements('config', $lids); + $batch['operations'] = $operations; + $redirect = current_path(); + + batch_set($batch); + batch_process($redirect); +} + +function lingotek_config_upload_selected($lid_map) { + //allows for passing in an array of lids, not as efficient as passing a textgroup-lid map + //in some cases + if(is_array($lid_map) && isset($lid_map[0]) && !is_array($lid_map[0])){ + $lids = $lid_map; + $query = db_select('locales_source', 'ls'); + $query->addField('ls', 'textgroup'); + $query->addField('ls', 'lid'); + $query->condition('lid', $lids, 'IN'); + $results = $query->execute(); + $lid_map = array(); + foreach($results as $result){ + $lid_map[$result->textgroup][$result->lid] = $result->lid; + } + } + $unique_set_ids = LingotekConfigSet::bulkGetSetId($lid_map); + $not_current_lids = !empty($unique_set_ids) ? LingotekConfigSet::getEditedLidsInSets($unique_set_ids) : array(); + + if (empty($not_current_lids) && empty($lid_map)) { drupal_set_message(t('No config items have been edited and need to be uploaded at this time. To translate a config item for the first time, check the corresponding box and upload.'), 'status', FALSE); drupal_goto('admin/settings/lingotek/manage/config'); } - LingotekConfigSet::markLidsNotCurrent($not_current_lids); + foreach($lid_map as $textgroup) { + LingotekConfigSet::markLidsNotCurrent($textgroup); + } + if(!empty($not_current_lids)) { + LingotekConfigSet::markLidsNotCurrent($not_current_lids); + } + + // Update the translation_agent_id in the locales_target table to be owned by Lingotek + LingotekSync::updateLingotekTranslationAgentId($lid_map, LingotekSync::TRANSLATION_AGENT_ID_LINGOTEK); $batch = array( 'title' => t('Uploading Content To Lingotek'), 'finished' => 'lingotek_sync_upload_config_finished' ); - $operations = lingotek_get_sync_upload_config_batch_elements($unique_set_ids); $batch['operations'] = $operations; $redirect = 'admin/settings/lingotek/manage/config'; @@ -373,8 +559,8 @@ function lingotek_config_upload_selected($lids) { batch_process($redirect); // Run batch operations to upload all of the selected nodes to Lingotek } -function lingotek_config_download_selected($action, $lids) { - $set_ids = array_unique(LingotekSync::getSetIdsFromLids($lids)); +function lingotek_config_download_selected($action, $ids, $set_ids_passed = false) { + $set_ids = $set_ids_passed ? $ids : array_unique(LingotekSync::getSetIdsFromLids($ids)); if (empty($set_ids)) { drupal_set_message(t('No translations to download at this time. Make sure configuration items have been uploaded to Lingotek before trying to get translations for them.'), 'status', FALSE); drupal_goto('admin/settings/lingotek/manage/config'); diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.dashboard.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.dashboard.inc old mode 100644 new mode 100755 index e56612f7..d40e03a2 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.dashboard.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.dashboard.inc @@ -35,7 +35,7 @@ function lingotek_dashboard() { ); } else { // Show the standard misconfiguration message. - drupal_set_message(t('The Lingotek Translation module is not fully configured. To complete setup, run the') . ' ' . l(t('Lingotek Configuration Wizard'), 'admin/config/lingotek/setup'), 'warning'); + drupal_set_message(t('The Lingotek Translation module is not fully configured. To complete setup, run the') . ' ' . l(t('Lingotek Configuration Wizard'), LINGOTEK_MENU_LANG_BASE_URL . '/setup'), 'warning'); } $output[] = lingotek_support_footer(); return $output; @@ -140,11 +140,15 @@ function lingotek_dashboard_command_ajax() { switch ($method) { case 'POST': // Add/Insert Target Language $message = 'POST: Insert a new Target Language'; - $lingotek_locale = $_POST['code']; - $parameters = $_POST; + $lingotek_locale = check_plain($_POST['code']); + $parameters = array(); + foreach($_POST as $key => $value){ + $parameters[$key] = check_plain($value); + } if (strlen($lingotek_locale) > 1) { // Adds the language locale to the local list of languages and to the Lingotek project (via the API). - $add = lingotek_add_target_language($lingotek_locale); + $include_all_docs_in_project = variable_get('lingotek_enable_language_specific_profiles', FALSE) ? FALSE : TRUE; + $add = lingotek_add_target_language($lingotek_locale, $include_all_docs_in_project); $response = lingotek_get_language_details($lingotek_locale); $response['tms_added'] = $add; } @@ -170,8 +174,13 @@ function lingotek_dashboard_command_ajax() { default: // Retrieve the Target Languages and Status $message = 'GET: retrieve the details of the locale(s)'; + $parameters = $_GET; - $response = isset($_GET['code']) ? lingotek_get_language_details($_GET['code']) : lingotek_get_language_details(); + $temp = array(); + foreach ($_GET as $key => $value){ + $temp[$key] = check_plain($value); + } + $response = isset($temp['code']) ? lingotek_get_language_details($temp['code']) : lingotek_get_language_details(); break; } //lingotek_json_output_cors(array('response' => $response, 'message' => $message, 'data' => $data, 'method' => $method )); diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.define.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.define.inc index 914c661e..1c2cf7b1 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.define.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.define.inc @@ -33,7 +33,7 @@ if ($use_local_override) { if (variable_get('lingotek_use_stage_servers', FALSE)) { // Stage Servers lingotek_define('LINGOTEK_GMC_SERVER', 'https://gmc.lingotek.com/cms'); - lingotek_define('LINGOTEK_API_SERVER', 'http://cms.lingotek.com'); + lingotek_define('LINGOTEK_API_SERVER', 'https://cms.lingotek.com'); lingotek_define('LINGOTEK_AP_OAUTH_KEY', 'd944c2ae-b66e-4322-b37e-40ba0a495eb7'); lingotek_define('LINGOTEK_AP_OAUTH_SECRET', 'e4ae98ca-835b-4d9f-8faf-116ce9c69424'); } @@ -51,7 +51,7 @@ lingotek_define('LINGOTEK_DEV', variable_get('lingotek_dev', FALSE)); * Defines the path URLs */ define('LINGOTEK_MENU_LANG_BASE_URL', 'admin/config/regional/lingotek'); -define('LINGOTEK_MENU_MAIN_BASE_URL', 'admin/settings/lingotek'); //admin/config/lingotek +define('LINGOTEK_MENU_MAIN_BASE_URL', 'admin/settings/lingotek'); define('LINGOTEK_NOTIFY_URL', 'lingotek/notify'); // notification callback URL /** diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.drush.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.drush.inc new file mode 100644 index 00000000..c9f1525d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.drush.inc @@ -0,0 +1,262 @@ + 'push', + 'description' => dt('Push all the instances of an entity up to Lingotek TMS'), + 'arguments' => array( + 'entity' => 'The entity type to be pushed (config, node, etc.)', + ), + 'required-arguments' => TRUE, + 'options' => array( + 'bundles' => 'Comma delimited list of bundles (e.g. article, blog)', + ), + 'examples' => array( + 'drush lt-push config' => 'Upload all checked config to the TMS', + 'drush lt-push node --bundles=article,blog' => 'upload nodes of type article and blog to the TMS', + ), + ); + $items['lt-cleanup'] = array( + 'command-hook' => 'cleanup', + 'description' => dt('Run the Lingotek field language cleanup utility'), + 'arguments' => array( + 'entity' => 'The entity type to process (taxonomy_term, node, etc.)', + ), + 'required-arguments' => TRUE, + 'examples' => array( + 'drush lt-cleanup node' => 'Cleanup language field settings on nodes', + ), + ); + $items['lt-prepare-blocks'] = array( + 'command-hook' => 'prepare_blocks', + 'callback arguments' => array( + array( + 'blocks' => 'blocks', + ), + 'lingotek_translate_config_blocks', + ), + 'description' => dt('Prepare blocks for Lingotek translation.'), + 'examples' => array( + 'drush lt-prepare-blocks' => '', + ), + ); + $items['lt-prepare-taxonomies'] = array( + 'command-hook' => 'prepare_vocab', + 'callback arguments' => array( + array( + 'taxonomy' => 'taxonomy', + ), + 'lingotek_translate_config_taxonomies', + ), + 'description' => dt('Prepare vocabularies for Lingotek translation.'), + 'examples' => array( + 'drush lt-prepare-taxonomies' => '', + ), + ); + $items['lt-prepare-menus'] = array( + 'command-hook' => 'prepare_menus', + 'callback arguments' => array( + array( + 'menu' => 'menu', + ), + 'lingotek_translate_config_menus', + ), + 'description' => dt('Prepare menus for Lingotek translation.'), + 'examples' => array( + 'drush lt-prepare-menus' => '', + ), + ); + // Commented out: error reported /home/{user}/.composer/vendor/drush/drush/commands/core/drupal/batch.inc on line 45 and 137 + // $items['lt-prepare-builtins'] = array( + // 'command-hook' => 'prepare_builtins', + // 'description' => dt('Prepare built-in strings for Lingotek translation.'), + // 'examples' => array( + // 'drush lt-prepare-builtins' => '', + // ), + // ); + + return $items; +} + +/** + * Callback function for drush command batch upload translatable content + */ +function drush_lingotek_push($entity_type) { + try { + $operations = array(); + if ($entity_type == 'config') { + // adapted from lingotek_config_upload_selected() in lingotek.confg.inc + $query = db_select('locales_source', 'ls'); + $query->addField('ls', 'textgroup'); + $query->addField('ls', 'lid'); + $results = $query->execute(); + $lid_map = array(); + foreach ($results as $result) { + $lid_map[$result->textgroup][$result->lid] = $result->lid; + } + $unique_set_ids = LingotekConfigSet::bulkGetSetId($lid_map); + foreach ($lid_map as $textgroup) { + LingotekConfigSet::markLidsNotCurrent($textgroup); + } + if (!empty($not_current_lids)) { + LingotekConfigSet::markLidsNotCurrent($not_current_lids); + } + $operations = lingotek_get_sync_upload_config_batch_elements($unique_set_ids); + } + else { + $query = new EntityFieldQuery(); + $entity_ids = array(); + $query->entityCondition('entity_type', $entity_type); + if ($bundles = drush_get_option('bundles')) { + $list = explode(',', $bundles); + $query->entityCondition('bundle', $list, 'IN'); + } + $result = $query->execute(); + if (is_array($result[$entity_type])) { + $entity_ids = array_keys($result[$entity_type]); + } + if (!empty($entity_ids)) { + foreach ($entity_ids as $id) { + $operations[] = array( + 'lingotek_entity_upload', + array($id, $entity_type) + ); + } + } + else { + throw new Exception(dt('No entities of @type were found in the database', array('@type' => $entity_type))); + } + } + $batch = array( + 'operations' => $operations, + 'finished' => 'lingotek_drush_push_complete', + 'title' => dt('Upload entities to Lingotek TMS'), + 'init_message' => dt('Beginning batch uploads of @type to Lingotek', array('@type' => $entity_type)), + ); + batch_set($batch); + drush_backend_batch_process(); + } catch (Exception $e) { + drush_set_error('lt-push', $e->getMessage()); + } +} + +/** + * Callback function for drush command lt-cleanup + */ +function drush_lingotek_cleanup($entity_type) { + try { + $batch = lingotek_field_language_data_cleanup_batch_create($entity_type, FALSE); + batch_set($batch); + drush_backend_batch_process(); + } catch (Exception $e) { + drush_set_error('lt-cleanup', $e->getMessage()); + } +} + +/** + * Implements callback_batch_finished() + * + */ +function lingotek_drush_push_complete($success, $results, $operations) { + if ($success) { + drush_log(dt('Push to Lingotek TMS is complete.'), 'ok'); + } +} + +/** + * Callback function for drush command lt-prepare-blocks + */ +function drush_lingotek_prepare_blocks($config_groups, $variable) { + try { + lingotek_admin_prepare_blocks(); + drush_lingotek_prepare_item($config_groups, $variable); + } catch (Exception $e) { + drush_set_error('lt-prepare-blocks', $e->getMessage()); + } +} + +/** + * Callback function for drush command lt-prepare-taxonomies + */ +function drush_lingotek_prepare_vocab($config_groups, $variable) { + try { + lingotek_admin_prepare_taxonomies(); + drush_lingotek_prepare_item($config_groups, $variable); + } catch (Exception $e) { + drush_set_error('lt-prepare-taxonomies', $e->getMessage()); + } +} + +/** + * Callback function for drush command lt-prepare-menus + */ +function drush_lingotek_prepare_menus($config_groups, $variable) { + try { + lingotek_admin_prepare_menus(); + drush_lingotek_prepare_item($config_groups, $variable); + } catch (Exception $e) { + drush_set_error('lt-prepare-menus', $e->getMessage()); + } +} + +// Commented out: error reported /home/{user}/.composer/vendor/drush/drush/commands/core/drupal/batch.inc on line 45 and 137 +/** + * Callback function for drush command lt-prepare-builtins + */ +// function drush_lingotek_prepare_builtins() { +// try { +// lingotek_admin_prepare_builtins($additional_operations); +// drush_backend_batch_process(); +// } catch (Exception $e) { +// drush_set_error('lt-prepare-builtins', $e->getMessage()); +// } +// } + +/** + * @param $config_groups + * array: of the form ('menu' => 'menu) + * @param $variable + * name of a drupal variable to set. + */ +function drush_lingotek_prepare_item($config_groups, $variable) { + try { + // refresh all strings for this config type + // combine string refresh operations with other additional operations + $config_refresh_batch = i18n_string_refresh_batch($config_groups, $delete = FALSE); + lingotek_admin_setup_nonbuiltins_batch($config_refresh_batch['operations']); + drush_backend_batch_process(); + variable_set($variable, 1); + } catch (Exception $e) { + throw $e; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.info b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.info index 35e58ec6..a24ece69 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.info +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.info @@ -3,11 +3,10 @@ description = "Extends Drupal's multilingual features by integrating with the Li package = Multilingual core = "7.x" php = "5.3" -configure = admin/config/lingotek/setup +configure = admin/config/regional/lingotek/setup dependencies[] = locale -dependencies[] = field_ui dependencies[] = title dependencies[] = block dependencies[] = menu @@ -35,6 +34,9 @@ files[] = lib/Drupal/lingotek/LingotekEntity.php files[] = lib/Drupal/lingotek/LingotekPhase.php files[] = lib/Drupal/lingotek/LingotekSync.php files[] = lib/Drupal/lingotek/LingotekTranslatableEntity.php +files[] = lib/Drupal/lingotek/LingotekXMLElement.php +files[] = lib/Drupal/lingotek/LingotekException.php +files[] = lib/Drupal/lingotek/LingotekProfile.php files[] = tests/lingotek.base.test files[] = tests/lingotek.access.test @@ -44,9 +46,9 @@ files[] = lingotek_views_handler_workbench_link.inc stylesheets[all][] = style/base.css -; Information added by Drupal.org packaging script on 2015-03-24 -version = "7.x-6.02" +; Information added by Drupal.org packaging script on 2017-04-26 +version = "7.x-7.21" core = "7.x" project = "lingotek" -datestamp = "1427170701" +datestamp = "1493247252" diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.install b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.install index 1700a5c9..854800e6 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.install +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.install @@ -46,6 +46,7 @@ function lingotek_install() { lingotek_migration_2(); $sandbox = array(); lingotek_update_7401($sandbox); + lingotek_update_7607($sandbox); } /** @@ -187,6 +188,12 @@ function lingotek_schema() { 'id', 'config_key', ), + 'indexes' => array( + 'idx_id' => array('id'), + 'idx_config_key' => array('config_key'), + 'idx_id_and_config_key_and_value' => array('id', 'config_key', 'value'), + 'idx_config_key_and_value' => array('config_key', 'value') + ), ); $schema['lingotek_translation_agent'] = array( @@ -239,6 +246,10 @@ function lingotek_schema() { 'primary key' => array( 'lid', ), + 'indexes' => array( + 'idx_lid_and_current' => array('lid', 'current'), + 'idx_set_id' => array('set_id') + ), ); return $schema; @@ -270,7 +281,7 @@ function lingotek_schema_alter(&$schema) { 'description' => "translation tool mapping", 'length' => 10, 'not null' => TRUE, - 'default' => '1', // unknown + 'default' => 1, // unknown ); @@ -424,7 +435,8 @@ function lingotek_update_7206(&$sandbox) { } /** - * Change the value for LINGOTEK_ENABLED to a string to avoid collision. + * Change the value for old LINGOTEK_ENABLED define ('lingotek') to a string to + * avoid collision. */ function lingotek_update_7207(&$sandbox) { $updated = array(); @@ -432,7 +444,7 @@ function lingotek_update_7207(&$sandbox) { $name = 'language_content_type_' . $type; if (variable_get($name) == 5) { $updated[] = $type; - variable_set($name, LINGOTEK_ENABLED); + variable_set($name, 'lingotek'); } } @@ -582,7 +594,7 @@ function lingotek_update_7403(&$sandbox) { foreach ($types as $t) { $translation_setting = variable_get('language_content_type_' . $t->type); if ($translation_setting == 'lingotek') { - variable_set('language_content_type_' . $t->type, "1"); + variable_set('language_content_type_' . $t->type, '1'); $fields = field_info_instances("node", $t->type); foreach ($fields as $field_name => $instance) { $field = field_info_field($field_name); @@ -711,27 +723,30 @@ function lingotek_update_7502(&$sandbox) { variable_del('lingotek_translate_comments_node_types'); variable_del('lingotek_translate_comments'); - // set assumed status of CURRENT for all comment translations - // select all comment entity ids in the lingotek_entity_metadata table - $entity_type = 'comment'; - $query = db_select('lingotek_entity_metadata', 'l'); - $query->fields('l', array('entity_id'))->condition('entity_type', $entity_type); - $query->distinct('entity_id'); - $result = $query->execute(); - $entity_ids = $result->fetchCol(); - - // foreach entity, set the target statuses - if (!empty($entity_ids)) { - $entities = entity_load('comment', $entity_ids); - foreach ($entities as $entity) { - $entity_id = $entity->cid; - foreach (array_keys(Lingotek::getLanguages()) as $lingotek_locale) { - if (Lingotek::convertDrupal2Lingotek($entity->language) != $lingotek_locale) { // only add status when not the source language - LingotekSync::setTargetStatus($entity_type, $entity_id, $lingotek_locale, LingotekSync::STATUS_CURRENT); - } - else { // remove status when source language equal to target language - lingotek_keystore_delete($entity_type, $entity_id, "target_sync_status_$lingotek_locale"); - lingotek_keystore($entity_type, $entity_id, 'upload_status', LingotekSync::STATUS_CURRENT); + if (module_exists('comment')) { + // set assumed status of CURRENT for all comment translations + // select all comment entity ids in the lingotek_entity_metadata table + $entity_type = 'comment'; + $query = db_select('lingotek_entity_metadata', 'l'); + $query->fields('l', array('entity_id')) + ->condition('entity_type', $entity_type); + $query->distinct('entity_id'); + $result = $query->execute(); + $entity_ids = $result->fetchCol(); + + // foreach entity, set the target statuses + if (!empty($entity_ids)) { + $entities = entity_load('comment', $entity_ids); + foreach ($entities as $entity) { + $entity_id = $entity->cid; + foreach (array_keys(Lingotek::getLanguages()) as $lingotek_locale) { + if (Lingotek::convertDrupal2Lingotek($entity->language) != $lingotek_locale) { // only add status when not the source language + LingotekSync::setTargetStatus($entity_type, $entity_id, $lingotek_locale, LingotekSync::STATUS_CURRENT); + } + else { // remove status when source language equal to target language + lingotek_keystore_delete($entity_type, $entity_id, "target_sync_status_$lingotek_locale"); + LingotekSync::setUploadStatus($entity_type, $entity_id, LingotekSync::STATUS_CURRENT); + } } } } @@ -994,3 +1009,161 @@ function lingotek_update_7606(&$sandbox) { return t("Added flag confirming setup complete for comment translation (currently disabled)."); } } + +/** + * Convert 'create_lingotek_document' and 'sync_method' to 'auto_upload' and + * 'auto_download', respectively. + */ +function lingotek_update_7607(&$sandbox) { + // convert the profile settings + $profiles = variable_get('lingotek_profiles', array()); + foreach ($profiles as &$profile) { + if (isset($profile['sync_method'])) { + $profile['auto_download'] = $profile['sync_method']; + unset($profile['sync_method']); + } + if (isset($profile['create_lingotek_document'])) { + $profile['auto_upload'] = $profile['create_lingotek_document']; + unset($profile['create_lingotek_document']); + } + } + variable_set('lingotek_profiles', $profiles); + cache_clear_all(); + // modify the custom lines from the database + db_update('lingotek_entity_metadata') + ->fields(array('entity_key' => 'auto_download')) + ->condition('entity_key', 'sync_method') + ->execute(); + db_update('lingotek_entity_metadata') + ->fields(array('entity_key' => 'auto_upload')) + ->condition('entity_key', 'create_lingotek_document') + ->execute(); + + variable_set('lingotek_update_7607', TRUE); + return t('Converted profile references of "sync method" to "auto-download" and references of "create lingotek document" to "auto-upload".'); +} + +/** + * Set all entities using a custom (one-off) profile to be disabled, as custom + * profiles are no longer supported. + */ +function lingotek_update_7608(&$sandbox) { + // change profile from CUSTOM to DISABLED + db_update('lingotek_entity_metadata') + ->fields(array('value' => LingotekSync::PROFILE_DISABLED)) + ->condition('entity_key', 'profile') + ->condition('value', LingotekSync::PROFILE_CUSTOM) + ->execute(); + + // remove any custom profile settings (no longer relevant) + $deprecated_keys = array('allow_community_translation','auto_download','auto_upload','lingotek_nodes_translation_method','url_alias_translation'); + foreach ($deprecated_keys as $key) { + db_delete('lingotek_entity_metadata') + ->condition('entity_key', $key) + ->execute(); + } + + // remove the custom profile option from the variable if profiles exist. + $profiles = variable_get('lingotek_profiles', FALSE); + if ($profiles !== FALSE) { + if (isset($profiles['CUSTOM'])) { + unset($profiles['CUSTOM']); + } + variable_set('lingotek_profiles', $profiles); + + return t('Converted all nodes previously assigned to custom profiles to be disabled, and removed related rows that are no longer used.'); + } +} + +/** + * Rebuild registry to see LingotekProfile class after upgrade to 7.00 + */ +function lingotek_update_7700(&$sandbox) { + registry_rebuild(); + return t('Rebuilt registry to include LingotekProfile class for module v7.x-7.00+'); +} + +/** + * Add a language column containing the site's default language to the Bean module's table + * to ensure compatibility. + */ +function lingotek_update_7701(&$sandbox) { + $module = 'bean'; + $table = 'bean'; + $output = t('Bean module not installed. No action taken.'); + if (module_exists('bean')) { + if (!db_field_exists('bean', 'language')) { + $schema = drupal_get_schema_unprocessed($module, $table); + $schema['bean']['fields']['language'] = array( + 'type' => 'char', + 'length' => 8, + 'not null' => TRUE, + 'default' => language_default()->language, + 'description' => 'Language used for third party translation', + ); + + db_add_field('bean', 'language', $schema['bean']['fields']['language']); + $output = t('Column @column was created for Bean module support.', array('@column' => 'Language')); + } + else { + $output = t('The @column column already exists. No action taken.', array('@column' => 'Language')); + } + } + + return $output; +} + +/** + * Adds indices to the lingotek_config_metadata and lingotek_config_map tables + */ +function lingotek_update_7721(&$sandbox) { + $output = ''; + + if (db_table_exists('lingotek_config_metadata')) { + if (db_field_exists('lingotek_config_metadata', 'id')) { + if (!db_index_exists('lingotek_config_metadata', 'idx_id')) { + db_add_index('lingotek_config_metadata', 'idx_id', array('id')); + $output .= 'Index added for id in lingotek_config_metadata table.' . "\r\n"; + } + } + if (db_field_exists('lingotek_config_metadata', 'config_key')) { + if (!db_index_exists('lingotek_config_metadata', 'idx_config_key')) { + db_add_index('lingotek_config_metadata', 'idx_config_key', array('config_key')); + $output .= 'Index added for config_key in lingotek_config_metadata table.' . "\r\n"; + } + } + if (db_field_exists('lingotek_config_metadata', 'id') && db_field_exists('lingotek_config_metadata', 'config_key') && db_field_exists('lingotek_config_metadata', 'value')) { + if (!db_index_exists('lingotek_config_metadata', 'idx_id_and_config_key_and_value')) { + db_add_index('lingotek_config_metadata', 'idx_id_and_config_key_and_value', array('id', 'config_key', 'value')); + $output .= 'Index added for id and config_key and value in lingotek_config_metadata table.' . "\r\n"; + } + } + if (db_field_exists('lingotek_config_metadata', 'config_key') && db_field_exists('lingotek_config_metadata', 'value')) { + if (!db_index_exists('lingotek_config_metadata', 'idx_config_key_and_value')) { + db_add_index('lingotek_config_metadata', 'idx_config_key_and_value', array('config_key', 'value')); + $output .= 'Index added for config_key and value in lingotek_config_metadata table.' . "\r\n"; + } + } + } + + if (db_table_exists('lingotek_config_map')) { + if (db_field_exists('lingotek_config_map', 'lid') && db_field_exists('lingotek_config_map', 'current')) { + if (!db_index_exists('lingotek_config_map', 'idx_lid_and_current')) { + db_add_index('lingotek_config_map', 'idx_lid_and_current', array('lid', 'current')); + $output .= 'Index added for lid and current in lingotek_config_map table.' . "\r\n"; + } + } + if (db_field_exists('lingotek_config_map', 'set_id')) { + if (!db_index_exists('lingotek_config_map', 'idx_set_id')) { + db_add_index('lingotek_config_map', 'idx_set_id', array('set_id')); + $output .= 'Index added for set_id in lingotek_config_map table.' . "\r\n"; + } + } + } + + if (empty($output)) { + $output = 'All indices already present. No action taken.'; + } + + return t($output); +} \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.module b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.module old mode 100644 new mode 100755 index dd77532b..ea8fa905 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.module +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.module @@ -224,6 +224,14 @@ function lingotek_menu() { ); $items[LINGOTEK_MENU_LANG_BASE_URL . '/manage'] = $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage']; + $items['lingotek/update-marked-value/%/%/%'] = array( + 'access arguments' => array('administer lingotek'), + 'file' => 'lingotek.bulk_grid.inc', + 'page arguments' => array(2, 3, 4), + 'page callback' => 'lingotek_update_marked_item_single', + 'type' => MENU_CALLBACK, + ); + $entity_types = lingotek_managed_entity_types(); foreach ($entity_types as $machine_name => $entity_type) { $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/' . $machine_name] = array( @@ -262,6 +270,14 @@ function lingotek_menu() { 'type' => MENU_CALLBACK, ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/legend'] = array( + 'access arguments' => array('administer lingotek'), + 'file' => 'lingotek.bulk_grid.inc', + 'page callback' => 'lingotek_grid_legend', + 'page arguments' => array(), + 'type' => MENU_CALLBACK, + ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/filters/%'] = array( 'access arguments' => array('administer lingotek'), 'file' => 'lingotek.bulk_grid.inc', @@ -286,6 +302,29 @@ function lingotek_menu() { 'type' => MENU_CALLBACK, ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/async-update/%'] = array( + 'access arguments' => array('administer lingotek'), + 'file' => 'lingotek.bulk_grid.inc', + 'page callback' => 'lingotek_grid_query_status', + 'page arguments' => array(5), + 'type' => MENU_CALLBACK, + ); + + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/auto-download/%'] = array( + 'access arguments' => array('administer lingotek'), + 'file' => 'lingotek.bulk_grid.inc', + 'page callback' => 'lingotek_grid_automatic_download', + 'page arguments' => array(5), + 'type' => MENU_CALLBACK, + ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/force-down-all/%'] = array( + 'access arguments' => array('administer lingotek'), + 'file' => 'lingotek.bulk_grid.inc', + 'page callback' => 'config_download_ready', + 'page arguments' => array(5), + 'type' => MENU_CALLBACK, + ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/download-ready/%'] = array( 'access arguments' => array('administer lingotek'), 'file' => 'lingotek.bulk_grid.inc', @@ -328,6 +367,14 @@ function lingotek_menu() { 'type' => MENU_CALLBACK, ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/delete-translations/%/%'] = array( + 'access arguments' => array('administer lingotek'), + 'file' => 'lingotek.bulk_grid.inc', + 'page callback' => 'lingotek_popup', + 'page arguments' => array('entity_delete_translations', 5, 6), + 'type' => MENU_CALLBACK, + ); + $items[LINGOTEK_MENU_MAIN_BASE_URL . '/manage/reset/%/%'] = array( 'access arguments' => array('administer lingotek'), 'file' => 'lingotek.bulk_grid.inc', @@ -348,7 +395,7 @@ function lingotek_menu() { // Easy Install Screens ---------------------------------------- // Setup Path Router - $items['admin/config/lingotek/setup'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/setup'] = array( 'title' => 'Lingotek Setup Path Router', 'description' => 'Figures out the necessary setup path.', 'file' => 'lingotek.setup.inc', @@ -360,7 +407,7 @@ function lingotek_menu() { ); // New Account - $items['admin/config/lingotek/new-account'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/new-account'] = array( 'title' => 'Create a Lingotek Account', 'description' => 'Setup a new Lingotek account.', 'file' => 'lingotek.setup.inc', @@ -372,7 +419,7 @@ function lingotek_menu() { ); // Current Users - $items['admin/config/lingotek/account-settings'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/account-settings'] = array( 'title' => 'Lingotek Account Login', 'description' => 'Manage your Lingotek account settings.', 'file' => 'lingotek.setup.inc', @@ -383,7 +430,18 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/community-select'] = array( + // Current Enterprise Users (0 for enterprise, 1 for sandbox) + $items[LINGOTEK_MENU_LANG_BASE_URL . '/enterprise-account-settings/%'] = array( + 'title' => 'Lingotek Enterprise Account Login', + 'description' => 'Manage your Lingotek Enterprise account settings.', + 'file' => 'lingotek.setup.inc', + 'page callback' => 'lingotek_enterprise_account_setting', + 'page arguments' => array(5), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer lingotek'), + ); + + $items[LINGOTEK_MENU_LANG_BASE_URL . '/community-select'] = array( 'title' => 'Choose Your Community', 'description' => 'Select a community to use with this site.', 'file' => 'lingotek.setup.inc', @@ -394,7 +452,7 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/project-vault-select'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/project-vault-select'] = array( 'title' => 'Choose Your Project, Workflow, and TM Vault', 'description' => 'Select project, workflow, and translation memory vault to use.', 'file' => 'lingotek.setup.inc', @@ -405,7 +463,7 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/node-translation-settings'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/node-translation-settings'] = array( 'title' => 'Enable Content Types', 'description' => 'Select the nodes and fields you want translated.', 'file' => 'lingotek.setup.inc', @@ -416,7 +474,7 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/comment-translation-settings'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/comment-translation-settings'] = array( 'title' => 'Enable Comment Translation', 'description' => 'Select any node types you want the comments translated.', 'file' => 'lingotek.setup.inc', @@ -427,7 +485,7 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/additional-translation-settings'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/additional-translation-settings'] = array( 'title' => 'Enable Additional Translation', 'description' => 'Select any additional types you want translated.', 'file' => 'lingotek.setup.inc', @@ -438,7 +496,7 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/content-type-choose-fields-ajax/%'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/content-type-choose-fields-ajax/%'] = array( 'title' => 'Content Types Ajax', 'description' => 'Ajax functionality for content options.', 'file' => 'lingotek.setup.inc', @@ -448,7 +506,7 @@ function lingotek_menu() { 'access arguments' => array('administer lingotek') ); - $items['admin/config/lingotek/setup-language-switcher'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/setup-language-switcher'] = array( 'title' => 'Enable Language Switcher', 'description' => 'Enable the default language switcher', 'file' => 'lingotek.setup.inc', @@ -459,7 +517,7 @@ function lingotek_menu() { 'options' => array('attributes' => array('class' => array('overlay-exclude'))), ); - $items['admin/config/lingotek/setup-complete'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/setup-complete'] = array( 'title' => 'Setup Complete', 'description' => 'The Lingotek Translation module has been configured and is now ready to use.', 'file' => 'lingotek.setup.inc', @@ -469,7 +527,7 @@ function lingotek_menu() { 'access arguments' => array('administer lingotek') ); - $items['admin/config/lingotek/node-updates'] = array( + $items[LINGOTEK_MENU_LANG_BASE_URL . '/node-updates'] = array( 'title' => 'Lingotek Node Updates', 'description' => 'Updates your nodes to support multiple languages.', 'file' => 'lingotek.setup.inc', @@ -489,15 +547,6 @@ function lingotek_menu() { 'access arguments' => array('administer lingotek') ); - $items[LINGOTEK_MENU_MAIN_BASE_URL . '/clearexceptions/%'] = array( - 'description' => 'Remove all one-off profiles', - 'file' => 'lingotek.module', - 'page callback' => 'lingotek_clear_exceptions_modal', - 'page arguments' => array(4), - 'type' => MENU_CALLBACK, - 'access arguments' => array('administer lingotek'), - ); - return $items; } @@ -547,6 +596,19 @@ function lingotek_module_implements_alter(&$implementations, $hook) { unset($implementations['lingotek']); $implementations['lingotek'] = $group; break; + case 'node_delete': + // Our node_delete implementation must run before the translation + // module's, so when a source node is deleted, all the target nodes + // will be deleted. + if (module_exists('translation')) { + $lingotek_group = $implementations['lingotek']; + $translation_group = $implementations['translation']; + unset($implementations['lingotek']); + unset($implementations['translation']); + $implementations['lingotek'] = $lingotek_group; + $implementations['translation'] = $translation_group; + } + break; } } @@ -692,8 +754,9 @@ function lingotek_form_node_form_alter(&$form, $form_state, $form_id) { // Include the translation-management tab always $form = lingotek_get_node_settings_form($form, $form_state, $node); - // Set the default language on new node creation. - if (empty($source_node->nid) && $form['language']['#default_value'] == LANGUAGE_NONE) { + // Set the default language on new node creation if language is a parameter + // and if the language parameter is language-neutral. + if (empty($source_node->nid) && !empty($form['language']['#default_value']) && $form['language']['#default_value'] == LANGUAGE_NONE) { $form['language']['#default_value'] = lingotek_get_source_language(); } @@ -709,7 +772,7 @@ function lingotek_form_node_form_alter(&$form, $form_state, $form_id) { $form = array_merge($form, lingotek_get_language_override_form($form, $form_state, $node)); -// Disable the language field if the node is synced with Lingotek. + // Disable the language field if the node is synced with Lingotek. if (!empty($node->nid)) { $document_id = lingotek_keystore('node', $node->nid, 'document_id'); if (isset($document_id) && $document_id != 0) { @@ -720,10 +783,12 @@ function lingotek_form_node_form_alter(&$form, $form_state, $form_id) { // Disable all Lingotek-managed fields if the node already exists and the // current language isn't the source language and the administrator has not // selected the Lingotek preference to allow local editing of translations. - if (!empty($source_node->nid) && $current_language != $source_node->language && !variable_get('lingotek_allow_local_edits', FALSE)) { + $is_entity_translation_target = !empty($form_state['entity_translation']['form_langcode']) && $form_state['entity_translation']['form_langcode'] != $source_node->language; + + if (!empty($source_node->nid) && lingotek_managed_entity('node', $node) && (lingotek_is_node_translation($node) || $is_entity_translation_target) && !variable_get('lingotek_allow_local_edits', FALSE)) { $lingotek_fields = variable_get('lingotek_enabled_fields'); $enabled_fields = !empty($lingotek_fields['node'][$node->type]) ? $lingotek_fields['node'][$node->type] : NULL; - if (!$enabled_fields && lingotek_oneoff_translate($node)) { + if (!$enabled_fields) { $enabled_fields = lingotek_get_translatable_fields_by_content_type('node', $node->type); } foreach ($enabled_fields as $field) { @@ -743,8 +808,11 @@ function lingotek_form_node_form_alter(&$form, $form_state, $form_id) { $message .= '
      ' . t('To make changes to this translation you can go to the !url.', array('!url' => $link)); - - drupal_set_message($message, 'warning', FALSE); + // Make sure the message is written only on initial form load, not on + // submission (since form-alter functions may be called multiple times). + if (empty($form_state['input'])) { + drupal_set_message($message, 'warning', FALSE); + } } } @@ -779,7 +847,7 @@ function lingotek_get_language_override_form($form, &$form_state, $node = NULL) foreach ($profiles as $k => $p) { $form[$k . '_override'] = array( '#type' => 'hidden', - '#value' => !empty($p['allow_source_overwriting']) ? 'true' : 'false', + '#value' => $p->getAttribute('allow_source_overwriting') ? 'true' : 'false', ); } $form['actions']['submit']['#submit'][] = 'lingotek_get_language_override_form_submit'; @@ -834,7 +902,6 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { drupal_add_css(drupal_get_path('module', 'lingotek') . '/style/form.css'); - $show_advanced = LingotekAccount::instance()->showAdvanced(); $enabled_languages = lingotek_get_target_locales(TRUE); $node_language = Lingotek::convertDrupal2Lingotek($node->language); @@ -871,9 +938,6 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { '#markup' => t('This is a target node for the language code: @lang. To change the Lingotek settings please edit the source node.', array('@lang' => $node->language)), ); - $form['lingotek']['content_begin'] = array( - '#markup' => '
      ', - ); return $form; //this is a target node and thus should not have lingotek settings } @@ -898,7 +962,7 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { '#description' => t("The Lingotek Translation module was developed to help you translate your site. The module integrates the Lingotek translation management system directly into Drupal, so that your users can leverage the power of Lingotek's translation tools and services without ever having to leave the comfort of your Drupal environment."), ); if (!empty($node->lingotek['document_id'])) { - $form['lingotek']['lingotek_note']['#description'] .= t('

      NOTE: This node has already been uploaded to Lingotek. To change the workflow, you must use the Change Workflow action on the Manage tab rather than modifying the Translation Profile here.

      '); + $form['lingotek']['lingotek_note']['#description'] .= t('

      NOTE: This node has already been uploaded to Lingotek. If changing to a profile with additional target languages, those languages will be added locally and in the TMS. If changing to a profile without all current target languages then the default setting is to leave the target language in the TMS but not locally; this can be changed in the Lingotek Preferences. To change the workflow, however, you must use the Change Workflow action on the Manage tab rather than modifying the Translation Profile here.

      '); } // With the Translation Management tab in place, disable it and return the @@ -913,13 +977,48 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { return $form; } + // Don't mess with the profile of an existing node (only the new ones). + $form['preserve_profile'] = array( + '#type' => 'hidden', + '#value' => isset($node->nid) ? 1 : 0, + '#attributes' => array( + 'id' => 'lingotek-preserve-profile', + ), + ); + $form['lingotek']['profile'] = array( '#type' => 'select', '#title' => t('Translation Profile'), '#options' => lingotek_get_profile_options(), - '#default_value' => $node->lingotek['profile'], ); + if (!$bulk_grid) { + + // Set the initial profile default. + $bundle_profile = LingotekProfile::loadByBundle($entity_type, $form['#bundle']); + $form['lingotek']['profile']['#default_value'] = isset($node->lingotek['profile']) ? $node->lingotek['profile'] : $bundle_profile->getId(); + + // Do some js handling of profile changes + $entity_profiles = variable_get('lingotek_entity_profiles', array()); + $node_profiles = $entity_profiles['node']; + $bundle_profiles_by_langcode = lingotek_filter_profiles_by_bundle_and_langcode($form['type']['#value'], $node_profiles); + + $form['lingotek']['bundle_profiles'] = array( + '#type' => 'hidden', + '#value' => json_encode($bundle_profiles_by_langcode), + '#attributes' => array( + 'id' => 'lingotek-bundle-profiles', + ), + ); + $form['lingotek']['language_specific_profiles'] = array( + '#type' => 'hidden', + '#value' => variable_get('lingotek_enable_language_specific_profiles', '0'), + '#attributes' => array( + 'id' => 'lingotek-language-specific-profiles', + ), + ); + } + // Warning note when enabling nodes with existing Lingotek translations $overwrite_markup = '
      '; if (isset($nids) && lingotek_previously_managed_translations($entity_type, $nids)) { @@ -931,91 +1030,14 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { $form['lingotek']['document_id'] = array( '#type' => 'value', - '#value' => $node->lingotek['document_id'], + '#value' => !empty($node->lingotek['document_id']) ? $node->lingotek['document_id'] : '', ); $form['lingotek']['upload_status'] = array( '#type' => 'value', - '#value' => $node->lingotek['upload_status'], - ); - - $form['lingotek']['content_begin'] = array( - '#markup' => '
      ', + '#value' => !empty($node->lingotek['upload_status']) ? $node->lingotek['upload_status'] : '', ); - $content_translation_note = ''; - if (!lingotek_node_based_trans_ready()) { - $content_translation_note = t("Note: Requires Content Translation module (translation) and Multilingual content module (i18n_node)."); - } - - if (!$bulk_grid) { - $form['lingotek']['lingotek_nodes_translation_method'] = array( - '#type' => 'radios', - '#title' => t('Method for storing translations'), - '#options' => array( - 'field' => t('Fields (Recommended) - all translations are stored in a single node'), - 'node' => t('Nodes - create a new node per language'), - ), - 'field' => array( - '#description' => t('A newer and recommended method. It appears newer versions of Drupal will use this method.'), - ), - 'node' => array( - '#description' => t('The classical method. Use for backwards compatibility. @note', array('@note' => $content_translation_note)), - ), - '#disabled' => isset($node->nid) || !lingotek_node_based_trans_ready(), - '#default_value' => $node->lingotek['lingotek_nodes_translation_method'], - ); - } - - $form['lingotek']['create_lingotek_document'] = array( - '#type' => 'checkbox', - '#title' => t('Upload Content Automatically'), - '#default_value' => $node->lingotek['create_lingotek_document'], - '#description' => t('When enabled, your Drupal content (including saved edits) will automatically be uploaded to Lingotek for translation.
      When disabled, you are required to manually upload your content by clicking the "Upload" button on the Translations tab.'), - ); - - $form['lingotek']['sync_method'] = array( - '#type' => 'checkbox', - '#title' => t('Download Translations Automatically'), - '#default_value' => $node->lingotek['sync_method'], - '#description' => t('When enabled, completed translations will automatically be downloaded from Lingotek.
      When disabled, you are required to manually download translations by clicking the "Download" button on the Translations tab.'), - ); - - // URL Alias Translation. - $form['lingotek']['url_alias_translation'] = array( - '#type' => 'select', - '#title' => t('URL Alias Translation'), - '#default_value' => $node->lingotek['url_alias_translation'], - '#options' => lingotek_get_url_alias_translations(), - '#description' => t('Choose how you would like to translate the URL alias. The last option requires that you install both the Title and Pathauto modules, and define a path pattern.'), - ); - - if ($show_advanced) { - /* REMOVED UNTIL FEATURE IS IMPLEMENTED - // Target Localization - $form['lingotek']['allow_target_localization'] = array( - '#type' => 'checkbox', - '#title' => t('Allow Target Localization'), - '#description' => t('When enabled, localized target-language content can be created in Drupal and then uploaded for in-place translation. Note: to prevent loss of the original localized content when using this option, you should have a versioning tool enabled for Drupal, such as Workbench Moderation.'), - '#default_value' => $node->lingotek['allow_target_localization'], - ); - // Source Overwriting - $form['lingotek']['allow_source_overwriting'] = array( - '#type' => 'checkbox', - '#title' => t('Allow Source Overwriting'), - '#description' => t('When enabled, source content may be created initially in a language other than the one set in Drupal and then translated in place of the original source content (ie. no separate target content will be created). Note: to prevent the loss of the source content when using this option, you should have a versioning tool enabled for Drupal, such as Workbench Moderation.'), - '#default_value' => $node->lingotek['allow_source_overwriting'], - ); - */ - // Community Translation - $form['lingotek']['allow_community_translation'] = array( - '#type' => 'checkbox', - '#title' => t('Allow Crowdsourced Translation'), - '#description' => t('When enabled, anonymous site visitors will be presented with a link allowing them to contribute translations for this node.'), - '#default_value' => $node->lingotek['allow_community_translation'], - ); - } - if ($bulk_grid) { $form['submit'] = array( '#type' => 'submit', @@ -1025,9 +1047,9 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { } // include additional form components - $form = lingotek_add_workflow_settings_form($form, $form_state, $node); - $form = lingotek_add_advanced_form($form, $form_state, $node); - $form = lingotek_add_menu_link_form($form, $form_state, $node); + if (!variable_get('lingotek_advanced_menu_links', FALSE)) { + $form = lingotek_add_menu_link_form($form, $form_state, $node); + } return $form; } @@ -1036,7 +1058,6 @@ function lingotek_get_node_settings_form($form, &$form_state, $node = NULL) { * This is not a hook. This is a helper function to save all lingotek related data. */ function lingotek_entity_save($entity, $entity_type) { - if (!variable_get('lingotek_login_id')) { // Lingotek is installed but has not been setup yet // (could use function lingotek_is_config_missing() but did not want @@ -1061,76 +1082,64 @@ function lingotek_entity_save($entity, $entity_type) { list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); - $has_not_been_uploaded = !isset($entity->lingotek['document_id']) || empty($entity->lingotek['document_id']); + $has_been_uploaded = !empty($entity->lingotek['document_id']); - $remove = lingotek_get_profile_fields($has_not_been_uploaded, TRUE); + $remove = lingotek_get_profile_fields(!$has_been_uploaded, TRUE); db_delete('lingotek_entity_metadata') ->condition('entity_type', $entity_type) ->condition('entity_id', $id) ->condition('entity_key', $remove, 'IN') ->execute(); + lingotek_cache_clear($entity_type, $id); - // Set the default value for whether the entity's content actually changed. - $entity_changed = TRUE; + // Always store the entity's profile, in case the profile of the same content + // type changes in the future to something else. + + if ($entity_type == 'menu_link') { + $is_source_menu_link = lingotek_is_menu_link_source($id); + // Don't put target menu links in the lingotek entity metadata table + if (!$is_source_menu_link) { + return; + } + } - if ($entity->lingotek['profile'] == LingotekSync::PROFILE_CUSTOM) { - $query = db_insert('lingotek_entity_metadata')->fields(array('entity_type', 'entity_id', 'entity_key', 'value')); + if (!isset($entity->lingotek['profile'])) { + drupal_set_message(t('Unable to load Lingotek profile for @entity_type. Make sure user has Lingotek permission when manipulating entities.', array('@entity_type' => $entity_type)), 'error'); + return; + } - foreach ($remove as $key) { - if (isset($entity->lingotek[$key])) { - $query->values(array($entity_type, $id, $key, $entity->lingotek[$key])); + lingotek_keystore($entity_type, $id, 'profile', $entity->lingotek['profile']); + // If the hash changed, handle upload status and target statuses + $entity_changed = lingotek_entity_changed($entity_type, $entity, $id); + if ($entity_changed) { + if ($entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { + if ($has_been_uploaded) { + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_EDITED); } + LingotekSync::setAllTargetStatus($entity_type, $id, LingotekSync::STATUS_UNTRACKED); } - $query->execute(); - - // check for a workflow change - if (!empty($entity->lingotek['workflow_id']) && !empty($entity->lingotek['document_id'])) { - $curr_workflow = $entity->lingotek['workflow_id']; - $document_id = $entity->lingotek['document_id']; - if (!isset($entity->original->lingotek['workflow_id']) || - $entity->original->lingotek['workflow_id'] != $curr_workflow) { - $api = LingotekApi::instance(); - $prefill_checked = (isset($entity->lingotek['prefill_phases_checkbox']) ? $entity->lingotek['prefill_phases_checkbox'] : NULL); - $prefill_phase = ($prefill_checked ? $entity->lingotek['prefill_phase_select'] : NULL); - $result = $api->changeWorkflow(array($document_id), $curr_workflow, $prefill_phase); - if ($result === TRUE) { - LingotekSync::setAllTargetStatus($entity_type, $id, LingotekSync::STATUS_PENDING); - lingotek_keystore($entity_type, $id, 'workflow_id', $curr_workflow); - } - else { - LingotekLog::error('Failed to change workflow for @et #@id: @error', array('@et' => $entity_type, '@id' => $id, '@error' => $result)); + else { + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_NONE); + // Create the target entries in lingotek_entity_metadata + if ($has_been_uploaded) { + if (!in_array($entity->lingotek['upload_status'], array(LingotekSync::STATUS_NONE ,LingotekSync::STATUS_TARGET, LingotekSync::STATUS_DELETED))) { + LingotekSync::setAllTargetStatus($entity_type, $id, LingotekSync::STATUS_EDITED); } } } } - else { // Specific Profile - $defaults = lingotek_load_profile_defaults($entity_type); - $profile_overridden = (!isset($defaults[$bundle]['profile']) || $defaults[$bundle]['profile'] != $entity->lingotek['profile']); - if ($profile_overridden) { - lingotek_keystore($entity_type, $id, 'profile', $entity->lingotek['profile']); - } - else { - lingotek_keystore_delete($entity_type, $id, 'profile'); - } - // If the hash changed, handle target statuses - $entity_changed = lingotek_entity_changed($entity_type, $entity, $id); - if ($entity_changed) { - if ($entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { - LingotekSync::setAllTargetStatus($entity_type, $id, LingotekSync::STATUS_UNTRACKED); - } - else { - // if this is a node-based translation target and target localization is enabled, set to TARGET_EDITED - // TODO: + $existing_targets = LingotekSync::getAllTargetStatusForEntity($entity_type, $id); - // if this is a source node and target localization is enabled, set all targets to TARGET_LOCALIZE - // TODO: + if (count($existing_targets) == 0) { + // Get the source language and available target languages. + $source = isset($entity->language) ? $entity->language : language_default()->language; + $source = Lingotek::convertDrupal2Lingotek($source); + $target_locales = Lingotek::getLanguagesWithoutSource($source); - if ($entity->lingotek['upload_status'] != LingotekSync::STATUS_TARGET) { - LingotekSync::setAllTargetStatus($entity_type, $id, LingotekSync::STATUS_EDITED); - } - } + foreach (array_keys($target_locales) as $locale) { + lingotek_keystore($entity_type, $id, 'target_sync_status_' . $locale, LingotekSync::STATUS_NONE); } } @@ -1172,12 +1181,59 @@ function lingotek_get_node_settings_form_submit($form, $form_state) { $node = lingotek_entity_load_single($entity_type, $nid); $node->lingotek = array_replace($node->lingotek, $form_state['values']['lingotek']); lingotek_entity_save($node, $entity_type); + if (variable_get('lingotek_enable_language_specific_profiles', FALSE)) { + lingotek_sync_target_locales($node, $entity_type); + } } node_types_rebuild(); menu_rebuild(); } } +function lingotek_sync_target_locales($entity, $entity_type) { + $ln = LingotekEntity::load($entity, $entity_type); + $api = LingotekApi::instance(); + $document_id = $ln->lingotekDocumentId(); + + $source = isset($ln->language) ? $ln->language : language_default()->language; + $source = Lingotek::convertDrupal2Lingotek($source); + $target_locales = Lingotek::getLanguagesWithoutSource($source); + $profile = LingotekProfile::loadById($ln->getMetadataValue('profile')); + $target_locales = $profile->filterTargetLocales(array_keys($target_locales)); + + $targetTranslations = (array) $api->listTranslationTargets($document_id); + $drupal_target_locales = $target_locales; + $tms_target_locales = array(); + foreach ($targetTranslations['translationTargets'] as $targetObject) { + $item = (array) $targetObject; + $tms_target_locales[$item['language']] = 1; + } + foreach ($drupal_target_locales as $dtl => $val1) { + if (LingotekSync::getTargetStatus($document_id, $dtl) == LingotekSync::STATUS_UNTRACKED) { + LingotekSync::setTargetStatus($ln->getEntityType(), $ln->getId(), $dtl, LingotekSync::STATUS_NONE); + } + foreach ($tms_target_locales as $ttl => $val2) { + if ($dtl == $ttl) { + unset($drupal_target_locales[$dtl]); + unset($tms_target_locales[$dtl]); + } + } + } + + foreach($drupal_target_locales as $dtl => $val) { + $workflow = isset($val['workflow_id']) ? $val['workflow_id'] : ''; + $api->addTranslationTarget($document_id, NULL, $dtl, $workflow); + } + + foreach ($tms_target_locales as $ttl => $val) { + if (variable_get('lingotek_remove_target_from_tms', FALSE)) { + $api->removeTranslationTarget($document_id, NULL, $ttl); + } + LingotekSync::setTargetStatus($ln->getEntityType(), $ln->getId(), $ttl, LingotekSync::STATUS_UNTRACKED); + } + lingotek_get_and_update_target_progress($ln->getEntityType(), $document_id, 1, 1); +} + function lingotek_get_change_workflow_form_submit($form, $form_state) { if (!empty($form_state['nids'])) { $nids = $form_state['nids']; @@ -1188,36 +1244,56 @@ function lingotek_get_change_workflow_form_submit($form, $form_state) { else { $prefill_phase = NULL; } - - // SUBMIT THE WORKFLOW CHANGES TO TMS $entity_type = $form_state['entity_type']; - $document_ids = LingotekSync::getDocIdsFromEntityIds($entity_type, $nids); + // SUBMIT THE WORKFLOW CHANGES TO TMS + lingotek_entity_change_workflow($entity_type, $nids, $workflow_id, $prefill_phase); + } +} + +/** + * Changes an entity's workflow locally and on the TMS + * @param string $entity_type + * The entity type + * @param array $entity_ids + * The entity id + */ +function lingotek_entity_change_workflow($entity_type, $entity_ids, $workflow_id, $prefill_phase = null) { + $document_ids = $entity_type !== 'config' ? LingotekSync::getDocIdsFromEntityIds($entity_type, $entity_ids) + : LingotekSync::getConfigDocIdsFromSetIds($entity_ids); + if($entity_type === 'config'){ + foreach($entity_ids as $id) { + LingotekSync::updateConfigSetWorkflow($id, $workflow_id); + } + } if (!$document_ids) { return; } + // TODO: this should really be a batch that runs after the modal screen closes. + drupal_set_time_limit(0); $api = LingotekApi::instance(); $api->changeWorkflow($document_ids, $workflow_id, $prefill_phase); // CREATE/UPDATE WORKFLOW ENTRIES IN THE LINGOTEK METADATA TABLE - foreach ($nids as $nid) { - lingotek_keystore($entity_type, $nid, 'workflow_id', $workflow_id); - LingotekSync::setAllTargetStatus($entity_type, $nid, LingotekSync::STATUS_PENDING); + if($entity_type === 'config') { + LingotekSync::bulkSetAllTargetStatus($entity_type, $entity_ids, LingotekSync::STATUS_PENDING); + return; + } + LingotekSync::bulkSetAllTargetStatus($entity_type, $entity_ids, LingotekSync::STATUS_PENDING); + foreach ($entity_ids as $id) { + lingotek_keystore($entity_type, $id, 'workflow_id', $workflow_id); } - } } function lingotek_get_global_profile() { return array( 'name' => '', - 'document_id' => NULL, 'lingotek_nodes_translation_method' => variable_get('lingotek_nodes_translation_method'), - 'create_lingotek_document' => 0, - 'sync_method' => 0, + 'auto_upload' => 0, + 'auto_download' => 0, 'allow_target_localization' => 0, 'allow_source_overwriting' => 0, 'allow_community_translation' => 0, 'url_alias_translation' => 0, - 'upload_status' => LingotekSync::STATUS_EDITED, 'project_id' => variable_get('lingotek_project'), 'workflow_id' => variable_get('lingotek_workflow'), 'vault_id' => variable_get('lingotek_vault'), @@ -1227,62 +1303,44 @@ function lingotek_get_global_profile() { /** * Return all profiles in an associative array, by name */ -function lingotek_get_profiles_by_name($include_custom = FALSE, $use_tfunction = TRUE) { +function lingotek_get_profiles_by_name($localize = TRUE) { $profile_options = array(); + // TODO: switch this lingotek_get_profiles as arrays to objects, update usage of _by_name $profiles = lingotek_get_profiles(); foreach ($profiles as $key => $profile) { - $profile_options[$key] = $use_tfunction ? t($profile['name']) : $profile['name']; - } - if ($include_custom) { - $profile_options[LingotekSync::PROFILE_CUSTOM] = $use_tfunction ? t('Custom') : 'Custom'; + $profile_options[$key] = $localize ? t($profile->getName()) : $profile->getName(); } - $profile_options[LingotekSync::PROFILE_DISABLED] = $use_tfunction ? t('Disabled') : 'Disabled'; - $profile_options[LingotekSync::PROFILE_CONFIG] = $use_tfunction ? t('Config') : 'Config'; + $profile_options[LingotekSync::PROFILE_DISABLED] = $localize ? t('Disabled') : 'Disabled'; + $profile_options[LingotekSync::PROFILE_CONFIG] = $localize ? t('Config') : 'Config'; return $profile_options; } -function lingotek_get_profiles() { +function lingotek_get_profiles($build_objects = TRUE) { $profiles = variable_get('lingotek_profiles', array()); if (empty($profiles)) { $profiles[] = array( 'name' => 'Automatic', - 'create_lingotek_document' => 1, - 'sync_method' => 1, + 'auto_upload' => 1, + 'auto_download' => 1, ); $profiles[] = array( 'name' => 'Manual', - 'create_lingotek_document' => 0, - 'sync_method' => 0, + 'auto_upload' => 0, + 'auto_download' => 0, ); variable_set('lingotek_profiles', $profiles); } - return $profiles; -} - -function lingotek_set_profiles($profiles) { - variable_set('lingotek_profiles', $profiles); -} - -function lingotek_get_profile_settings($profile_id) { - // merge of global profile settings and profile-specific settings - $profiles = lingotek_get_profiles(); - $global_profile = lingotek_get_global_profile(); - $profile = isset($profiles[$profile_id]) ? $profiles[$profile_id] : array(); - return array_merge($global_profile, $profile); -} + if (!$build_objects) { + return $profiles; + } -function lingotek_set_profile_settings($profile_id, $settings) { - // Profiles need to know each of their settings, since each profile - // changes independently from all others. There is no direct - // inheritance tree for profiles. There is only inheritance for - // entities -> bundles. - $global_profile = lingotek_get_global_profile(); - $profiles = lingotek_get_profiles(); - $differences = array_diff($settings, $global_profile); - $profiles[$profile_id] = $differences; - variable_set('lingotek_profiles', $profiles); + $profile_objects = array(); + foreach ($profiles as $id => $attributes) { + $profile_objects[$id] = LingotekProfile::loadById($id); + } + return $profile_objects; } function lingotek_get_profile_options() { @@ -1290,9 +1348,8 @@ function lingotek_get_profile_options() { $profile_options = array(); foreach ($profiles as $key => $profile) { - $profile_options[$key] = t($profile['name']); + $profile_options[$key] = t($profile->getName()); } - $profile_options[LingotekSync::PROFILE_CUSTOM] = t('Custom'); $profile_options[LingotekSync::PROFILE_DISABLED] = t('Disabled'); unset($profile_options[LingotekSync::PROFILE_CONFIG]); @@ -1300,6 +1357,13 @@ function lingotek_get_profile_options() { return $profile_options; } +function lingotek_get_workflow_options() { + $api = LingotekApi::instance(); + $workflows = $api->listWorkflows(); + + return $workflows; +} + /** * Implements hook_node_view(). */ @@ -1310,15 +1374,23 @@ function lingotek_node_view($node, $view_mode) { if ($view_mode != 'full') { return; } + + // If anonymous users don't have permission, don't deliver the crowd-sourced workbench link + $has_permissions = user_access('manage projects') || user_access('translation') ? TRUE : FALSE; + + if (!$has_permissions) { + return; + } + $source_node = lingotek_get_source_node($node); + if (!lingotek_managed_entity('node', $source_node) || !lingotek_enabled_langcode($language->language)) { return; } $lingotek_has_doc_id = !empty($source_node->lingotek['document_id']); $community_translation_allowed = !empty($source_node->lingotek['allow_community_translation']); - if ($community_translation_allowed && $lingotek_has_doc_id && lingotek_supported_type($source_node->type) && Lingotek::isSupportedLanguage($node->language)) { - + if ($community_translation_allowed && $lingotek_has_doc_id && isset($source_node->lingotek['profile']) && $source_node->lingotek['profile'] != LingotekSync::PROFILE_DISABLED) { if ($language->language != $source_node->language) { $link = lingotek_get_workbench_url($source_node->lingotek['document_id'], $language->lingotek_locale, t('Help make it better.')); if ($link != '') { @@ -1334,9 +1406,9 @@ function lingotek_node_view($node, $view_mode) { $can_translate = user_access('translation') && ($source_node->lingotek['profile'] != LingotekSync::PROFILE_DISABLED); $uploaded_to_lingotek = !empty($source_node->lingotek['document_id']); - $is_target = $node->lingotek['upload_status'] == 'TARGET'; + $is_target = !empty($node->lingotek['upload_status']) && $node->lingotek['upload_status'] == 'TARGET'; - if ($can_translate && $uploaded_to_lingotek || $is_target) { + if ($can_translate && ($uploaded_to_lingotek || $is_target)) { global $language; $node->content['lingotek_link']['#markup'] = lingotek_workbench_icon('node', $source_node->nid, Lingotek::convertDrupal2Lingotek($language->language)); } @@ -1352,12 +1424,14 @@ function lingotek_node_presave($node) { $node->title = $node->title_field[$node->language][0]['value']; } } - - // Setting node menu language to neutral, so that it can be localized. - if (isset($node->menu)) { - $link = $node->menu; - if (lingotek_translate_menu_link($link)) { - $node->menu['language'] = LANGUAGE_NONE; + // Setting node menu language to neutral, so that it can be localized, if advanced handling + // of menu links is off. + if (!variable_get('lingotek_advanced_menu_links', FALSE)) { + if (isset($node->menu)) { + $link = $node->menu; + if (lingotek_translate_menu_link($link)) { + $node->menu['language'] = LANGUAGE_NONE; + } } } } @@ -1423,28 +1497,89 @@ function lingotek_entity_load_single($entity_type, $entity_id) { $entity->language = LANGUAGE_NONE; } } + // Beans don't load a language property, so we'll assign it. + if ($entity_type == 'bean') { + $entity->language = language_default()->language; + } + // Groups don't load a language property, so we'll assign it. + if ($entity_type == 'group') { + $language = lingotek_get_group_source($entity->gid); + $entity->language = $language; + } + // Paragraphs don't load a language property, so we'll assign it. + if ($entity_type == 'paragraphs_item') { + $language = lingotek_get_paragraphs_item_source($entity->item_id); + $entity->language = $language; + } + if ($entity_type == 'file') { + $entity->language = lingotek_get_file_source($entity->fid); + } } return $entity; } +/** + * Implements hook_entitycache_load(). + * + * Entity cache might not trigger hook_entity_load() + * so Lingotek passes on $entities to lingotek_entity_load() + */ +function lingotek_entitycache_load($entities, $type) { + lingotek_entity_load($entities, $type); +} + /** * Implements hook_entity_load(). */ function lingotek_entity_load($entities, $entity_type) { - if (empty($entities)) { return; } - $special_entities = array('field_collection_item', 'message_type', 'taxonomy_term'); + $default_special_entities = array('field_collection_item', 'message_type', 'taxonomy_term', 'bean'); + $special_entities = variable_get('lingotek_special_entity_types', $default_special_entities); if (in_array($entity_type, $special_entities)) { foreach ($entities as $e) { lingotek_normalize_special_field_language($entity_type, $e); } } - $global_profile = lingotek_get_global_profile(); - $node_profile_defaults = lingotek_load_profile_defaults($entity_type); // return assoc array by node type - $profiles_list = lingotek_get_profiles(); + // Check for callbacks and cron jobs, otherwise return + $is_anonymous = user_is_anonymous(); + $dest_url = drupal_get_destination(); + $dest_str = $dest_url['destination']; + $is_cron = FALSE; + $is_cron_var = FALSE; + $is_lingotek = FALSE; + $is_admin_url = FALSE; + $is_drush = FALSE; + $is_entitycache_enabled = FALSE; + $has_permissions = user_access('administer lingotek') || user_access('manage projects') || user_access('translation') || user_access('use lingotek developer tools')? TRUE : FALSE; + global $locks; + + if ($is_anonymous) { + if (isset($locks['cron'])) { + $is_cron_var = TRUE; + } + if (strpos($dest_str, 'cron') !== FALSE) { + $is_cron = TRUE; + } + if (strpos($dest_str, 'lingotek') !== FALSE) { + $is_lingotek = TRUE; + } + if (strpos($dest_str, 'admin') !== FALSE) { + $is_admin_url = TRUE; + } + if (php_sapi_name() == "cli") { + $is_drush = TRUE; + } + if (module_exists('entitycache')) { + $is_entitycache_enabled = TRUE; + } + } + + if ($is_anonymous && !$is_cron && !$is_lingotek && !$is_admin_url && !$is_cron_var && !$has_permissions && !$is_drush && !$is_entitycache_enabled) { + return; + } $query = db_select('lingotek_entity_metadata', 'l') ->fields('l', array('entity_id', 'entity_key', 'value')) @@ -1459,30 +1594,23 @@ function lingotek_entity_load($entities, $entity_type) { } foreach ($entities as &$entity) { -// if (!lingotek_supported_type($entity->type)) { -// continue; -// } - list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); - // Node profile inheritance heirarchy - // Step 1: get global profile - $entity->lingotek = $global_profile; - // Step 2a: add entity-specific profile, if it exists - if ($id && isset($values[$id]['profile']) && is_numeric($values[$id]['profile'])) { - $entity->lingotek = array_merge($entity->lingotek, $profiles_list[$values[$id]['profile']]); + // Pull profile values for the entity + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + if (isset($values[$id]['profile'])) { + // Load the profile directly if we already know the profile ID. + $entity_profile = LingotekProfile::loadById($values[$id]['profile']); } - // Step 2b: otherwise, add bundle-specific profile - elseif (array_key_exists($bundle, $node_profile_defaults)) { - $entity->lingotek = array_merge($entity->lingotek, $node_profile_defaults[$bundle]); + else { + $entity_profile = LingotekProfile::loadByEntity($entity_type, $entity); } - // Step 3: add node-specific overrides - if ($id && isset($values[$id])) { + $entity->lingotek = $entity_profile->getAttributes(); + $entity->lingotek['profile'] = $entity_profile->getId(); + + // Overlay node-specific values (like doc ID, status, etc.) onto the entity + if (!empty($values[$id])) { $entity->lingotek = array_merge($entity->lingotek, $values[$id]); } - // Step 4: if no profile, then disabled. - if (!isset($entity->lingotek['profile']) || !strlen($entity->lingotek['profile'])) { - $entity->lingotek['profile'] = LingotekSync::PROFILE_DISABLED; - } } } @@ -1506,13 +1634,13 @@ function lingotek_entity_update($entity, $entity_type) { return; } - list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); - lingotek_entity_save($entity, $entity_type); } function lingotek_entity_upload_triggered($entity, $entity_type, $changed = TRUE, &$context = array()) { - list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + if ($entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { + return; + } if (isset($entity->lingotek_upload_override)) { if ($entity->lingotek_upload_override) { @@ -1521,11 +1649,13 @@ function lingotek_entity_upload_triggered($entity, $entity_type, $changed = TRUE return; } - if ((!lingotek_enabled_bundle($entity_type, $bundle) && $entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) || !isset($entity->language) || (!Lingotek::isSupportedLanguage($entity->language) && !($entity->language == LANGUAGE_NONE && $entity_type == 'taxonomy_term'))) { + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + + if (!isset($entity->language) || (!Lingotek::isSupportedLanguage($entity->language) && !($entity->language == LANGUAGE_NONE && $entity_type == 'taxonomy_term'))) { return; } - if (isset($entity->lingotek['upload_status']) && ($entity->lingotek['upload_status'] == LingotekSync::STATUS_TARGET)) { + if (isset($entity->lingotek['upload_status']) && (in_array($entity->lingotek['upload_status'], array(LingotekSync::STATUS_TARGET, LingotekSync::STATUS_DELETED)))) { return; } @@ -1533,13 +1663,17 @@ function lingotek_entity_upload_triggered($entity, $entity_type, $changed = TRUE return; } - if (!$changed && $entity->lingotek['upload_status'] == 'CURRENT') { + if (!$changed && isset($entity->lingotek['upload_status']) && $entity->lingotek['upload_status'] == 'CURRENT') { return; //entity has no changes. } + if ($changed && isset($entity->lingotek['document_id'])) { + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_EDITED); + } + if (!isset($entity->lingotek['document_id']) && isset($entity->lingotek['upload_status']) && $entity->lingotek['upload_status'] !== LingotekSync::STATUS_ERROR) { + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_NONE); + } - lingotek_keystore($entity_type, $id, 'upload_status', LingotekSync::STATUS_EDITED); - - if ($entity->lingotek['create_lingotek_document']) { + if (isset($entity->lingotek['auto_upload']) && $entity->lingotek['auto_upload']) { lingotek_entity_upload($entity, $entity_type, $context); } } @@ -1590,11 +1724,27 @@ function lingotek_entity_upload($entity, $entity_type, &$context = array()) { $ln = LingotekEntity::load($entity, $entity_type); $entity_has_doc_id = $ln->getMetadataValue('document_id'); module_invoke_all('lingotek_pre_upload', $ln); + + // Get the source language and available target languages. + $source = isset($ln->language) ? $ln->language : language_default()->language; + $source = Lingotek::convertDrupal2Lingotek($source); + $target_locales = Lingotek::getLanguagesWithoutSource($source); + + // Pull the desired target locales for the entity's current profile. + if (variable_get('lingotek_enable_language_specific_profiles', FALSE)) { + $profile = LingotekProfile::loadById($entity->lingotek['profile']); + $target_locales = $profile->filterTargetLocales(array_keys($target_locales)); + $with_targets = $target_locales; + } + else { + $with_targets = TRUE; + } + if ($entity_has_doc_id) { $success = LingotekApi::instance()->updateContentDocument($ln); } else { - $success = LingotekApi::instance()->addContentDocument($ln, TRUE); + $success = LingotekApi::instance()->addContentDocument($ln, $with_targets); if (!empty($ln->lingotek['allow_source_overwriting'])) { lingotek_keystore($entity_type, $id, 'original_language', $ln->language); } @@ -1603,7 +1753,7 @@ function lingotek_entity_upload($entity, $entity_type, &$context = array()) { if ($empty_context) { if ($success) { drupal_set_message(t('@node_title sent to Lingotek successfully.', array('@node_title' => $ln->getTitle()))); - lingotek_keystore($entity_type, $id, 'upload_status', LingotekSync::STATUS_CURRENT); + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_CURRENT); } else { drupal_set_message(t('Unable to send @node_title to Lingotek.', array('@node_title' => $ln->getTitle())), 'error'); @@ -1616,7 +1766,7 @@ function lingotek_entity_upload($entity, $entity_type, &$context = array()) { $context['results']['uploaded_nids'] = array(); } $context['results']['uploaded_nids'][] = $id; - lingotek_keystore($entity_type, $id, 'upload_status', LingotekSync::STATUS_CURRENT); + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_CURRENT); } else { $context['results']['upload_fails'] = isset($context['results']['upload_fails']) && is_numeric($context['results']['upload_fails']) ? $context['results']['upload_fails'] + 1 : 1; @@ -1629,17 +1779,28 @@ function lingotek_entity_upload($entity, $entity_type, &$context = array()) { lingotek_keystore($entity_type, $id, 'last_uploaded', time()); - $source = isset($ln->language) ? $ln->language : language_default()->language; - $source = Lingotek::convertDrupal2Lingotek($source); - $languages = Lingotek::getLanguagesWithoutSource($source); - if ($entity_type == 'taxonomy_term' && $entity->language == LANGUAGE_NONE && !empty($languages['en_US'])) { - unset($languages['en_US']); + if ($entity_type == 'taxonomy_term' && $entity->language == LANGUAGE_NONE && !empty($target_locales['en_US'])) { + unset($target_locales['en_US']); + } + + if ($entity_type == 'bean' && $entity->language == LANGUAGE_NONE) { + $site_locale = Lingotek::convertDrupal2Lingotek(language_default()->language); + if(!empty($target_locales[$site_locale])){ + unset($target_locales[$site_locale]); + } + } + + if ($entity_type == 'group' && $entity->language == LANGUAGE_NONE) { + $site_locale = Lingotek::convertDrupal2Lingotek(language_default()->language); + if (!empty($target_locales[$site_locale])) { + unset($target_locales[$site_locale]); + } } - // Add pending statuses for all languages on successful upload only + // Add pending statuses for all target locales on successful upload only if ($success) { - foreach ($languages as $lingotek_locale) { - lingotek_keystore($entity_type, $id, 'target_sync_status_' . $lingotek_locale, LingotekSync::STATUS_PENDING); + foreach (array_keys($target_locales) as $locale) { + lingotek_keystore($entity_type, $id, 'target_sync_status_' . $locale, LingotekSync::STATUS_PENDING); } } @@ -1657,11 +1818,12 @@ function lingotek_entity_download_triggered($entity, $entity_type, $lingotek_loc $entity = lingotek_entity_load_single($entity_type, $entity); } - if (!$entity->lingotek['sync_method']) { - return FALSE; + $profile = LingotekProfile::loadByEntity($entity_type, $entity); + if ($profile->isAutoDownload($lingotek_locale)) { + return lingotek_entity_download($entity, $entity_type, $lingotek_locale, $context); } + return FALSE; - return lingotek_entity_download($entity, $entity_type, $lingotek_locale, $context); } /* @@ -1681,6 +1843,9 @@ function lingotek_entity_download($entity, $entity_type, $lingotek_locale, &$con if (is_numeric($entity)) { $entity = lingotek_entity_load_single($entity_type, $entity); } + elseif (empty($entity->language)) { + lingotek_entity_init_language($entity_type, $entity); + } if ($entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { return FALSE; @@ -1703,7 +1868,7 @@ function lingotek_entity_download($entity, $entity_type, $lingotek_locale, &$con $document_id = $entity->lingotek['document_id']; LingotekLog::trace('lingotek_download_document @doc_id (@target)', array('@doc_id' => $document_id, '@target' => $lingotek_locale)); - $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale, FALSE); + $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale); $params = array( 'documentId' => $document_id, 'targetLanguage' => $lingotek_locale, @@ -1716,9 +1881,13 @@ function lingotek_entity_download($entity, $entity_type, $lingotek_locale, &$con $text = $_lingotek_client->downloadTriggered("downloadDocument", $params); try { - $xml = new SimpleXMLElement($text); + $xml = new LingotekXMLElement($text); + if (!lingotek_current_xml_format($xml)) { + drupal_set_message(t('Outdated or invalid format detected when downloading translations for @entity_type #@entity_id. Please re-upload the document to the TMS.', array('@entity_type' => $entity_type, '@entity_id' => $id)), 'error', FALSE); + throw new LingotekException(t('Outdated or invalid format detected when downloading translations for @entity_type #@entity_id. Please re-upload the document to the TMS.', array('@entity_type' => $entity_type, '@entity_id' => $id))); + } } catch (Exception $e) { - LingotekLog::error("downloadDocument FAILED. Error: @error. Text: !xml.", array('!xml' => $text, '@error' => $e->getMessage())); + LingotekLog::error("downloadDocument FAILED for @entity_type #@entity_id. Error: @error.", array('@entity_type' => $entity_type, '@entity_id' => $id, '@error' => $e->getMessage())); if ($context) { $context['results']['download_fails'] = isset($context['results']['download_fails']) && is_numeric($context['results']['download_fails']) ? $context['results']['download_fails'] + 1 : 1; @@ -1762,6 +1931,13 @@ function lingotek_entity_download($entity, $entity_type, $lingotek_locale, &$con if ($status == LingotekSync::STATUS_READY) { lingotek_keystore($entity_type, $id , 'target_sync_status_' . $lingotek_locale, LingotekSync::STATUS_CURRENT); } + else if ($status == LingotekSync::STATUS_READY_INTERIM) { + lingotek_keystore($entity_type, $id , 'target_sync_status_' . $lingotek_locale, LingotekSync::STATUS_INTERIM); + } + // If local translations have been deleted than status will be DELETED. Just make the status CURRENT because you are downloading existing translations from Lingotek + if ($status == LingotekSync::STATUS_DELETED) { + lingotek_keystore($entity_type, $id , 'target_sync_status_' . $lingotek_locale, LingotekSync::STATUS_CURRENT); + } // If a target is downloaded in-place, mark it as changed so the user knows not to upload it again. if (lingotek_is_inplace_source_download($entity_type, $storage_entity, $lingotek_locale)) { lingotek_keystore($entity_type, $id, 'source_language_' . $lingotek_locale, $entity->language); @@ -1880,6 +2056,7 @@ function lingotek_is_module_setup($redirect = TRUE) { } drupal_goto($redirect_link); // If something is missing - Go to the Setup Process } + lingotek_check_for_updates(); return TRUE; } @@ -1900,13 +2077,13 @@ function lingotek_is_config_missing() { $val = variable_get($required_variable, NULL); if (empty($val)) { if ($required_variable == 'lingotek_login_id') { - return 'admin/config/lingotek/new-account'; + return LINGOTEK_MENU_LANG_BASE_URL . '/new-account'; } elseif ($required_variable == 'lingotek_community_identifier') { - return 'admin/config/lingotek/community-select'; + return LINGOTEK_MENU_LANG_BASE_URL . '/community-select'; } elseif ($required_variable == 'lingotek_project' || $required_variable == 'lingotek_workflow' || $required_variable == 'lingotek_vault') { - return 'admin/config/lingotek/project-vault-select'; + return LINGOTEK_MENU_LANG_BASE_URL . '/project-vault-select'; } elseif ($required_variable == 'lingotek_oauth_consumer_id' || $required_variable == 'lingotek_oauth_consumer_secret') { return TRUE; @@ -1916,13 +2093,13 @@ function lingotek_is_config_missing() { // special handling of lingotek_enabled_fields, since none could be selected $enabled_fields = variable_get('lingotek_enabled_fields', FALSE); if (!isset($enabled_fields['node'])) { - return 'admin/config/lingotek/node-translation-settings'; + return LINGOTEK_MENU_LANG_BASE_URL . '/node-translation-settings'; } if (module_exists('comment') && !isset($enabled_fields['comment'])) { - return 'admin/config/lingotek/comment-translation-settings'; + return LINGOTEK_MENU_LANG_BASE_URL . '/comment-translation-settings'; } if (variable_get('lingotek_translate_config', 'EMPTY') === 'EMPTY') { - return 'admin/config/lingotek/additional-translation-settings'; + return LINGOTEK_MENU_LANG_BASE_URL . '/additional-translation-settings'; } return FALSE; // all required configuration variables are set @@ -1951,21 +2128,33 @@ function lingotek_comment_view($comment, $view_mode, $langcode) { $comment->content['lingotek_link']['#markup'] = lingotek_workbench_icon('comment', $comment->cid, Lingotek::convertDrupal2Lingotek($language->language)); } } + /** * Implements hook_entity_info_alter(). */ function lingotek_entity_info_alter(&$entity_info) { + $profiles = lingotek_get_profiles(); + $field_translation = FALSE; + + //Check if there is any active profile that's using field Translation. + foreach ($profiles as $profile) { + if ($profile->getUsage() > 0 && !$profile->isNodeBased()) { + $field_translation = TRUE; + break; + } + } + if (isset($entity_info['comment'])) { - $entity_info['comment']['translation']['lingotek'] = TRUE; + $entity_info['comment']['translation']['lingotek'] = $field_translation; } if (isset($entity_info['field_collection_item'])) { - $entity_info['field_collection_item']['translation']['lingotek'] = TRUE; + $entity_info['field_collection_item']['translation']['lingotek'] = $field_translation; } if (isset($entity_info['message_type'])) { - $entity_info['message_type']['translation']['lingotek'] = TRUE; + $entity_info['message_type']['translation']['lingotek'] = $field_translation; } if (isset($entity_info['taxonomy_term'])) { - $entity_info['taxonomy_term']['translation']['lingotek'] = TRUE; + $entity_info['taxonomy_term']['translation']['lingotek'] = $field_translation; } } @@ -1995,10 +2184,12 @@ function lingotek_get_host_language($entity_type, $entity, $read_only = TRUE) { $site_default_lang = language_default(); $site_default_langcode = $site_default_lang->language; + $host_language = isset($_POST['language']) ? $_POST['language'] : NULL; - if (!$read_only && isset($_POST['language'])) { - return $_POST['language']; + if (!$read_only && isset($host_language)) { + return $host_language; } + $host = (module_exists('field_collection') && $entity_type == 'field_collection_item' ? lingotek_get_fc_parent($entity) : NULL); if (!$host) { // unable to locate parent. Abort. @@ -2027,9 +2218,17 @@ function lingotek_get_host_language($entity_type, $entity, $read_only = TRUE) { } function lingotek_normalize_special_field_language($entity_type, $entity, $read_only = TRUE) { - $host_language = lingotek_get_host_language($entity_type, $entity, $read_only); - $excluded_fields = field_read_fields(array('type' => 'field_collection')); + $excluded_fields = &drupal_static(__FUNCTION__); + if (!isset($excluded_fields)) { + if ($cache = cache_get('lingotek__excluded_fc_fields')) { + $excluded_fields = $cache->data; + } + else { + $excluded_fields = field_read_fields(array('type' => 'field_collection')); + cache_set('lingotek__excluded_fc_fields', $excluded_fields, 'cache', CACHE_TEMPORARY); + } + } foreach ($entity as $key => $value) { // for each field in the entity // if the given field is a member of the field types then normalize the languages @@ -2088,11 +2287,15 @@ function lingotek_normalize_special_field_language($entity_type, $entity, $read_ * Implements hook_entity_presave(). */ function lingotek_entity_presave($entity, $entity_type) { - $special_entities = array('field_collection_item', 'taxonomy_term', 'message_type', 'fieldable_panels_pane'); + // handle special entities + $default_special_entities = array('field_collection_item', 'taxonomy_term', 'message_type', 'fieldable_panels_pane', 'bean'); + $special_entities = variable_get('lingotek_special_entity_types', $default_special_entities); if (!in_array($entity_type, $special_entities)) { return; } + lingotek_normalize_special_field_language($entity_type, $entity, FALSE); + } /** @@ -2154,7 +2357,6 @@ function lingotek_form_comment_form_alter(&$form, $form_state) { * Implements hook_field_language_alter(). */ function lingotek_field_language_alter(&$display_language, $context) { - $entity = $context['entity']; $entity_type = $context['entity_type']; // If no language is set on the entity itself, do nothing. @@ -2172,11 +2374,10 @@ function lingotek_field_language_alter(&$display_language, $context) { // because Lingotek translation hasn't finished yet, or synchonization with // Lingotek hasn't yet occurred. In this case, fall back to displaying // the default language for each field. - $field_collection_field_types = field_read_fields(array('type' => 'field_collection')); foreach ($display_language as $field => $display_language_code) { - $is_field_collection = isset($field_collection_field_types[$field]); - if ($is_field_collection) { + $field_data = field_info_field($field); + if ($field_data['module'] == 'field_collection') { continue; } // Use the entity language if the field does not have an entry in the display @@ -2187,6 +2388,20 @@ function lingotek_field_language_alter(&$display_language, $context) { } } +/** + * Implements hook_node_delete(). + */ +function lingotek_node_delete($node) { + $target_ids = lingotek_get_target_node_ids($node->nid); + + if (!empty($target_ids)) { + $entity = lingotek_entity_load_single('node', $node->nid); + if (isset($entity->lingotek['lingotek_nodes_translation_method']) && $entity->lingotek['lingotek_nodes_translation_method'] == 'node') { + lingotek_delete_target_nodes('node', $target_ids); + } + } +} + /* * Implements hook_entity_delete(). * @@ -2198,10 +2413,26 @@ function lingotek_entity_delete($entity, $type) { $api = LingotekApi::instance(); $api->removeDocument($entity->lingotek['document_id'], FALSE); } - db_delete('lingotek_entity_metadata') - ->condition('entity_type', $type) - ->condition('entity_id', lingotek_entity_extract_ids($type, $entity)) - ->execute(); + + list($id, $vid, $bundle) = lingotek_entity_extract_ids($type, $entity); + LingotekSync::delete_entity_from_metadata($type, $id); + + // Set a flag for the deleted entity so it's not re-uploaded if it + // has nested entities within fields, such as field collections. + if ($type != 'i18n_translation') { + LingotekSync::setUploadStatus($type, $id, LingotekSync::STATUS_DELETED); + } +} + +/* + * Removes the target nodes of a source node. + */ +function lingotek_delete_target_nodes($entity_type, $target_ids) { + foreach ($target_ids as $id) { + LingotekSync::delete_entity_from_metadata($entity_type, $id); + node_delete($id); + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_DELETED); + } } function lingotek_get_profile_fields($include_readonly = TRUE, $include_changeable = TRUE) { @@ -2219,8 +2450,8 @@ function lingotek_get_profile_fields($include_readonly = TRUE, $include_changeab $profile_fields = array_merge($profile_fields, array( 'profile', 'name', - 'create_lingotek_document', - 'sync_method', + 'auto_upload', + 'auto_download', 'allow_target_localization', 'allow_source_overwriting', 'allow_community_translation', @@ -2277,18 +2508,37 @@ function lingotek_get_change_workflow_form($form, &$form_state, $node = NULL) { $bulk_grid = FALSE; $multiple = FALSE; $curr_workflow_id = ''; - + $entity_type = $form_state['entity_type']; if (isset($form_state['nids'])) { $nids = $form_state['nids']; $bulk_grid = TRUE; $multiple = count($nids) > 1; if (!$multiple) { - $node = lingotek_node_load_default(reset($nids)); - $curr_workflow_id = $node->lingotek['workflow_id']; + if($entity_type === 'config'){ + $curr_workflow_id = LingotekSync::getWorkflowIdFromConfigSet($nids[0]); + } + elseif ($entity_type == 'menu_link') { + $curr_workflow_id = LingotekSync::getWorkflowIdFromEntityId($nids[0]); + } + elseif ($entity_type == 'paragraphs_item') { + $curr_workflow_id = LingotekSync::getWorkflowIdFromEntityId($nids[0]); + } + else { + $node = lingotek_node_load_default(reset($nids)); + $curr_workflow_id = $node->lingotek['workflow_id']; + } } else { if (!$second_run) { - drupal_set_message(t('You will be changing the workflow for @number nodes.', array('@number' => count($nids))), 'warning'); + if ($entity_type == 'menu_link') { + drupal_set_message(t('You will be changing the workflow for @number menu links.', array('@number' => count($nids))), 'warning'); + } + elseif ($entity_type == 'paragraphs_item') { + drupal_set_message(t('You will be changing the workflow for @number paragraph items.', array('@number' => count($nids))), 'warning'); + } + else { + drupal_set_message(t('You will be changing the workflow for @number comments.', array('@number' => count($nids))), 'warning'); + } } $node = new stdClass(); $node->lingotek = lingotek_get_global_profile(); @@ -2444,34 +2694,38 @@ function lingotek_get_phases_by_workflow_id($workflow_id) { } /** - * Implements hook_menu_link_insert(). - * - * @see lingotek_menu_link_update() - */ +* Implements hook_menu_link_insert(). +* +* @see lingotek_menu_link_update() +*/ function lingotek_menu_link_insert($link) { - lingotek_menu_link_update($link); + if (!variable_get('lingotek_advanced_menu_links', FALSE)) { + lingotek_menu_link_update($link); + } } /** - * Implements hook_menu_link_update(). - * - * Update the menu link's language to be language neutral, if desired, - * otherwise set it to be the same as it's node. - */ +* Implements hook_menu_link_update(). +* +* Update the menu link's language to be language neutral, if desired, +* otherwise set it to be the same as it's node. +*/ function lingotek_menu_link_update($link) { - if (lingotek_translate_menu_link($link)) { - lingotek_set_menu_link_language($link['mlid'], LANGUAGE_NONE); - } - else { - if (!strstr($link['link_path'], '/')) { - // No link language to set! - return; + if (!variable_get('lingotek_advanced_menu_links', FALSE)) { + if (lingotek_translate_menu_link($link)) { + lingotek_set_menu_link_language($link['mlid'], LANGUAGE_NONE); } - list($path, $node_id) = explode('/', $link['link_path']); - if ($node_id && (int) $node_id) { - // Get the node's language. - $node = node_load($node_id); - lingotek_set_menu_link_language($link['mlid'], $node->language); + else { + if (!strstr($link['link_path'], '/')) { + // No link language to set! + return; + } + list($path, $node_id) = explode('/', $link['link_path']); + if ($node_id && (int) $node_id) { + // Get the node's language. + $node = node_load($node_id); + lingotek_set_menu_link_language($link['mlid'], $node->language); + } } } } @@ -2529,187 +2783,13 @@ function lingotek_field_attach_create_bundle($entity_type, $bundle) { variable_set('lingotek_entity_profiles', $profiles); } -function lingotek_clear_exceptions_modal($bundle_name) { - ctools_include('node.pages', 'node', ''); - ctools_include('modal'); - ctools_include('ajax'); - - $form_state = array( - 'ajax' => TRUE, - 'entity_type' => 'node', - 'bundle_name' => $bundle_name, - ); - - $output = ctools_modal_form_wrapper('lingotek_clear_exceptions_form', $form_state); - - if (!empty($form_state['executed'])) { - $commands = array(); - $commands[] = ctools_modal_command_dismiss(); - $commands[] = ctools_ajax_command_reload(); - print ajax_render($commands); - drupal_exit(); - } - - print ajax_render($output); -} - -function lingotek_clear_exceptions_form($form, $form_state) { - $profile_options = lingotek_get_profiles_by_name(); - $content_type = $form_state['bundle_name']; - $entity_profiles = variable_get('lingotek_entity_profiles'); - $profile_index = $entity_profiles['node'][$content_type]; - $profile = $profile_options[$profile_index]; - $description = t("This will apply the selected profile (@profile) to all @content_type nodes that have previously been configured independent of the rest of their content type. Note: some settings may remain different such as vault, project, workflow, and translation method (node-based/field-based).", array('@profile' => $profile, '@content_type' => $content_type)); - - $form['clear_exceptions'] = array( - '#type' => 'fieldset', - '#title' => t('Clear Profile Exceptions'), - '#description' => filter_xss($description), - ); - $form['clear_exceptions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Submit'), - ); - $form['bundle_name'] = array( - '#type' => 'hidden', - '#value' => $content_type, - ); - - return $form; -} - -function lingotek_clear_exceptions_form_submit($form_state, $form) { - $content_type = $form_state['bundle_name']['#value']; - $delete_keys = "'allow_community_translation', 'create_lingotek_document', 'sync_method', 'url_alias_translation', 'profile'"; - $delete_query = db_query("DELETE lem.* FROM {node} AS n JOIN lingotek_entity_metadata AS lem ON n.nid = lem.entity_id WHERE entity_key IN (" . $delete_keys . ") AND type = '" . $content_type . "'"); - $delete_query->execute(); - drupal_set_message(t('All translation profile exceptions for nodes of type "@content_type" have been removed.', array('@content_type' => $content_type))); -} - -function lingotek_add_workflow_settings_form($form, $form_state, $node) { - // show workflow-change option only if there are more than one workflow and if it's an existing node - $workflows = LingotekApi::instance()->listWorkflows(); - if ($workflows && count($workflows) > 1) { - $form['lingotek']['workflow_id'] = array( - '#type' => 'select', - '#title' => t('Workflow'), - '#prefix' => '
      ', - '#description' => t('Choose the Workflow to associate with this content item.'), - '#default_value' => $node->lingotek['workflow_id'], - '#options' => $workflows, - '#empty_option' => '(select one)', - ); - if (empty($node->nid)) { // don't show prefill stuff on new nodes - $form['lingotek']['workflow_id']['#suffix'] = '
      '; - } - else { - // add the callback and checkbox and phase-select - $form['lingotek']['workflow_id']['#ajax'] = array( - 'callback' => 'lingotek_get_change_workflow_form_callback', - 'wrapper' => 'prefill-phases-div', - 'method' => 'replace', - 'effect' => 'fade', - ); - $form['lingotek']['prefill_phases_checkbox'] = array( - '#type' => 'checkbox', - '#title' => t('Restore to a phase in the new workflow'), - '#default_value' => TRUE, - '#description' => t('Prefill the new workflow with translations from the previous workflow'), - '#states' => array( - 'invisible' => array(':input[name="lingotek[workflow_id]"]' => array('value' => $node->lingotek['workflow_id'])), - ) - ); - - $form['lingotek']['prefill_phase_select'] = array( - '#title' => t("Desired Prefill Phase"), - '#description' => t('Please select the highest phase which should be prefilled for the new workflow'), - '#type' => 'select', - '#states' => array( - 'visible' => array(':input[name="lingotek[workflow_id]"]' => array('!value' => $node->lingotek['workflow_id']), - array(':input[name="lingotek[prefill_phases_checkbox]"]' => array('checked' => TRUE))), - ), - '#suffix' => '
      ', - ); - if (isset($form_state['values']['lingotek']['workflow_id']) && $form_state['values']['lingotek']['workflow_id'] != NULL) { - $form['lingotek']['prefill_phase_select']['#options'] = lingotek_get_phases_by_workflow_id($form_state['values']['lingotek']['workflow_id']); - } - else { - $form['lingotek']['prefill_phase_select']['#options'] = array('-1' => '(first choose a workflow)'); - $form['lingotek']['prefill_phase_select']['#disabled'] = TRUE; - } - } - } - return $form; -} - -function lingotek_add_advanced_form($form, $form_state, $node) { - $show_advanced = LingotekAccount::instance()->showAdvanced(); - if ($show_advanced) { - // Only show these options if the Lingotek document hasn't yet been created. - if (!$node->lingotek['document_id'] && class_exists('LingotekApi')) { - - // Available projects. - if ($projects = LingotekApi::instance()->listProjects()) { - - $form['lingotek']['project_id'] = array( - '#type' => 'select', - '#title' => 'Project', - '#description' => t('Select the translation project with which this item should be associated.'), - '#default_value' => $node->lingotek['project_id'], - '#options' => $projects, - ); - } - - // Translation Memory (TM) Vault. - if ($vaults = LingotekApi::instance()->listVaults()) { - $form['lingotek']['vault_id'] = array( - '#type' => 'select', - '#title' => t('TM Vault'), - '#description' => t('Choose the TM vault to associate with this content item.'), - '#default_value' => $node->lingotek['vault_id'], - '#options' => $vaults, - ); - } - } // END: Document not created yet - } - - $form['lingotek']['content_end'] = array( - '#markup' => '
      ', - ); - - $form['lingotek']['advanced'] = array( - '#type' => 'fieldset', - '#title' => t('Advanced'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#group' => 'developer_settings', - '#access' => user_access('use lingotek developer tools'), - ); - $form['lingotek']['advanced']['document_id'] = array( - '#type' => 'textfield', - '#title' => t('Document Id'), - '#description' => t("Read/Overwrite the document ID associated with the document. This can break the translation process but can also be used to help figure out if something is wrong."), - '#default_value' => $node->lingotek['document_id'], - ); - $form['lingotek']['advanced']['current_lingonode'] = array( - '#type' => 'textarea', - '#title' => t('Node Data'), - '#value' => !empty($node->nid) ? (json_encode(lingotek_keystore('node', $node->nid))) : t('None'), - '#disabled' => TRUE, - '#attributes' => array('rows' => '2') - ); - - return $form; -} - function lingotek_add_menu_link_form($form, $form_state, $node) { if (isset($form['menu']['link'])) { - // if menu translation is enabled, if the default language is english, - // and if either 'english' or 'language neutral' is selected for the node - // being created, then show the option to set the menu item as 'und' for - // i18n_string translation. + // if menu translation is enabled, if the default language is english, + // and if either 'english' or 'language neutral' is selected for the node + // being created, then show the option to set the menu item as 'und' for + // i18n_string translation. $i18n_language = 'en'; - $form['menu']['link']['lingotek_translate'] = array( '#type' => 'select', '#title' => 'Menu translation through Lingotek', @@ -2736,3 +2816,143 @@ function lingotek_field_attach_delete_bundle($entity_type, $bundle, $instances) variable_set('lingotek_entity_profiles', $entity_profiles); } } + +function lingotek_check_for_updates() { + global $base_url; + $mandatory_updates = array( + 'lingotek_update_7607', + ); + foreach ($mandatory_updates as $update) { + if (!variable_get($update, FALSE)) { + drupal_set_message(t('Some ' . l('database updates', $base_url . '/update.php') . ' are required for the Lingotek Translation module.')); + } + } +} + +/** + * Implements hook_lingotek_entity_upload_alter(). + */ +function lingotek_lingotek_entity_upload_alter(&$params) { + $entity = $params['entity']; + $entity_type = $params['entity_type']; + $xml = $params['xml']; + $langcode = $params['langcode']; + + // Pull metatags for translation + if (module_exists('metatag') && variable_get('lingotek_translate_metatags') && metatag_entity_supports_metatags($entity_type) && !empty($entity->metatags[$langcode])) { + lingotek_attach_xml($xml, 'metatags', $entity->metatags[$langcode]); + } +} + +/** + * Implements hook_lingotek_entity_download_alter(). + */ +function lingotek_lingotek_entity_download_alter(&$params) { + $entity = $params['entity']; + $entity_type = $params['entity_type']; + $xml = $params['xml']; + $langcode = $params['langcode']; + + // Handle metatag translations + if (module_exists('metatag') && variable_get('lingotek_translate_metatags') && metatag_entity_supports_metatags($entity_type) && !empty($xml->metatags)) { + $metatags = array($langcode => array()); + foreach ($xml->metatags as $tag => $content) { + foreach ($content as $k => $v) { + foreach ($v as $value => $element) { + foreach ($element as $text) { + $metatags[$langcode][$k] = array(); + $metatags[$langcode][$k]['value'] = lingotek_unfilter_placeholders(decode_entities($text)); + } + } + } + // Remove the self-reference to the metatags tag. + unset($content[0]); + } + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + metatag_metatags_save($entity_type, $id, $vid, $metatags, $langcode); + } +} + +function lingotek_attach_xml($xml, $name, $value) { + if (is_object($value) || is_array($value)) { + $sub_xml = $xml->addChild($name); + foreach ($value as $k => $v) { + lingotek_attach_xml($sub_xml, $k, $v); + } + } + else { + $sub_xml = $xml->addChild($name); + if ($value) { + $element = $sub_xml->addChild('element'); + //TODO: encode $value to prevent xml corruption. + $element->addCData(lingotek_filter_placeholders($value)); + } + } +} + +function lingotek_filter_profiles_by_bundle_and_langcode($bundle, $profiles) { + + $profile_langcode_map = array(); + + // Separate the bundle from the locale and get the corresponding langcodes. + foreach ($profiles as $k => $v) { + if (strpos($k, '__')) { + list($profile_bundle, $locale) = explode('__', $k); + $langcode = Lingotek::convertLingotek2Drupal($locale); + } + else { + $profile_bundle = $k; + $langcode = 'DEFAULT'; + } + + // Prune the profile settings that aren't related to this bundle. + if ($bundle == $profile_bundle) { + $profile_langcode_map[$langcode] = $v; + } + } + + // Convert the "inherit" ones to the default for this bundle. + foreach ($profile_langcode_map as $k => $v) { + if ($v === LingotekSync::PROFILE_INHERIT) { + $profile_langcode_map[$k] = $profile_langcode_map['DEFAULT']; + } + } + + return $profile_langcode_map; +} + +/** +* Gets all nodes in a given translation set without using node_access tag. This +* function is just a copy of translation module's translation_node_get_translations() +* function but with the addTag('node_access') removed. +* +* @param $tnid +* The translation source nid of the translation set, the identifier of the +* node used to derive all translations in the set. +* +* @return +* Array of partial node objects (nid, title, language) representing all +* nodes in the translation set, in effect all translations of node $tnid. +* including node $tnid itself. Because these are partial nodes, you need to +* node_load() the full node, if you need more properties. The array is +* indexed by language code. +*/ +function lingotek_node_get_translations($tnid) { + if (is_numeric($tnid) && $tnid) { + $translations = &drupal_static(__FUNCTION__, array()); + + if (!isset($translations[$tnid])) { + $translations[$tnid] = array(); + $result = db_select('node', 'n') + ->fields('n', array('nid', 'type', 'uid', 'status', 'title', 'language')) + ->condition('n.tnid', $tnid) + ->execute(); + + foreach ($result as $node) { + $langcode = entity_language('node', $node); + $translations[$tnid][$langcode] = $node; + } + } + return $translations[$tnid]; + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.page.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.page.inc index 88dd47d6..d2d1fb38 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.page.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.page.inc @@ -10,7 +10,7 @@ */ function lingotek_download_translations_form($form, $form_state, $node, $document = NULL) { - module_load_include('lingotek', 'lingotek.bulk_grid', 'inc'); + module_load_include('inc', 'lingotek', 'lingotek.bulk_grid'); $form = array(); $document = is_null($document) ? LingotekDocument::load($node->lingotek['document_id']) : $document; @@ -20,7 +20,8 @@ function lingotek_download_translations_form($form, $form_state, $node, $documen $sum_progress = 0; if ($document_progress->results == 'success') { - $rows = array(); + $lang_rows = array(); + $phase_rows = array(); foreach ($document_progress->translationTargets as $target) { $current_phase = $document->currentPhase($target->id); $marked_complete = FALSE; @@ -30,25 +31,29 @@ function lingotek_download_translations_form($form, $form_state, $node, $documen $phase_complete_percent = 0; } $language_link = l(lingotek_language_field_lookup('native', $target->language), 'lingotek/workbench/node/' . $node->nid . '/' . $target->language, array('attributes' => array('target' => '_blank'))); - $language_link .= ' (' . lingotek_language_field_lookup('name', $target->language) . ')'; + $language_name = lingotek_language_field_lookup('name', $target->language); + $language_link .= ' (' . $language_name . ')'; - $row = array( + $lang_row = array( 'column_1' => array('data' => $language_link, 'width' => '40%'), 'column_2' => array('data' => lingotek_grid_create_progress_bar($target->percentComplete), 'width' => '40%'), 'column_3' => array('data' => ''),//$target->percentComplete == 100 ? '' : '', 'width' => '20%' '#attributes' => array('class' => array('bold-row')), + 'language_name' => $language_name, ); LingotekLog::trace("lingotek_pm table row [@locale]", array('@locale' => $target->language)); - $rows[$target->language] = $row; + $lang_rows[$target->language] = $lang_row; + foreach ($target->phases as $phase) { - $row = array( + $phase_row = array( 'column_1' => $phase->name, 'column_2' => lingotek_grid_create_progress_bar($phase->percentComplete), 'column_3' => $phase->isMarkedComplete ? '' : '', '#attributes' => array('class' => array('no-checkbox-row')), + 'language_name' => $language_name, ); - $rows[$target->language . '_' . $phase->name] = $row; + $phase_rows[$target->language . '_' . $phase->name] = $phase_row; $marked_complete = $phase->isMarkedComplete; } @@ -63,6 +68,27 @@ function lingotek_download_translations_form($form, $form_state, $node, $documen } } + // Sort rows by language name + $lang_rows_sorted = array(); + foreach ($lang_rows as $key => $row) { + $lang_rows_sorted[$key] = $row['language_name']; + } + + array_multisort($lang_rows_sorted, SORT_ASC, $lang_rows); + + // Combine lang_rows and phase_rows in alphabetical order + $rows = array(); + foreach ($lang_rows as $lang_key => $lang_row) { + unset($lang_row['language_name']); + $rows [$lang_key] = $lang_row; + foreach ($phase_rows as $phase_key => $phase_row) { + unset($phase_row['language_name']); + if (strpos($phase_key, $lang_key) !== false) { + $rows [$phase_key] = $phase_row; + } + } + } + $form['fieldset'] = array( '#type' => 'fieldset', '#title' => t('Download Translations'), @@ -315,8 +341,6 @@ function lingotek_pm($node) { ), ); - $sync_status = $node->lingotek['upload_status']; - // node translation support if ($node->tnid != 0 && $node->tnid != $node->nid) { $output[] = array( @@ -333,21 +357,28 @@ function lingotek_pm($node) { return $output; } + if((isset($node->lingotek['upload_status']) && $node->lingotek['upload_status'] == LingotekSync::STATUS_NONE) || !isset($node->lingotek['upload_status'])){ + $content_push_form = drupal_get_form('lingotek_push_form', $node); + $output['content_push'] = array( + '#markup' => drupal_render($content_push_form), + ); + return $output; + } if (lingotek_supported_node($node) && Lingotek::isSupportedLanguage($node->language)) { - if (($node->lingotek['upload_status'] == LingotekSync::STATUS_EDITED)) { + if (isset($node->lingotek['upload_status']) && $node->lingotek['upload_status'] == LingotekSync::STATUS_EDITED) { $content_push_form = drupal_get_form('lingotek_push_form', $node); $output['content_push'] = array( '#markup' => drupal_render($content_push_form), ); } - if ($node->lingotek['document_id']) { + if (!empty($node->lingotek['document_id'])) { $document = LingotekDocument::load($node->lingotek['document_id']); $progress = $document->getProgress(); - if ($progress->results == 'success') { + if ($progress && $progress->results == 'success') { $download_form = drupal_get_form('lingotek_download_translations_form', $node, $document); $output[] = array( @@ -374,7 +405,7 @@ function lingotek_pm($node) { $status = $document->getImportStatus(); $message = ' ' . check_plain($status) . ' ' . l(' ' . t('Refresh'), '', array('html' => TRUE, 'attributes' => array('class' => 'ltk-icon', 'title' => t('Refresh'), 'onclick' => array('location.reload();return false;')))); - drupal_set_message(t('Content Import Status:') . ' ' . $message, 'status'); + drupal_set_message(t('Content Import Status:') . ' ' . $message, 'status');//xss checks not necessary here. Text relies on html, and content is all generated in php $output['import_status'] = array( '#type' => 'fieldset', @@ -520,8 +551,8 @@ function lingotek_mark_phases_complete($form, $form_state, $node, $document = NU foreach ($targets as $target) { $language = Lingotek::convertLingotek2Drupal($target->language); $current_phase = $document->currentPhase($target->id); - - $phase_complete_percent = empty($current_phase->percentComplete) ? 0 : $current_phase->percentComplete; + $curr_percent = $current_phase->percentComplete; + $phase_complete_percent = empty($curr_percent) ? 0 : $curr_percent; if ($current_phase && $current_phase->canBeMarkedComplete()) { $phase_link = l($current_phase->name, '', array('attributes' => array( @@ -645,9 +676,10 @@ function page_sync_comment_translations($comment_id) { */ function lingotek_workbench_redirect($entity_type, $entity_id, $lingotek_locale) { $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale); + $entitycache_table = "cache_entity_$entity_type"; - if (module_exists('entitycache')) { - cache_clear_all($entity_id, "cache_entity_$entity_type"); + if (module_exists('entitycache') && db_table_exists($entitycache_table)) { + cache_clear_all($entity_id, $entitycache_table); } if ($entity_type == 'config') { @@ -678,31 +710,35 @@ function lingotek_workbench_redirect($entity_type, $entity_id, $lingotek_locale) * This is a function that redirects to the entity specified for a particular lingotek locale (e.g., en_US, de_DE, fr_FR) */ function lingotek_entity_view_redirect($entity_type, $entity_id, $lingotek_locale) { + $drupal_languages = language_list(); $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale, TRUE); - $url = drupal_get_normal_path($entity_type . '/' . $entity_id); //assumes url detection - $url_language_detection_enabled = TRUE; //TO-DO: detect when it is not enabled, currently it is assumed + $language = isset($drupal_languages[$drupal_language_code])? $drupal_languages[$drupal_language_code] : language_default(); + $entity = entity_load_single($entity_type, $entity_id); + if(!$entity){ + drupal_not_found(); + } - if ($drupal_language_code) { - $default_language = language_default(); - if ($drupal_language_code !== $default_language->language) { - $url = $drupal_language_code . "/" . $url; + // check for node-based target translation + if(lingotek_uses_node_translation($entity)) { + $target_nodes = lingotek_node_get_translations($entity_id); + foreach ($target_nodes as $langcode => $node_info) { + if($drupal_language_code == $langcode){ + // re-set entity to the target node instead of the source node + $entity_id = $target_nodes[$langcode]->nid; + $entity = entity_load_single($entity_type, $entity_id); + continue; + } } } - else { - drupal_set_message(t('The language requested is not available. The default language will be shown instead.'), 'warning'); - } - if (!$url_language_detection_enabled) { //TO-DO: consider extending this functionality to use additional language detection methods - $output = array(); - $message = t('The URL detection method is required for viewing content in a specified language. In order to use this feature an administrator will need to enable the URL detection method in the Language detection and selection settings', array('@link' => url('admin/config/regional/language/configure'))); - $output['message'] = array( - '#type' => 'fieldset', - '#title' => t('This feature requires URL language detection to be enabled'), - '#description' => $message, - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - drupal_set_message($message, 'warning'); - return $output; - } - drupal_goto($url); + + $entity_uri = entity_uri($entity_type, $entity); + $path = $entity_uri['path']; + $url_language_detection_enabled = TRUE; //TO-DO: detect when it is not enabled, currently it is assumed + + $options = array( + 'language' => $language + ); + + locale_language_url_rewrite_url($path, $options); + drupal_goto($path, $options); } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.remote.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.remote.inc index 5871492a..7d59d0db 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.remote.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.remote.inc @@ -24,7 +24,7 @@ function lingotek_get_target_node($node, $drupal_language_code) { return $node; } - $tset = translation_node_get_translations($node->nid); + $tset = lingotek_node_get_translations($node->nid); if ($node->tnid == 0) { $node->tnid = $node->nid; @@ -70,8 +70,8 @@ function lingotek_get_target_node($node, $drupal_language_code) { $localized_node->comment = $node->comment; $localized_node->promote = $node->promote; $localized_node->sticky = $node->sticky; - $localized_node->status = 1; // default to published if revisions are not enabled. - $localized_node->create_lingotek_document = FALSE; + $localized_node->status = $node->status; + $localized_node->auto_upload = FALSE; // Grandfather field settings/values from source node to target. $source_fields = field_info_instances('node', $node->type); @@ -86,12 +86,17 @@ function lingotek_get_target_node($node, $drupal_language_code) { $lingotek_fields = variable_get('lingotek_enabled_fields'); foreach ($lingotek_fields['node'][$localized_node->type] as $field_name) { + // Continue if translation of original node titles is enabled. + if ($field_name == 'title') { + continue; + } $field = $node->$field_name; $f = array(); if (isset($field[$node->language])) { foreach ($field[$node->language] as $key => $value) { - if (isset($value['format'])) + if (isset($value['format'])) { $f[$drupal_language_code][$key]['format'] = $value['format']; + } } } @@ -99,7 +104,7 @@ function lingotek_get_target_node($node, $drupal_language_code) { } node_save($localized_node); - lingotek_keystore('node', $localized_node->nid, 'upload_status', LingotekSync::STATUS_TARGET); + LingotekSync::setUploadStatus('node', $localized_node->nid, LingotekSync::STATUS_TARGET); // Child node should keep its parent node's profile, for rules integration. $parent_profile = lingotek_keystore('node', $node->nid, 'profile'); if ($parent_profile !== FALSE) { @@ -110,260 +115,390 @@ function lingotek_get_target_node($node, $drupal_language_code) { return $localized_node; } -function lingotek_process_entity_xml($xml, &$entity, $entity_type, $drupal_language_code, $node_based_translation = FALSE, $url_alias_translation = 0) { - list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); +function lingotek_process_entity_xml($xml, &$entity, $entity_type, $langcode, $node_based_translation = FALSE, $url_alias_translation = 0) { + + $language_list = language_list('language'); + $source_entity = $entity; + + $hook_params = array( + 'entity_type' => $entity_type, + 'entity' => $entity, + 'xml' => $xml, + 'langcode' => $langcode, + ); + + // First allow other modules to manipulate the downloaded content. + drupal_alter('lingotek_entity_download', $hook_params); - // Set $node_based if the entity type is a node, inherit otherwise. + // Special handling of menu links + if ($entity_type == 'menu_link') { + $translation_set = lingotek_get_translation_set($entity->mlid, $entity->menu_name); + lingotek_process_menu_link_xml($xml, $entity, $entity_type, $langcode, $translation_set); + return; + } + + // Set $node_based_translation only if the entity type is a node, inherit otherwise. + // (The entity could be a field collection or other entity type, underneath) if ($entity_type == 'node') { $node_based_translation = lingotek_uses_node_translation($entity) ? TRUE : FALSE; - } - // Must check for entity type and node-based translation, as the entity type - // could be a field collection nested within a node-based node. - $source_entity = $entity_type == 'node' && $node_based_translation ? lingotek_get_source_node($entity) : $entity; - - // Add revision_id to be the same as the entity_id if it was not found or if - // it is a comment. - if ($vid === NULL || $entity_type == 'comment') { - $vid = $id; + if ($node_based_translation) { + $source_entity = lingotek_get_source_node($entity); + } } + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + $fields_arrays = array(); $delta = 0; - $last_tag = NULL; - foreach ($xml as $tag => $content) { - if ($tag == $last_tag) { - $delta++; + $last_field_name = NULL; + + foreach ($xml as $field_name => $content) { + // URL alias translation (currently available for nodes only). + if ($entity_type == 'node') { + // Check if we use normal title instead of field title. + if (variable_get('lingotek_translate_original_node_titles', FALSE) && $field_name == 'title' && $node_based_translation) { + $target = check_plain($content); + db_update('node') + ->fields(array('title' => $target)) + ->condition('nid', $id) + ->execute(); + db_update('node_revision') + ->fields(array('title' => $target)) + ->condition('vid', $vid) + ->execute(); + + // Set URL alias. + if ($url_alias_translation == 2 && module_exists('pathauto') && $entity->language != LANGUAGE_NONE) { + module_load_include('inc', 'pathauto'); + $uri = entity_uri('node', $entity); + $entity_unchanged = entity_load_unchanged('node', $id); + pathauto_create_alias('node', 'update', $uri['path'], array('node' => clone $entity_unchanged), $entity->type, $langcode); + } + } + elseif ($field_name == 'url_alias' && $url_alias_translation == 1) { + lingotek_save_url_alias($content, $entity, $langcode); + $last_field_name = $field_name; + continue; + } } - else { - $delta = 0; + + // Handle multiple instances of the same field (handled in Drupal using 'delta') + $delta = ($field_name == $last_field_name) ? ++$delta : 0; + + // Get field info or skip if not found + $field_info = field_info_field($field_name); + if (empty($field_info)) { + LingotekLog::warning('Invalid field downloaded from Lingotek: @field_name. The field may no longer exist; or if you have upgraded the Lingotek module since you last uploaded the document, the document format may have changed. You should re-upload @entity_type #@entity_id', array('@field_name' => $field_name, '@entity_type' => $entity_type, '@entity_id' => $id)); + $last_field_name = $field_name; + continue; } - if ($tag == 'url_alias' && $url_alias_translation == 1) { - $target = check_plain($content); - //URL Alias related to the page: - $conditions = array('source' => 'node/' . $id); - if ($entity->language != LANGUAGE_NONE) { - $conditions['language'] = $entity->language; - } - $path = path_load($conditions); - if ($path !== FALSE) { - $conditions['language'] = $drupal_language_code; - if ($path['alias'] != $target || $entity->language != $drupal_language_code) { - $original = path_load($conditions); - $conditions['alias'] = $target; - if ($original === FALSE) { - path_save($conditions); - } - else { - path_delete($original); - path_save($conditions); - } - } + $translatable_field = !empty($field_info['translatable']); + $field_language = $node_based_translation && !$translatable_field ? LANGUAGE_NONE : $langcode; + + // Field-Collection Fields: Pull the underlying entities and recurse. + if (module_exists('field_collection') && $field_info['type'] == 'field_collection') { + lingotek_process_field_collection_xml($content, $entity_type, $entity, $field_name, $delta, $langcode, $node_based_translation); + $last_field_name = $field_name; + continue; + } + + // Field-Collection Entities: Set all field-collection fields to translatable, + // if the parent node is not using node-based translation. + if ($entity_type == 'field_collection_item' && !$node_based_translation && $field_info['translatable'] != 1) { + $field_info['translatable'] = 1; + field_update_field($field_info); + } + + // Handle individual fields from here down. + $insert_array = array( + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'entity_id' => $id, + 'revision_id' => $vid === NULL || $entity_type == 'comment' ? $id : $vid, + 'language' => $field_language, + 'delta' => $delta, + 'deleted' => '0', + ); + + foreach ($content as $column_name => $text) { + $db_column_name = $field_info['field_name'] . '_' . $column_name; + $insert_array[$db_column_name] = lingotek_unfilter_placeholders(decode_entities((string) $text->element)); + + // Truncate too-long title fields + if ($db_column_name == 'title_field_value' && strlen($insert_array[$db_column_name]) > 254) { + $insert_array[$db_column_name] = substr($insert_array[$db_column_name], 0, 254); + $langcode = $insert_array['language']; + LingotekLog::info('The @lang (@langcode) title was truncated, since the translation exceeded the maximum of 255 characters.', array('@lang' => $language_list[$langcode]->name, '@langcode' => Lingotek::convertDrupal2Lingotek($langcode))); } } - else { //this part of the xml is a field - $drupal_field_name = $tag; - $target_key = 'value'; - $subfield_parts = explode('__', $tag); - if (count($subfield_parts) == 2) { - $drupal_field_name = $subfield_parts[0]; - $target_key = $subfield_parts[1]; + + // Try to carry over untranslated columns. + try { + $source_field_data = field_get_items($entity_type, $source_entity, $field_name); + } catch (EntityMalformedException $e) { + $source_field_data = array(); + } + $curr_field_data = & $entity->$field_name; + lingotek_add_untranslated_source_fields($insert_array, $source_field_data, $curr_field_data, $field_info, $entity->language, $delta); + + // Save to both field_data tables and field_revision tables + $field_table_names = array('field_revision_' . $field_name, 'field_data_' . $field_name); + foreach ($field_table_names as $fname) { + lingotek_save_field_record($fname, $insert_array); + // Node-based translations within a field-based translation system by default + // should also have the language-neutral fields saved for normalization if + // underneath a field collection. This prevents source-language content + // from blocking out translated content from being shown. + if ($node_based_translation && $entity_type == 'field_collection_item' && $field_language != LANGUAGE_NONE) { + $insert_array['language'] = LANGUAGE_NONE; + lingotek_save_field_record($fname, $insert_array); + $insert_array['language'] = $field_language; } + } - $field = field_info_field($drupal_field_name); + // After every field insert, reset the caches and reload the entity + // TODO: Do we really need to do this every time we save a field, or + // can we just do this once at the end? + cache_clear_all('field:' . $entity_type . ':' . $id, 'cache_field'); + entity_get_controller($entity_type)->resetCache(array($id)); + $entity = lingotek_entity_load_single($entity_type, $id); - // Try to get the source field's text format, if available - try { - $source_field_data = field_get_items($entity_type, $source_entity, $drupal_field_name); - } catch (EntityMalformedException $e) { - $source_field_data = array(); - } + //Set URL alias + if ($field_name == 'title_field' && $url_alias_translation == 2 && module_exists('pathauto') && $entity->language != LANGUAGE_NONE) { + lingotek_save_pathauto_alias($entity_type, $entity, $langcode); + } + $last_field_name = $field_name; + } +} - $translatable_field = !empty($field['translatable']); - $field_language = $node_based_translation && !$translatable_field ? LANGUAGE_NONE : $drupal_language_code; - - if (isset($field)) { - $curr_field_data = & $entity->$drupal_field_name; - $index = 0; - if (module_exists('link') && $field['type'] == 'link_field') { - $target_key = array( - 'title', - 'url', - ); - } - if (!is_array($target_key)) { - $target_key = array($target_key); - } - $insert_array = array( - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'entity_id' => $id, - 'revision_id' => $vid, - 'language' => $field_language, - 'delta' => $delta, - 'deleted' => '0', - ); - $field_names = array('field_revision_' . $field['field_name'], 'field_data_' . $field['field_name']); - - if (module_exists('field_collection') && $field['type'] == 'field_collection') { - $default = language_default(); - $default_language = $default->language; - if (isset($curr_field_data[LANGUAGE_NONE][$delta]['value'])) { - $field_collection_id = $curr_field_data[LANGUAGE_NONE][$delta]['value']; - } - elseif (isset($curr_field_data[$default_language][$delta]['value'])) { - $field_collection_id = $curr_field_data[$language_default][$delta]['value']; - } - else { - if (!$node_based_translation) { - // The field-collection field must be empty. - continue; - } - // If it does not exist and the profile is node-based, create a new FC. - $field_collection_item = entity_create('field_collection_item', array('field_name' => $field['field_name'])); - $field_collection_item->setHostEntity($entity_type, $entity); - $field_collection_item->save(); - $field_collection_id = $field_collection_item->item_id; - } - $field_collection_item = lingotek_entity_load_single('field_collection_item', $field_collection_id); - if (!$field_collection_item) { - // The field collection was removed, so disregard any info on it - continue; - } - $field_collection_item->type = $field['field_name']; - $field_collection_item->language = $entity->language; - $field_collection_item->nid = $field_collection_item->item_id; - $field_collection_item->vid = $field_collection_item->revision_id; - - // RECURSION FOR FIELD COLLECTIONS - lingotek_process_entity_xml($content, $field_collection_item, 'field_collection_item', $drupal_language_code, $node_based_translation); - $last_tag = $tag; - continue; - } - else { - foreach ($content as $text) { - if ($entity_type == 'field_collection_item' && !$node_based_translation && $field['translatable'] != 1) { - $field['translatable'] = 1; - field_update_field($field); - } - - $is_link = in_array('url', $target_key); - $without_title = (count($content) == 1); - - if ($is_link && $without_title) { - $array_key = $target_key[1]; - } - else { - $array_key = $target_key[$index]; - } - $db_field_name = $field['field_name'] . '_' . $array_key; - $insert_array[$db_field_name] = lingotek_unfilter_placeholders(decode_entities($text)); - if ($db_field_name == 'title_field_value' && strlen($insert_array[$db_field_name]) > 254) { - $insert_array[$db_field_name] = substr($insert_array[$db_field_name], 0, 254); - $language_list = language_list('language'); - $langcode = $insert_array['language']; - LingotekLog::info('The @lang (@langcode) title was truncated, since the translation exceeded the maximum of 255 characters.', array('@lang' => $language_list[$langcode]->name, '@langcode' => Lingotek::convertDrupal2Lingotek($langcode))); - } - - // Assign the field format. - $field_format = NULL; - if (!empty($source_field_data[0]['format'])) { - $field_format = $source_field_data[0]['format']; - } - elseif (!empty($curr_field_data[$entity->language][0]['format'])) { - $field_format = $curr_field_data[$entity->language][0]['format']; - } - if (!empty($field_format)) { - $format_db_field_name = $field['field_name'] . '_format'; - $insert_array[$format_db_field_name] = $field_format; - } - $index++; - } - } - foreach ($field_names as $field_name) { - // using drupal_write_record to avoid node_save - node_save overwrites publications unless called on both revised and published versions of the node (i.e. workbench_moderation) - // UPDATE: This could perhaps be rewritten now that workbench_moderation is supported through the rules module - try { - drupal_write_record($field_name, $insert_array); - } catch (PDOException $e) { - $primary_keys = array( - 'entity_type', - 'entity_id', - 'revision_id', - 'deleted', - 'delta', - 'language', - ); - drupal_write_record($field_name, $insert_array, $primary_keys); - } - } +/* + * Inserts newly downloaded menu link data into the menu_links table + */ +function lingotek_process_menu_link_xml($xml, &$entity, $entity_type, $langcode, $translation_set) { + $item = array( + 'link_title' => '', + 'mlid' => 0, + 'plid' => 0, + 'menu_name' => $entity->menu_name, + 'weight' => 0, + 'link_path' => $entity->link_path, + 'options' => array(), + 'customized' => 1, + 'module' => 'menu', + 'expanded' => 0, + 'hidden' => 0, + 'has_children' => 0, + 'language' => $langcode, + ); + + foreach ($xml as $field_name => $content) { + foreach ($content as $column_name => $text) { + $value = lingotek_unfilter_placeholders(decode_entities((string) $text->element)); + + if ($field_name == 'title') { + $item['link_title'] = $value; } + elseif ($field_name == 'description') { + $item['options']['attributes']['title'] = $value; + } + } + } + + $translations = array( + $langcode => $item, + ); - cache_clear_all('field:' . $entity_type . ':' . $id, 'cache_field'); - entity_get_controller($entity_type)->resetCache(array($id)); - // Pull the latest changes from the database - $entity = lingotek_entity_load_single($entity_type, $id); - - //Set URL alias - if ($tag == 'title_field' && $url_alias_translation == 2 && module_exists('pathauto') && $entity->language != LANGUAGE_NONE) { - module_load_include('inc', 'pathauto'); - $uri = entity_uri('node', $entity); - $entity_unchanged = entity_load_unchanged('node', $id); - pathauto_create_alias('node', 'update', $uri['path'], array('node' => clone $entity_unchanged), $entity->type, $drupal_language_code); + $translation_set->add_translations($translations); + $item['translation_set'] = $translation_set; + menu_link_save($item); +} + +/* + * Carry over the columns that weren't translated from the source field + */ +function lingotek_add_untranslated_source_fields(&$params, $source_field_data, $curr_field_data, $field_info, $entity_langcode, $delta) { + + if (empty($field_info['columns']) || empty($field_info['field_name'])) { + return; + } + + foreach (array_keys($field_info['columns']) as $column_name) { + $db_column_name = $field_info['field_name'] . '_' . $column_name; + // if it's set in the translated stuff, then don't carry over. + if (isset($params[$db_column_name])) { + continue; + } + + $field_column = NULL; + if (isset($source_field_data[$delta][$column_name])) { + $field_column = $source_field_data[$delta][$column_name]; + } + elseif (isset($curr_field_data[$entity_langcode][$delta][$column_name])) { + $field_column = $curr_field_data[$entity_langcode][$delta][$column_name]; + } + if ($field_column !== NULL) { + // Arrays, such as those in attributes fields, should be serialized first. + if (is_array($field_column)) { + $field_column = serialize($field_column); } + $params[$db_column_name] = $field_column; } - $last_tag = $tag; } } /* - * Save a segment - * - * Helper method so that if a node has the tnid deferred until later, it will - * get it from the database instead. - * - * @param $source_text - * Source text - * @param $target_text - * Target text that should be saved in the segment - * @param $target_language - * Target language as used by lingotek (locale_country) - * @param $doc_id - * Document Id - * @return - * boolean, TRUE if the api call was successful + * Save a PathAuto alias */ +function lingotek_save_pathauto_alias($entity_type, $entity, $langcode) { + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + module_load_include('inc', 'pathauto'); + $uri = entity_uri($entity_type, $entity); + $entity_unchanged = entity_load_unchanged($entity_type, $id); + pathauto_create_alias($entity_type, 'update', $uri['path'], array($entity_type => clone $entity_unchanged), $bundle, $langcode); +} -function lingotek_save_segment($source_text, $target_text, $target_language, $doc_id) { - $param = array - ( - "sourceText" => $source_text, - "targetText" => $target_text, - "targetLanguage" => $target_language, - "documentId" => $doc_id, - "overwrite" => 0, - ); - $save_segment = LingotekApi::instance()->request("saveSegment", $param); - return ($save_segment->results == "success"); +/* + * Save a field, either insert or update + */ +function lingotek_save_field_record($fname, $params) { + // using drupal_write_record to avoid node_save - node_save overwrites publications unless called on both revised and published versions of the node (i.e. workbench_moderation) + // UPDATE: This could perhaps be rewritten now that workbench_moderation is supported through the rules module + $entity_type = $params['entity_type']; + $entity_id = $params['entity_id']; + $langcode = $params['language']; + $lingotek_locale = Lingotek::convertDrupal2Lingotek($langcode); + try { + LingotekSync::deleteLastSyncError($entity_type, $entity_id); + drupal_write_record($fname, $params); + } catch (PDOException $e) { + + /** + *Error logging became necessary for this try-catch when writing to the database + *because the drupal_write_record() was failing and we had no way of knowing + *why it was happening or even that is was happening because of the try-catch. + * + *The following code logs the error with the approriate detail and also displays + *a drupal message is the error is that the data was too long to be inserted + *into a specific column so that end users can know what happened and know why + *a particular translation is not working + * + *@author t.murphy + * + */ + + $state_error = $e->errorInfo[0]; + $driver_error = $e->errorInfo[1]; + $error_message = $e->errorInfo[2]; + + if ($state_error !== '23000' && $driver_error !== '1062') { + $log_message = t('The following error occurred while writing to the database: SQLSTATE error @sqlError (Driver-specific error code @driverError): @errorMessage', array( '@sqlError' => $state_error, '@driverError' => $driver_error, '@errorMessage' => $error_message)); + LingotekLog::error($log_message, $e); + } + if ($state_error === '22001') { + $str_args = array(); + foreach ($e->args as $key => $value) { + array_push($str_args, (string)$value); + } + $message = t('Entity @entity_id had the translation @errorDetail', array('@entity_id' => $str_args[3], '@errorDetail' => $e->errorInfo[2])); + drupal_set_message($message, 'error'); + + LingotekSync::setTargetStatus($entity_type, $entity_id, $lingotek_locale, LingotekSync::STATUS_ERROR); + LingotekSync::setLastSyncError($entity_type, $entity_id, $error_message); + } + + $primary_keys = array( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'language', + ); + drupal_write_record($fname, $params, $primary_keys); + } } -#API ADDERS /* - * Apply the phase template to the Lingotek document - * - * This saves the chosen workflow to the Lingotek platform. - * - * @param $translation_target_id - * Translation Target Id (Id for the target language stored with the document associated with a node) - * @param $phase_template_id - * Workflow Id to be added + * Process a field-collection field's xml + */ +function lingotek_process_field_collection_xml($xml, $entity_type, &$entity, $field_name, $delta, $langcode, $node_based_translation) { + $field_info = field_info_field($field_name); + $curr_field_data = &$entity->$field_name; + $default = language_default(); + $default_language = $default->language; + + if (isset($curr_field_data[LANGUAGE_NONE][$delta]['value'])) { + $field_collection_id = $curr_field_data[LANGUAGE_NONE][$delta]['value']; + } + elseif (isset($curr_field_data[$default_language][$delta]['value'])) { + $field_collection_id = $curr_field_data[$default_language][$delta]['value']; + } + else { + if (!$node_based_translation) { + // The field-collection field must be empty. + return; + } + // If it does not exist and the profile is node-based, create a new FC. + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_info['field_name'])); + + // Grandfather fields (anything that's an array) from the source field collection + $original_entity = lingotek_entity_load_single($entity_type, $entity->tnid); + $original_field_collection = lingotek_entity_load_single('field_collection_item', $original_entity->{$field_name}[LANGUAGE_NONE][$delta]['value']); + if (!empty($original_field_collection)) { + foreach ($original_field_collection as $k => $v) { + if (is_array($v)) { + $field_collection_item->$k = $v; + } + } + } + + $field_collection_item->setHostEntity($entity_type, $entity); + $field_collection_item->save(); + $field_collection_id = $field_collection_item->item_id; + } + $field_collection_item = lingotek_entity_load_single('field_collection_item', $field_collection_id); + if (!$field_collection_item) { + // The field collection was removed, so disregard any info on it + return; + } + $field_collection_item->type = $field_info['field_name']; + $field_collection_item->language = $entity->language; + + // RECURSION FOR FIELD COLLECTIONS + lingotek_process_entity_xml($xml, $field_collection_item, 'field_collection_item', $langcode, $node_based_translation); +} + +/* + * Save a URL Alias (nodes only) */ +function lingotek_save_url_alias($content, $node, $drupal_langcode) { + $target = check_plain($content); -function lingotek_add_phase_template($translation_target_id, $phase_template_id) { - $params = array('translationTargetId' => $translation_target_id, 'phaseTemplateId' => $phase_template_id); - LingotekApi::instance()->request("applyPhaseTemplate", $params); + //URL Alias related to the page: + $conditions = array('source' => 'node/' . $node->nid); + if ($node->language != LANGUAGE_NONE) { + $conditions['language'] = $node->language; + } + $path = path_load($conditions); + if ($path !== FALSE) { + $conditions['language'] = $drupal_langcode; + if ($path['alias'] != $target || $node->language != $drupal_langcode) { + $original = path_load($conditions); + $conditions['alias'] = $target; + if ($original === FALSE) { + path_save($conditions); + } + else { + path_delete($original); + path_save($conditions); + } + } + } } +#API ADDERS + /* * Create a project and return it's id. * @param $name @@ -428,44 +563,6 @@ function lingotek_get_url_alias_translations() { return $methods; } -/* - * Get the Lingotek user's cms key for the community they are currently logged in with - */ - -function lingotek_get_cms_key() { - global $_lingotek_client; - - $output = LingotekApi::instance()->request("getCMSKey"); - if ($output->results == "success") { - variable_del('lingotek_password'); - return $output->cms; - } - else { - return ""; - } -} - -/* - * Get the Lingotek user's current communities - */ - -function lingotek_get_communities() { - $options = array(); - - if (!$_lingotek_client->canLogIn()) { - return $options; - } - - $list_communities = LingotekApi::instance()->request("listCommunities", array()); - if ($list_communities->results == "success") { - foreach ($list_communities->communities as $community) { - $options[$community->id] = $community->name; - } - } - - return $options; -} - /** * Get the target language objects for a Lingotek document associated with a node. * @@ -545,37 +642,6 @@ function lingotek_get_phase_name($phase_id) { return $phase_name; } -/* - * Get available synchronization methods for keeping nodes up-to-date - */ - -function lingotek_get_sync_methods() { - $methods = array(); - $methods[0] = t("Never"); // Manual - $methods[1] = t("Always"); // Automatic - $methods[100] = t("100%"); - return $methods; -} - -/* - * Get the translation target - * - * This fetches an target language object for a specific document. - * - * @param $translation_target_id - * Id for the target language object - * @return - * Object representing a target language for a specific document in the lingotek platform - */ - -function lingotek_get_translation_target($translation_target_id) { - $params = array('translationTargetId' => $translation_target_id); - $output = LingotekApi::instance()->request("getTranslationTarget", $params); - if ($output->results == "success") { - return $output; - } -} - /** * Get the url to open the Lingotek Workbench. * @@ -683,22 +749,3 @@ function lingotek_get_workbench_url_by_phases($document_id, $phases, $label = FA return $l; } - -/* - * Get the xliff information of the node - * - * This fetches an xliff representation of the source document. - * - * @param $doc_id - * Document id that associates the node to the Lingotek platform - * @return - * xml text of the xliff - */ - -function lingotek_get_xliff($doc_id) { - global $_lingotek_client; - - $xliff_text = ""; - $params = array('documentId' => $doc_id); - return $_lingotek_client->downloadTriggered("downloadDocumentAsXliff", $params); -} diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.rules.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.rules.inc index 2f74daed..67e69a4e 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.rules.inc @@ -63,6 +63,23 @@ function lingotek_rules_action_info() { ), ), ); + $actions['lingotek_entity_change_workflow'] = array( + 'group' => t('Lingotek'), + 'label' => t('Change Workflow'), + 'base' => 'lingotek_rules_entity_change_workflow', + 'parameter' => array( + 'entity' => array( + 'type' => 'entity', + 'label' => t('Entity'), + ), + 'language' => array( + 'type' => 'text', + 'label' => t('Workflow'), + 'options list' => 'lingotek_get_workflow_options', + ), + ), + ); + return $actions; } @@ -106,6 +123,14 @@ function lingotek_rules_entity_change_profile($wrapper, $profile, $settings, $st lingotek_entity_save($entity, $entity_type); } +function lingotek_rules_entity_change_workflow($wrapper, $workflow_id, $settings, $state) { + $entity_type = $wrapper->type(); + $entity_id = $wrapper->getIdentifier(); + $ids = array(); + $ids[] = $entity_id; + + lingotek_entity_change_workflow($entity_type, $ids, $workflow_id); +} /** * Implements hook_rules_condition_info(). diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.session.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.session.inc index 2e997bf1..1756c177 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.session.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.session.inc @@ -258,7 +258,12 @@ class LingotekSession { $error = ''; } elseif (isset($response->results) && $response->results == "fail") { - $error = $response->error; + if (!empty($response->error)){ + $error = $response->error; + } + else { + $error = t('Make sure your password is compatible with single sign on. Visit https://sso.lingotek.com/login.'); + } } } elseif (isset($data->error)) { diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.setup.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.setup.inc index a2eab127..0a925817 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.setup.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.setup.inc @@ -13,7 +13,7 @@ function lingotek_setup() { // Does the install already have connection credentials? if (lingotek_is_config_missing()) { - drupal_goto('admin/config/lingotek/new-account'); // If something is missing - Go though the Setup Process + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/new-account'); // If something is missing - Go though the Setup Process } else { drupal_goto('admin/settings/lingotek/settings'); // We already have credentials, goto the Dashboard @@ -70,9 +70,11 @@ function lingotek_setup_new_account_form() { $form['submit'] = array( '#type' => 'submit', - '#value' => t('Next') // Create Account + '#value' => t('Create New Lingotek Account') // Create Account ); - $form['lingotek_back_button'] = lingotek_setup_link('admin/config/lingotek/account-settings', t('Enterprise Customers - Connect Here')); + + $form['lingotek_back_button'] = lingotek_connect_account_link('Do you already have a Lingotek account? ', LINGOTEK_MENU_LANG_BASE_URL . '/enterprise-account-settings/0', t('Connect Lingotek Account')); + $form['lingotek_sandbox_connect_link'] = lingotek_connect_account_link('Do you have a Lingotek sandbox account? ', LINGOTEK_MENU_LANG_BASE_URL . '/enterprise-account-settings/1', t('Connect Sandbox Account')); $form['lingotek_support_footer'] = lingotek_support_footer(); return $form; @@ -214,9 +216,9 @@ function lingotek_setup_new_account_form_submit($form, &$form_state) { variable_set('lingotek_notify_url', $notify_url); variable_set('lingotek_cms_tag', $lead['distribution']); - $_SESSION['lingotek_setup_path'] = array('admin/config/lingotek/new-account'); + $_SESSION['lingotek_setup_path'] = array(LINGOTEK_MENU_LANG_BASE_URL . '/new-account'); drupal_set_message(t('Your new Lingotek account has been setup.')); - drupal_goto('admin/config/lingotek/setup-language-switcher'); + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/setup-language-switcher'); } // END: if 200: Success else { @@ -225,9 +227,9 @@ function lingotek_setup_new_account_form_submit($form, &$form_state) { } // END: if error. } // END: Provision new Community else { // If the user already has all the required community credentials, just direct them to the next step. - $_SESSION['lingotek_setup_path'] = array('admin/config/lingotek/new-account'); + $_SESSION['lingotek_setup_path'] = array(LINGOTEK_MENU_LANG_BASE_URL . '/new-account'); drupal_set_message(t('Your Lingotek account settings have been saved.')); - drupal_goto('admin/config/lingotek/setup-language-switcher'); + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/setup-language-switcher'); } } @@ -238,13 +240,15 @@ function lingotek_setup_new_account_form_submit($form, &$form_state) { */ function lingotek_setup_account_settings_form() { + $sandbox_message = variable_get('lingotek_use_stage_servers', FALSE) ? ' (Sandbox)' : '' ; + $current_login_id = variable_get('lingotek_login_id', ''); $current_password = variable_get('lingotek_password', ''); $form = array(); $form['lingotek_user_directions_1'] = array( '#type' => 'item', - '#title' => 'Account Login', + '#title' => 'Account Login' . $sandbox_message, '#description' => 'Connect an existing Lingotek account. This option is for users with an active paid TMS subscription.' ); @@ -273,7 +277,7 @@ function lingotek_setup_account_settings_form() { '#type' => 'submit', '#value' => t('Next'), // Login ); - $form['lingotek_back_button'] = lingotek_setup_link('admin/config/lingotek/new-account', t('No subscription? Go back and register for free.')); + $form['lingotek_back_button'] = lingotek_setup_link_on_right(LINGOTEK_MENU_LANG_BASE_URL . '/new-account', t('No subscription? Go back and register for free.')); $form['lingotek_support_footer'] = lingotek_support_footer(); return $form; @@ -295,11 +299,11 @@ function lingotek_setup_account_settings_form_submit($form, $form_state) { } else { // Login Successful: Valid Account - $_SESSION['lingotek_setup_path'] = array('admin/config/lingotek/account-settings'); + $_SESSION['lingotek_setup_path'] = array(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings'); drupal_set_message(t('Your account settings have been saved.')); variable_set('lingotek_login_id', $login_id); variable_set('lingotek_password', $password); - drupal_goto('admin/config/lingotek/community-select'); + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/community-select'); } // END: Valid Login } @@ -319,7 +323,7 @@ function lingotek_community_select_form() { if ($success == FALSE) { drupal_set_message(check_plain($msg), 'error'); - drupal_goto('admin/config/lingotek/account-settings'); // Shouldnt be here. So something messed up. Go back. + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings'); // Shouldnt be here. So something messed up. Go back. } else { $community_integrations = $msg; @@ -330,15 +334,15 @@ function lingotek_community_select_form() { variable_set('lingotek_community_identifier', $ci->community_id); variable_set('lingotek_oauth_consumer_id', $ci->key); variable_set('lingotek_oauth_consumer_secret', $ci->secret); - drupal_goto('admin/config/lingotek/project-vault-select'); // Default path, if they belong to one community. + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/project-vault-select'); // Default path, if they belong to one community. } elseif ($count > 1) { // More than 1 community // Stay on this page } else { // 0 Results, we have an error. Or, we should create a community for them. - drupal_set_message(t('Error Accessing Account. Please contact customer service.'), 'error'); - drupal_goto('admin/config/lingotek/account-settings'); // Shouldnt be here. So something messed up. Go back. + drupal_set_message(t('Failed to connect to the Lingotek service: Missing integration method.'), 'error'); + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings'); // Shouldnt be here. So something messed up. Go back. } } // END: Community Select Paths @@ -367,7 +371,7 @@ function lingotek_community_select_form() { $form['lingotek_button_spacer'] = array('#markup' => '
       
      '); if (is_array($_SESSION['lingotek_setup_path'])) { - if (end($_SESSION['lingotek_setup_path']) == 'admin/config/lingotek/community-select') { + if (end($_SESSION['lingotek_setup_path']) == LINGOTEK_MENU_LANG_BASE_URL . '/community-select') { $null = array_pop($_SESSION['lingotek_setup_path']); } // if the user went back, remove the last element, which is this page. $form['lingotek_back_button'] = lingotek_setup_link(end($_SESSION['lingotek_setup_path']), t('Previous Step')); @@ -396,10 +400,10 @@ function lingotek_community_select_form_submit($form, $form_state) { variable_set('lingotek_oauth_consumer_id', $community->key); variable_set('lingotek_oauth_consumer_secret', $community->secret); - $_SESSION['lingotek_setup_path'][] = 'admin/config/lingotek/community-select'; + $_SESSION['lingotek_setup_path'][] = LINGOTEK_MENU_LANG_BASE_URL . '/community-select'; drupal_set_message(t('Your site has been securely connected to your community (@community).', array('@community' => $community_selected))); - drupal_goto('admin/config/lingotek/project-vault-select'); // Move to Project Select Step. + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/project-vault-select'); // Move to Project Select Step. } /** @@ -413,11 +417,11 @@ function lingotek_project_vault_select_form() { if ($community_settings === FALSE) { drupal_set_message(t('Error Retrieving Account Information.'), 'error'); - drupal_goto('admin/config/lingotek/account-settings'); // Error geting projects. Go back. + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings'); // Error geting projects. Go back. } elseif ($community_settings['project'] === FALSE || $community_settings['workflow'] === FALSE || $community_settings['vault'] === FALSE) { drupal_set_message(t('Error Retrieving Account Information.'), 'error'); - drupal_goto('admin/config/lingotek/account-settings'); // Error geting projects. Go back. + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings'); // Error geting projects. Go back. } $form['lingotek_user_directions'] = array('#markup' => '

      Select or create the default project and vault that you would like to use.

      '); @@ -445,9 +449,7 @@ function lingotek_project_vault_select_form() { '#title' => t('Project'), '#default_value' => $project_already_set ? 0 : 1, '#options' => $project_options, - //'#description' => t('The Lingotek Project that you would like to use for this site.'), '#required' => TRUE, - //'#attributes' => array('onclick' => "(function(){ console.log($); alert('hi'); })();"), ); $form['project_new'] = array( @@ -539,7 +541,7 @@ function lingotek_project_vault_select_form() { // submit, etc. if (is_array($_SESSION['lingotek_setup_path'])) { - if (end($_SESSION['lingotek_setup_path']) == 'admin/config/lingotek/project-vault-select') { + if (end($_SESSION['lingotek_setup_path']) == LINGOTEK_MENU_LANG_BASE_URL . '/project-vault-select') { $null = array_pop($_SESSION['lingotek_setup_path']); } // if the user went back, remove the last element, which is this page. $form['lingotek_back_button'] = lingotek_setup_link(end($_SESSION['lingotek_setup_path']), t('Previous Step')); @@ -611,12 +613,13 @@ function lingotek_project_vault_select_form_submit($form, $form_state) { } } - $_SESSION['lingotek_setup_path'][] = 'admin/config/lingotek/project-vault-select'; + $_SESSION['lingotek_setup_path'][] = LINGOTEK_MENU_LANG_BASE_URL . '/project-vault-select'; drupal_set_message(t('Your Lingotek project, workflow, and vault selections have been setup and saved.')); - drupal_goto('admin/config/lingotek/setup-language-switcher'); + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/setup-language-switcher'); } function lingotek_setup_language_switcher_form($form, $form_state) { + drupal_add_js(drupal_get_path('module', 'lingotek') . '/js/lingotek.admin.js'); $current_theme = variable_get('theme_default', 'none'); //global $theme_key; $query = db_select('block', 'b'); @@ -666,7 +669,7 @@ function lingotek_setup_language_switcher_form($form, $form_state) { ); if (is_array($_SESSION['lingotek_setup_path'])) { - if (end($_SESSION['lingotek_setup_path']) == 'admin/config/lingotek/setup-language-switcher') { + if (end($_SESSION['lingotek_setup_path']) == LINGOTEK_MENU_LANG_BASE_URL . '/setup-language-switcher') { $null = array_pop($_SESSION['lingotek_setup_path']); } // if the user went back, remove the last element, which is this page. $form['lingotek_back_button'] = lingotek_setup_link(end($_SESSION['lingotek_setup_path']), t('Previous Step')); @@ -688,8 +691,8 @@ function lingotek_setup_language_switcher_form($form, $form_state) { function lingotek_setup_language_switcher_form_submit($form, $form_state) { lingotek_admin_language_switcher_form_submit($form, $form_state); - $_SESSION['lingotek_setup_path'][] = 'admin/config/lingotek/setup-language-switcher'; - drupal_goto('admin/config/lingotek/node-translation-settings'); + $_SESSION['lingotek_setup_path'][] = LINGOTEK_MENU_LANG_BASE_URL . '/setup-language-switcher'; + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . '/node-translation-settings'); } /** @@ -910,7 +913,7 @@ function lingotek_setup_node_updates_form($form, $form_state) { $form['lingotek_middle_spacer'] = array('#markup' => '
       
      '); - $form['lingotek_back_button'] = lingotek_setup_link('admin/config/lingotek/new-account', t('Previous Step')); + $form['lingotek_back_button'] = lingotek_setup_link(LINGOTEK_MENU_LANG_BASE_URL . '/new-account', t('Previous Step')); $form['submit'] = array( '#type' => 'submit', @@ -923,29 +926,32 @@ function lingotek_setup_node_updates_form($form, $form_state) { /** * Setup Button */ -function lingotek_setup_link($path = 'admin/config/lingotek/new-account', $text = 'Previous Step') { +function lingotek_setup_link($path, $text = 'Previous Step') { return array( '#markup' => '' . l($text, $path) . '' ); } +//this covers the case of the link being on the right of a non-default theme button +function lingotek_setup_link_on_right($path, $text = 'Previous Step') { + return array( + '#markup' => ''. l($text, $path) . '' + ); +} -/** - * Future Page - Form Submit - */ -function lingotek_setup_node_updates_form_submit($form, $form_state) { - - $transition_node_titles = $form_state['values']['transition_node_titles']; - $update_node_language = $form_state['values']['update_node_language']; +function lingotek_connect_account_link($message, $path, $text = 'Previous Step') { + return array( + '#markup' => '
      '. $message . l($text, $path) . '' + ); +} - if ($transition_node_titles == 1) { - // Do the Node Titles +function lingotek_enterprise_account_setting($is_staging) { + if ($is_staging) { + variable_set('lingotek_use_stage_servers', TRUE); } - - if ($update_node_language == 1) { - // Update The Node Languages + else { + variable_set('lingotek_use_stage_servers', FALSE); } - - //drupal_goto( 'admin/config/lingotek/setup-complete' ); + drupal_goto(LINGOTEK_MENU_LANG_BASE_URL . "/account-settings"); } /* diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.sync.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.sync.inc old mode 100644 new mode 100755 index 93835318..d20f6c07 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.sync.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.sync.inc @@ -31,7 +31,12 @@ function lingotek_notify_url_update($show_messages = TRUE) { $success = LingotekSync::updateNotifyUrl(); if ($show_messages) { if ($success) { - drupal_set_message(t("Notification callback URL successfully updated.")); + if ($success === 'success') { + drupal_set_message(t("Notification callback URL successfully updated.")); + } + else if ($success === 'localhost_url') { + drupal_set_message(t("Lingotek will not be able to notify Drupal, since the callback URL contains localhost."), 'warning'); + } } else { drupal_set_message(t("Notification callback URL was not successfully updated."), 'error'); @@ -67,7 +72,7 @@ function lingotek_get_trans_obj($document_id, $document_idx) { $document_id = $document_idx; } } - + if ($id) { $entity = lingotek_entity_load_single($type, $id); $trans_obj = LingotekEntity::load($entity, $type); @@ -78,7 +83,7 @@ function lingotek_get_trans_obj($document_id, $document_idx) { break; } - LingotekLog::info('Did not find doc ID @doc_id yet on attempt #@attempt, retrying...', + LingotekLog::info('Did not find doc ID @doc_id yet on attempt #@attempt, retrying...', array('@attempt' => $attempts, '@doc_id' => $document_id)); sleep(2); $attempts++; @@ -93,15 +98,14 @@ function lingotek_get_trans_obj($document_id, $document_idx) { * Registers the site translation notfication callback. */ function lingotek_notifications() { - drupal_page_is_cacheable(FALSE); - LingotekLog::trace('Received
      @data
      ', array('@data' => var_export($_GET, TRUE)), 'callback'); + LingotekLog::trace('Received
      @data
      ', array('@data' => check_plain(var_export($_GET, TRUE))), 'callback'); $document_id = ( isset($_GET['doc_id']) ) ? $_GET['doc_id'] : NULL;// uuid $document_idx = ( isset($_GET['doc_idx']) ) ? $_GET['doc_idx'] : NULL;// this is the deprecated document number $lingotek_locale = ( isset($_GET['target_code']) ) ? $_GET['target_code'] : NULL; $project_id = ( isset($_GET['project_id']) ) ? $_GET['project_id'] : NULL; - $completed = ( isset($_GET['completed']) ) ? $_GET['completed'] : 1; + $completed = ( isset($_GET['completed']) && $_GET['completed'] === 'true' ) ? 1 : 0; $security_token = ( isset($_GET['security_token']) ) ? $_GET['security_token'] : NULL; $stored_security_token = variable_get('lingotek_notify_security_token', NULL); @@ -119,14 +123,14 @@ function lingotek_notifications() { include_once('lingotek.batch.inc'); $target_drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale); - + $trans_obj = lingotek_get_trans_obj($document_id, $document_idx); $downloaded = FALSE; if ($trans_obj) { $trans_obj->preDownload($lingotek_locale, $completed); - + $replacements = array( '@trans_type' => get_class($trans_obj), '@document' => $document_id, @@ -134,7 +138,7 @@ function lingotek_notifications() { '@project_id' => $project_id, '@id' => $trans_obj->getId(), ); - + if ($downloaded = $trans_obj->downloadTriggered($lingotek_locale)) { LingotekLog::trace('Updated local content for @trans_type @id based on hit from external API for document: @document, language code @language_code, project ID: @project_id', $replacements, 'api'); @@ -143,9 +147,9 @@ function lingotek_notifications() { LingotekLog::trace('Unable to update local content for @trans_type @id based on hit from external API for document: @document, language code @language_code, project ID: @project_id', $replacements, 'api'); } - + $trans_obj->postDownload($lingotek_locale, $completed); - + } else { LingotekLog::error('Lingotek document ID (@doc_id) not found.', array('@doc_id' => $document_id)); @@ -165,11 +169,11 @@ function lingotek_notifications() { $found = (isset($trans_obj) && $trans_obj); $response = array_merge($_GET, array( - 'target_drupal_language_code' => $target_drupal_language_code, - 'type' => isset($trans_obj) ? $trans_obj->getEntityType() : '', - 'id' => isset($trans_obj) ? $trans_obj->getId() : '', - 'found' => $found, - 'download' => $downloaded, + 'target_drupal_language_code' => check_plain($target_drupal_language_code), + 'type' => isset($trans_obj) ? check_plain($trans_obj->getEntityType()) : '', + 'id' => isset($trans_obj) ? check_plain($trans_obj->getId()) : '', + 'found' => check_plain($found), + 'download' => check_plain($downloaded), )); return drupal_json_output($response); @@ -189,7 +193,11 @@ function lingotek_sync_endpoint() { switch ($method) { case 'GET': - $request['parameters'] = $parameters = $_GET; + $temp = array(); + foreach ($_GET as $key => $value){ + $temp[$key] = check_plain($value); + } + $request['parameters'] = $parameters = $temp; /* $request['doc_ids'] = $document_ids = isset($parameters['doc_ids']) ? array_map(function($val) { return trim($val); }, explode(',', $parameters['doc_ids'])) : array(); */ @@ -227,7 +235,6 @@ function lingotek_get_and_update_target_progress($entity_type, $document_ids, $c $progress_report = $api->getProgressReport($project_id, $document_ids); $targets_count = LingotekSync::getTargetCountByDocumentIds($document_ids); - if (isset($progress_report) && $progress_report->results == 'success') { $delete_nids_maybe = array(); $entity_values = array(); @@ -238,11 +245,11 @@ function lingotek_get_and_update_target_progress($entity_type, $document_ids, $c list($entity_id, $entity_type) = LingotekSync::getEntityIdFromDocId($doc_id, $entity_type); switch ($error->status) { case 'IN_QUEUE': - lingotek_keystore($entity_type, $entity_id, 'upload_status', LingotekSync::STATUS_PENDING); + LingotekSync::setUploadStatus($entity_type, $entity_id, LingotekSync::STATUS_PENDING); break; case 'NOT_FOUND': default: - lingotek_keystore($entity_type, $entity_id, 'upload_status', LingotekSync::STATUS_FAILED); + LingotekSync::setUploadStatus($entity_type, $entity_id, LingotekSync::STATUS_ERROR); lingotek_keystore($entity_type, $entity_id, 'last_sync_error', substr($error, 0, 255)); LingotekLog::error('Received unexpected error status (@status) from Lingotek for @entity_type #@id:
      @error
      ', array('@status' => $error->status, '@entity_type' => $entity_type, '@id' => $entity_id, '@error' => $error)); } @@ -260,6 +267,7 @@ function lingotek_get_and_update_target_progress($entity_type, $document_ids, $c } foreach ($completion as $language => $percent) { $status = LingotekSync::getTargetStatus($doc_id, $language); + $to_status = $status; if (isset($progress_report->workflowCompletedByDocumentIdAndTargetLocale->$doc_id->$language)) { if ($progress_report->workflowCompletedByDocumentIdAndTargetLocale->$doc_id->$language) { // If the workflow is complete if ($status != LingotekSync::STATUS_CURRENT) { // If the status is not current @@ -270,15 +278,22 @@ function lingotek_get_and_update_target_progress($entity_type, $document_ids, $c } } else { // If the workflow is not complete - $to_status = LingotekSync::STATUS_PENDING; // Set it to pending + if ($percent == 0){ + $to_status = LingotekSync::STATUS_PENDING; // Set it to pending + } + else{ + $to_status = $status; + } + } + if ($status != LingotekSync::STATUS_UNTRACKED) { + $entity_values[] = array($entity_type, $entity_id, 'target_sync_status_' . $language, $to_status); } - $entity_values[] = array($entity_type, $entity_id, 'target_sync_status_' . $language, $to_status); } } // update source status when necessary $entity_source_status = lingotek_keystore($entity_type, $entity_id, 'upload_status'); - if ($entity_source_status == LingotekSync::STATUS_FAILED || $entity_source_status == LingotekSync::STATUS_PENDING) { - lingotek_keystore($entity_type, $entity_id, 'upload_status', LingotekSync::STATUS_CURRENT); + if ($entity_source_status == LingotekSync::STATUS_ERROR || $entity_source_status == LingotekSync::STATUS_PENDING) { + LingotekSync::setUploadStatus($entity_type, $entity_id, LingotekSync::STATUS_CURRENT); } } @@ -296,6 +311,7 @@ function lingotek_get_and_update_target_progress($entity_type, $document_ids, $c 'value' => $value, )) ->execute(); + lingotek_cache_clear($entity_type, $entity_id); } return $progress_report; } @@ -328,8 +344,8 @@ function lingotek_update_config_progress($document_ids) { if (!is_array($document_ids)) { $document_ids = array($document_ids); } - $config_profile = lingotek_get_profile_settings(LingotekSync::PROFILE_CONFIG); - $project_id = array_key_exists('project_id', $config_profile) ? $config_profile['project_id'] : variable_get('lingotek_project', ''); + $config_profile = LingotekProfile::loadById(LingotekSync::PROFILE_CONFIG); + $project_id = $config_profile->getProjectId(); $progress_report = $api->getProgressReport($project_id, $document_ids); @@ -337,7 +353,7 @@ function lingotek_update_config_progress($document_ids) { $cids = array(); $cfg_values = array(); $trans_obj = NULL; - + if (isset($progress_report->errors)) { foreach (get_object_vars($progress_report->errors) as $doc_id => $error) { $set = LingotekConfigSet::loadByLingotekDocumentId($doc_id); @@ -347,19 +363,19 @@ function lingotek_update_config_progress($document_ids) { break; case 'NOT_FOUND': default: - $set->setMetadataValue('upload_status', LingotekSync::STATUS_FAILED); + $set->setMetadataValue('upload_status', LingotekSync::STATUS_ERROR); LingotekLog::error('Received unexpected error status (@status) from Lingotek for config chunk #@id:
      @error
      ', array('@status' => $error->status, '@id' => $chunk, '@error' => $error)); } } } - + foreach ($progress_report->byDocumentIdAndTargetLocale as $doc_id => $completion) { $trans_obj = LingotekConfigSet::loadByLingotekDocumentId($doc_id); if (!$trans_obj) { LingotekLog::error("Lingotek doc ID '@doc_id' not found", array('@doc_id' => $doc_id)); continue; } - + foreach ($completion as $language => $percent) { $status = LingotekSync::getTargetStatus($doc_id, $language); if (isset($progress_report->workflowCompletedByDocumentIdAndTargetLocale->$doc_id->$language)) { diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.util.inc b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.util.inc old mode 100644 new mode 100755 index 0be2b83b..e06c61dc --- a/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.util.inc +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/lingotek.util.inc @@ -107,11 +107,13 @@ function lingotek_keystore($entity_type, $entity_id, $key = "", $value = "", $up $existing_value = lingotek_keystore($entity_type, $entity_id, $key); if ($existing_value === FALSE) { // insert $success = drupal_write_record('lingotek_entity_metadata', $row); + lingotek_cache_clear($entity_type, $entity_id); return $success ? "$entity_id : $key => $value (INSERTED)" : "$entity_id : $key !=> $value (INSERT FAILED)"; } elseif ($update_on_dup) { // key exists -> update unset($row[db_escape_field('created')]); // retain original created timestamp $success = drupal_write_record('lingotek_entity_metadata', $row, array(db_escape_field('entity_type'), db_escape_field('entity_id'), db_escape_field('entity_key'))); + lingotek_cache_clear($entity_type, $entity_id); return $success ? "$entity_id : $key => $value (UPDATED)" : "$entity_id : $key !=> $value (UPDATE FAILED)"; } else { // key exists -> ignore (ignore on duplicate key) @@ -141,7 +143,9 @@ function lingotek_keystore_delete($entity_type, $id, $key) { $query->condition('entity_type', $entity_type); $query->condition('entity_id', $id); $query->condition('entity_key', $key); - return $query->execute(); + $result = $query->execute(); + lingotek_cache_clear($entity_type, $id); + return $result; } /** @@ -176,19 +180,26 @@ function lingotek_keystore_delete_multiple($entity_type, $entity_ids, $lingokey, ->condition('entity_id', $delete_nids, 'IN') ->condition('entity_key', $lingokey, $condition) ->execute(); + foreach ($delete_nids as $eid) { + lingotek_cache_clear($entity_type, $eid); + } } } function lingotek_keystore_delete_all($entity_type, $id) { db_delete('lingotek_entity_metadata') ->condition('entity_type', $entity_type) - ->condition('entity_id', $id)->execute(); + ->condition('entity_id', $id)->execute(); + lingotek_cache_clear($entity_type, $id); } function lingotek_keystore_delete_all_multiple($entity_type, $ids) { db_delete('lingotek_entity_metadata') ->condition('entity_type', $entity_type) - ->condition('entity_id', $ids, 'IN')->execute(); + ->condition('entity_id', $ids, 'IN')->execute(); + foreach ($ids as $eid) { + lingotek_cache_clear($entity_type, $eid); + } } /* @@ -236,17 +247,9 @@ function watchdog_format_object($object) { return '
      ' . htmlspecialchars(var_export($object, TRUE)) . '
      '; } -function lingotek_oneoff_translate($node) { - // TODO: Run checks on this if necessary - // (It appears this is currently unnecessary, as all nodes - // that make it to this point are already requested for - // translation.) - return TRUE; -} - function lingotek_get_translatable_field_types() { // What types of fields DO we translate? - $included_fields = array('text', 'text_long', 'text_textfield', 'text_textarea', 'text_textarea_with_summary', 'field_collection_embed'); + $included_fields = array('text', 'text_long', 'text_with_summary', 'field_collection', 'image'); if (module_exists('link')) { $included_fields[] = 'link_field'; } @@ -260,13 +263,15 @@ function lingotek_get_translatable_field_types() { function lingotek_get_translatable_fields_by_content_type($entity_type, $type) { $all_fields = field_info_instances($entity_type, $type); + $all_field_types = field_info_fields(); $translatable_field_types = lingotek_get_translatable_field_types(); $desired_fields = array(); foreach ($all_fields as $field_name => $field_info) { - if (in_array($field_info['widget']['type'], $translatable_field_types)) { + if (in_array($all_field_types[$field_info['field_name']]['type'], $translatable_field_types)) { $desired_fields[$field_name] = $field_name; } } + return $desired_fields; } @@ -282,46 +287,59 @@ function lingotek_get_enabled_fields($entity_type, $bundle) { } /** - * Return the xml representation of the source content for a node. + * Return the xml representation of the source content for an entity. * - * @param object $node - * A Drupal node. + * @param $entity_type + * A string containing a Drupal entity type. + * @param object $entity + * A Drupal entity. * * @return string - * The XML representation of the node in Lingotek format. + * The XML representation of the entity in Lingotek format. */ function lingotek_entity_xml_body($entity_type, $entity) { $translatable = array(); $translate = variable_get('lingotek_enabled_fields', array()); list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + $fields_desired = isset($translate[$entity_type][$bundle]) ? $translate[$entity_type][$bundle] : array(); - if (empty($fields_desired) && lingotek_oneoff_translate($entity)) { + + if (empty($fields_desired)) { $fields_desired = lingotek_get_translatable_fields_by_content_type($entity_type, $bundle); } foreach ($fields_desired as $value) { $field = field_info_field($value); - if (isset($field)) { + // Enable menu links title and description to masquerade as fields + if (isset($field) || $entity_type == 'menu_link') { array_push($translatable, $value); } } - $content = lingotek_xml_fields($entity, $translatable, $entity->language); + // Workaround for the bean module's source-language problem. + if ($entity_type == 'bean') { + $entity->language = language_default()->language; + } + + // Workaround for the group module's source-language problem. + if ($entity_type == 'group') { + $entity->language = lingotek_get_group_source($entity->gid); + } + + if ($entity_type == 'paragraphs_item') { + $entity->language = lingotek_get_paragraphs_item_source($entity->item_id); + } + + if ($entity_type == 'file') { + $entity->language = lingotek_get_file_source($entity->fid); + } - /* deprecated with config translation - //Menus related to the page: - // Do we still want this? Config translation translates these items - $menu = menu_link_get_preferred('node/' . $entity->nid); - $txt = $menu['link_title']; - if ($txt != "") { - $content = $content . "\n"; - } */ + $content_obj = lingotek_xml_fields($entity_type, $entity, $translatable, $entity->language); if ($entity_type == 'node') { - //URL Alias related to the page: - $url_alias_translation = isset($entity->lingotek['url_alias_translation']) ? $entity->lingotek['url_alias_translation'] : 0; - if ($url_alias_translation == 1) { + // URL Alias related to the page: + if (!empty($entity->lingotek['url_alias_translation'])) { $conditions = array('source' => 'node/' . $entity->nid); if ($entity->language != LANGUAGE_NONE) { $conditions['language'] = $entity->language; @@ -329,92 +347,156 @@ function lingotek_entity_xml_body($entity_type, $entity) { $path = path_load($conditions); if ($path !== FALSE) { $url = $path['alias']; - $content = $content . "\n"; + $url_alias_obj = $content_obj->addChild('url_alias'); + $url_alias_obj->addCData($url); } } + // Set $node_based if the entity type is a node, inherit otherwise. + if (in_array('title', $fields_desired) && variable_get('lingotek_translate_original_node_titles', FALSE) && lingotek_uses_node_translation($entity)) { + $title_obj = $content_obj->addChild('title'); + $title_obj->addCData($entity->title); + } } - return "$content"; -} + try { + $hook_params = array( + 'entity_type' => $entity_type, + 'entity' => $entity, + 'xml' => $content_obj, + 'langcode' => $entity->language, + ); -function lingotek_xml_fields($entity, $translatable, $language) { - $content = ''; + // Allow other modules to manipulate the uploaded content. + drupal_alter('lingotek_entity_upload', $hook_params); + } + catch (Exception $e) { + LingotekLog::error("Failed to parse or modify XML before uploading. Error: @error. Text: !xml.", array('!xml' => $text, '@error' => $e->getMessage())); + } - foreach ($translatable as $field) { - $content_container = & $entity->$field; + return $content_obj->asXML(); +} - $field_language = is_array($content_container) && array_key_exists($language, $content_container) ? $language : LANGUAGE_NONE; +function lingotek_xml_fields($entity_type, $entity, $translatable_fields, $language) { + list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); + $content_obj = new LingotekXMLElement(''); + $translatable_field_types = lingotek_get_translatable_field_types(); + $enabled_fields = variable_get('lingotek_enabled_fields'); + $all_field_info = field_info_fields(); + $has_invalid_xml = FALSE; + + foreach ($translatable_fields as $field_name) { + if ($entity_type == 'menu_link') { + $field_language = $entity->language; + $field_content = lingotek_get_menu_link_field_content($field_name, $entity); + $field_columns = lingotek_get_menu_link_field_columns(); + } + else { + $field_columns = $all_field_info[$field_name]['columns']; + $field_content = & $entity->$field_name; + $field_language = is_array($field_content) && array_key_exists($language, $field_content) ? $language : LANGUAGE_NONE; + } // Deal with not being initialized right, such as pre-existing titles. - if (!isset($content_container[$field_language])) { + if (!isset($field_content[$field_language])) { continue; } - // We may split compound Drupal fields into several Lingotek fields. - $target_keys = array( - 'value' => '', // Most text fields - 'summary' => 'summary', // "Long text with summary" fields have this sub-field value as well. - ); - if (module_exists('link')) { - $target_keys['link'] = array( - 'title' => '', - 'url' => '', - ); - } - // Create fields from all target keys. - foreach ($target_keys as $target_key => $element_suffix) { - $array_key = NULL; - if (is_array($element_suffix)) { - foreach ($element_suffix as $tarkey => $val) { - $array_key = $tarkey; + foreach ($field_content[$field_language] as $delta) { + // Initialize a tag the field for each delta of the field in source language. + $field_obj = $content_obj->addChild($field_name); + + foreach ($field_columns as $column_name => $column_attributes) { + if (empty($delta[$column_name])) { continue; } - } - foreach ($content_container[$field_language] as $delta) { - if (!empty($delta[$target_key]) || (isset($array_key) && !empty($delta[$array_key]))) { - $element_name = $field; - if ($target_key == 'value' && isset($delta['revision_id']) && module_exists('field_collection')) { - $current_field = '<' . $element_name . '>'; - $enabled_fields = variable_get('lingotek_enabled_fields'); - $sub_entity = lingotek_entity_load_single('field_collection_item', $delta['value']); - $sub_fields = field_info_instances('field_collection_item', $field); - $translatable_field_types = lingotek_get_translatable_field_types(); - $translatable_sub_fields = array(); - foreach ($sub_fields as $sub_field => $f) { - if (in_array($f['widget']['type'], $translatable_field_types) && isset($enabled_fields['field_collection_item'][$field]) && in_array($sub_field, $enabled_fields['field_collection_item'][$field])) { - $translatable_sub_fields[] = $sub_field; - } - } - $current_field .= lingotek_xml_fields($sub_entity, $translatable_sub_fields, $field_language); - $current_field .= ''; - $content .= $current_field; + + if (!lingotek_translatable_field_column($entity_type, $bundle, $field_name, $column_name)) { + continue; + } + + // Handle nested field-collection entities + // TODO: make a better way of detecting if this is a field-collection column! + if ($column_name == 'value' && isset($delta['revision_id']) && module_exists('field_collection')) { + $sub_entity = lingotek_entity_load_single('field_collection_item', $delta['value']); + // if the field collection is disabled for Lingotek translation, skip it. + if ($sub_entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { continue; } - if (!is_array($element_suffix) && !empty($element_suffix)) { - $element_name .= '__' . $element_suffix; - } - $current_field = '<' . $element_name . '>'; - if (is_array($element_suffix)) { - foreach ($element_suffix as $t_key => $t_val) { - if (isset($delta[$t_key])) { - $delta[$t_key] = lingotek_filter_placeholders($delta[$t_key]); - $current_field .= '' . "\n"; - } + $sub_fields = field_info_instances('field_collection_item', $field_name); + $translatable_sub_fields = array(); + + foreach ($sub_fields as $sub_field => $f) { + + $field_type_is_translatable = in_array($all_field_info[$f['field_name']]['type'], $translatable_field_types); + $field_collection_is_enabled_for_translation = isset($enabled_fields['field_collection_item'][$field_name]); + $sub_field_is_enabled_for_translation = $field_collection_is_enabled_for_translation && in_array($sub_field, $enabled_fields['field_collection_item'][$field_name]); + $sub_field_is_enabled_field_collection = $field_collection_is_enabled_for_translation && isset($enabled_fields['field_collection_item'][$sub_field]); + + if ($field_type_is_translatable && $field_collection_is_enabled_for_translation && ($sub_field_is_enabled_for_translation || $sub_field_is_enabled_field_collection)) { + $translatable_sub_fields[] = $sub_field; } } - else { - $delta[$target_key] = lingotek_filter_placeholders($delta[$target_key]); - $current_field .= '' . "\n"; - } - $current_field .= ''; - $content .= $current_field; + $subcontent_obj = lingotek_xml_fields('field_collection_item', $sub_entity, $translatable_sub_fields, $field_language); + $field_obj->addXML($subcontent_obj); + continue; + } + + $delta_column_content = lingotek_filter_placeholders($delta[$column_name]); + + if (lingotek_element_contains_invalid_xml($delta_column_content)) { + $has_invalid_xml = TRUE; + break; } + // Handle element suffixes (all columns should be suffixed now) + $column_obj = $field_obj->addChild($column_name); + $element = $column_obj->addChild('element'); + $element->addCData($delta_column_content); } } } + // If the document has invalid characters, flag it + if ($has_invalid_xml) { + lingotek_keystore($entity_type, $id, 'invalid_xml', LingotekSync::INVALID_XML_PRESENT); + lingotek_keystore($entity_type, $id, 'last_sync_error', 'Entity contains invalid XML characters'); + LingotekSync::setUploadStatus($entity_type, $id, LingotekSync::STATUS_ERROR); + } + else { + lingotek_keystore_delete($entity_type, $id, 'invalid_xml'); + lingotek_keystore($entity_type, $id, 'last_sync_error', ''); + } + return $content_obj; +} + +/** +* Checks for invalid xml characters +*/ +function lingotek_element_contains_invalid_xml($element) { + $invalid = FALSE; + + // Valid XML Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + for($i = 0; $i < strlen($element); $i++) { + $char = substr($element, $i, 1); + $dec = ord($char); + if ($dec == 9 || $dec == 10 || $dec == 13) { + continue; + } + elseif ($dec >= 32 && $dec <= 55295) { + continue; + } + elseif ($dec >= 57344 && $dec <= 65533) { + continue; + } + elseif ($dec >= 65536 && $dec <= 1114111) { + continue; + } + else { + $invalid = TRUE; + break; + } + } - return $content; + return $invalid; } /** @@ -438,12 +520,12 @@ function lingotek_support_footer() { /** * Menu access callback. * - * Only display Lingotek tab for node types, which have translation enabled + * Only display Lingotek tab for node types that have translation enabled * and where the current node is not language neutral (which should span * all languages). */ function lingotek_access($node, $permission) { - if (Lingotek::isSupportedLanguage($node->language) && node_access('update', $node) && $node->lingotek['profile'] != LingotekSync::PROFILE_DISABLED) { + if (Lingotek::isSupportedLanguage($node->language) && node_access('update', $node) && isset($node->lingotek['profile']) && $node->lingotek['profile'] != LingotekSync::PROFILE_DISABLED) { return user_access($permission); } return FALSE; @@ -484,18 +566,15 @@ function lingotek_access_dev_tools($node, $permission) { // OR when not showing advanced features $user_access = user_access($permission); - if (($user_access && module_exists('entity_translation') && entity_translation_node_supported_type($node->type) && !lingotek_node_pushed($node)) || !LingotekAccount::instance()->showAdvanced()) { - return FALSE; + if ($user_access) { + if ((module_exists('entity_translation') && entity_translation_node_supported_type($node->type) && !lingotek_node_pushed($node)) || !LingotekAccount::instance()->showAdvanced()) { + return FALSE; + } } // Default: Standard user access return $user_access; } -function lingotek_enabled_bundle($entity_type, $bundle) { - $setting = variable_get('lingotek_entity_profiles'); - return isset($setting[$entity_type][$bundle]) && $setting[$entity_type][$bundle] != LingotekSync::PROFILE_DISABLED; -} - /** * Returns an array of disabled bundles for a given entity_type * @@ -519,17 +598,6 @@ function lingotek_get_bundles_by_profile($entity_type, $profile) { return $profiled_bundles; } -/** - * Returns whether the given node type has support for translations. - * - * @return - * Boolean value. - */ -function lingotek_supported_type($type) { - $lingotek_supported_explicitly = variable_get('language_content_type_' . $type, NULL) != '0'; - return ($lingotek_supported_explicitly && lingotek_enabled_bundle('node', $type)); -} - function lingotek_supported_node($node) { return $node->lingotek['profile'] != LingotekSync::PROFILE_DISABLED; } @@ -613,16 +681,11 @@ function lingotek_get_source_language() { return language_default('language'); } -// END: lingotek_get_source_language() - - /** * Return a whitelist of entity types supported for translation by the Lingotek module */ function lingotek_managed_entity_types($include_all = FALSE) { $type_info = entity_get_info(); - //$types = array_keys($type_info); - $whitelist = array('node', 'comment', 'message_type', 'fieldable_panels_pane'); // supported types if ($include_all) { // include the entities that should not normally be managed separately @@ -633,6 +696,21 @@ function lingotek_managed_entity_types($include_all = FALSE) { if (module_exists('taxonomy') && variable_get('lingotek_advanced_taxonomy_terms', FALSE)) { $whitelist[] = 'taxonomy_term'; } + if (module_exists('bean') && variable_get('lingotek_translate_beans', FALSE)) { + $whitelist[] = 'bean'; + } + if (module_exists('group') && variable_get('lingotek_translate_groups', FALSE)) { + $whitelist[] = 'group'; + } + if (module_exists('entity_menu_links') && variable_get('lingotek_advanced_menu_links', FALSE)) { + $whitelist[] = 'menu_link'; + } + if (module_exists('paragraphs') && variable_get('lingotek_translate_paragraphs', FALSE)) { + $whitelist[] = 'paragraphs_item'; + } + if (module_exists('file_entity') && variable_get('lingotek_translate_files', FALSE)) { + $whitelist[] = 'file'; + } $whitelist = array_flip($whitelist); $enabled_types = array_intersect_key($type_info, $whitelist); @@ -655,6 +733,24 @@ function lingotek_managed_entity_types($include_all = FALSE) { if (isset($enabled_types['fieldable_panels_pane'])) { $enabled_types['fieldable_panels_pane']['label'] = 'Fieldable Panels Panes'; } + if (isset($enabled_types['bean'])) { + $enabled_types['bean']['label'] = 'Beans'; + } + if (isset($enabled_types['group'])) { + $enabled_types['group']['label'] = 'Groups'; + } + if (isset($enabled_types['menu_link'])) { + $enabled_types['menu_link']['label'] = 'Menu Links'; + } + if (isset($enabled_types['paragraphs_item'])) { + $enabled_types['paragraphs_item']['label'] = 'Paragraphs'; + } + if (isset($enabled_types['file'])) { + $enabled_types['file']['label'] = 'Files'; + } + + // Allow other modules to add/remove entity types to the list supported. + drupal_alter('lingotek_managed_entity_types', $enabled_types, $include_all); return $enabled_types; } @@ -733,10 +829,38 @@ function lingotek_delete_target_language($lingotek_locale) { '@lingotek_locale' => $lingotek_locale) ); - // Remove the Target Language from the Lingotek Project. - $project_id = variable_get('lingotek_project', ''); - $api = LingotekApi::instance(); - $result = $api->removeTranslationTarget(NULL, $project_id, $lingotek_locale); + // Remove the Target Language from the entire Lingotek Project + // if language-specific profiles aren't in use. (If language- + // specific profiles are in use, then we assume a more sophisticated + // user that may want old translations preserved on the TMS even if + // the Lingotek module is no longer managing that language.) + if (!variable_get('lingotek_enable_language_specific_profiles')) { + $project_id = variable_get('lingotek_project', ''); + $api = LingotekApi::instance(); + $result = $api->removeTranslationTarget(NULL, $project_id, $lingotek_locale); + } + + // Remove from profiles and entity-profiles mappings + $profiles = variable_get('lingotek_profiles', array()); + foreach ($profiles as &$profile_attribs) { + if (isset($profile_attribs['target_language_overrides'][$lingotek_locale])) { + unset($profile_attribs['target_language_overrides'][$lingotek_locale]); + } + } + variable_set('lingotek_profiles', $profiles); + + $entity_profiles = variable_get('lingotek_entity_profiles', array()); + foreach ($entity_profiles as &$bundles) { + foreach (array_keys($bundles) as $bundle_handle) { + if (strpos($bundle_handle, '__')) { + list($bundle_name, $locale) = explode('__', $bundle_handle); + if ($locale == $lingotek_locale) { + unset($bundles[$bundle_handle]); + } + } + } + } + variable_set('lingotek_entity_profiles', $entity_profiles); } return $result; @@ -756,41 +880,63 @@ function lingotek_set_target_language($drupal_language_code, $lingotek_enable = $one_success = FALSE; //tracks if any result has been true $lingotek_locale = is_null($lingotek_locale) ? Lingotek::convertDrupal2Lingotek($drupal_language_code, FALSE) : $lingotek_locale; - if (is_string($drupal_language_code) && strlen($drupal_language_code) && $lingotek_enable && $lingotek_locale && $api_add) { - $api = LingotekApi::instance(); - $projects = LingotekSync::getSyncProjects(); - foreach ($projects as $project_id) { - $result = $api->addTranslationTarget(NULL, $project_id, $lingotek_locale); - if ($result) { - LingotekSync::insertTargetEntriesForAllEntities($lingotek_locale); - LingotekSync::insertTargetEntriesForAllSets($lingotek_locale); + if (is_string($drupal_language_code) && strlen($drupal_language_code) && $lingotek_enable && $lingotek_locale) { + if ($api_add) { + // Add the language globally to all documents in the project + $api = LingotekApi::instance(); + $projects = LingotekSync::getSyncProjects(); + foreach ($projects as $project_id) { + $result = $api->addTranslationTarget(NULL, $project_id, $lingotek_locale); + if ($result) { + LingotekSync::insertTargetEntriesForAllEntities($lingotek_locale); + LingotekSync::insertTargetEntriesForAllSets($lingotek_locale); + } + $one_success = $one_success || $result; + } + if (!$one_success) { + drupal_set_message(t('@lingotek_locale could not be added as a language for Lingotek to translate.', array('@lingotek_locale' => $lingotek_locale)), 'error', FALSE); + LingotekLog::error("Target language could not be added: @drupal_language_code (@lingotek_locale)", array( + '@drupal_language_code' => $drupal_language_code, + '@lingotek_locale' => $lingotek_locale)); + + return FALSE; } - $one_success = $one_success || $result; - } - - if ($one_success == TRUE) { - db_update('languages') - ->fields(array( - 'enabled' => 1, - 'lingotek_enabled' => $lingotek_enable ? 1 : 0, - 'lingotek_locale' => $lingotek_locale - )) - ->condition('language', $drupal_language_code) - ->execute(); - drupal_static_reset('language_list'); - LingotekLog::info("Target language added: @drupal_language_code (@lingotek_locale)", array( - '@drupal_language_code' => $drupal_language_code, - '@lingotek_locale' => $lingotek_locale)); } else { - drupal_set_message(t('@lingotek_locale could not be added as a language for Lingotek to translate.', array('@lingotek_locale' => $lingotek_locale)), 'error', FALSE); - LingotekLog::error("Target language could not be added: @drupal_language_code (@lingotek_locale)", array( - '@drupal_language_code' => $drupal_language_code, - '@lingotek_locale' => $lingotek_locale)); + // Don't add the language globally to all documents in the project. + // Instead, disable this language in all profiles except config. + $profiles = lingotek_get_profiles(); + foreach ($profiles as $profile) { + if ($profile->getId() !== LingotekSync::PROFILE_CONFIG) { + $profile->disableTargetLocale($lingotek_locale); + } + } + // If there are any documents related to config translation, + // automatically add the target language to all of them + $config_profile = LingotekProfile::loadById(LingotekSync::PROFILE_CONFIG); + $doc_ids = $config_profile->getDocumentIds(); + if ($doc_ids) { + $api = LingotekApi::instance(); + foreach ($doc_ids as $doc_id) { + $api->addTranslationTarget($doc_id, NULL, $lingotek_locale); + } + } } - } - return $result; + db_update('languages') + ->fields(array( + 'enabled' => 1, + 'lingotek_enabled' => $lingotek_enable ? 1 : 0, + 'lingotek_locale' => $lingotek_locale + )) + ->condition('language', $drupal_language_code) + ->execute(); + drupal_static_reset('language_list'); + LingotekLog::info("Target language added: @drupal_language_code (@lingotek_locale)", array( + '@drupal_language_code' => $drupal_language_code, + '@lingotek_locale' => $lingotek_locale)); + } + return TRUE; } function lingotek_lookup_language_by_locale($lingotek_locale) { @@ -813,6 +959,54 @@ function lingotek_lookup_locale_exists($drupal_language_code) { return FALSE; } +function lingotek_create_path_prefix($drupal_code) { + $prefix = ''; + $prefix_without_hyphen = ''; + $prefix_with_hyphen = $drupal_code; + + // Create prefix without hyphen + if (strpos($drupal_code, '-') !== FALSE) { + $hyphen_pos = strpos($drupal_code, '-'); + $prefix_without_hyphen = substr($drupal_code, 0, $hyphen_pos); + } + else { + $prefix_without_hyphen = $drupal_code; + } + + $prefixes = db_select('languages', 'l') + ->fields('l', array('prefix')) + ->execute() + ->fetchCol(); + + // See if the path prefix without hyphen is already being used. If so, try prefix with hyphen + $prefix_without_hyphen_used = FALSE; + foreach($prefixes as $prefix) { + if ($prefix === $prefix_without_hyphen) { + $prefix_without_hyphen_used = TRUE; + break; + } + } + if (!$prefix_without_hyphen_used) { + $prefix = $prefix_without_hyphen; + } + + // See if the path prefix with hyphen is already being used. + $prefix_with_hyphen_used = FALSE; + if ($prefix_without_hyphen_used) { + foreach($prefixes as $prefix) { + if ($prefix === $drupal_code) { + $prefix_with_hyphen_used = TRUE; + break; + } + } + if (!$prefix_with_hyphen_used) { + $prefix = $prefix_with_hyphen; + } + } + + return $prefix; +} + /** * Adds the target language as being enabled. */ @@ -833,9 +1027,22 @@ function lingotek_add_target_language($lingotek_locale, $call_api = TRUE) { // DOES NOT EXIST, INSERT NEW INTO LANGUAGE TABLE // If not add it to the languages table first and then tack on the lingotek_locale and enable it $drupal_language_code = Lingotek::convertLingotek2Drupal($lingotek_locale, FALSE); + + $predefined_languages = array(); + if (module_exists('locale')) { + $predefined_languages = _locale_prepare_predefined_list(); + } + + $drupal_code_with_locale = strtolower(str_replace("_", "-", $lingotek_locale)); + + // Use a predefined Drupal language code, if available + if (array_key_exists($drupal_code_with_locale, $predefined_languages)) { + $drupal_language_code = $drupal_code_with_locale; + } + if (lingotek_lookup_locale_exists($drupal_language_code)) { // drupal code is already being used, generate another - $errors = array($drupal_language_code); - $drupal_language_code = strtolower(str_replace("_", "-", $lingotek_locale)); + $errors = array($drupal_language_code); + $drupal_language_code = $drupal_code_with_locale; if (lingotek_lookup_locale_exists($drupal_language_code)) { $errors[] = $drupal_language_code; LingotekLog::error("Cannot add language code. Attempted language codes already being used: !errors", array('!errors' => $errors)); @@ -843,11 +1050,11 @@ function lingotek_add_target_language($lingotek_locale, $call_api = TRUE) { } } + $prefix = lingotek_create_path_prefix($drupal_language_code); $name = isset($_POST['language']) ? $_POST['language'] : NULL; $native = isset($_POST['native']) ? $_POST['native'] : NULL; $direction = isset($_POST['direction']) && (strcasecmp('RTL', $_POST['direction']) == 0 ) ? LANGUAGE_RTL : LANGUAGE_LTR; $domain = ''; - $prefix = ''; locale_add_language($drupal_language_code, $name, $native, $direction, $domain, $prefix); // Function from the Locale module. } @@ -916,12 +1123,10 @@ function lingotek_get_target_locales($codes_only = TRUE) { } /* - * Get the Lingotek Content Types - returns the content types OR ids only + * Get the Lingotek Content Types. Returns the names of the content types * - * @param $ids_only - * Boolean - return ids only rather than associative array of content type definitions * @return - * Mixed - associative array (or an array of keys when ids_only is TRUE) + * Array of content type keys */ function lingotek_get_content_types() { @@ -1013,6 +1218,18 @@ function lingotek_cleanup_field_languages_for_taxonomy_terms() { lingotek_cleanup_field_languages('taxonomy_term'); } +function lingotek_cleanup_field_languages_for_beans() { + lingotek_cleanup_field_languages('bean'); +} + +function lingotek_cleanup_field_languages_for_groups() { + lingotek_cleanup_field_languages('group'); +} + +function lingotek_cleanup_field_languages_for_paragraphs() { + lingotek_cleanup_field_languages('paragraphs_item'); +} + /** * Report all Lingotek translations to the Entity Translation module */ @@ -1032,7 +1249,7 @@ function lingotek_cleanup_notify_entity_translation() { $bundle_name = !empty($info['entity keys']['bundle']) ? $info['entity keys']['bundle'] : NULL; if ($bundle_name && lingotek_managed_by_entity_translation($entity->$bundle_name)) { $addtl_params = array('created' => $r->created, 'changed' => $r->modified); - list($languages_updated, $updates) = lingotek_entity_translation_save_status($r->entity_type, $entity, array(Lingotek::convertLingotek2Drupal($locale, FALSE)), $addtl_params); + list($languages_updated, $updates) = lingotek_entity_translation_save_status($r->entity_type, $entity, array(Lingotek::convertLingotek2Drupal($locale)), $addtl_params); $total_updates += $updates; } } @@ -1075,7 +1292,7 @@ function lingotek_set_priority() { * Gets the default profile info, mapped by entity type */ function lingotek_load_profile_defaults($entity_type) { - $profile_defaults = lingotek_get_profiles(); + $profile_defaults = lingotek_get_profiles(FALSE); $entity_profile_defaults = variable_get('lingotek_entity_profiles', array()); $profile_map = array(); if (array_key_exists($entity_type, $entity_profile_defaults)) { @@ -1147,7 +1364,7 @@ function lingotek_migration_2() { } catch (DatabaseSchemaObjectExistsException $e) { // already exists (no need to do anything) } - drupal_static_reset('language_list'); + lingotek_add_missing_locales(); } /** @@ -1335,22 +1552,16 @@ function lingotek_workbench_icon($entity_type, $id, $lingotek_locale, $tooltip = return l($text, $url, array('attributes' => array('class' => array('lingotek-translation-link'), 'target' => '_blank', 'title' => $tooltip), 'html' => TRUE)); } -function lingotek_list_entities_with_language($entity_type, $bundle, $language) { - $ids = array(); - $fields = field_info_instances($entity_type, $bundle); - foreach ($fields as $name => $field_info) { - $ids = $ids + lingotek_list_entities_with_field_in_language_by_bundle($entity_type, $bundle, $name, $language); - } - - return $ids; -} - function lingotek_list_nodes_translated_in_language($language, $bundle = NULL) { + $disabled_bundles = lingotek_get_disabled_bundles('node'); $query = db_select('node') ->fields('node', array('tnid')); if (!is_null($bundle)) { $query->condition('node.type', $bundle); } + if (!empty($disabled_bundles)) { + $query->condition('type', $disabled_bundles, 'NOT IN'); + } $query->condition('node.tnid', 0, '<>') ->condition('language', $language) ->where('node.nid != node.tnid'); @@ -1425,7 +1636,7 @@ function lingotek_filter_placeholders($segment_text, $protect_vars = FALSE) { // of an HTML tag). It also protects everything inside square brackets // that do not fall inside angle brackets. $patterns = array( - '/(\[[!@%\w:=\/\s_-]+\]\s*)(?![^<]*\>)/', // wrap everything in square brackets + '/(\[[!@%\w:=\/\&\;\s_-]+\]\s*)(?![^<]*\>)/', // wrap everything in square brackets ); if ($protect_vars) { $patterns[] = '/([!@%][\w_-]+\s*)(?![^<]*\>)/'; // wrap everything beginning with !,@,% @@ -1466,16 +1677,16 @@ function lingotek_unfilter_placeholders($segment_text) { * whether to redirect to the next setup destination or just return the current one */ function lingotek_get_entity_setup_path($entity_type, $next=FALSE) { - $node_next = 'admin/config/lingotek/comment-translation-settings'; + $node_next = LINGOTEK_MENU_LANG_BASE_URL . '/comment-translation-settings'; if (!module_exists('comment')) { - $node_next = 'admin/config/lingotek/additional-translation-settings'; + $node_next = LINGOTEK_MENU_LANG_BASE_URL . '/additional-translation-settings'; } $entities = array( - 'node' => array('current' => 'admin/config/lingotek/node-translation-settings', + 'node' => array('current' => LINGOTEK_MENU_LANG_BASE_URL . '/node-translation-settings', 'next' => $node_next), - 'comment' => array('current' => 'admin/config/lingotek/comment-translation-settings', - 'next' => 'admin/config/lingotek/additional-translation-settings'), - 'config' => array('current' => 'admin/config/lingotek/additional-translation-settings', + 'comment' => array('current' => LINGOTEK_MENU_LANG_BASE_URL . '/comment-translation-settings', + 'next' => LINGOTEK_MENU_LANG_BASE_URL . '/additional-translation-settings'), + 'config' => array('current' => LINGOTEK_MENU_LANG_BASE_URL . '/additional-translation-settings', 'next' => 'admin/settings/lingotek'), ); if (isset($entities[$entity_type])) { @@ -1516,22 +1727,8 @@ function lingotek_get_bundles_by_profile_id($profile_id) { } return $bundles; } - $entities = entity_get_info(); - $lentities = variable_get('lingotek_entity_profiles'); - foreach ($entities as $entity_name => $entity) { - if (!isset($lentities[$entity_name])) { - unset($entities[$entity_name]); - } - foreach ($entity['bundles'] as $bundle_name => $bundle) { - if (isset($lentities[$entity_name][$bundle_name]) && $lentities[$entity_name][$bundle_name] === (string)$profile_id) { - if (!isset($bundles[$entity_name])) { - $bundles[$entity_name] = array(); - } - $bundles[$entity_name][$bundle_name] = TRUE; - } - } - } - return $bundles; + $profile = LingotekProfile::loadById($profile_id); + return $profile->getBundles(); } /* @@ -1554,116 +1751,8 @@ function lingotek_get_document_id_tree() { } function lingotek_get_all_entities_by_profile($profile_id) { - // gather all bundles for searching, as some entities may be one-offs - // even though Lingotek is not enabled for the entire bundle. - $all_profiles = lingotek_get_profiles(); - $all_profiles[LingotekSync::PROFILE_CUSTOM] = TRUE; - $all_profiles[LingotekSync::PROFILE_DISABLED] = TRUE; - $all_bundles = lingotek_get_bundles_by_profile_id(array_keys($all_profiles)); - $all_entities = array(); - // aggregate all entity-type-specific results into a single numbered array - foreach (array_keys($all_bundles) as $entity_type) { - $entities = lingotek_get_entities_by_profile_and_entity_type($profile_id, $entity_type); - foreach ($entities as $e) { - $all_entities[] = $e; - } - } - return $all_entities; -} - -/* - * Get all entities that are enabled for lingotek translation, by profile and entity_type - * - * Return an array of entity-specific arrays containing document_id, type, and id - */ -function lingotek_get_entities_by_profile_and_entity_type($profile_id, $entity_type) { - // get all bundles that belong to the given profile - $all_bundles = lingotek_get_bundles_by_profile_id($profile_id); // this function accepts an id or an array of ids - $bundles = array(); - $entities = array(); - - if (isset($all_bundles[$entity_type])) { - $bundles = array($entity_type => $all_bundles[$entity_type]); - } - - // get all entities that belond to those bundles - foreach ($bundles as $entity_type => $entity_bundles) { - if ($entity_type == 'comment') { - $ref_tables = array(); - foreach (array_keys($entity_bundles) as $key) { - $tmp_array = explode('_', $key); - $key = implode('_', array_slice($tmp_array, 2)); - $ref_tables[] = $key; - } - $query = db_select('' . $entity_type . '', 'e') - ->fields('e', array('cid')); - $query->join('node', 'n', "n.nid = e.nid AND n.type IN ('" . implode("','", $ref_tables) . "')"); - $results = $query->execute()->fetchCol(); - foreach ($results as $id) { - $entities[] = array('id' => $id, 'type' => $entity_type); - } - } - else { - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', $entity_type) - ->entityCondition('bundle', array_keys($entity_bundles), 'IN'); - $result = $query->execute(); - unset($query); - if (isset($result[$entity_type])) { - foreach ($result[$entity_type] as $id => $entity_data) { - $entities[] = array('id' => $id, 'type' => $entity_type); - } - } - } - // END OPTIMIZED WAY - } - - // subtract all entities specifically *not* set to the given profile - $query = db_select('lingotek_entity_metadata', 'lem') - ->fields('lem', array('entity_id', 'entity_type')) - ->condition('lem.entity_key', 'profile') - ->condition('lem.value', $profile_id, is_array($profile_id) ? 'NOT IN' : '!=') - ->condition('lem.entity_type', $entity_type); - $result = $query->execute(); - $subtract_entity_ids = $result->fetchAll(); - - $doc_ids = lingotek_get_document_id_tree(); - $subtractions = array(); - foreach ($subtract_entity_ids as $sei) { - if (!isset($subtractions[$sei->entity_type])) { - $subtractions[$sei->entity_type] = array(); - } - $subtractions[$sei->entity_type][$sei->entity_id] = TRUE; - } - $filtered_entities = array(); - foreach ($entities as $e) { - if (!isset($subtractions[$e['type']][$e['id']])) { - if (isset($doc_ids[$e['type']][$e['id']])) { - $e['document_id'] = $doc_ids[$e['type']][$e['id']]; - } - $filtered_entities[$e['id']] = $e; - } - } - - // add all entities specifically set to the given profile - $query = db_select('lingotek_entity_metadata', 'lem') - ->fields('lem', array('entity_id', 'entity_type')) - ->condition('lem.entity_key', 'profile') - ->condition('lem.value', $profile_id, is_array($profile_id) ? 'IN' : '='); - if ($entity_type != 'all') { - $query->condition('lem.entity_type', $entity_type); - } - $result = $query->execute(); - $add_entity_ids = $result->fetchAll(); - foreach ($add_entity_ids as $aei) { - $addition = array('id' => $aei->entity_id, 'type' => $aei->entity_type); - if (isset($doc_ids[$aei->entity_type][$aei->entity_id])) { - $addition['document_id'] = $doc_ids[$aei->entity_type][$aei->entity_id]; - } - $filtered_entities[$aei->entity_id] = $addition; - } - - return $filtered_entities; + $profile = LingotekProfile::loadById($profile_id); + return $profile->getEntities(); } /** @@ -1699,7 +1788,7 @@ function lingotek_cleanup_field_collection_fields() { foreach ($fc_langs as $fc_lang) { $fc_fields_found += (int) $fc_lang; } - db_query("UPDATE IGNORE {field_data_" . $fc_field_name . "} SET language = '" . LANGUAGE_NONE . "'"); + db_query("UPDATE IGNORE {field_data_" . $fc_field_name . "} SET language = '" . LANGUAGE_NONE . "' where entity_type <> 'group'"); // Find all unnecessary fields (language-specific) for removal. $fc_langs = lingotek_cleanup_get_dirty_field_collection_fields("field_data_" . $fc_field_name); @@ -1933,9 +2022,14 @@ function lingotek_set_batch_function($func_name) { * an array of arrays containing entity type and entity ID */ function lingotek_get_enabled_entities_by_type($entity_type) { - $profiles = lingotek_get_profiles(); + $profiles = lingotek_get_profiles(FALSE); $profile_ids = array_keys($profiles); - return lingotek_get_entities_by_profile_and_entity_type($profile_ids, $entity_type); + $entities = array(); + foreach ($profile_ids as $id) { + $profile = LingotekProfile::loadById($id); + $entities += $profile->getEntities($entity_type); + } + return $entities; } function lingotek_previously_managed_translations($entity_type, $entity_ids = NULL) { @@ -1960,7 +2054,7 @@ function lingotek_previously_managed_translations($entity_type, $entity_ids = NU $query->entityCondition('entity_type', $entity_type) ->entityCondition('entity_id', $entity_ids, 'IN'); $entities = $query->execute(); - $entity_ids = array_keys($entities[$entity_type]); + $entity_ids = !empty($entities[$entity_type]) ? array_keys($entities[$entity_type]) : array(); $enabled_entities = lingotek_get_enabled_entities_by_type($entity_type); $entity_ids = array_diff($entity_ids, array_keys($enabled_entities)); @@ -1980,7 +2074,7 @@ function lingotek_previously_managed_translations($entity_type, $entity_ids = NU function lingotek_is_node_translation($node = NULL) { // The case of creating a new node-based translation. if (isset($_GET['translation']) && isset($_GET['target']) && (lingotek_entity_load_single('node', (int) $_GET['translation']))) { - return $_GET['translation']; + return check_plain($_GET['translation']); } // locate a translation source node ID, if available $tnid = !empty($node->tnid) && $node->tnid !== $node->nid && $node->tnid !== 0 ? $node->tnid : NULL; @@ -2008,8 +2102,8 @@ function lingotek_uses_node_translation($node = NULL) { return FALSE; } - $profile_info = lingotek_get_profile_settings($node->lingotek['profile']); - if ($profile_info['lingotek_nodes_translation_method'] == 'node') { + $profile = LingotekProfile::loadById($node->lingotek['profile']); + if ($profile->isNodeBased()) { return TRUE; } @@ -2017,35 +2111,22 @@ function lingotek_uses_node_translation($node = NULL) { return FALSE; } - function lingotek_managed_entity($entity_type, $entity) { - list($id, $vid, $bundle) = lingotek_entity_extract_ids($entity_type, $entity); - - // If there is no $bundle property, then nothing to manage for now. - if (!$bundle) { - return FALSE; - } - // If this is being newly created, see if the content type is enabled - if (!$id) { - return lingotek_enabled_bundle($entity_type, $bundle); - } - // Check the entity itself for overrides. - $query = db_select('lingotek_entity_metadata', 'lem') - ->fields('lem', array('value')) - ->condition('lem.entity_type', $entity_type) - ->condition('lem.entity_id', $id) - ->condition('lem.entity_key', 'profile'); - $result = $query->execute()->fetchField(); - if ($result !== FALSE) { - if ($result === LingotekSync::PROFILE_DISABLED) { + if (isset($entity->lingotek['profile'])) { + if ($entity->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) { return FALSE; } else { + // Profile is present and not set to disabled, so avoid calling + // loadByEntity... return TRUE; } } - // No overrides, so check the entity's bundle. - return lingotek_enabled_bundle($entity_type, $bundle); + $profile = LingotekProfile::loadByEntity($entity_type, $entity); + if ($profile->getId() == LingotekSync::PROFILE_DISABLED) { + return FALSE; + } + return TRUE; } function lingotek_entity_extract_ids($entity_type, $entity) { @@ -2059,7 +2140,7 @@ function lingotek_entity_extract_ids($entity_type, $entity) { $vid = NULL; $bundle_name = $entity->type; } - elseif ($entity_type == 'field_collection_item') { + elseif ($entity_type == 'field_collection_item' && is_object($entity)) { $entity_id = $entity->item_id; $vid = $entity->revision_id; $bundle_name = $entity->field_name; @@ -2083,6 +2164,27 @@ function lingotek_enabled_langcode($langcode) { } function lingotek_entity_langcode($entity_type, $entity) { + // Assume all bean content is created in the site's default language. + if ($entity_type == 'bean') { + return language_default()->language; + } + // Get the group source language. Sometimes the gid for a group isn't present. + if ($entity_type == 'group') { + if (isset($entity->gid)) { + $source_language = lingotek_get_group_source($entity->gid); + } + else { + $source_language = isset($entity->language) ? $entity->language : NULL; + } + return $source_language; + } + if ($entity_type == 'paragraphs_item') { + return lingotek_get_paragraphs_item_source($entity->item_id); + } + if ($entity_type == 'file') { + return lingotek_get_file_source($entity->fid); + } + $info = entity_get_info($entity_type); // Default to string 'language' if no language-related entity key is found. $lang_key = !empty($info['entity keys']['language']) ? $info['entity keys']['language'] : 'language'; @@ -2111,3 +2213,497 @@ function lingotek_notify_if_no_languages_added() { drupal_set_message(t('No languages are enabled yet for Lingotek Translation. Please add one or more languages (located under "Your site Languages" on the Dashboard tab).'), 'warning'); } } + +function lingotek_entity_locale($entity_type, $entity) { + $langcode = lingotek_entity_langcode($entity_type, $entity); + return Lingotek::convertDrupal2Lingotek($langcode); +} + +function lingotek_entity_init_language($entity_type, $entity) { + // For now, this is only a problem with bean entities not having a language at this point + if ($entity_type == 'bean' && empty($entity->language)) { + $entity->language = language_default()->language; + } + if ($entity_type == 'group' && empty($entity->language)) { + $entity->language = language_default()->language; + } +} + +function lingotek_get_current_locales($entity_type, $entity_id) { + $query = db_select('lingotek_entity_metadata', 'lem') + ->fields('lem', array('entity_key')) + ->condition('lem.entity_type', $entity_type) + ->condition('lem.entity_id', $entity_id) + ->condition('lem.entity_key', 'target_sync_status_%', 'LIKE'); + $result = $query->execute()->fetchCol(); + + $locales = array(); + foreach ($result as $target_str) { + $locales[] = str_replace('target_sync_status_', '', $target_str); + } + return $locales; +} + +function lingotek_translatable_field_column($entity_type, $bundle, $field_name, $column_name) { + + // This variable may be overridden to include perhaps field columns + // called 'url' or 'params' or etc. + $translatable_field_columns = lingotek_get_translatable_field_columns(); + + // FORMAT FOR VARIABLE 'lingotek_translatable_field_columns_by_entity_type': + // array( + // '[entity_type]' => array( + // '[bundle]' => array( + // '[field_name]' => array( + // '[column_name1]' => TRUE, + // '[column_name2]' => TRUE, + // ), + // ), + // ), + // ); + $translatable_field_columns_by_entity_type = variable_get('lingotek_translatable_field_columns_by_entity_type', array()); + + // If a field array is not empty for a given entity type, then look to it + // for what field columns to include for a given entity type and bundle. + // This way fields included by default may be excluded for specific bundles + // and vice versa. + if (!empty($translatable_field_columns_by_entity_type[$entity_type][$bundle][$field_name])) { + return !empty($translatable_field_columns_by_entity_type[$entity_type][$bundle][$field_name][$column_name]); + } + else { + return in_array($column_name, $translatable_field_columns); + } +} + +function lingotek_get_translatable_field_columns() { + // What columns DO we translate? + $default_translatable_columns = array('value', 'summary', 'alt', 'title'); + + // Allow override of translatable columns using the variables table. + return variable_get('lingotek_translatable_column_defaults', $default_translatable_columns); +} + +/* + * Determine if the passed XML object is the current format (as of v7.x-7.00) + */ +function lingotek_current_xml_format($xml) { + foreach ($xml as $field_name => $content) { + foreach ($content as $column_name => $text) { + if ($column_name == 'element' && !$text->count()) { + return FALSE; + } + } + } + return TRUE; +} + +/* + * Find the languages for a given entity in Drupal (searches node-based and field-based translations) + */ +function lingotek_get_languages($entity_type, $entity_id) { + if ($entity_type == 'menu_link') { + $translations = array(); + $entity = menu_link_load($entity_id); + $menu_langcode = $entity['language']; + $tsid = $entity['i18n_tsid']; + $translation_set = i18n_translation_set_load($tsid, 'menu_link'); + + if ($translation_set) { + $translations = $translation_set->get_translations(); + } + else { + $translations[$menu_langcode] = TRUE; + } + + return array_keys($translations); + } + + $existing_translations = array(); + $drupal_langcodes = language_list(); + $entity = lingotek_entity_load_single($entity_type, $entity_id); + + // check for node-based translations + if ($entity_type == 'node' && module_exists('translation')) { + $target_nodes = lingotek_node_get_translations($entity_id); + foreach ($target_nodes as $langcode => $node_info) { + $existing_translations[$langcode] = TRUE; + } + } + + // check for field-based translations + foreach ($entity as $k => $v) { + if (is_array($v) && !empty($v)) { + foreach (array_keys($v) as $langcode) { + if (!empty($drupal_langcodes[$langcode])) { + $existing_translations[$langcode] = TRUE; + } + } + } + } + return array_keys($existing_translations); +} + +/** + * Returns an array of the nid's of target nodes for node based translation + * + * @param $nid The id of the source node + * @return $result An array of nid's for target nodes + */ +function lingotek_get_target_node_ids($nid) { + $result = db_select('node', 'n') + ->fields('n', array('nid')) + ->condition('n.tnid', $nid, '=') + ->execute() + ->fetchCol(); + + $source_nid_key = array_search($nid, $result); + if ($source_nid_key !== FALSE) { + unset($result[$source_nid_key]); + } + + return $result; +} + +/** + * Delete translations for node based entities + * + * @param $nid The id of the source node + */ +function lingotek_delete_node_translations($nid) { + // Get the source node + $source_node = lingotek_node_load_default($nid); + $target_nids = lingotek_get_target_node_ids($nid); + $target_statuses = LingotekSync::getAllTargetStatusForEntity('node', $nid); + + foreach ($target_nids as $target_nid) { + entity_delete('node', $target_nid); + } + // If the status is current and the translations haven't been disassociated, set status to READY see INT-563 + foreach ($target_statuses as $langcode => $status) { + if ($status == LingotekSync::STATUS_CURRENT && isset($source_node->lingotek['document_id'])) { + LingotekSync::setTargetStatus('node', $nid, $langcode, LingotekSync::STATUS_READY); + } + else { + LingotekSync::setTargetStatus('node', $nid, $langcode, LingotekSync::STATUS_DELETED); + } + } +} + +/** + * Delete translations for field based entities + * + * @param $entity_type + * @param $entity + */ +function lingotek_delete_field_translations($entity_type, $entity) { + $target_status = LingotekSync::STATUS_NONE; + $entity_data = entity_extract_ids($entity_type, $entity); + $fields = field_info_instances($entity_type); + $source_langcode[] = lingotek_entity_langcode($entity_type, $entity); + $langcodes = array_diff(lingotek_get_languages($entity_type, $entity_data[0]), $source_langcode); // removes source language code from array so source content isn't deleted + + // Removes all target language content in each field that has translations + foreach ($fields[$entity_data[2]] as $field) { + $field_name = $field['field_name']; + foreach($langcodes as $langcode) { + $lingotek_locale = Lingotek::convertDrupal2Lingotek($langcode); + if (isset($entity->lingotek['document_id'])) { + $status = LingotekSync::getTargetStatus($entity->lingotek['document_id'], $lingotek_locale); + // If the status is current and the translations haven't been disassociated, set status to READY see INT-563 + if ($status == LingotekSync::STATUS_CURRENT) { + $target_status = LingotekSync::STATUS_READY; + } + elseif ($status == LingotekSync::STATUS_INTERIM) { + $target_status = LingotekSync::STATUS_READY_INTERIM; + } + } + if ($langcode && !empty($entity->$field_name)) { + if (isset($field['display']['default']['type']) && $field['display']['default']['type'] === 'image') { + if (isset($entity->{$field_name}[$langcode][0]['alt'])) { + $entity->{$field_name}[$langcode][0]['alt'] = ''; + } + if (isset($entity->{$field_name}[$langcode][0]['title'])) { + $entity->{$field_name}[$langcode][0]['title'] = ''; + } + } + else { + $entity->{$field_name}[$langcode] = array(); + } + } + LingotekSync::setTargetStatus($entity_type, $entity_data[0], $lingotek_locale, $target_status); + } + } + entity_save($entity_type, $entity); +} + +function lingotek_delete_menu_link_translations($mlids) { + foreach ($mlids as $mlid) { + $menu_link = menu_link_load($mlid); + $tsid = $menu_link['i18n_tsid']; + $translation_set = i18n_translation_set_load($tsid, 'menu_link'); + $translations = $translation_set->get_translations(); + $has_doc_id = LingotekSync::getDocumentId('menu_link', $mlid); + + foreach($translations as $langcode => $menu_link) { + $translation_mlid = $menu_link['mlid']; + if ($translation_mlid != $mlid) { + $count += 1; + menu_link_delete($translation_mlid); + $lingotek_locale = Lingotek::convertDrupal2Lingotek($langcode); + $status = LingotekSync::getMenuLinkTargetStatus($mlid, $lingotek_locale); + + if ($status == LingotekSync::STATUS_CURRENT && $has_doc_id) { + $target_status = LingotekSync::STATUS_READY; + LingotekSync::setTargetStatus('menu_link', $mlid, $lingotek_locale, $target_status); + } + else { + LingotekSync::deleteTargetStatus('menu_link', $mlid, $lingotek_locale); + } + } + } + } +} + +function lingotek_delete_menu_links($mlids) { + foreach ($mlids as $mlid) { + $menu_link = menu_link_load($mlid); + $tsid = $menu_link['i18n_tsid']; + $translation_set = i18n_translation_set_load($tsid, 'menu_link'); + + if ($translation_set) { + $translations = $translation_set->get_translations(); + + foreach($translations as $langcode => $menu_link) { + $translation_mlid = $menu_link['mlid']; + menu_link_delete($translation_mlid); + } + } + else { + menu_link_delete($mlid); + } + } +} + +/** + * Get cached Lingotek-specific metadata for a given entity + */ +function lingotek_cache_get($entity_type, $entity_id) { + $entry = cache_get('lingotek__' . $entity_type . '__' . $entity_id); + if ($entry) { + return $entry->data; + } + return array(); +} + +/** + * Set cached Lingotek-specific metadata for a given entity + */ +function lingotek_cache_set($entity_type, $entity_id, $info_to_cache) { + cache_set('lingotek__' . $entity_type . '__' . $entity_id, $info_to_cache, 'cache', CACHE_TEMPORARY); +} + +/** + * Clear cached Lingotek-specific metadata for a given entity. + * - If entity_id is blank, then clear metadata for all entities of the type. + * - If both are blank, then clear metadata for all entities. + */ +function lingotek_cache_clear($entity_type = NULL, $entity_id = NULL) { + if ($entity_type == NULL && $entity_id == NULL) { + cache_clear_all('lingotek__', 'cache', TRUE); + return; + } + elseif ($entity_id == NULL) { + cache_clear_all('lingotek__' . $entity_type . '__', 'cache', TRUE); + return; + } + cache_clear_all('lingotek__' . $entity_type . '__' . $entity_id, 'cache'); +} + +/** + * Loads a source language for a group type + */ +function lingotek_get_group_source($entity_id) { + if (!module_exists('entity_translation')) { + drupal_set_message(t('The Entity Translation module is required for translation of Group module types.'), 'warning', FALSE); + return language_default()->language; + } + + $row = db_select('entity_translation', 'et') + ->fields('et') + ->condition('entity_type', 'group') + ->condition('entity_id', $entity_id) + ->condition('source', '') + ->execute() + ->fetch(PDO::FETCH_ASSOC); + + return $row['language']; +} + +/** + * Builds an array that masquerades as fields for Menu Links for upload + */ +function lingotek_get_menu_link_fields() { + $menu_link_fields = array(); + + $menu_link_fields['link_title'] = array( + 'label' => 'Title', + 'field_name' => 'title', + ); + + $menu_link_fields['link_description'] = array( + 'label' => 'Description', + 'field_name' => 'description', + ); + + return $menu_link_fields; +} + +/** + * Builds an array that masquerades as field_content for Menu Links for upload + */ +function lingotek_get_menu_link_field_content($field_name, $entity) { + $field_content = array(); + $field_array = array(); + $field_language = $entity->language; + + if ($field_name == 'title') { + $field_array = array( + 'value' => $entity->link_title, + ); + } + elseif ($field_name == 'description') { + if (isset($entity->options['attributes']['title'])) { + $field_array = array( + 'value' => $entity->options['attributes']['title'], + ); + } + } + + $field_content[$field_language][] = $field_array; + + return $field_content; +} + +/** + * Builds an array that masquerades as field_columns for Menu Links for upload + */ +function lingotek_get_menu_link_field_columns() { + $field_columns = array(); + $field_columns['value'] = array(); + + return $field_columns; +} +/** + * Retrieves a translation set for menu links + */ +function lingotek_get_translation_set($mlid, $menu_name) { + $source_menu_link = menu_link_load($mlid); + $source_tsid = $source_menu_link['i18n_tsid']; + + // Create new translation set + if ($source_tsid == 0) { + $translation_set = i18n_translation_set_build('menu_link'); + $translations = array( + $source_menu_link->language => $source_menu_link, + ); + $translation_set->add_translations($translations); + } + // Load existing translation set + else { + $translation_set = i18n_translation_set_load($source_tsid, 'menu_link'); + } + + return $translation_set; +} + +/** + * Returns whether a menu link is the source menu link from a i18n translation set + */ +function lingotek_is_menu_link_source($mlid) { + $menu_link = menu_link_load($mlid); + $tsid = $menu_link['i18n_tsid']; + + if ($tsid == 0) { + return TRUE; + } + + $query = db_select('menu_links', 'ml') + ->condition('ml.i18n_tsid', $tsid); + $query->addExpression('MIN(mlid)', 'source'); + $source = $query->execute()->fetch(PDO::FETCH_ASSOC); + $source_menu_link = $source['source']; + + if ($source_menu_link == $mlid) { + return TRUE; + } + + return FALSE; +} + +/** + * Returns a list of taxonomy vocabularies that have field instances. + * + * @return array + * An array containing the vocabulary/bundle machine name, keyed by vid. + */ +function lingotek_get_advanced_vocabularies() { + $query = db_select('taxonomy_vocabulary', 'tv'); + $query->fields('tv', array('vid', 'machine_name')); + $query->join('field_config_instance', 'fci', 'tv.machine_name = fci.bundle'); + $vocabularies = $query->execute()->fetchAllKeyed(); + drupal_alter('lingotek_advanced_taxonomies', $vocabularies); + + return $vocabularies; +} + +/* + * Loads a source language for a paragraphs_item + */ +function lingotek_get_paragraphs_item_source($entity_id) { + if (!module_exists('entity_translation')) { + if (variable_get('lingotek_translate_paragraphs', FALSE)) { + drupal_set_message(t('The Entity Translation module is required for translation of Paragraphs module types.'), 'warning', FALSE); + } + return language_default()->language; + } + + $source = db_select('entity_translation', 'et') + ->fields('et', array('language')) + ->condition('entity_type', 'paragraphs_item') + ->condition('entity_id', $entity_id) + ->condition('source', '') + ->execute() + ->fetchField(); + + if (!$source) { + return language_default()->language; + } + + return $source; +} + + /* + * Loads a source language for a file entity + */ +function lingotek_get_file_source($entity_id) { + if (!module_exists('entity_translation')) { + if (variable_get('lingotek_translate_files', FALSE)) { + drupal_set_message(t('The Entity Translation module is required for translation of File Entity module types.'), 'warning', FALSE); + } + return language_default()->language; + } + + $source = db_select('entity_translation', 'et') + ->fields('et', array('language')) + ->condition('entity_type', 'file') + ->condition('entity_id', $entity_id) + ->condition('source', '') + ->execute() + ->fetchField(); + + if (!$source) { + return language_default()->language; + } + + return $source; + } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/style/base.css b/profiles/commerce_kickstart/modules/contrib/lingotek/style/base.css index 794defee..ec9cb5ae 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/style/base.css +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/style/base.css @@ -1,4 +1,3 @@ - .lingotek-highlight { background-color: #ecf2f9 !important; } @@ -23,10 +22,6 @@ div.form-item-create-lingotek-document-workbench-moderation, div.form-item-sync- margin-left: 50px; } -/*div.form-item-create-lingotek-document-workbench-moderation label, div.form-item-syncMethod-wb label { - font-weight: normal !important; -}*/ - div.form-item.form-item-lingotek-create-documents-by-default-workbench-moderation, div.form-item.form-item-wb-options { margin-left: 50px; @@ -118,83 +113,282 @@ input.lingotek-content-settings-table.form-checkbox { -moz-columns:100px 2; } -a.language-icon { +#lingotek-bulk-grid-form a.ltk-source-icon { background-color:#80a49e; - border-radius: 3px; margin-right: 3px; padding: 1px; padding-left: 4px; padding-right: 4px; - color: #fff !important; + color: #fff; + white-space: nowrap; + border: 1px solid #80a49e; + text-decoration: none; + font-family: 'Roboto Mono', monospace; +} + +#lingotek-bulk-grid-form span.ltk-source-icon-config-current { + background-color: #5FDC64; + border: 1px solid #5FDC64; + color: #ffffff; + margin-right: 3px; + padding: 1px; + padding-left: 4px; + padding-right: 4px; + white-space: nowrap; + text-decoration: none; + font-family: 'Roboto Mono', monospace; +} + +#lingotek-bulk-grid-form span.ltk-source-icon-config-current:hover { + background-color: #43A047; + border: 1px solid #43A047; +} + +#lingotek-bulk-grid-form a.language-icon { + background-color:#80a49e; + margin-right: 3px; + padding: 1px; + padding-left: 4px; + padding-right: 4px; + color: #fff; white-space: nowrap; border: 1px solid #fff; + text-decoration: none; + font-family: 'Roboto Mono', monospace; } -.language-icon.target-non-lingotek { - background-color: inherit; - border: 1px solid #ccc; - color: #aaa !important; +#lingotek-bulk-grid-form span.ltk-target-none { + background-color: #FFFFFF; + border: 1px solid #999999; + color: #999999; + margin-right: 3px; + padding: 1px; + padding-left: 4px; + padding-right: 4px; + white-space: nowrap; + text-decoration: none; + font-family: 'Roboto Mono', monospace; } -.language-icon.target-pending { - background-color: #CE2029; - border: 1px solid #CE2029; +#lingotek-bulk-grid-form span.ltk-target-none:hover { + border: 1px solid #8C8C8C; + color: #8C8C8C; } -.language-icon.target-current { - background-color: rgba(68,135,204,0.9); - border: 1px solid rgba(68,135,204,1); +#lingotek-bulk-grid-form span.ltk-target-disabled { + background-color: #FFFFFF; + border: 1px solid #999999; + color: #999999; + margin-right: 3px; + padding: 1px; + padding-left: 4px; + padding-right: 4px; + white-space: nowrap; + text-decoration: line-through; + font-family: 'Roboto Mono', monospace; } -.language-icon.target-ready { - background-color: #59ad1a; - border: 1px solid #59ad1a; +a.tooltips { + position: relative; + display: inline; + text-decoration: none; + color: inherit; } -.language-icon.target-edited { - background-color: #585858; - border: 1px solid #585858; +a:hover.tooltips { + text-decoration: none; } -.language-icon.target-untracked { - background-color: #999; - border: 1px solid #999; +a.tooltips span { + position: absolute; + right: -75px; + color: #FFFFFF; + background: #333; + line-height: 18px; + text-align: center; + visibility: hidden; + border-radius: 6px; + text-decoration: none; + padding: 5px; + font-size: 13px; } -.language-icon.target-target_localize { - background-color:#999; - border: 1px solid #999; +a.tooltips span:after { + content: ''; + position: absolute; + bottom: 100%; + left: 50%; + margin-left: -8px; + width: 0; height: 0; + border-bottom: 8px solid #333; + border-right: 8px solid transparent; + border-left: 8px solid transparent; + text-decoration: none; } -.language-icon.target-target_edited { - color: #000 !important; - background-color:inherit; - border: 1px #000 solid; +a:hover.tooltips span { + visibility: visible; + top: 30px; + left: 50%; + margin-left: -90px; + z-index: 999; + text-decoration: none; } -.language-icon.target-pending.target-disabled { - background-color: inherit; - border: 1px solid rgba(206,32,41,0.5); - color: #aaa !important; +#lingotek-bulk-grid-form .ltk-source-icon.source-none { + background-color: #ffffff; + border: 1px solid #999999; + color: #999999; } -.language-icon.target-ready.target-disabled { - background-color: inherit; - border: 1px solid rgba(89,173,26,0.8); - color: #aaa !important; +#lingotek-bulk-grid-form .ltk-source-icon.source-none:hover { + border: 1px solid #8C8C8C; + color: #8C8C8C; +} + +#lingotek-bulk-grid-form .ltk-source-icon.source-edited { + background-color: #ffc107; + border: 1px solid #ffc107; + color: #575757; +} + +#lingotek-bulk-grid-form .ltk-source-icon.source-edited:hover { + background-color: #FFB300; + border: 1px solid #FFB300; +} + +#lingotek-bulk-grid-form .ltk-source-icon.source-current { + background-color: #5FDC64; + border: 1px solid #5FDC64; + color: #ffffff; } -.language-icon.target-current.target-disabled { +#lingotek-bulk-grid-form .ltk-source-icon.source-current:hover { + background-color: #43A047; + border: 1px solid #43A047; +} + +#lingotek-bulk-grid-form .ltk-source-icon.source-error { + background-color: #B71C1C; + border: 1px solid #B71C1C; + color: #ffffff; +} + +#lingotek-bulk-grid-form .ltk-source-icon.source-error:hover { + background-color: #9E0303; + border: 1px solid #9E0303; +} + +#lingotek-bulk-grid-form .ltk-source-icon.source-disabled { + background-color: #FAFAFA; + border: 1px solid #999999; + color: #999999; + text-decoration: line-through; +} + +#lingotek-bulk-grid-form .language-icon.target-pending { + background-color: #F37F16; + border: 1px solid #F37F16; +} + +#lingotek-bulk-grid-form .language-icon.target-pending:hover { + background-color: #E67209; + border: 1px solid #E67209; +} + +#lingotek-bulk-grid-form .language-icon.target-current { + background-color: #5FDC64; + border: 1px solid #5FDC64; +} + +#lingotek-bulk-grid-form .language-icon.target-current:hover { + background-color: #43A047; + border: 1px solid #43A047; +} + +#lingotek-bulk-grid-form .language-icon.target-ready { + background-color: #2196F3; + border: 1px solid #2196F3; +} + +#lingotek-bulk-grid-form .language-icon.target-ready:hover { + background-color: #1E88E5; + border: 1px solid #1E88E5; +} + +#lingotek-bulk-grid-form .language-icon.target-interim { + background-color: #FFFFFF; + border: 1px solid #4CAF50; + color: #4CAF50; +} + +#lingotek-bulk-grid-form .language-icon.target-interim:hover { + border: 1px solid #43A047; + color: #43A047 +} + +#lingotek-bulk-grid-form .language-icon.target-ready_interim { + background-color: #FFFFFF; + border: 1px solid #2196F3; + color: #2196F3; +} + +#lingotek-bulk-grid-form .language-icon.target-ready_interim:hover { + border: 1px solid #1E88E5; + color: #1E88E5; +} + +#lingotek-bulk-grid-form .language-icon.target-edited { + background-color: #FFC107; + border: 1px solid #FFC107; + color: #575757; +} + +#lingotek-bulk-grid-form .language-icon.target-edited:hover { + background-color: #FFB300; + border: 1px solid #FFB300; +} + +#lingotek-bulk-grid-form .language-icon.target-untracked { + background-color: #999999; + border: 1px solid #999999; +} + +#lingotek-bulk-grid-form .language-icon.target-untracked:hover { + background-color: #8C8C8C; + border: 1px solid #8C8C8C; +} + +#lingotek-bulk-grid-form .language-icon.target-error { + background-color: #B71C1C; + border: 1px solid #B71C1C; +} + +#lingotek-bulk-grid-form .language-icon.target-error:hover { + background-color: #9E0303; + border: 1px solid #9E0303; +} + +#lingotek-bulk-grid-form .language-icon.target-non-lingotek { background-color: inherit; - border: 1px solid rgba(68,135,204,0.8); - color: #aaa !important; + border: 1px solid #ccc; + color: #aaa; } .ltk-complete-check { color: green; font-size: 150%; } - +.ltk-options { + padding-top: 5px; + cursor: pointer; + margin-left: 15px; +} +.ltk-force-down { + position:relative; + padding-top: 3px; + font-size: 100%; +} .ltk-upload { color: #444; } @@ -202,18 +396,24 @@ a.language-icon { color: #000; } -.ltk-download { - color: #59ad1a; +#lingotek-bulk-grid-form .ltk-download { + color: #2196F3; } -.ltk-download:hover { - color: green; +#lingotek-bulk-grid-form .ltk-download:hover { + color: #1E88E5; +} +.ltk-legend { + color: #ddd; +} +.ltk-legend:hover { + color: #3479c0; } -.ltk-refresh { - color: #CE2029; +#lingotek-bulk-grid-form .ltk-refresh { + color: #F37F16; } -.ltk-refresh:hover { - color: darkred; +#lingotek-bulk-grid-form .ltk-refresh:hover { + color: #E67209; } .ltk-muted { @@ -231,9 +431,20 @@ a.language-icon { color: green; } +.notify-filtered-action { + font-size: 70%; + padding-left:30px; + position:absolute; +} + +.notify-checked-action { + font-size: 70%; + padding-left:30px; + position:absolute; +} + .no-localized-title, .no-localized-title a { color: #444; - /*font-style: italic;*/ } .lingotek-node-actions, .lingotek-language-source i { @@ -250,12 +461,14 @@ a.ltk-action:hover { color: #00348D; } -.ltk-upload-button { +.ltk-marked-checkbox { + font-size: 150%; + white-space: nowrap; color: #333; cursor: pointer; } -.ltk-upload-button:hover { +.ltk-marked-checkbox:hover { color: #000; } @@ -310,49 +523,46 @@ a.ltk-action:hover { background-color: #CCCCCC } -a.tooltips { - position: relative; - display: inline; - text-decoration: none; - color: inherit; -} - -a:hover.tooltips { - text-decoration: none; -} - -a.tooltips span { - position: absolute; - right: -75px; - color: #FFFFFF; - background: #333; - line-height: 18px; - text-align: center; - visibility: hidden; - border-radius: 6px; - text-decoration: none; - padding: 5px; - font-size: 13px; -} - -a.tooltips span:after { - content: ''; - position: absolute; - bottom: 100%; - left: 50%; - margin-left: -8px; - width: 0; height: 0; - border-bottom: 8px solid #333; - border-right: 8px solid transparent; - border-left: 8px solid transparent; - text-decoration: none; -} - -a:hover.tooltips span { - visibility: visible; - top: 30px; - left: 50%; - margin-left: -90px; - z-index: 999; - text-decoration: none; +.legend-table td{ + padding-top: 0; + padding-bottom: 0; + padding-left: 5px; + height: 26px; + +} +.legend-table div{ + text-align: center; + margin:auto; + padding-top: 5px; + padding-bottom: 5px; +} +#edit-search-submit { + border-radius: 0; + border-top: 1px solid #d2d2d2; + font-family: 'FontAwesome'; + padding-top: 3px; + padding-bottom: 4px; + -moz-transform: scale(-1, 1); + -webkit-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +#edit-search { + margin:0; + padding-top:3px; + padding-bottom:3px; +} +#more-options{ + transition:.3s; +} +.more-options-flip { + transform: rotateY(180deg); +} +.form-item-search label { + padding:0; + margin:0; +} +#lingotek-admin-additional-translation-settings-form label{ + white-space: pre-wrap; } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.base.test b/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.base.test index 0661eb57..ea542402 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.base.test +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.base.test @@ -62,7 +62,7 @@ class LingotekFunctionalTest extends DrupalWebTestCase { public function setUp() { parent::setUp('lingotek'); - variable_set(LINGOTEK_ENABLED, 'language_content_type_page'); + variable_set('language_content_type_page', '1' ); $unprivileged_permissions = array_keys(node_permission()); $this->unprivileged_user = $this->drupalCreateUser($unprivileged_permissions); @@ -84,7 +84,7 @@ class LingotekFunctionalTest extends DrupalWebTestCase { 'lingotek_pid' => self::LINGOTEK_PASSWORD, ); - $this->drupalPost('admin/config/lingotek/account-settings', $form_values, t('Next')); + $this->drupalPost(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings', $form_values, t('Next')); $this->drupalLogout(); } } diff --git a/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.setup.test b/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.setup.test index 3621d844..4ea6f3de 100644 --- a/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.setup.test +++ b/profiles/commerce_kickstart/modules/contrib/lingotek/tests/lingotek.setup.test @@ -32,13 +32,13 @@ class LingotekTestCase extends DrupalWebTestCase { } public function testEnterpriseSetup() { - $this->drupalGet('admin/config/lingotek/setup'); + $this->drupalGet(LINGOTEK_MENU_LANG_BASE_URL . '/setup'); $this->assertResponse(403, 'Only allow access to users with permissions'); $this->drupalLogin($this->admin_user); - $this->drupalGet('admin/config/lingotek/setup'); - $this->assertUrl('admin/config/lingotek/new-account', array(), 'Redirect to first step of start page'); + $this->drupalGet(LINGOTEK_MENU_LANG_BASE_URL . '/setup'); + $this->assertUrl(LINGOTEK_MENU_LANG_BASE_URL . '/new-account', array(), 'Redirect to first step of start page'); // 1: Account Settings. $settings = array( @@ -46,7 +46,7 @@ class LingotekTestCase extends DrupalWebTestCase { 'lingotek_pid' => 'lingotek', ); debug($settings); - $this->drupalPost('admin/config/lingotek/account-settings', $settings, 'Next'); + $this->drupalPost(LINGOTEK_MENU_LANG_BASE_URL . '/account-settings', $settings, 'Next'); $this->assertText('Your account settings have been saved.', 'Step 1: Account Settings - Abililty to login'); // 2: Community. @@ -154,13 +154,13 @@ class LingotekTestCase extends DrupalWebTestCase { public function testProSetup() { debug('Beginning Pro Setup'); - $this->drupalGet('admin/config/lingotek/setup'); + $this->drupalGet(LINGOTEK_MENU_LANG_BASE_URL . '/setup'); $this->assertResponse(403, 'Only allow access to users with permissions'); $this->drupalLogin($this->admin_user); - $this->drupalGet('admin/config/lingotek/setup'); - $this->assertUrl('admin/config/lingotek/new-account', array(), 'Redirect to first step of start page'); + $this->drupalGet(LINGOTEK_MENU_LANG_BASE_URL . '/setup'); + $this->assertUrl(LINGOTEK_MENU_LANG_BASE_URL . '/new-account', array(), 'Redirect to first step of start page'); //1: Account Settings $settings = array( diff --git a/profiles/commerce_kickstart/modules/contrib/link/README.txt b/profiles/commerce_kickstart/modules/contrib/link/README.txt new file mode 100644 index 00000000..387cc594 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/link/README.txt @@ -0,0 +1,33 @@ +Module description +------------------ +The link module can be count to the top 50 modules in Drupal installations and provides a standard custom content field for links. With this module links can be added easily to any content types and profiles and include advanced validating and different ways of storing internal or external links and URLs. It also supports additional link text title, site wide tokens for titles and title attributes, target attributes, css class attribution, static repeating values, input conversion, and many more. + +Requirements / Dependencies +--------------------------- +1. Drupal 6: Custom content module (CCK) +2. Drupal 7: Fields API is provided already by core [no dependencies]. +3. Drupal 8: Link module is in core now. No module installation needed. Yay! Don't forget to activate it. It's deactivated by default. + +INFO Since some misleading user reports we need to clarify here - Link module is NOT about to add links to any menus or the navigation nor primary/secondary menu. This can be done with default menu module (part of Drupal core). The Link module provides an additional custom field for storing and validating links to be added with any content type, which means another input block additional to your text-body, title, image and any other input you can make on new content creation. + +Installation +------------ +1. Drop the entire link module directory into your 'sites/all/modules' folder +2. Enable the module from the Administration area modules page (admin/build/modules) +3. Create or Edit a content-type and add a new field of type link (admin/content/types in D6, admin/structure/types in D7) + +Configuration +------------- +Configuration is only slightly more complicated than a text field. Link text titles for URLs can be made required, set as instead of URL, optional (default), or left out entirely. If no link text title is provided, the trimmed version of the complete URL will be displayed. The target attribute should be set to "_blank", "top", or left out completely (checkboxes provide info). The rel=nofollow attribute prevents the link from being followed by certain search engines. More info at Wikipedia (http://en.wikipedia.org/wiki/Spam_in_blogs#rel.3D.22nofollow.22). + +Example +------- +If you were to create a field named 'My New Link', the default display of the link would be: where items between [] characters would be customized based on the user input. + +The link module supports both, internal and external URLs. URLs are validated on input. Here are some examples of data input and the default view of a link: http://drupal.org results in http://drupal.org, but drupal.org results in http://drupal.org, while will convert into http://drupal.org and node/74971 into http://drupal.org/project/link + +Anchors and query strings may also be used in any of these cases, including: node/74971/edit?destination=node/74972#pager + +Theming and Output +------------------ +Since link module is mainly a data storage field in a modular framework, the theming and output is up to the site builder and other additional modules. There are many modules in the Drupal repository, which control the output of fields perfectly and can handle rules, user actions, markup dependencies, and can vary the output under many different conditions, with much more efficience and flexibility for different scenarios. Please check out modules like views, display suite, panels, etc for such needs. \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/link/link.info b/profiles/commerce_kickstart/modules/contrib/link/link.info index 016b7990..4ff71e25 100644 --- a/profiles/commerce_kickstart/modules/contrib/link/link.info +++ b/profiles/commerce_kickstart/modules/contrib/link/link.info @@ -12,15 +12,16 @@ files[] = tests/link.attribute.test files[] = tests/link.crud.test files[] = tests/link.crud_browser.test files[] = tests/link.token.test +files[] = tests/link.entity_token.test files[] = tests/link.validate.test ; Views Handlers files[] = views/link_views_handler_argument_target.inc files[] = views/link_views_handler_filter_protocol.inc -; Information added by Drupal.org packaging script on 2014-10-21 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2016-01-15 +version = "7.x-1.4" core = "7.x" project = "link" -datestamp = "1413924830" +datestamp = "1452830642" diff --git a/profiles/commerce_kickstart/modules/contrib/link/link.module b/profiles/commerce_kickstart/modules/contrib/link/link.module index b0c53c37..1e9d088b 100644 --- a/profiles/commerce_kickstart/modules/contrib/link/link.module +++ b/profiles/commerce_kickstart/modules/contrib/link/link.module @@ -207,7 +207,7 @@ function link_field_instance_settings_form($field, $instance) { $form['attributes']['class'] = array( '#type' => 'textfield', '#title' => t('Additional CSS Class'), - '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces.'), + '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces. Only alphanumeric characters and hyphens are allowed'), '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'], ); $form['attributes']['configurable_title'] = array( @@ -291,7 +291,7 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode */ function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $value) { - _link_process($items[$delta], $delta, $field, $entity); + _link_process($items[$delta], $delta, $field, $entity, $instance); } } @@ -300,7 +300,7 @@ function link_field_insert($entity_type, $entity, $field, $instance, $langcode, */ function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $value) { - _link_process($items[$delta], $delta, $field, $entity); + _link_process($items[$delta], $delta, $field, $entity, $instance); } } @@ -371,8 +371,24 @@ function _link_load($field, $item, $instance) { /** * Prepares the item attributes and url for storage. + * + * @param $item + * Link field values. + * + * @param $delta + * The sequence number for current values. + * + * @param $field + * The field structure array. + * + * @param $entity + * Entity object. + * + * @param $instance + * The instance structure for $field on $entity's bundle. + * */ -function _link_process(&$item, $delta, $field, $entity) { +function _link_process(&$item, $delta, $field, $entity, $instance) { // Trim whitespace from URL. if (!empty($item['url'])) { $item['url'] = trim($item['url']); @@ -391,7 +407,8 @@ function _link_process(&$item, $delta, $field, $entity) { // Don't save an invalid default value (e.g. 'http://'). if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) { - if (!link_validate_url($item['url'])) { + $langcode = !empty($entity) ? field_language($instance['entity_type'], $entity, $instance['field_name']) : LANGUAGE_NONE; + if (!link_validate_url($item['url'], $langcode)) { unset($item['url']); } } @@ -403,7 +420,7 @@ function _link_process(&$item, $delta, $field, $entity) { function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) { if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) { // Validate the link. - if (link_validate_url(trim($item['url'])) == FALSE) { + if (!link_validate_url(trim($item['url']), $langcode)) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'link_required', 'message' => t('The value %value provided for %field is not a valid URL.', array( @@ -474,19 +491,21 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type ); if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) { - global $user; - // Load the entity if necessary for entities in views. - if (isset($entity->{$property_id})) { - $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); - $entity_loaded = array_pop($entity_loaded); - } - else { - $entity_loaded = $entity; + $text_tokens = token_scan($item['url']); + if (!empty($text_tokens)) { + // Load the entity if necessary for entities in views. + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded)); } - $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded)); } - $type = link_validate_url($item['url']); + $type = link_url_type($item['url']); // If the type of the URL cannot be determined and URL validation is disabled, // then assume LINK_EXTERNAL for later processing. if ($type == FALSE && $instance['settings']['validate_url'] === 0) { @@ -496,12 +515,12 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { $url_parts = _link_parse_url($url); if (!empty($url_parts['url'])) { - $item['url'] = $url_parts['url']; - $item += array( - 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, + $item['url'] = url($url_parts['url'], + array('query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, 'absolute' => !empty($instance['settings']['absolute_url']), 'html' => TRUE, + ) ); } @@ -541,15 +560,18 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { // Replace title tokens. if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) { - // Load the entity if necessary for entities in views. - if (isset($entity->{$property_id})) { - $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); - $entity_loaded = array_pop($entity_loaded); - } - else { - $entity_loaded = $entity; + $text_tokens = token_scan($title); + if (!empty($text_tokens)) { + // Load the entity if necessary for entities in views. + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $title = token_replace($title, array($entity_token_type => $entity_loaded)); } - $title = token_replace($title, array($entity_token_type => $entity_loaded)); $title = filter_xss($title, array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); $item['html'] = TRUE; } @@ -599,22 +621,25 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { // Handle "title" link attribute. if (!empty($item['attributes']['title']) && module_exists('token')) { - // Load the entity (necessary for entities in views). - if (isset($entity->{$property_id})) { - $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); - $entity_loaded = array_pop($entity_loaded); - } - else { - $entity_loaded = $entity; + $text_tokens = token_scan($item['attributes']['title']); + if (!empty($text_tokens)) { + // Load the entity (necessary for entities in views). + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded)); } - $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded)); $item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); } // Handle attribute classes. if (!empty($item['attributes']['class'])) { $classes = explode(' ', $item['attributes']['class']); foreach ($classes as &$class) { - $class = drupal_html_class($class); + $class = drupal_clean_css_identifier($class); } $item['attributes']['class'] = implode(' ', $classes); } @@ -660,7 +685,7 @@ function _link_parse_url($url) { * Replaces the PHP parse_str() function. * * Because parse_str replaces the following characters in query parameters name - * in order to maintain compability with deprecated register_globals directive: + * in order to maintain compatibility with deprecated register_globals directive: * * - chr(32) ( ) (space) * - chr(46) (.) (dot) @@ -700,6 +725,9 @@ function link_theme() { 'link_formatter_link_plain' => array( 'variables' => array('element' => NULL, 'field' => NULL), ), + 'link_formatter_link_host' => array( + 'variables' => array('element' => NULL), + ), 'link_formatter_link_absolute' => array( 'variables' => array('element' => NULL, 'field' => NULL), ), @@ -859,7 +887,7 @@ function link_field_process($element, $form_state, $complete_form) { ); } - // If the title field is avaliable or there are field accepts multiple values + // If the title field is available or there are field accepts multiple values // then allow the individual field items display the required asterisk if needed. if (isset($element['title']) || isset($element['_weight'])) { // To prevent an extra required indicator, disable the required flag on the @@ -885,6 +913,11 @@ function link_field_formatter_info() { 'field types' => array('link_field'), 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), + 'link_host' => array( + 'label' => t('Host, as plain text'), + 'field types' => array('link_field'), + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), 'link_url' => array( 'label' => t('URL, as link'), 'field types' => array('link_field'), @@ -1015,6 +1048,14 @@ function theme_link_formatter_link_plain($vars) { return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options); } +/** + * Theme function for 'host' text field formatter. + */ +function theme_link_formatter_link_host($vars) { + $host = @parse_url($vars['element']['url']); + return isset($host['host']) ? check_plain($host['host']) : ''; +} + /** * Formats a link as an absolute URL. */ @@ -1142,7 +1183,7 @@ function link_views_api() { /** * Forms a valid URL if possible from an entered address. - * + * * Trims whitespace and automatically adds an http:// to addresses without a * protocol specified * @@ -1153,7 +1194,7 @@ function link_views_api() { */ function link_cleanup_url($url, $protocol = 'http') { $url = trim($url); - $type = link_validate_url($url); + $type = link_url_type($url); if ($type === LINK_EXTERNAL) { // Check if there is no protocol specified. @@ -1173,18 +1214,59 @@ function link_cleanup_url($url, $protocol = 'http') { /** * Validates a URL. - * + * + * @param $text + * Url to be validated. + * + * @param $langcode + * An optional language code to look up the path in. + * + * @return boolean + * True if a valid link, FALSE otherwise. + */ +function link_validate_url($text, $langcode = NULL) { + $text = link_cleanup_url($text); + $type = link_url_type($text); + + if ($type && ($type == LINK_INTERNAL || $type == LINK_EXTERNAL)) { + $flag = valid_url($text, TRUE); + if (!$flag) { + $normal_path = drupal_get_normal_path($text, $langcode); + $parsed_link = parse_url($normal_path, PHP_URL_PATH); + if ($normal_path != $parsed_link) { + $normal_path = $parsed_link; + } + $flag = drupal_valid_path($normal_path); + } + if (!$flag) { + $flag = file_exists($normal_path); + } + if (!$flag) { + $uri = file_build_uri($normal_path); + $flag = file_exists($uri); + } + } + else { + $flag = (bool) $type; + } + + return $flag; +} + +/** + * Type check a URL. + * * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail * addresses following the RFC 2368 standard for mailto address formation. * * @param string $text - * Url to be validated. + * Url to be checked. * * @return mixed * Returns boolean FALSE if the URL is not valid. On success, returns one of * the LINK_(linktype) constants. */ -function link_validate_url($text) { +function link_url_type($text) { // @TODO Complete letters. $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( "æ", // æ diff --git a/profiles/commerce_kickstart/modules/contrib/link/tests/link.attribute.test b/profiles/commerce_kickstart/modules/contrib/link/tests/link.attribute.test index 603847ef..36e6be5e 100644 --- a/profiles/commerce_kickstart/modules/contrib/link/tests/link.attribute.test +++ b/profiles/commerce_kickstart/modules/contrib/link/tests/link.attribute.test @@ -254,6 +254,41 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { } } + function testFormatterHost() { + $content_type_friendly = $this->randomName(20); + $content_type_machine = strtolower($this->randomName(10)); + + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); + + + // Now add a singleton field. + $single_field_name_friendly = $this->randomName(20); + $single_field_name_machine = strtolower($this->randomName(10)); + //$single_field_name = 'field_'. $single_field_name_machine; + $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); + + // Okay, now we want to make sure this display is changed: + $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); + $edit = array( + 'fields[field_'. $single_field_name_machine .'][label]' => 'above', + 'fields[field_'. $single_field_name_machine .'][type]' => 'link_host', + ); + $this->drupalPost(NULL, $edit, t('Save')); + + $this->createNodeTypeUser($content_type_machine); + + $link_text = 'Display'; + $link_url = 'http://www.example.com/'; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText('www.example.com'); + $this->assertNoText($link_text); + $this->assertNoLinkByHref($link_url); + } + function testFormatterURL() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); diff --git a/profiles/commerce_kickstart/modules/contrib/link/tests/link.crud_browser.test b/profiles/commerce_kickstart/modules/contrib/link/tests/link.crud_browser.test index c90e86d1..be042609 100644 --- a/profiles/commerce_kickstart/modules/contrib/link/tests/link.crud_browser.test +++ b/profiles/commerce_kickstart/modules/contrib/link/tests/link.crud_browser.test @@ -312,4 +312,148 @@ class LinkUITest extends DrupalWebTestcase { $this->assertFalse($instance['settings']['attributes']['class'], 'By default, no class should be set.'); $this->assertFalse($instance['settings']['title_value'], 'By default, no title should be set.'); } + + /** + * If we're creating a new field and just hit 'save' on the default options, we want to make + * sure they are set to the expected results. + */ + function testCRUDCreateFieldWithClass() { + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); + + // create field + $name = strtolower($this->randomName()); + $edit = array( + 'fields[_add_new_field][label]' => $name, + 'fields[_add_new_field][field_name]' => $name, + 'fields[_add_new_field][type]' => 'link_field', + 'fields[_add_new_field][widget_type]' => 'link_field', + ); + $this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save')); + + $this->drupalPost(NULL, array(), t('Save field settings')); + $link_class_name = 'basic-link-' . strtolower($this->randomName()); + $edit = array( + 'instance[settings][attributes][class]' => $link_class_name, + ); + $this->drupalPost(NULL, $edit, t('Save settings')); + + // Is field created? + $this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added'); + node_types_rebuild(); + menu_rebuild(); + + _field_info_collate_fields(TRUE); + $instances = field_info_instances('node', 'page'); + + $instance = $instances['field_' . $name]; + $this->assertFalse($instance['required'], 'Make sure field is not required.'); + $this->assertEqual($instance['settings']['title'], 'optional', 'Title should be optional by default.'); + $this->assertTrue($instance['settings']['validate_url'], 'Make sure validation is on.'); + $this->assertTrue($instance['settings']['enable_tokens'], 'Enable Tokens should be on by default.'); + $this->assertEqual($instance['settings']['display']['url_cutoff'], 80, 'Url cutoff should be at 80 characters.'); + $this->assertEqual($instance['settings']['attributes']['target'], 'default', 'Target should be "default"'); + $this->assertFalse($instance['settings']['attributes']['rel'], 'Rel should be blank by default.'); + $this->assertEqual($instance['settings']['attributes']['class'], $link_class_name, 'One class should be set.'); + $this->assertFalse($instance['settings']['title_value'], 'By default, no title should be set.'); + + // Now, let's create a node with this field and make sure the link shows up: + // create page form + $field_name = 'field_' . $name; + $this->drupalGet('node/add/page'); + $this->assertField($field_name . '[und][0][url]', 'URL found'); + + $input = array( + 'title' => 'This & That', + 'href' => 'http://www.example.com/', + ); + + $edit = array( + 'title' => $field_name, + $field_name . '[und][0][title]' => $input['title'], + $field_name . '[und][0][url]' => $input['href'], + ); + $this->drupalPost(NULL, $edit, t('Save')); + + $url = $this->getUrl(); + + // change to anonymous user + $this->drupalLogout(); + $this->drupalGet($url); + + $this->assertRaw('This & That'); + $this->assertPattern('|class\s?=\s?"' . $link_class_name . '"|', "Class $link_class_name exists on page."); + } + +/** + * If we're creating a new field and just hit 'save' on the default options, we want to make + * sure they are set to the expected results. + */ + function testCRUDCreateFieldWithTwoClasses() { + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); + + // create field + $name = strtolower($this->randomName()); + $edit = array( + 'fields[_add_new_field][label]' => $name, + 'fields[_add_new_field][field_name]' => $name, + 'fields[_add_new_field][type]' => 'link_field', + 'fields[_add_new_field][widget_type]' => 'link_field', + ); + $this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save')); + + $this->drupalPost(NULL, array(), t('Save field settings')); + $link_class_name = 'basic-link ' . strtoupper($this->randomName()); + $edit = array( + 'instance[settings][attributes][class]' => $link_class_name, + ); + $this->drupalPost(NULL, $edit, t('Save settings')); + + // Is field created? + $this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added'); + node_types_rebuild(); + menu_rebuild(); + + _field_info_collate_fields(TRUE); + $instances = field_info_instances('node', 'page'); + + $instance = $instances['field_' . $name]; + $this->assertFalse($instance['required'], 'Make sure field is not required.'); + $this->assertEqual($instance['settings']['title'], 'optional', 'Title should be optional by default.'); + $this->assertTrue($instance['settings']['validate_url'], 'Make sure validation is on.'); + $this->assertTrue($instance['settings']['enable_tokens'], 'Enable Tokens should be on by default.'); + $this->assertEqual($instance['settings']['display']['url_cutoff'], 80, 'Url cutoff should be at 80 characters.'); + $this->assertEqual($instance['settings']['attributes']['target'], 'default', 'Target should be "default"'); + $this->assertFalse($instance['settings']['attributes']['rel'], 'Rel should be blank by default.'); + $this->assertEqual($instance['settings']['attributes']['class'], $link_class_name, 'Two classes should be set.'); + $this->assertFalse($instance['settings']['title_value'], 'By default, no title should be set.'); + + // Now, let's create a node with this field and make sure the link shows up: + // create page form + $field_name = 'field_' . $name; + $this->drupalGet('node/add/page'); + $this->assertField($field_name . '[und][0][url]', 'URL found'); + + $input = array( + 'title' => 'This & That', + 'href' => 'http://www.example.com/', + ); + + $edit = array( + 'title' => $field_name, + $field_name . '[und][0][title]' => $input['title'], + $field_name . '[und][0][url]' => $input['href'], + ); + $this->drupalPost(NULL, $edit, t('Save')); + + $url = $this->getUrl(); + + // change to anonymous user + $this->drupalLogout(); + $this->drupalGet($url); + + $this->assertRaw('This & That'); + $this->assertPattern('|class\s?=\s?"' . $link_class_name . '"|', "Classes $link_class_name exist on page."); + } } diff --git a/profiles/commerce_kickstart/modules/contrib/link/tests/link.entity_token.test b/profiles/commerce_kickstart/modules/contrib/link/tests/link.entity_token.test new file mode 100644 index 00000000..1f51fab4 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/link/tests/link.entity_token.test @@ -0,0 +1,155 @@ + 'Link entity tokens test', + 'description' => 'Tests that a link field appears properly in entity tokens', + 'group' => 'Link', + 'dependencies' => array('token', 'entity', 'entity_token'), + ); + } + + function setUp($modules = array()) { + parent::setUp(array('token', 'entity', 'entity_token')); + } + + /** + * Creates a link field, fills it, then uses a loaded node to test tokens. + */ + function testFieldTokenNodeLoaded() { + // create field + $settings = array( + 'instance[settings][enable_tokens]' => 0, + ); + $field_name = $this->createLinkField('page', + $settings); + + // create page form + $this->drupalGet('node/add/page'); + //$field_name = 'field_' . $name; + $this->assertField($field_name . '[und][0][title]', 'Title found'); + $this->assertField($field_name . '[und][0][url]', 'URL found'); + + $token_url_tests = array( + 1 => array( + 'href' => 'http://example.com/' . $this->randomName(), + 'label' => $this->randomName(), + ), + 2 => array( + 'href' => 'http://example.com/' . $this->randomName() . '?property=value', + 'label' => $this->randomName(), + ), + 3 => array( + 'href' => 'http://example.com/' . $this->randomName() . '#position', + 'label' => $this->randomName(), + ), + 4 => array( + 'href' => 'http://example.com/' . $this->randomName() . '#lower?property=value2', + 'label' => $this->randomName(), + ), + ); + //$this->assert('pass', '
      ' . print_r($token_url_tests, TRUE) . '
      ');
      +
      +    foreach ($token_url_tests as &$input) {
      +      $this->drupalGet('node/add/page');
      +  
      +      $edit = array(
      +        'title' => $input['label'],
      +        $field_name . '[und][0][title]' => $input['label'],
      +        $field_name . '[und][0][url]' => $input['href'],
      +      );
      +      $this->drupalPost(NULL, $edit, t('Save'));
      +      $url = $this->getUrl();
      +      $input['url'] = $url;
      +    }
      +
      +    // change to anonymous user
      +    $this->drupalLogout();
      +    
      +    foreach ($token_url_tests as $index => $input2) {
      +      $node = node_load($index);
      +      $this->assertNotEqual(NULL, $node, "Do we have a node?");
      +      $this->assertEqual($node->nid, $index, "Test that we have a node.");
      +      $token_name = '[node:' . str_replace('_', '-', $field_name) . ':url]';
      +      $assert_data = token_replace($token_name,
      +                      array('node' => $node));
      +      $this->assertEqual($input2['href'], $assert_data, "Test that the url token has been set to " . $input2['href'] . ' - ' . $assert_data);
      +    }
      +  }
      +  
      +  /**
      +   * Creates a link field, fills it, then uses a loaded and node_view'd node to test tokens.
      +   */
      +  function testFieldTokenNodeViewed() {
      +    // create field
      +    $settings = array(
      +      'instance[settings][enable_tokens]' => 0,
      +    );
      +    $field_name = $this->createLinkField('page',
      +                                        $settings);
      +
      +    // create page form
      +    $this->drupalGet('node/add/page');
      +    //$field_name = 'field_' . $name;
      +    $this->assertField($field_name . '[und][0][title]', 'Title found');
      +    $this->assertField($field_name . '[und][0][url]', 'URL found');
      +
      +    $token_url_tests = array(
      +      1 => array(
      +        'href' => 'http://example.com/' . $this->randomName(),
      +        'label' => $this->randomName(),
      +      ),
      +      2 => array(
      +        'href' => 'http://example.com/' . $this->randomName() . '?property=value',
      +        'label' => $this->randomName(),
      +      ),
      +      3 => array(
      +        'href' => 'http://example.com/' . $this->randomName() . '#position',
      +        'label' => $this->randomName(),
      +      ),
      +      4 => array(
      +        'href' => 'http://example.com/' . $this->randomName() . '#lower?property=value2',
      +        'label' => $this->randomName(),
      +      ),
      +    );
      +    //$this->assert('pass', '
      ' . print_r($token_url_tests, TRUE) . '
      ');
      +
      +    foreach ($token_url_tests as &$input) {
      +      $this->drupalGet('node/add/page');
      +  
      +      $edit = array(
      +        'title' => $input['label'],
      +        $field_name . '[und][0][title]' => $input['label'],
      +        $field_name . '[und][0][url]' => $input['href'],
      +      );
      +      $this->drupalPost(NULL, $edit, t('Save'));
      +      $url = $this->getUrl();
      +      $input['url'] = $url;
      +    }
      +
      +    // change to anonymous user
      +    $this->drupalLogout();
      +    
      +    foreach ($token_url_tests as $index => $input2) {
      +      $node = node_load($index);
      +      $node_array = node_view($node, 'full');
      +      $this->assertNotEqual(NULL, $node, "Do we have a node?");
      +      $this->assertEqual($node->nid, $index, "Test that we have a node.");
      +      $token_name = '[node:' . str_replace('_', '-', $field_name) . ':url]';
      +      $assert_data = token_replace($token_name,
      +                      array('node' => $node));
      +      $this->assertEqual($input2['href'], $assert_data, "Test that the url token has been set to " . $input2['href'] . ' - ' . $assert_data);
      +    }
      +  }
      +  
      +}
      \ No newline at end of file
      diff --git a/profiles/commerce_kickstart/modules/contrib/link/tests/link.token.test b/profiles/commerce_kickstart/modules/contrib/link/tests/link.token.test
      index 21bc4a04..617260e6 100644
      --- a/profiles/commerce_kickstart/modules/contrib/link/tests/link.token.test
      +++ b/profiles/commerce_kickstart/modules/contrib/link/tests/link.token.test
      @@ -426,4 +426,6 @@ class LinkTokenTest extends LinkBaseTestClass {
       
           $this->assertRaw('This & That');
         }
      +  
      +  
       }
      diff --git a/profiles/commerce_kickstart/modules/contrib/link/tests/link.validate.test b/profiles/commerce_kickstart/modules/contrib/link/tests/link.validate.test
      index 764eeaad..a9ac116c 100644
      --- a/profiles/commerce_kickstart/modules/contrib/link/tests/link.validate.test
      +++ b/profiles/commerce_kickstart/modules/contrib/link/tests/link.validate.test
      @@ -22,25 +22,24 @@ class LinkValidateTestCase extends LinkBaseTestClass {
       
           $field_name = $this->createLinkField();
       
      -    $permission = 'create page content';
      -    $this->checkPermissions(array($permission), TRUE);
      -
      -    $this->drupalGet('node/add/page');
      -
           $label = $this->randomName();
      -    $edit = array(
      +    $settings = array(
             'title' => $label,
      -      $field_name . '[und][0][title]' => $label,
      -      $field_name . '[und][0][url]' => $url,
      +      $field_name => array(
      +        LANGUAGE_NONE=> array(
      +          array(
      +            'title' => $label,
      +            'url' => $url,
      +          )
      +        ),
      +      ),
           );
      -    $this->drupalPost(NULL, $edit, t('Save'));
      -    $this->assertRaw(' has been created.', 'Node created');
       
      -    $nid = 1; //$matches[1];
      +    $node = $this->drupalCreateNode($settings);
       
      -    $node = node_load($nid);
      +    $this->assertNotNull($node, ' has been created.', 'Node created');
       
      -    $this->assertEqual($url, $node->{$field_name}['und'][0]['url']);
      +    $this->assertEqual($url, $node->{$field_name}[LANGUAGE_NONE][0]['url']);
         }
       }
       
      @@ -269,7 +268,13 @@ class LinkValidateTest extends LinkValidateTestCase {
       
         // Validate that an internal url would be accepted.
         function test_link_internal_url() {
      -    $this->link_test_validate_url('node/32');
      +    // Create the content first.
      +    $node = $this->drupalCreateNode();
      +
      +    $link = 'node/' . $node->nid;
      +    $this->link_test_validate_url($link);
      +    $type = link_url_type($link);
      +    $this->assertEqual(LINK_INTERNAL, $type, 'Test ' . $link . ' is an internal link.');
         }
       
         // Validate a simple mailto.
      @@ -340,7 +345,7 @@ class LinkValidateSpecificURL extends LinkValidateTestCase {
         /**
          * Here, we're testing that a very long url is stored properly in the db.
          *
      -   * Basicly, trying to test http://drupal.org/node/376818
      +   * Basically, trying to test http://drupal.org/node/376818
          */
         function testLinkURLFieldIsBig() {
           $long_url = 'http://th.wikipedia.org/wiki/%E0%B9%82%E0%B8%A3%E0%B8%87%E0%B9%80%E0%B8%A3%E0%B8%B5%E0%B8%A2%E0%B8%99%E0%B9%80%E0%B8%9A%E0%B8%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%B8%B2%E0%B8%8A%E0%B8%B9%E0%B8%97%E0%B8%B4%E0%B8%A8_%E0%B8%99%E0%B8%84%E0%B8%A3%E0%B8%A8%E0%B8%A3%E0%B8%B5%E0%B8%98%E0%B8%A3%E0%B8%A3%E0%B8%A1%E0%B8%A3%E0%B8%B2%E0%B8%8A';
      @@ -363,6 +368,10 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
             'group' => 'Link',
           );
         }
      +  
      +  function setUp() {
      +    parent::setUp('link');
      +  }
       
         /**
          * Translates the LINK type constants to english for display and debugging of tests
      @@ -386,10 +395,10 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
           }
         }
       
      -  // Make sure that a link labelled  works.
      +  // Make sure that a link labeled  works.
         function testValidateFrontLink() {
           $valid = link_validate_url('');
      -    $this->assertEqual(LINK_FRONT, $valid, 'Make sure that front link is verfied and identified');
      +    $this->assertEqual(LINK_FRONT, $valid, 'Make sure that front link is verified and identified');
         }
       
         function testValidateEmailLink() {
      @@ -409,7 +418,7 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
       
         function testValidateNewsArticleLink() {
           $valid = link_validate_url('news:hj0db8$vrm$1@news.eternal-september.org');
      -    $this->assertEqual(LINK_NEWS, $valid, 'Make sure link to specific article valiates as news.');
      +    $this->assertEqual(LINK_NEWS, $valid, 'Make sure link to specific article validates as news.');
         }
       
         function testValidateBadNewsgroupLink() {
      @@ -418,16 +427,18 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
         }
       
         function testValidateInternalLinks() {
      +    $tempfile = drupal_tempnam('public://files', 'test');
           $links = array(
      -      'node/5',
             'rss.xml',
      -      'files/test.jpg',
      -      '/var/www/test',
      +      file_uri_target($tempfile),
      +      drupal_realpath($tempfile),
           );
           
           foreach ($links as $link) {
      +      $type = link_url_type($link);
      +      $this->assertEqual(LINK_INTERNAL, $type, 'Test ' . $link . ' is an internal link.');
             $valid = link_validate_url($link);
      -      $this->assertEqual(LINK_INTERNAL, $valid, 'Test ' . $link . ' internal link.');
      +      $this->assertTrue($valid, 'Test ' . $link . ' is valid internal link.');
           }
         }
       
      @@ -446,7 +457,6 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
             'http://255.255.255.255:4823/',
             'www.test-site.com',
             'http://example.com/index.php?q=node/123',
      -      'http://example.com/index.php?page=this\that',
             'http://example.com/?first_name=Joe Bob&last_name=Smith',
             // Anchors
             'http://www.example.com/index.php#test',
      @@ -464,8 +474,10 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
             }
           }
           foreach ($links as $link) {
      +      $type = link_url_type($link);
      +      $this->assertEqual(LINK_EXTERNAL, $type, 'Testing that ' . $link . ' is an external link.');
             $valid = link_validate_url($link);
      -      $this->assertEqual(LINK_EXTERNAL, $valid, 'Testing that ' . $link . ' is a valid external link.');
      +      $this->assertTrue($valid, 'Test ' . $link . ' is valid external link.');
             // The following two lines are commented out and only used for comparisons.
             //$valid2 = valid_url($link, TRUE);
             //$this->assertEqual(TRUE, $valid2, "Using valid_url() on $link.");
      @@ -485,6 +497,8 @@ class LinkValidateUrlLight extends DrupalWebTestCase {
             'http://www.testß.com/', // ß not allowed in domain names!
             'http://www.example.frog/', // Bad TLD
             //'http://www.-fudge.com/', // domains can't have sections starting with a dash.
      +      'http://example.com/index.php?page=this\that',
      +      'example@example.com',
           );
           foreach ($links as $link) {
             $valid = link_validate_url($link);
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/.gitignore b/profiles/commerce_kickstart/modules/contrib/mailjet/.gitignore
      deleted file mode 100644
      index 723ef36f..00000000
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/.gitignore
      +++ /dev/null
      @@ -1 +0,0 @@
      -.idea
      \ No newline at end of file
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/README.md b/profiles/commerce_kickstart/modules/contrib/mailjet/README.md
      index 14b688b6..86944769 100644
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/README.md
      +++ b/profiles/commerce_kickstart/modules/contrib/mailjet/README.md
      @@ -3,11 +3,21 @@ drupal-mailjet-module-apiv3
       
       Mailjet APIv3 module for Drupal
       
      -This module for Drupal 7.x. provides complete control of Drupal Email settings with Mailjet and also adds specific Drupal Commerce email marketing functionality such as triggered marketing emails and marketing campaign revenue statistics.
      +This module for Drupal 7.x. provides complete control of Drupal Email 
      +settings with Mailjet and also adds specific Drupal Commerce email marketing 
      +functionality such as triggered marketing emails and marketing campaign revenue statistics.
       
      -The Mailjet Module for Drupal 7.x configures your default Drupal SMTP settings to use Mailjet's SMTP relay with enhanced deliverability and tracking.  The module also provides the ability to synchronise your Drupal opt-in contacts and send bulk and targeted emails to them with real time statistics including opens, clicks, geography, average time to click, unsubs, etc. 
      +The Mailjet Module for Drupal 7.x configures your default Drupal SMTP 
      +settings to use Mailjet's SMTP relay with enhanced deliverability and 
      +tracking. The module also provides the ability to synchronise your Drupal 
      +opt-in contacts and send bulk and targeted emails to them with real time 
      +statistics including opens, clicks, geography, average time to click, unsubs, etc. 
       
      -Mailjet is a powerful all-in-one email service provider used to get maximum insight and deliverability results from both  marketing and transactional emails. Our analytics tools and intelligent APIs give senders the best understanding of how to maximize benefits for each individual contact and campaign email after email. 
      +Mailjet is a powerful all-in-one email service provider used to get maximum 
      +insight and deliverability results from both  marketing and transactional 
      +emails. Our analytics tools and intelligent APIs give senders the best 
      +understanding of how to maximize benefits for each individual contact and 
      +campaign email after email. 
       
       Requirements
       ------------
      @@ -26,50 +36,89 @@ Recommended modules
         For the list module you need to install:
         * viewfield (https://www.drupal.org/project/viewfield)
       
      +
      +Prerequisites
      +-------------
      +
      +The Mailjet plugin relies on the PHPMailer v5.2.21 for sending emails.
      +
      +To install PHPMailer via composer use:
      +
      +```
      +composer require phpmailer/phpmailer=~5.2
      +```
      +
      +To install PHPMailer manually:
      +1) Get the PHPMailer v5.2.22 from GitHub here:
      +http://github.com/PHPMailer/PHPMailer/archive/v5.2.22.zip
      +2) Extract the archive and rename the folder "PHPMailer-5.2.22" to "phpmailer".
      +3) Upload the "phpmailer" folder to your server inside
      +DRUPAL_ROOT/sites/all/libraries/.
      +4) Verify that the file class.phpmailer.php is correctly located at this
      +path: DRUPAL_ROOT/sites/all/libraries/phpmailer/class.phpmailer.php
      +* Note: Libraries API can be used to move the library to an alternative
      +location, if needed, e.g. for use in a distribution.
      +
       Installation
       ------------
       
       1. Download a release from https://www.drupal.org/project/mailjet.
       2. Upload the module in your Drupal sites/all/modules/ directory.
       3. Log in as administrator in Drupal.
      -4. Enable the Mailjet settings module on the Administer > Site building > Modules page.
      -5. Fill in required settings on the Administer > Site configuration > Mailjet settings page.
      +4. Enable the Mailjet settings module on the Administer > Site building > 
      +Modules page.
      +5. Fill in required settings on the Administer > Site configuration > Mailjet
      + settings page.
       6. You will be required to enter API key and Secret Key, if you do not have any, 
           you should go to https://www.mailjet.com/
      -    And signup for your credential data. You should enter those credentials under your 
      -    API tab (your_site/admin/config/system/mailjet/api). 
      +    And signup for your credential data. You should enter those credentials 
      +    under your API tab (your_site/admin/config/system/mailjet/api). 
       
       Configuration
       -------------
       
      -1. The site can be set up to use the mailjet module as an email gateway, this can be easily configured, by clicking on the Settings tab => your_site/admin/config/system/mailjet , and then selecting the checkbox on the top, "Send emails through Mailjet", click "Save Settings" button on the bottom of the page. 
      -You can test that feature by sending a test email, just click the button on the top of the page Send test email in Settings tab.
      -2. If you want to enable the Campaign feature, you should enable the mailjet_campaign module, you can do that from Administer > Site building > Modules page (your_site/admin/modules)
      -3.  Enabling the campaign sub module will create additional menu item in your administration menu, 
      -    the new menu is called "Campaign" (your_site/admin/mailjet/campaign). 
      +1. The site can be set up to use the mailjet module as an email gateway, this
      +    can be easily configured, by clicking on the Settings tab => 
      +    your_site/admin/config/system/mailjet, and then selecting the checkbox on 
      +    the top, "Send emails through Mailjet", click "Save Settings" button on the 
      +    bottom of the page. 
      +    You can test that feature by sending a test email, just click the button on 
      +    the top of the page Send test email in Settings tab.
      +2. If you want to enable the Campaign feature, you should enable the 
      +    mailjet_campaign module, you can do that from Administer > Site building > 
      +    Modules page (your_site/admin/modules)
      +3.  Enabling the campaign sub module will create additional menu item in your
      +    administration menu, the new menu is called "Campaign" (your_site/admin/mailjet/campaign). 
       4. Clicking this menu item will display all the campaigns created by the administrator, 
           from this point you will be able to create new campaigns as well, 
           the same way you do that on mailjet.com.
      -5. If you want to create a campaign simply go to the campaign page => your_site/admin/mailjet/campaign
      -    On the top right side of the page that will be presented there is a button “Create a campaign”, 
      -    clicking that button will lead you to a new page presenting a form that needs to be full fill, 
      -    this is the first out of three steps of creating a new campaign. The following fields are requiered - 
      -    title of the campaign, language, and contact list that you already created, 
      -    select your edition mode and click “Save and continue”.
      -    In the next step you should enter the “Sender name”, choose template of the email and write your email, 
      +5. If you want to create a campaign simply go to the campaign page => 
      +your_site/admin/mailjet/campaign
      +    On the top right side of the page that will be presented there is a 
      +    button “Create a campaign”, clicking that button will lead you to a new 
      +    page presenting a form that needs to be full fill, 
      +    this is the first out of three steps of creating a new campaign. The 
      +    following fields are requiered - title of the campaign, language, and 
      +    contact list that you already created, select your edition mode and click
      +    “Save and continue”.
      +    In the next step you should enter the “Sender name”, choose template of 
      +    the email and write your email, 
           if you want you can add links inside the email body(the "TEXT" text area) 
           leading to your site and if a customer click on that link and purchase any product 
           from your site this order will be recorded in the ROI stats feature 
      -    where you can see how good is your conversion rate, click the “Done” button on the bottom of 
      -    the email text area, and click “Continue”, in the next step which is the last one you can choose 
      +    where you can see how good is your conversion rate, click the “Done” 
      +    button on the bottom of 
      +    the email text area, and click “Continue”, in the next step which is the 
      +    last one you can choose 
           to send the email now or schedule it for later, click “Save and send”.
      -6. If you enable the stats module 2 menu items will appear Dashboard where you can see the results 
      -    of the mail campaigns and the Mailjet ROI stats, clicking the ROI stats you can see the actual 
      -    conversion of your campaigns, this feature will display a view which will present the campaign name, 
      +6.  If you enable the stats module 2 menu items will appear Dashboard where 
      +    you can see the results of the mail campaigns and the Mailjet ROI stats,
      +    clicking the ROI statsyou can see the actual conversion of your campaigns,
      +    this feature will display a view which will present the campaign name, 
           number of orders made by users who clicked on the link of your site in your email campaign.
       7. My account menu item will redirect you to the mailjet logging page.
      -8. Upgrade menu link will redirect you to the pricing list of mailjet where you can pick up a plan 
      -    and upgrade your account.
      +8. Upgrade menu link will redirect you to the pricing list of mailjet where 
      +    you can pick up a plan and upgrade your account.
       9. The contacts menu item allows you to create lists that can be used for your campaigns.
           If you click on Contacts the list of all contact lists will be displayed on the top right 
           side of the screen a button for creating a new contact list is available => Create a contact list. 
      @@ -79,8 +128,8 @@ You can test that feature by sending a test email, just click the button on the
           Upload from CSV, Copy/Paste from Excel, Copy/Paste from TXT, CSV, RTF. 
           Upload your file and click Create. 
           On the next step you should choose the import type email or mailjet_list_view click create.
      -10. If you want to enable the trigger_examples sub-module you need to enable the views_bulk_operations
      -    module and apply the following patch to it:  
      +10. If you want to enable the trigger_examples sub-module you need to enable 
      +    the views_bulk_operations module and apply the following patch to it:  
           https://www.drupal.org/files/issues/views-vbo-patch-anon-users.patch
             
       Author
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/README.txt b/profiles/commerce_kickstart/modules/contrib/mailjet/README.txt
      new file mode 100644
      index 00000000..1bcff6ac
      --- /dev/null
      +++ b/profiles/commerce_kickstart/modules/contrib/mailjet/README.txt
      @@ -0,0 +1,39 @@
      +---
      +This module for Drupal 7.x. provides complete control of Email settings with
      +Drupal and Mailjet.
      +It simply replaces your default SMTP settings in order to make all your emails
      +go through Mailjet: this will improve your deliverability and allow you
      +to optimize your campaigns.
      +
      +Mailjet helps to send and track emails in real time,
      +while ensuring their deliverability.
      +You'll take advantage of our reporting tools and get advanced statistics
      +to monitor and optimize your emails.
      +
      +Prerequisites
      +-------------
      +
      +The Mailjet plugin relies on the PHPMailer v5.2.21 for sending emails.
      +This script must be uplodaded manually by the user in the Drupal instalation.
      +1) Get the PHPMailer v5.2.21 from GitHub here:
      +http://github.com/PHPMailer/PHPMailer/archive/v5.2.21.zip
      +2) Extract the archive and rename the folder "PHPMailer-5.2.21" to "phpmailer".
      +3) Upload the "phpmailer" folder to your server inside
      +DRUPAL_ROOT/sites/all/libraries/.
      +4) Verify that the file class.phpmailer.php is correctly located at this
      +path: DRUPAL_ROOT/sites/all/libraries/phpmailer/class.phpmailer.php
      +* Note: Libraries API can be used to move the library to an alternative
      +location, if needed, e.g. for use in a distribution.
      +
      +Installation
      +------------
      +
      +1) Upload all content in your Drupal sites/all/modules/ directory.
      +2) Log in as administrator in Drupal.
      +3) Enable the Mailjet settings module on the Administer > Site building > Modules page.
      +4) Fill in required settings on the Administer > Site configuration > Mailjet settings page
      +
      +Author
      +------
      +Mailjet SAS
      +plugins@mailjet.com
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.admin.inc b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.admin.inc
      index 93ec8cf1..3738b9e8 100644
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.admin.inc
      +++ b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.admin.inc
      @@ -44,7 +44,6 @@ function mailjet_admin_settings_form()
                       "spam" => 0,
                       "blocked" => 0,
                       "unsub" => 0,
      -                "typofix" => 0
                   );
                   $tracking_url = variable_get("mj_callback_url");
                   $current_events = array();
      @@ -93,12 +92,6 @@ function mailjet_admin_settings_form()
                       '#default_value' => $check['unsub']
                   );
       
      -            $form['tracking']['tracking_typofix'] = array(
      -                '#type' => 'checkbox',
      -                '#title' => t(' Typofix events'),
      -                '#default_value' => $check['typofix']
      -            );
      -
                   $form['tracking']['tracking_url'] = array(
                       '#type' => 'hidden',
                       '#default_value' => $tracking_url
      @@ -303,7 +296,6 @@ function mailjet_admin_settings_tracking($form, &$form_state)
               "spam" => $form_state['values']['tracking_spam'],
               "blocked" => $form_state['values']['tracking_blocked'],
               "unsub" => $form_state['values']['tracking_unsub'],
      -        "typofix" => $form_state['values']['tracking_typofix']
           );
           $current_events = unserialize($form_state['values']['current_events']);
           if (mailjet_user_trackingupdate($tracking, $current_events)) {
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.iframes.inc b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.iframes.inc
      index a002d6c0..b9b861ae 100644
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.iframes.inc
      +++ b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.iframes.inc
      @@ -11,7 +11,7 @@ function mailjet_register_iframe() {
           $defaultLang = 'en';
           $locale = isset($langCodesMap[$language->language]) ? $langCodesMap[$language->language] : $langCodesMap[$defaultLang];
           
      -    $default_url = IFRAME_URL."reseller/signup?r=commerceguys&p=drupal-3.0&locale={$locale}&cb=";
      +    $default_url = IFRAME_URL."reseller/signup?r=commerceguys&sp=display&p=drupal-3.0&locale={$locale}&cb=";
           $callbackurl = urlencode($base_url . '?q=/admin/config/system/mailjet/api/register/alter_callback');
           return '';
       }
      @@ -33,8 +33,8 @@ function mailjet_plans_iframe() {
           $langCodesMap = array('en' => 'en_US', 'fr' => 'fr_FR', 'de' => 'de_DE', 'es' => 'es_ES');
           $defaultLang = 'en';
           $locale = isset($langCodesMap[$language->language]) ? $langCodesMap[$language->language] : $langCodesMap[$defaultLang];
      -    
      -    $callbackurl = 'r=commerceguys&t='.$token.'&show_menu=none&locale='.$locale;
      +
      +    $callbackurl = 'r=commerceguys&t='.$token.'&show_menu=none&sp=display&locale='.$locale;
       
           $default_url = IFRAME_URL."reseller/pricing/?";
       
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.mail.inc b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.mail.inc
      index b8d2b696..2031dc6c 100644
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.mail.inc
      +++ b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/mailjet.mail.inc
      @@ -57,14 +57,26 @@ class MailjetSmtpMailSystem implements MailSystemInterface {
             $body = $message['params']['body'][0];
           }
       
      -    // If we use the phpmailer library internally the path to the library should be changed.
      -    if (file_exists(drupal_get_path('module', 'mailjet') . '/includes/phpmailer/class.phpmailer.php')) {
      -      require_once drupal_get_path('module', 'mailjet') . '/includes/phpmailer/class.phpmailer.php';
      -    }
      -    else {
      -      drupal_set_message(t('Unable to send mail : PHPMailer library does not exist.'), 'error');
      -
      -      return FALSE;
      +    if(!class_exists('PHPMailer')) {
      +      // If the PHPMailer class is not yet auto-loaded, try to load the library
      +      // using Libraries API, if present.
      +      if (function_exists('libraries_load')) {
      +        $library = libraries_load('phpmailer');
      +        if (empty($library) || empty($library['loaded'])) {
      +          watchdog('mailjet', 'Unable to send mail : Libraries API can not load PHPMailer library.', array(), WATCHDOG_ERROR);
      +          drupal_set_message(t('Unable to send mail : PHPMailer library does not exist.'), 'error');
      +          return FALSE;
      +        }
      +      }
      +      // Libraries API not present, try manually loading the class file.
      +      elseif (file_exists('sites/all/libraries/phpmailer/PHPMailerAutoload.php')) {
      +        require_once 'sites/all/libraries/phpmailer/PHPMailerAutoload.php';
      +      }
      +      else {
      +        drupal_set_message(t('Unable to send mail : PHPMailer library does not exist.'), 'error');
      +        watchdog('mailjet', 'Unable to send mail : Libraries API and PHPMailer library does not exist .', array(), WATCHDOG_ERROR);
      +        return FALSE;
      +      }
           }
       
           // Create a new PHPMailer object - autoloaded from registry.
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/LICENSE b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/LICENSE
      deleted file mode 100644
      index 8e0763d1..00000000
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/LICENSE
      +++ /dev/null
      @@ -1,504 +0,0 @@
      -		  GNU LESSER GENERAL PUBLIC LICENSE
      -		       Version 2.1, February 1999
      -
      - Copyright (C) 1991, 1999 Free Software Foundation, Inc.
      -     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      - Everyone is permitted to copy and distribute verbatim copies
      - of this license document, but changing it is not allowed.
      -
      -[This is the first released version of the Lesser GPL.  It also counts
      - as the successor of the GNU Library Public License, version 2, hence
      - the version number 2.1.]
      -
      -			    Preamble
      -
      -  The licenses for most software are designed to take away your
      -freedom to share and change it.  By contrast, the GNU General Public
      -Licenses are intended to guarantee your freedom to share and change
      -free software--to make sure the software is free for all its users.
      -
      -  This license, the Lesser General Public License, applies to some
      -specially designated software packages--typically libraries--of the
      -Free Software Foundation and other authors who decide to use it.  You
      -can use it too, but we suggest you first think carefully about whether
      -this license or the ordinary General Public License is the better
      -strategy to use in any particular case, based on the explanations below.
      -
      -  When we speak of free software, we are referring to freedom of use,
      -not price.  Our General Public Licenses are designed to make sure that
      -you have the freedom to distribute copies of free software (and charge
      -for this service if you wish); that you receive source code or can get
      -it if you want it; that you can change the software and use pieces of
      -it in new free programs; and that you are informed that you can do
      -these things.
      -
      -  To protect your rights, we need to make restrictions that forbid
      -distributors to deny you these rights or to ask you to surrender these
      -rights.  These restrictions translate to certain responsibilities for
      -you if you distribute copies of the library or if you modify it.
      -
      -  For example, if you distribute copies of the library, whether gratis
      -or for a fee, you must give the recipients all the rights that we gave
      -you.  You must make sure that they, too, receive or can get the source
      -code.  If you link other code with the library, you must provide
      -complete object files to the recipients, so that they can relink them
      -with the library after making changes to the library and recompiling
      -it.  And you must show them these terms so they know their rights.
      -
      -  We protect your rights with a two-step method: (1) we copyright the
      -library, and (2) we offer you this license, which gives you legal
      -permission to copy, distribute and/or modify the library.
      -
      -  To protect each distributor, we want to make it very clear that
      -there is no warranty for the free library.  Also, if the library is
      -modified by someone else and passed on, the recipients should know
      -that what they have is not the original version, so that the original
      -author's reputation will not be affected by problems that might be
      -introduced by others.
      -
      -  Finally, software patents pose a constant threat to the existence of
      -any free program.  We wish to make sure that a company cannot
      -effectively restrict the users of a free program by obtaining a
      -restrictive license from a patent holder.  Therefore, we insist that
      -any patent license obtained for a version of the library must be
      -consistent with the full freedom of use specified in this license.
      -
      -  Most GNU software, including some libraries, is covered by the
      -ordinary GNU General Public License.  This license, the GNU Lesser
      -General Public License, applies to certain designated libraries, and
      -is quite different from the ordinary General Public License.  We use
      -this license for certain libraries in order to permit linking those
      -libraries into non-free programs.
      -
      -  When a program is linked with a library, whether statically or using
      -a shared library, the combination of the two is legally speaking a
      -combined work, a derivative of the original library.  The ordinary
      -General Public License therefore permits such linking only if the
      -entire combination fits its criteria of freedom.  The Lesser General
      -Public License permits more lax criteria for linking other code with
      -the library.
      -
      -  We call this license the "Lesser" General Public License because it
      -does Less to protect the user's freedom than the ordinary General
      -Public License.  It also provides other free software developers Less
      -of an advantage over competing non-free programs.  These disadvantages
      -are the reason we use the ordinary General Public License for many
      -libraries.  However, the Lesser license provides advantages in certain
      -special circumstances.
      -
      -  For example, on rare occasions, there may be a special need to
      -encourage the widest possible use of a certain library, so that it becomes
      -a de-facto standard.  To achieve this, non-free programs must be
      -allowed to use the library.  A more frequent case is that a free
      -library does the same job as widely used non-free libraries.  In this
      -case, there is little to gain by limiting the free library to free
      -software only, so we use the Lesser General Public License.
      -
      -  In other cases, permission to use a particular library in non-free
      -programs enables a greater number of people to use a large body of
      -free software.  For example, permission to use the GNU C Library in
      -non-free programs enables many more people to use the whole GNU
      -operating system, as well as its variant, the GNU/Linux operating
      -system.
      -
      -  Although the Lesser General Public License is Less protective of the
      -users' freedom, it does ensure that the user of a program that is
      -linked with the Library has the freedom and the wherewithal to run
      -that program using a modified version of the Library.
      -
      -  The precise terms and conditions for copying, distribution and
      -modification follow.  Pay close attention to the difference between a
      -"work based on the library" and a "work that uses the library".  The
      -former contains code derived from the library, whereas the latter must
      -be combined with the library in order to run.
      -
      -		  GNU LESSER GENERAL PUBLIC LICENSE
      -   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
      -
      -  0. This License Agreement applies to any software library or other
      -program which contains a notice placed by the copyright holder or
      -other authorized party saying it may be distributed under the terms of
      -this Lesser General Public License (also called "this License").
      -Each licensee is addressed as "you".
      -
      -  A "library" means a collection of software functions and/or data
      -prepared so as to be conveniently linked with application programs
      -(which use some of those functions and data) to form executables.
      -
      -  The "Library", below, refers to any such software library or work
      -which has been distributed under these terms.  A "work based on the
      -Library" means either the Library or any derivative work under
      -copyright law: that is to say, a work containing the Library or a
      -portion of it, either verbatim or with modifications and/or translated
      -straightforwardly into another language.  (Hereinafter, translation is
      -included without limitation in the term "modification".)
      -
      -  "Source code" for a work means the preferred form of the work for
      -making modifications to it.  For a library, complete source code means
      -all the source code for all modules it contains, plus any associated
      -interface definition files, plus the scripts used to control compilation
      -and installation of the library.
      -
      -  Activities other than copying, distribution and modification are not
      -covered by this License; they are outside its scope.  The act of
      -running a program using the Library is not restricted, and output from
      -such a program is covered only if its contents constitute a work based
      -on the Library (independent of the use of the Library in a tool for
      -writing it).  Whether that is true depends on what the Library does
      -and what the program that uses the Library does.
      -  
      -  1. You may copy and distribute verbatim copies of the Library's
      -complete source code as you receive it, in any medium, provided that
      -you conspicuously and appropriately publish on each copy an
      -appropriate copyright notice and disclaimer of warranty; keep intact
      -all the notices that refer to this License and to the absence of any
      -warranty; and distribute a copy of this License along with the
      -Library.
      -
      -  You may charge a fee for the physical act of transferring a copy,
      -and you may at your option offer warranty protection in exchange for a
      -fee.
      -
      -  2. You may modify your copy or copies of the Library or any portion
      -of it, thus forming a work based on the Library, and copy and
      -distribute such modifications or work under the terms of Section 1
      -above, provided that you also meet all of these conditions:
      -
      -    a) The modified work must itself be a software library.
      -
      -    b) You must cause the files modified to carry prominent notices
      -    stating that you changed the files and the date of any change.
      -
      -    c) You must cause the whole of the work to be licensed at no
      -    charge to all third parties under the terms of this License.
      -
      -    d) If a facility in the modified Library refers to a function or a
      -    table of data to be supplied by an application program that uses
      -    the facility, other than as an argument passed when the facility
      -    is invoked, then you must make a good faith effort to ensure that,
      -    in the event an application does not supply such function or
      -    table, the facility still operates, and performs whatever part of
      -    its purpose remains meaningful.
      -
      -    (For example, a function in a library to compute square roots has
      -    a purpose that is entirely well-defined independent of the
      -    application.  Therefore, Subsection 2d requires that any
      -    application-supplied function or table used by this function must
      -    be optional: if the application does not supply it, the square
      -    root function must still compute square roots.)
      -
      -These requirements apply to the modified work as a whole.  If
      -identifiable sections of that work are not derived from the Library,
      -and can be reasonably considered independent and separate works in
      -themselves, then this License, and its terms, do not apply to those
      -sections when you distribute them as separate works.  But when you
      -distribute the same sections as part of a whole which is a work based
      -on the Library, the distribution of the whole must be on the terms of
      -this License, whose permissions for other licensees extend to the
      -entire whole, and thus to each and every part regardless of who wrote
      -it.
      -
      -Thus, it is not the intent of this section to claim rights or contest
      -your rights to work written entirely by you; rather, the intent is to
      -exercise the right to control the distribution of derivative or
      -collective works based on the Library.
      -
      -In addition, mere aggregation of another work not based on the Library
      -with the Library (or with a work based on the Library) on a volume of
      -a storage or distribution medium does not bring the other work under
      -the scope of this License.
      -
      -  3. You may opt to apply the terms of the ordinary GNU General Public
      -License instead of this License to a given copy of the Library.  To do
      -this, you must alter all the notices that refer to this License, so
      -that they refer to the ordinary GNU General Public License, version 2,
      -instead of to this License.  (If a newer version than version 2 of the
      -ordinary GNU General Public License has appeared, then you can specify
      -that version instead if you wish.)  Do not make any other change in
      -these notices.
      -
      -  Once this change is made in a given copy, it is irreversible for
      -that copy, so the ordinary GNU General Public License applies to all
      -subsequent copies and derivative works made from that copy.
      -
      -  This option is useful when you wish to copy part of the code of
      -the Library into a program that is not a library.
      -
      -  4. You may copy and distribute the Library (or a portion or
      -derivative of it, under Section 2) in object code or executable form
      -under the terms of Sections 1 and 2 above provided that you accompany
      -it with the complete corresponding machine-readable source code, which
      -must be distributed under the terms of Sections 1 and 2 above on a
      -medium customarily used for software interchange.
      -
      -  If distribution of object code is made by offering access to copy
      -from a designated place, then offering equivalent access to copy the
      -source code from the same place satisfies the requirement to
      -distribute the source code, even though third parties are not
      -compelled to copy the source along with the object code.
      -
      -  5. A program that contains no derivative of any portion of the
      -Library, but is designed to work with the Library by being compiled or
      -linked with it, is called a "work that uses the Library".  Such a
      -work, in isolation, is not a derivative work of the Library, and
      -therefore falls outside the scope of this License.
      -
      -  However, linking a "work that uses the Library" with the Library
      -creates an executable that is a derivative of the Library (because it
      -contains portions of the Library), rather than a "work that uses the
      -library".  The executable is therefore covered by this License.
      -Section 6 states terms for distribution of such executables.
      -
      -  When a "work that uses the Library" uses material from a header file
      -that is part of the Library, the object code for the work may be a
      -derivative work of the Library even though the source code is not.
      -Whether this is true is especially significant if the work can be
      -linked without the Library, or if the work is itself a library.  The
      -threshold for this to be true is not precisely defined by law.
      -
      -  If such an object file uses only numerical parameters, data
      -structure layouts and accessors, and small macros and small inline
      -functions (ten lines or less in length), then the use of the object
      -file is unrestricted, regardless of whether it is legally a derivative
      -work.  (Executables containing this object code plus portions of the
      -Library will still fall under Section 6.)
      -
      -  Otherwise, if the work is a derivative of the Library, you may
      -distribute the object code for the work under the terms of Section 6.
      -Any executables containing that work also fall under Section 6,
      -whether or not they are linked directly with the Library itself.
      -
      -  6. As an exception to the Sections above, you may also combine or
      -link a "work that uses the Library" with the Library to produce a
      -work containing portions of the Library, and distribute that work
      -under terms of your choice, provided that the terms permit
      -modification of the work for the customer's own use and reverse
      -engineering for debugging such modifications.
      -
      -  You must give prominent notice with each copy of the work that the
      -Library is used in it and that the Library and its use are covered by
      -this License.  You must supply a copy of this License.  If the work
      -during execution displays copyright notices, you must include the
      -copyright notice for the Library among them, as well as a reference
      -directing the user to the copy of this License.  Also, you must do one
      -of these things:
      -
      -    a) Accompany the work with the complete corresponding
      -    machine-readable source code for the Library including whatever
      -    changes were used in the work (which must be distributed under
      -    Sections 1 and 2 above); and, if the work is an executable linked
      -    with the Library, with the complete machine-readable "work that
      -    uses the Library", as object code and/or source code, so that the
      -    user can modify the Library and then relink to produce a modified
      -    executable containing the modified Library.  (It is understood
      -    that the user who changes the contents of definitions files in the
      -    Library will not necessarily be able to recompile the application
      -    to use the modified definitions.)
      -
      -    b) Use a suitable shared library mechanism for linking with the
      -    Library.  A suitable mechanism is one that (1) uses at run time a
      -    copy of the library already present on the user's computer system,
      -    rather than copying library functions into the executable, and (2)
      -    will operate properly with a modified version of the library, if
      -    the user installs one, as long as the modified version is
      -    interface-compatible with the version that the work was made with.
      -
      -    c) Accompany the work with a written offer, valid for at
      -    least three years, to give the same user the materials
      -    specified in Subsection 6a, above, for a charge no more
      -    than the cost of performing this distribution.
      -
      -    d) If distribution of the work is made by offering access to copy
      -    from a designated place, offer equivalent access to copy the above
      -    specified materials from the same place.
      -
      -    e) verify that the user has already received a copy of these
      -    materials or that you have already sent this user a copy.
      -
      -  For an executable, the required form of the "work that uses the
      -Library" must include any data and utility programs needed for
      -reproducing the executable from it.  However, as a special exception,
      -the materials to be distributed need not include anything that is
      -normally distributed (in either source or binary form) with the major
      -components (compiler, kernel, and so on) of the operating system on
      -which the executable runs, unless that component itself accompanies
      -the executable.
      -
      -  It may happen that this requirement contradicts the license
      -restrictions of other proprietary libraries that do not normally
      -accompany the operating system.  Such a contradiction means you cannot
      -use both them and the Library together in an executable that you
      -distribute.
      -
      -  7. You may place library facilities that are a work based on the
      -Library side-by-side in a single library together with other library
      -facilities not covered by this License, and distribute such a combined
      -library, provided that the separate distribution of the work based on
      -the Library and of the other library facilities is otherwise
      -permitted, and provided that you do these two things:
      -
      -    a) Accompany the combined library with a copy of the same work
      -    based on the Library, uncombined with any other library
      -    facilities.  This must be distributed under the terms of the
      -    Sections above.
      -
      -    b) Give prominent notice with the combined library of the fact
      -    that part of it is a work based on the Library, and explaining
      -    where to find the accompanying uncombined form of the same work.
      -
      -  8. You may not copy, modify, sublicense, link with, or distribute
      -the Library except as expressly provided under this License.  Any
      -attempt otherwise to copy, modify, sublicense, link with, or
      -distribute the Library is void, and will automatically terminate your
      -rights under this License.  However, parties who have received copies,
      -or rights, from you under this License will not have their licenses
      -terminated so long as such parties remain in full compliance.
      -
      -  9. You are not required to accept this License, since you have not
      -signed it.  However, nothing else grants you permission to modify or
      -distribute the Library or its derivative works.  These actions are
      -prohibited by law if you do not accept this License.  Therefore, by
      -modifying or distributing the Library (or any work based on the
      -Library), you indicate your acceptance of this License to do so, and
      -all its terms and conditions for copying, distributing or modifying
      -the Library or works based on it.
      -
      -  10. Each time you redistribute the Library (or any work based on the
      -Library), the recipient automatically receives a license from the
      -original licensor to copy, distribute, link with or modify the Library
      -subject to these terms and conditions.  You may not impose any further
      -restrictions on the recipients' exercise of the rights granted herein.
      -You are not responsible for enforcing compliance by third parties with
      -this License.
      -
      -  11. If, as a consequence of a court judgment or allegation of patent
      -infringement or for any other reason (not limited to patent issues),
      -conditions are imposed on you (whether by court order, agreement or
      -otherwise) that contradict the conditions of this License, they do not
      -excuse you from the conditions of this License.  If you cannot
      -distribute so as to satisfy simultaneously your obligations under this
      -License and any other pertinent obligations, then as a consequence you
      -may not distribute the Library at all.  For example, if a patent
      -license would not permit royalty-free redistribution of the Library by
      -all those who receive copies directly or indirectly through you, then
      -the only way you could satisfy both it and this License would be to
      -refrain entirely from distribution of the Library.
      -
      -If any portion of this section is held invalid or unenforceable under any
      -particular circumstance, the balance of the section is intended to apply,
      -and the section as a whole is intended to apply in other circumstances.
      -
      -It is not the purpose of this section to induce you to infringe any
      -patents or other property right claims or to contest validity of any
      -such claims; this section has the sole purpose of protecting the
      -integrity of the free software distribution system which is
      -implemented by public license practices.  Many people have made
      -generous contributions to the wide range of software distributed
      -through that system in reliance on consistent application of that
      -system; it is up to the author/donor to decide if he or she is willing
      -to distribute software through any other system and a licensee cannot
      -impose that choice.
      -
      -This section is intended to make thoroughly clear what is believed to
      -be a consequence of the rest of this License.
      -
      -  12. If the distribution and/or use of the Library is restricted in
      -certain countries either by patents or by copyrighted interfaces, the
      -original copyright holder who places the Library under this License may add
      -an explicit geographical distribution limitation excluding those countries,
      -so that distribution is permitted only in or among countries not thus
      -excluded.  In such case, this License incorporates the limitation as if
      -written in the body of this License.
      -
      -  13. The Free Software Foundation may publish revised and/or new
      -versions of the Lesser General Public License from time to time.
      -Such new versions will be similar in spirit to the present version,
      -but may differ in detail to address new problems or concerns.
      -
      -Each version is given a distinguishing version number.  If the Library
      -specifies a version number of this License which applies to it and
      -"any later version", you have the option of following the terms and
      -conditions either of that version or of any later version published by
      -the Free Software Foundation.  If the Library does not specify a
      -license version number, you may choose any version ever published by
      -the Free Software Foundation.
      -
      -  14. If you wish to incorporate parts of the Library into other free
      -programs whose distribution conditions are incompatible with these,
      -write to the author to ask for permission.  For software which is
      -copyrighted by the Free Software Foundation, write to the Free
      -Software Foundation; we sometimes make exceptions for this.  Our
      -decision will be guided by the two goals of preserving the free status
      -of all derivatives of our free software and of promoting the sharing
      -and reuse of software generally.
      -
      -			    NO WARRANTY
      -
      -  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
      -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
      -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
      -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
      -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
      -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
      -PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
      -LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
      -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
      -
      -  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
      -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
      -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
      -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
      -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
      -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
      -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
      -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
      -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
      -DAMAGES.
      -
      -		     END OF TERMS AND CONDITIONS
      -
      -           How to Apply These Terms to Your New Libraries
      -
      -  If you develop a new library, and you want it to be of the greatest
      -possible use to the public, we recommend making it free software that
      -everyone can redistribute and change.  You can do so by permitting
      -redistribution under these terms (or, alternatively, under the terms of the
      -ordinary General Public License).
      -
      -  To apply these terms, attach the following notices to the library.  It is
      -safest to attach them to the start of each source file to most effectively
      -convey the exclusion of warranty; and each file should have at least the
      -"copyright" line and a pointer to where the full notice is found.
      -
      -    
      -    Copyright (C)   
      -
      -    This library is free software; you can redistribute it and/or
      -    modify it under the terms of the GNU Lesser General Public
      -    License as published by the Free Software Foundation; either
      -    version 2.1 of the License, or (at your option) any later version.
      -
      -    This library is distributed in the hope that it will be useful,
      -    but WITHOUT ANY WARRANTY; without even the implied warranty of
      -    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      -    Lesser General Public License for more details.
      -
      -    You should have received a copy of the GNU Lesser General Public
      -    License along with this library; if not, write to the Free Software
      -    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      -
      -Also add information on how to contact you by electronic and paper mail.
      -
      -You should also get your employer (if you work as a programmer) or your
      -school, if any, to sign a "copyright disclaimer" for the library, if
      -necessary.  Here is a sample; alter the names:
      -
      -  Yoyodyne, Inc., hereby disclaims all copyright interest in the
      -  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
      -
      -  , 1 April 1990
      -  Ty Coon, President of Vice
      -
      -That's all there is to it!
      -
      -
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/PHPMailerAutoload.php b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/PHPMailerAutoload.php
      deleted file mode 100644
      index eaa2e303..00000000
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/PHPMailerAutoload.php
      +++ /dev/null
      @@ -1,49 +0,0 @@
      -
      - * @author Jim Jagielski (jimjag) 
      - * @author Andy Prevost (codeworxtech) 
      - * @author Brent R. Matzelle (original founder)
      - * @copyright 2012 - 2014 Marcus Bointon
      - * @copyright 2010 - 2012 Jim Jagielski
      - * @copyright 2004 - 2009 Andy Prevost
      - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
      - * @note This program is distributed in the hope that it will be useful - WITHOUT
      - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      - * FITNESS FOR A PARTICULAR PURPOSE.
      - */
      -
      -/**
      - * PHPMailer SPL autoloader.
      - * @param string $classname The name of the class to load
      - */
      -function PHPMailerAutoload($classname)
      -{
      -    //Can't use __DIR__ as it's only in PHP 5.3+
      -    $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php';
      -    if (is_readable($filename)) {
      -        require $filename;
      -    }
      -}
      -
      -if (version_compare(PHP_VERSION, '5.1.2', '>=')) {
      -    //SPL autoloading was introduced in PHP 5.1.2
      -    if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
      -        spl_autoload_register('PHPMailerAutoload', true, true);
      -    } else {
      -        spl_autoload_register('PHPMailerAutoload');
      -    }
      -} else {
      -    /**
      -     * Fall back to traditional autoload for old PHP versions
      -     * @param string $classname The name of the class to load
      -     */
      -    function __autoload($classname)
      -    {
      -        PHPMailerAutoload($classname);
      -    }
      -}
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/README.md b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/README.md
      deleted file mode 100644
      index 282447c0..00000000
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/README.md
      +++ /dev/null
      @@ -1,148 +0,0 @@
      -![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png)
      -
      -# PHPMailer - A full-featured email creation and transfer class for PHP
      -
      -Build status: [![Build Status](https://travis-ci.org/PHPMailer/PHPMailer.svg)](https://travis-ci.org/PHPMailer/PHPMailer)
      -[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/PHPMailer/PHPMailer/badges/quality-score.png?s=3758e21d279becdf847a557a56a3ed16dfec9d5d)](https://scrutinizer-ci.com/g/PHPMailer/PHPMailer/)
      -[![Code Coverage](https://scrutinizer-ci.com/g/PHPMailer/PHPMailer/badges/coverage.png?s=3fe6ca5fe8cd2cdf96285756e42932f7ca256962)](https://scrutinizer-ci.com/g/PHPMailer/PHPMailer/)
      -
      -## Class Features
      -
      -- Probably the world's most popular code for sending email from PHP!
      -- Used by many open-source projects: Drupal, SugarCRM, Yii, Joomla! and many more
      -- Integrated SMTP support - send without a local mail server
      -- Send emails with multiple TOs, CCs, BCCs and REPLY-TOs
      -- Multipart/alternative emails for mail clients that do not read HTML email
      -- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings
      -- SMTP authentication with LOGIN, PLAIN, NTLM and CRAM-MD5 mechanisms over SSL and TLS transports
      -- Native language support
      -- DKIM and S/MIME signing support
      -- Compatible with PHP 5.0 and later
      -- Much more!
      -
      -## Why you might need it
      -
      -Many PHP developers utilize email in their code. The only PHP function that supports this is the mail() function. However, it does not provide any assistance for making use of popular features such as HTML-based emails and attachments.
      -
      -Formatting email correctly is surprisingly difficult. There are myriad overlapping RFCs, requiring tight adherence to horribly complicated formatting and encoding rules - the vast majority of code that you'll find online that uses the mail() function directly is just plain wrong!
      -*Please* don't be tempted to do it yourself - if you don't use PHPMailer, there are many other excellent libraries that you should look at before rolling your own - try SwiftMailer, Zend_Mail, eZcomponents etc.
      -
      -The PHP mail() function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD and OS X platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP implementation allows email sending on Windows platforms without a local mail server.
      -
      -## License
      -
      -This software is licenced under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html). Please read LICENSE for information on the
      -software availability and distribution.
      -
      -## Installation & loading
      -
      -PHPMailer is available via [Composer/Packagist](https://packagist.org/packages/phpmailer/phpmailer). Alternatively, just copy the contents of the PHPMailer folder into somewhere that's in your PHP `include_path` setting. If you don't speak git or just want a tarball, click the 'zip' button at the top of the page in GitHub.
      -
      -PHPMailer provides an SPL-compatible autoloader, and that is the preferred way of loading the library - just `require '/path/to/PHPMailerAutoload.php';` and everything should work. The autoloader does not throw errors if it can't find classes so it prepends itself to the SPL list, allowing your own (or your framework's) autoloader to catch errors. SPL autoloading was introduced in PHP 5.1.0, so if you are using a version older than that you will need to require/include each class manually.
      -PHPMailer does *not* declare a namespace because namespaces were only introduced in PHP 5.3.
      -
      -### Minimal installation
      -
      -While installing the entire package manually or with composer is simple, convenient and reliable, you may want to include only vital files in your project. At the very least you will need [class.phpmailer.php](class.phpmailer.php). If you're using SMTP, you'll need [class.smtp.php](class.smtp.php), and if you're using POP-before SMTP, you'll need [class.pop3.php](class.pop3.php). For all of these, we recommend you use [the autoloader](PHPMailerAutoload.php) too. You can skip the [language](language/) folder if you're not showing errors to users and can make do with English-only errors. You may need the additional classes in the [extras](extras/) folder if you are using those features, including NTLM authentication, advanced HTML-to-text conversion and ics generation.
      -
      -## A Simple Example
      -
      -```php
      -isSMTP();                                      // Set mailer to use SMTP
      -$mail->Host = 'smtp1.example.com;smtp2.example.com';  // Specify main and backup SMTP servers
      -$mail->SMTPAuth = true;                               // Enable SMTP authentication
      -$mail->Username = 'user@example.com';                 // SMTP username
      -$mail->Password = 'secret';                           // SMTP password
      -$mail->SMTPSecure = 'tls';                            // Enable encryption, 'ssl' also accepted
      -
      -$mail->From = 'from@example.com';
      -$mail->FromName = 'Mailer';
      -$mail->addAddress('joe@example.net', 'Joe User');     // Add a recipient
      -$mail->addAddress('ellen@example.com');               // Name is optional
      -$mail->addReplyTo('info@example.com', 'Information');
      -$mail->addCC('cc@example.com');
      -$mail->addBCC('bcc@example.com');
      -
      -$mail->WordWrap = 50;                                 // Set word wrap to 50 characters
      -$mail->addAttachment('/var/tmp/file.tar.gz');         // Add attachments
      -$mail->addAttachment('/tmp/image.jpg', 'new.jpg');    // Optional name
      -$mail->isHTML(true);                                  // Set email format to HTML
      -
      -$mail->Subject = 'Here is the subject';
      -$mail->Body    = 'This is the HTML message body in bold!';
      -$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
      -
      -if(!$mail->send()) {
      -    echo 'Message could not be sent.';
      -    echo 'Mailer Error: ' . $mail->ErrorInfo;
      -} else {
      -    echo 'Message has been sent';
      -}
      -```
      -
      -You'll find plenty more to play with in the [examples](examples/) folder.
      -
      -That's it. You should now be ready to use PHPMailer!
      -
      -## Localization
      -PHPMailer defaults to English, but in the [language](language/) folder you'll find numerous (39 at the time of writing) translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this:
      -
      -```php
      -// To load the French version
      -$mail->setLanguage('fr', '/optional/path/to/language/directory/');
      -```
      -
      -We welcome corrections and new languages - if you're looking for corrections to do, run the [phpmailerLangTest.php](test/phpmailerLangTest.php) script in the tests folder and it will show any missing translations.
      -
      -## Documentation
      -
      -Generated documentation is [available online](http://phpmailer.github.io/PHPMailer/).
      -
      -You'll find some basic user-level docs in the [docs](docs/) folder, and you can generate complete API-level documentation using the [generatedocs.sh](docs/generatedocs.sh) shell script in the docs folder, though you'll need to install [PHPDocumentor](http://www.phpdoc.org) first. You may find [the unit tests](test/phpmailerTest.php) a good source of how to do various operations such as encryption.
      -
      -## Tests
      -
      -There is a PHPUnit test script in the [test](test/) folder.
      -
      -Build status: [![Build Status](https://travis-ci.org/PHPMailer/PHPMailer.svg)](https://travis-ci.org/PHPMailer/PHPMailer)
      -
      -If this isn't passing, is there something you can do to help?
      -
      -## Contributing
      -
      -Please submit bug reports, suggestions and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues).
      -
      -We're particularly interested in fixing edge-cases, expanding test coverage and updating translations.
      -
      -With the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone:
      -
      -`git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git`
      -
      -Please *don't* use the SourceForge or Google Code projects any more.
      -
      -## Changelog
      -
      -See [changelog](changelog.md).
      -
      -## History
      -- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/).
      -- Marcus Bointon (coolbru on SF) and Andy Prevost (codeworxtech) took over the project in 2004.
      -- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski.
      -- Marcus created his fork on [GitHub](https://github.com/Synchro/PHPMailer).
      -- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer.
      -- PHPMailer moves to the [PHPMailer organisation](https://github.com/PHPMailer) on GitHub.
      -
      -### What's changed since moving from SourceForge?
      -- Official successor to the SourceForge and Google Code projects.
      -- Test suite.
      -- Continuous integration with Travis-CI.
      -- Composer support.
      -- Public development.
      -- Additional languages and language strings.
      -- CRAM-MD5 authentication support.
      -- Preserves full repo history of authors, commits and branches from the original SourceForge project.
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/changelog.md b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/changelog.md
      deleted file mode 100644
      index 537dc3a1..00000000
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/changelog.md
      +++ /dev/null
      @@ -1,538 +0,0 @@
      -# ChangeLog
      -
      -## Version 5.2.8 (May 14th 2014)
      -* Increase timeout to match RFC2821 section 4.5.3.2 and thus not fail greetdelays, fixes #104
      -* Add timestamps to default debug output
      -* Add connection events and new level 3 to debug output options
      -* Chinese language update (Thanks to @binaryoung)
      -* Allow custom Mailer types (Thanks to @michield)
      -* Cope with spaces around SMTP host specs
      -* Fix processing of multiple hosts in connect string
      -* Added Galician translation (Thanks to @donatorouco)
      -* Autoloader now prepends
      -* Docs updates
      -* Add Latvian translation (Thanks to @eddsstudio)
      -* Add Belarusian translation (Thanks to @amaksymiuk)
      -* Make autoloader work better on older PHP versions
      -* Avoid double-encoding if mbstring is overloading mail()
      -* Add Portuguese translation (Thanks to @Jonadabe)
      -* Make quoted-printable encoder respect line ending setting
      -* Improve Chinese translation (Thanks to @PeterDaveHello)
      -* Add Georgian translation (Thanks to @akalongman)
      -* Add Greek translation (Thanks to @lenasterg)
      -* Fix serverHostname on PHP < 5.3
      -* Improve performance of SMTP class
      -* Implement automatic 7bit downgrade
      -* Add Vietnamese translation (Thanks to @vinades)
      -* Improve example images, switch to PNG
      -* Add Croatian translation (Thanks to @hrvoj3e)
      -* Remove setting the Return-Path and deprecate the Return-path property - it's just wrong!
      -* Fix language file loading if CWD has changed (@stephandesouza)
      -* Add HTML5 email validation pattern
      -* Improve Turkish translations (Thanks to @yasinaydin)
      -* Improve Romanian translations (Thanks to @aflorea)
      -* Fix quoted-printable encoding of multiparts
      -* Check php.ini for path to sendmail/qmail before using default
      -* Improve Farsi translation (Thanks to @MHM5000)
      -* Don't use quoted-printable encoding for multipart types
      -* Add Serbian translation (Thanks to ajevremovic at gmail.com)
      -* Remove useless PHP5 check
      -* Use SVG for build status badges
      -* Store MessageDate on creation
      -* Better default behaviour for validateAddress
      -
      -## Version 5.2.7 (September 12th 2013)
      -* Add Ukranian translation from @Krezalis
      -* Support for do_verp
      -* Fix bug in CRAM-MD5 AUTH
      -* Propagate Debugoutput option to SMTP class (@Reblutus)
      -* Determine MIME type of attachments automatically
      -* Add cross-platform, multibyte-safe pathinfo replacement (with tests) and use it
      -* Add a new 'html' Debugoutput type
      -* Clean up SMTP debug output, remove embedded HTML
      -* Some small changes in header formatting to improve IETF msglint test results
      -* Update test_script to use some recently changed features, rename to code_generator
      -* Generated code actually works!
      -* Update SyntaxHighlighter
      -* Major overhaul and cleanup of example code
      -* New PHPMailer graphic
      -* msgHTML now uses RFC2392-compliant content ids
      -* Add line break normalization function and use it in msgHTML
      -* Don't set unnecessary reply-to addresses
      -* Make fakesendmail.sh a bit cleaner and safer
      -* Set a content-transfer-encoding on multiparts (fixes msglint error)
      -* Fix cid generation in msgHTML (Thanks to @digitalthought)
      -* Fix handling of multiple SMTP servers (Thanks to @NanoCaiordo)
      -* SMTP->connect() now supports stream context options (Thanks to @stanislavdavid)
      -* Add support for iCal event alternatives (Thanks to @reblutus)
      -* Update to Polish language file (Thanks to Krzysztof Kowalewski)
      -* Update to Norwegian language file (Thanks to @datagutten)
      -* Update to Hungarian language file (Thanks to @dominicus-75)
      -* Add Persian/Farsi translation from @jaii
      -* Make SMTPDebug property type match type in SMTP class
      -* Add unit tests for DKIM
      -* Major refactor of SMTP class
      -* Reformat to PSR-2 coding standard
      -* Introduce autoloader
      -* Allow overriding of SMTP class
      -* Overhaul of PHPDocs
      -* Fix broken Q-encoding
      -* Czech language update (Thanks to @nemelu)
      -* Removal of excess blank lines in messages
      -* Added fake POP server and unit tests for POP-before-SMTP
      -
      -## Version 5.2.6 (April 11th 2013)
      -* Reflect move to PHPMailer GitHub organisation at https://github.com/PHPMailer/PHPMailer
      -* Fix unbumped version numbers
      -* Update packagist.org with new location
      -* Clean up Changelog
      -
      -## Version 5.2.5 (April 6th 2013)
      -* First official release after move from Google Code
      -* Fixes for qmail when sending via mail()
      -* Merge in changes from Google code 5.2.4 release
      -* Minor coding standards cleanup in SMTP class
      -* Improved unit tests, now tests S/MIME signing
      -* Travis-CI support on GitHub, runs tests with fake SMTP server
      -
      -## Version 5.2.4 (February 19, 2013)
      -* Fix tag and version bug.
      -* un-deprecate isSMTP(), isMail(), IsSendmail() and isQmail().
      -* Numerous translation updates
      -
      -## Version 5.2.3 (February 8, 2013)
      -* Fix issue with older PCREs and ValidateAddress() (Bugz: 124)
      -* Add CRAM-MD5 authentication, thanks to Elijah madden, https://github.com/okonomiyaki3000
      -* Replacement of obsolete Quoted-Printable encoder with a much better implementation
      -* Composer package definition
      -* New language added: Hebrew
      -
      -## Version 5.2.2 (December 3, 2012)
      -* Some fixes and syncs from https://github.com/Synchro/PHPMailer
      -* Add Slovak translation, thanks to Michal Tinka
      -
      -## Version 5.2.2-rc2 (November 6, 2012)
      -* Fix SMTP server rotation (Bugz: 118)
      -* Allow override of autogen'ed 'Date' header (for Drupal's
      -  og_mailinglist module)
      -* No whitespace after '-f' option (Bugz: 116)
      -* Work around potential warning (Bugz: 114)
      -
      -## Version 5.2.2-rc1 (September 28, 2012)
      -* Header encoding works with long lines (Bugz: 93)
      -* Turkish language update (Bugz: 94)
      -* undefined $pattern in EncodeQ bug squashed (Bugz: 98)
      -* use of mail() in safe_mode now works (Bugz: 96)
      -* ValidateAddress() now 'public static' so people can override the
      -  default and use their own validation scheme.
      -* ValidateAddress() no longer uses broken FILTER_VALIDATE_EMAIL
      -* Added in AUTH PLAIN SMTP authentication
      -
      -## Version 5.2.2-beta2 (August 17, 2012)
      -* Fixed Postfix VERP support (Bugz: 92)
      -* Allow action_function callbacks to pass/use
      -  the From address (passed as final param)
      -* Prevent inf look for get_lines() (Bugz: 77)
      -* New public var ($UseSendmailOptions). Only pass sendmail()
      -  options iff we really are using sendmail or something sendmail
      -  compatible. (Bugz: 75)
      -* default setting for LE returned to "\n" due to popular demand.
      -
      -## Version 5.2.2-beta1 (July 13, 2012)
      -* Expose PreSend() and PostSend() as public methods to allow
      -  for more control if serializing message sending.
      -* GetSentMIMEMessage() only constructs the message copy when
      - needed. Save memory.
      -* Only pass params to mail() if the underlying MTA is
      -  "sendmail" (as defined as "having the string sendmail
      -  in its pathname") [#69]
      -* Attachments now work with Amazon SES and others [Bugz#70]
      -* Debug output now sent to stdout (via echo) or error_log [Bugz#5]
      -* New var: Debugoutput (for above) [Bugz#5]
      -* SMTP reads now Timeout aware (new var: Timeout=15) [Bugz#71]
      -* SMTP reads now can have a Timelimit associated with them
      -  (new var: Timelimit=30)[Bugz#71]
      -* Fix quoting issue associated with charsets
      -* default setting for LE is now RFC compliant: "\r\n"
      -* Return-Path can now be user defined (new var: ReturnPath)
      -  (the default is "" which implies no change from previous
      -  behavior, which was to use either From or Sender) [Bugz#46]
      -* X-Mailer header can now be disabled (by setting to a
      -  whitespace string, eg "  ") [Bugz#66]
      -* Bugz closed: #68, #60, #42, #43, #59, #55, #66, #48, #49,
      -               #52, #31, #41, #5. #70, #69
      -
      -## Version 5.2.1 (January 16, 2012)
      -* Closed several bugs #5
      -* Performance improvements
      -* MsgHTML() now returns the message as required.
      -* New method: GetSentMIMEMessage() (returns full copy of sent message)
      -
      -## Version 5.2 (July 19, 2011)
      -* protected MIME body and header
      -* better DKIM DNS Resource Record support
      -* better aly handling
      -* htmlfilter class added to extras
      -* moved to Apache Extras
      -
      -## Version 5.1 (October 20, 2009)
      -* fixed filename issue with AddStringAttachment (thanks to Tony)
      -* fixed "SingleTo" property, now works with Senmail, Qmail, and SMTP in
      -  addition to PHP mail()
      -* added DKIM digital signing functionality, new properties:
      -  - DKIM_domain (sets the domain name)
      -  - DKIM_private (holds DKIM private key)
      -  - DKIM_passphrase (holds your DKIM passphrase)
      -  - DKIM_selector (holds the DKIM "selector")
      -  - DKIM_identity (holds the identifying email address)
      -* added callback function support
      -  - callback function parameters include:
      -    result, to, cc, bcc, subject and body
      -  - see the test/test_callback.php file for usage.
      -* added "auto" identity functionality
      -  - can automatically add:
      -    - Return-path (if Sender not set)
      -    - Reply-To (if ReplyTo not set)
      -  - can be disabled:
      -    - $mail->SetFrom('yourname@yourdomain.com','First Last',false);
      -    - or by adding the $mail->Sender and/or $mail->ReplyTo properties
      -
      -Note: "auto" identity added to help with emails ending up in spam or junk boxes because of missing headers
      -
      -## Version 5.0.2 (May 24, 2009)
      -* Fix for missing attachments when inline graphics are present
      -* Fix for missing Cc in header when using SMTP (mail was sent,
      -  but not displayed in header -- Cc receiver only saw email To:
      -  line and no Cc line, but did get the email (To receiver
      -  saw same)
      -
      -## Version 5.0.1 (April 05, 2009)
      -* Temporary fix for missing attachments
      -
      -## Version 5.0.0 (April 02, 2009)
      -With the release of this version, we are initiating a new version numbering
      -system to differentiate from the PHP4 version of PHPMailer.
      -Most notable in this release is fully object oriented code.
      -
      -### class.smtp.php:
      -* Refactored class.smtp.php to support new exception handling
      -* code size reduced from 29.2 Kb to 25.6 Kb
      -* Removed unnecessary functions from class.smtp.php:
      -  - public function Expand($name) {
      -  - public function Help($keyword="") {
      -  - public function Noop() {
      -  - public function Send($from) {
      -  - public function SendOrMail($from) {
      -  - public function Verify($name) {
      -
      -###  class.phpmailer.php:
      -* Refactored class.phpmailer.php with new exception handling
      -* Changed processing functionality of Sendmail and Qmail so they cannot be
      -  inadvertently used
      -* removed getFile() function, just became a simple wrapper for
      -  file_get_contents()
      -* added check for PHP version (will gracefully exit if not at least PHP 5.0)
      -* enhanced code to check if an attachment source is the same as an embedded or
      -  inline graphic source to eliminate duplicate attachments
      -
      -### New /test_script
      -We have written a test script you can use to test the script as part of your
      -installation. Once you press submit, the test script will send a multi-mime
      -email with either the message you type in or an HTML email with an inline
      -graphic. Two attachments are included in the email (one of the attachments
      -is also the inline graphic so you can see that only one copy of the graphic
      -is sent in the email). The test script will also display the functional
      -script that you can copy/paste to your editor to duplicate the functionality.
      -
      -### New examples
      -All new examples in both basic and advanced modes. Advanced examples show
      -   Exception handling.
      -
      -### PHPDocumentator (phpdocs) documentation for PHPMailer version 5.0.0
      -All new documentation
      -
      -## Version 2.3 (November 06, 2008)
      -* added Arabic language (many thanks to Bahjat Al Mostafa)
      -* removed English language from language files and made it a default within
      -  class.phpmailer.php - if no language is found, it will default to use
      -  the english language translation
      -* fixed public/private declarations
      -* corrected line 1728, $basedir to $directory
      -* added $sign_cert_file to avoid improper duplicate use of $sign_key_file
      -* corrected $this->Hello on line 612 to $this->Helo
      -* changed default of $LE to "\r\n" to comply with RFC 2822. Can be set by the user
      -  if default is not acceptable
      -* removed trim() from return results in EncodeQP
      -* /test and three files it contained are removed from version 2.3
      -* fixed phpunit.php for compliance with PHP5
      -* changed $this->AltBody = $textMsg; to $this->AltBody = html_entity_decode($textMsg);
      -* We have removed the /phpdoc from the downloads. All documentation is now on
      -  the http://phpmailer.codeworxtech.com website.
      -
      -## Version 2.2.1 () July 19 2008
      -* fixed line 1092 in class.smtp.php (my apologies, error on my part)
      -
      -## Version 2.2 () July 15 2008
      -* Fixed redirect issue (display of UTF-8 in thank you redirect)
      -* fixed error in getResponse function declaration (class.pop3.php)
      -* PHPMailer now PHP6 compliant
      -* fixed line 1092 in class.smtp.php (endless loop from missing = sign)
      -
      -## Version 2.1 (Wed, June 04 2008)
      -NOTE: WE HAVE A NEW LANGUAGE VARIABLE FOR DIGITALLY SIGNED S/MIME EMAILS. IF YOU CAN HELP WITH LANGUAGES OTHER THAN ENGLISH AND SPANISH, IT WOULD BE APPRECIATED.
      -
      -* added S/MIME functionality (ability to digitally sign emails)
      -  BIG THANKS TO "sergiocambra" for posting this patch back in November 2007.
      -  The "Signed Emails" functionality adds the Sign method to pass the private key
      -  filename and the password to read it, and then email will be sent with
      -  content-type multipart/signed and with the digital signature attached.
      -* fully compatible with E_STRICT error level
      -  - Please note:
      -    In about half the test environments this development version was subjected
      -    to, an error was thrown for the date() functions used (line 1565 and 1569).
      -    This is NOT a PHPMailer error, it is the result of an incorrectly configured
      -    PHP5 installation. The fix is to modify your 'php.ini' file and include the
      -    date.timezone = Etc/UTC (or your own zone)
      -    directive, to your own server timezone
      -  - If you do get this error, and are unable to access your php.ini file:
      -    In your PHP script, add
      -    `date_default_timezone_set('Etc/UTC');`
      -  - do not try to use
      -    `$myVar = date_default_timezone_get();`
      -    as a test, it will throw an error.
      -* added ability to define path (mainly for embedded images)
      -  function `MsgHTML($message,$basedir='')` ... where:
      -  `$basedir` is the fully qualified path
      -* fixed `MsgHTML()` function:
      -  - Embedded Images where images are specified by `://` will not be altered or embedded
      -* fixed the return value of SMTP exit code ( pclose )
      -* addressed issue of multibyte characters in subject line and truncating
      -* added ability to have user specified Message ID
      -  (default is still that PHPMailer create a unique Message ID)
      -* corrected unidentified message type to 'application/octet-stream'
      -* fixed chunk_split() multibyte issue (thanks to Colin Brown, et al).
      -* added check for added attachments
      -* enhanced conversion of HTML to text in MsgHTML (thanks to "brunny")
      -
      -## Version 2.1.0beta2 (Sun, Dec 02 2007)
      -* implemented updated EncodeQP (thanks to coolbru, aka Marcus Bointon)
      -* finished all testing, all known bugs corrected, enhancements tested
      -
      -Note: will NOT work with PHP4.
      -
      -Please note, this is BETA software **DO NOT USE THIS IN PRODUCTION OR LIVE PROJECTS; INTENDED STRICTLY FOR TESTING**
      -
      -## Version 2.1.0beta1
      -Please note, this is BETA software
      -** DO NOT USE THIS IN PRODUCTION OR LIVE PROJECTS
      - INTENDED STRICTLY FOR TESTING
      -
      -## Version 2.0.0 rc2 (Fri, Nov 16 2007), interim release
      -* implements new property to control VERP in class.smtp.php
      -  example (requires instantiating class.smtp.php):
      -  $mail->do_verp = true;
      -* POP-before-SMTP functionality included, thanks to Richard Davey
      -  (see class.pop3.php & pop3_before_smtp_test.php for examples)
      -* included example showing how to use PHPMailer with GMAIL
      -* fixed the missing Cc in SendMail() and Mail()
      -
      -## Version 2.0.0 rc1 (Thu, Nov 08 2007), interim release
      -* dramatically simplified using inline graphics ... it's fully automated and requires no user input
      -* added automatic document type detection for attachments and pictures
      -* added MsgHTML() function to replace Body tag for HTML emails
      -* fixed the SendMail security issues (input validation vulnerability)
      -* enhanced the AddAddresses functionality so that the "Name" portion is used in the email address
      -* removed the need to use the AltBody method (set from the HTML, or default text used)
      -* set the PHP Mail() function as the default (still support SendMail, SMTP Mail)
      -* removed the need to set the IsHTML property (set automatically)
      -* added Estonian language file by Indrek Päri
      -* added header injection patch
      -* added "set" method to permit users to create their own pseudo-properties like 'X-Headers', etc.
      -* fixed warning message in SMTP get_lines method
      -* added TLS/SSL SMTP support.
      -* PHPMailer has been tested with PHP4 (4.4.7) and PHP5 (5.2.7)
      -* Works with PHP installed as a module or as CGI-PHP
      -NOTE: will NOT work with PHP5 in E_STRICT error mode
      -
      -## Version 1.73 (Sun, Jun 10 2005)
      -* Fixed denial of service bug: http://www.cybsec.com/vuln/PHPMailer-DOS.pdf
      -* Now has a total of 20 translations
      -* Fixed alt attachments bug: http://tinyurl.com/98u9k
      -
      -## Version 1.72 (Wed, May 25 2004)
      -* Added Dutch, Swedish, Czech, Norwegian, and Turkish translations.
      -* Received: Removed this method because spam filter programs like
      -  SpamAssassin reject this header.
      -* Fixed error count bug.
      -* SetLanguage default is now "language/".
      -* Fixed magic_quotes_runtime bug.
      -
      -## Version 1.71 (Tue, Jul 28 2003)
      -* Made several speed enhancements
      -* Added German and Italian translation files
      -* Fixed HELO/AUTH bugs on keep-alive connects
      -* Now provides an error message if language file does not load
      -* Fixed attachment EOL bug
      -* Updated some unclear documentation
      -* Added additional tests and improved others
      -
      -## Version 1.70 (Mon, Jun 20 2003)
      -* Added SMTP keep-alive support
      -* Added IsError method for error detection
      -* Added error message translation support (SetLanguage)
      -* Refactored many methods to increase library performance
      -* Hello now sends the newer EHLO message before HELO as per RFC 2821
      -* Removed the boundary class and replaced it with GetBoundary
      -* Removed queue support methods
      -* New $Hostname variable
      -* New Message-ID header
      -* Received header reformat
      -* Helo variable default changed to $Hostname
      -* Removed extra spaces in Content-Type definition (#667182)
      -* Return-Path should be set to Sender when set
      -* Adds Q or B encoding to headers when necessary
      -* quoted-encoding should now encode NULs \000
      -* Fixed encoding of body/AltBody (#553370)
      -* Adds "To: undisclosed-recipients:;" when all recipients are hidden (BCC)
      -* Multiple bug fixes
      -
      -## Version 1.65 (Fri, Aug 09 2002)
      -* Fixed non-visible attachment bug (#585097) for Outlook
      -* SMTP connections are now closed after each transaction
      -* Fixed SMTP::Expand return value
      -* Converted SMTP class documentation to phpDocumentor format
      -
      -## Version 1.62 (Wed, Jun 26 2002)
      -* Fixed multi-attach bug
      -* Set proper word wrapping
      -* Reduced memory use with attachments
      -* Added more debugging
      -* Changed documentation to phpDocumentor format
      -
      -## Version 1.60 (Sat, Mar 30 2002)
      -* Sendmail pipe and address patch (Christian Holtje)
      -* Added embedded image and read confirmation support (A. Ognio)
      -* Added unit tests
      -* Added SMTP timeout support (*nix only)
      -* Added possibly temporary PluginDir variable for SMTP class
      -* Added LE message line ending variable
      -* Refactored boundary and attachment code
      -* Eliminated SMTP class warnings
      -* Added SendToQueue method for future queuing support
      -
      -## Version 1.54 (Wed, Dec 19 2001)
      -* Add some queuing support code
      -* Fixed a pesky multi/alt bug
      -* Messages are no longer forced to have "To" addresses
      -
      -## Version 1.50 (Thu, Nov 08 2001)
      -* Fix extra lines when not using SMTP mailer
      -* Set WordWrap variable to int with a zero default
      -
      -## Version 1.47 (Tue, Oct 16 2001)
      -* Fixed Received header code format
      -* Fixed AltBody order error
      -* Fixed alternate port warning
      -
      -## Version 1.45 (Tue, Sep 25 2001)
      -* Added enhanced SMTP debug support
      -* Added support for multiple ports on SMTP
      -* Added Received header for tracing
      -* Fixed AddStringAttachment encoding
      -* Fixed possible header name quote bug
      -* Fixed wordwrap() trim bug
      -* Couple other small bug fixes
      -
      -## Version 1.41 (Wed, Aug 22 2001)
      -* Fixed AltBody bug w/o attachments
      -* Fixed rfc_date() for certain mail servers
      -
      -## Version 1.40 (Sun, Aug 12 2001)
      -* Added multipart/alternative support (AltBody)
      -* Documentation update
      -* Fixed bug in Mercury MTA
      -
      -## Version 1.29 (Fri, Aug 03 2001)
      -* Added AddStringAttachment() method
      -* Added SMTP authentication support
      -
      -## Version 1.28 (Mon, Jul 30 2001)
      -* Fixed a typo in SMTP class
      -* Fixed header issue with Imail (win32) SMTP server
      -* Made fopen() calls for attachments use "rb" to fix win32 error
      -
      -## Version 1.25 (Mon, Jul 02 2001)
      -* Added RFC 822 date fix (Patrice)
      -* Added improved error handling by adding a $ErrorInfo variable
      -* Removed MailerDebug variable (obsolete with new error handler)
      -
      -## Version 1.20 (Mon, Jun 25 2001)
      -* Added quoted-printable encoding (Patrice)
      -* Set Version as public and removed PrintVersion()
      -* Changed phpdoc to only display public variables and methods
      -
      -## Version 1.19 (Thu, Jun 21 2001)
      -* Fixed MS Mail header bug
      -* Added fix for Bcc problem with mail(). *Does not work on Win32*
      -  (See PHP bug report: http://www.php.net/bugs.php?id=11616)
      -* mail() no longer passes a fifth parameter when not needed
      -
      -## Version 1.15 (Fri, Jun 15 2001)
      -Note: these changes contributed by Patrice Fournier
      -* Changed all remaining \n to \r\n
      -* Bcc: header no longer writen to message except
      -  when sent directly to sendmail
      -* Added a small message to non-MIME compliant mail reader
      -* Added Sender variable to change the Sender email
      -  used in -f for sendmail/mail and in 'MAIL FROM' for smtp mode
      -* Changed boundary setting to a place it will be set only once
      -* Removed transfer encoding for whole message when using multipart
      -* Message body now uses Encoding in multipart messages
      -* Can set encoding and type to attachments 7bit, 8bit
      -  and binary attachment are sent as is, base64 are encoded
      -* Can set Encoding to base64 to send 8 bits body
      -  through 7 bits servers
      -
      -## Version 1.10 (Tue, Jun 12 2001)
      -* Fixed win32 mail header bug (printed out headers in message body)
      -
      -## Version 1.09 (Fri, Jun 08 2001)
      -* Changed date header to work with Netscape mail programs
      -* Altered phpdoc documentation
      -
      -## Version 1.08 (Tue, Jun 05 2001)
      -* Added enhanced error-checking
      -* Added phpdoc documentation to source
      -
      -## Version 1.06 (Fri, Jun 01 2001)
      -* Added optional name for file attachments
      -
      -## Version 1.05 (Tue, May 29 2001)
      -* Code cleanup
      -* Eliminated sendmail header warning message
      -* Fixed possible SMTP error
      -
      -## Version 1.03 (Thu, May 24 2001)
      -* Fixed problem where qmail sends out duplicate messages
      -
      -## Version 1.02 (Wed, May 23 2001)
      -* Added multiple recipient and attachment Clear* methods
      -* Added Sendmail public variable
      -* Fixed problem with loading SMTP library multiple times
      -
      -## Version 0.98 (Tue, May 22 2001)
      -* Fixed problem with redundant mail hosts sending out multiple messages
      -* Added additional error handler code
      -* Added AddCustomHeader() function
      -* Added support for Microsoft mail client headers (affects priority)
      -* Fixed small bug with Mailer variable
      -* Added PrintVersion() function
      -
      -## Version 0.92 (Tue, May 15 2001)
      -* Changed file names to class.phpmailer.php and class.smtp.php to match
      -  current PHP class trend.
      -* Fixed problem where body not being printed when a message is attached
      -* Several small bug fixes
      -
      -## Version 0.90 (Tue, April 17 2001)
      -* Initial public release
      diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.phpmailer.php b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.phpmailer.php
      index 65d4c9d4..c06c3cdf 100644
      --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.phpmailer.php
      +++ b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.phpmailer.php
      @@ -1,3416 +1,2 @@
       
      - * @author Jim Jagielski (jimjag) 
      - * @author Andy Prevost (codeworxtech) 
      - * @author Brent R. Matzelle (original founder)
      - * @copyright 2012 - 2014 Marcus Bointon
      - * @copyright 2010 - 2012 Jim Jagielski
      - * @copyright 2004 - 2009 Andy Prevost
      - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
      - * @note This program is distributed in the hope that it will be useful - WITHOUT
      - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      - * FITNESS FOR A PARTICULAR PURPOSE.
      - */
      -
      -/**
      - * PHPMailer - PHP email creation and transport class.
      - * @package PHPMailer
      - * @author Marcus Bointon (Synchro/coolbru) 
      - * @author Jim Jagielski (jimjag) 
      - * @author Andy Prevost (codeworxtech) 
      - * @author Brent R. Matzelle (original founder)
      - */
      -class PHPMailer
      -{
      -    /**
      -     * The PHPMailer Version number.
      -     * @type string
      -     */
      -    public $Version = '5.2.8';
      -
      -    /**
      -     * Email priority.
      -     * Options: 1 = High, 3 = Normal, 5 = low.
      -     * @type int
      -     */
      -    public $Priority = 3;
      -
      -    /**
      -     * The character set of the message.
      -     * @type string
      -     */
      -    public $CharSet = 'iso-8859-1';
      -
      -    /**
      -     * The MIME Content-type of the message.
      -     * @type string
      -     */
      -    public $ContentType = 'text/plain';
      -
      -    /**
      -     * The message encoding.
      -     * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
      -     * @type string
      -     */
      -    public $Encoding = '8bit';
      -
      -    /**
      -     * Holds the most recent mailer error message.
      -     * @type string
      -     */
      -    public $ErrorInfo = '';
      -
      -    /**
      -     * The From email address for the message.
      -     * @type string
      -     */
      -    public $From = 'root@localhost';
      -
      -    /**
      -     * The From name of the message.
      -     * @type string
      -     */
      -    public $FromName = 'Root User';
      -
      -    /**
      -     * The Sender email (Return-Path) of the message.
      -     * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
      -     * @type string
      -     */
      -    public $Sender = '';
      -
      -    /**
      -     * The Return-Path of the message.
      -     * If empty, it will be set to either From or Sender.
      -     * @type string
      -     * @deprecated Email senders should never set a return-path header;
      -     * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
      -     * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
      -     */
      -    public $ReturnPath = '';
      -
      -    /**
      -     * The Subject of the message.
      -     * @type string
      -     */
      -    public $Subject = '';
      -
      -    /**
      -     * An HTML or plain text message body.
      -     * If HTML then call isHTML(true).
      -     * @type string
      -     */
      -    public $Body = '';
      -
      -    /**
      -     * The plain-text message body.
      -     * This body can be read by mail clients that do not have HTML email
      -     * capability such as mutt & Eudora.
      -     * Clients that can read HTML will view the normal Body.
      -     * @type string
      -     */
      -    public $AltBody = '';
      -
      -    /**
      -     * An iCal message part body.
      -     * Only supported in simple alt or alt_inline message types
      -     * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
      -     * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
      -     * @link http://kigkonsult.se/iCalcreator/
      -     * @type string
      -     */
      -    public $Ical = '';
      -
      -    /**
      -     * The complete compiled MIME message body.
      -     * @access protected
      -     * @type string
      -     */
      -    protected $MIMEBody = '';
      -
      -    /**
      -     * The complete compiled MIME message headers.
      -     * @type string
      -     * @access protected
      -     */
      -    protected $MIMEHeader = '';
      -
      -    /**
      -     * Extra headers that createHeader() doesn't fold in.
      -     * @type string
      -     * @access protected
      -     */
      -    protected $mailHeader = '';
      -
      -    /**
      -     * Word-wrap the message body to this number of chars.
      -     * @type int
      -     */
      -    public $WordWrap = 0;
      -
      -    /**
      -     * Which method to use to send mail.
      -     * Options: "mail", "sendmail", or "smtp".
      -     * @type string
      -     */
      -    public $Mailer = 'mail';
      -
      -    /**
      -     * The path to the sendmail program.
      -     * @type string
      -     */
      -    public $Sendmail = '/usr/sbin/sendmail';
      -
      -    /**
      -     * Whether mail() uses a fully sendmail-compatible MTA.
      -     * One which supports sendmail's "-oi -f" options.
      -     * @type bool
      -     */
      -    public $UseSendmailOptions = true;
      -
      -    /**
      -     * Path to PHPMailer plugins.
      -     * Useful if the SMTP class is not in the PHP include path.
      -     * @type string
      -     * @deprecated Should not be needed now there is an autoloader.
      -     */
      -    public $PluginDir = '';
      -
      -    /**
      -     * The email address that a reading confirmation should be sent to.
      -     * @type string
      -     */
      -    public $ConfirmReadingTo = '';
      -
      -    /**
      -     * The hostname to use in Message-Id and Received headers
      -     * and as default HELO string.
      -     * If empty, the value returned
      -     * by SERVER_NAME is used or 'localhost.localdomain'.
      -     * @type string
      -     */
      -    public $Hostname = '';
      -
      -    /**
      -     * An ID to be used in the Message-Id header.
      -     * If empty, a unique id will be generated.
      -     * @type string
      -     */
      -    public $MessageID = '';
      -
      -    /**
      -     * The message Date to be used in the Date header.
      -     * If empty, the current date will be added.
      -     * @type string
      -     */
      -    public $MessageDate = '';
      -
      -    /**
      -     * SMTP hosts.
      -     * Either a single hostname or multiple semicolon-delimited hostnames.
      -     * You can also specify a different port
      -     * for each host by using this format: [hostname:port]
      -     * (e.g. "smtp1.example.com:25;smtp2.example.com").
      -     * Hosts will be tried in order.
      -     * @type string
      -     */
      -    public $Host = 'localhost';
      -
      -    /**
      -     * The default SMTP server port.
      -     * @type int
      -     * @Todo Why is this needed when the SMTP class takes care of it?
      -     */
      -    public $Port = 25;
      -
      -    /**
      -     * The SMTP HELO of the message.
      -     * Default is $Hostname.
      -     * @type string
      -     * @see PHPMailer::$Hostname
      -     */
      -    public $Helo = '';
      -
      -    /**
      -     * The secure connection prefix.
      -     * Options: "", "ssl" or "tls"
      -     * @type string
      -     */
      -    public $SMTPSecure = '';
      -
      -    /**
      -     * Whether to use SMTP authentication.
      -     * Uses the Username and Password properties.
      -     * @type bool
      -     * @see PHPMailer::$Username
      -     * @see PHPMailer::$Password
      -     */
      -    public $SMTPAuth = false;
      -
      -    /**
      -     * SMTP username.
      -     * @type string
      -     */
      -    public $Username = '';
      -
      -    /**
      -     * SMTP password.
      -     * @type string
      -     */
      -    public $Password = '';
      -
      -    /**
      -     * SMTP auth type.
      -     * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5
      -     * @type string
      -     */
      -    public $AuthType = '';
      -
      -    /**
      -     * SMTP realm.
      -     * Used for NTLM auth
      -     * @type string
      -     */
      -    public $Realm = '';
      -
      -    /**
      -     * SMTP workstation.
      -     * Used for NTLM auth
      -     * @type string
      -     */
      -    public $Workstation = '';
      -
      -    /**
      -     * The SMTP server timeout in seconds.
      -     * @type int
      -     */
      -    public $Timeout = 10;
      -
      -    /**
      -     * SMTP class debug output mode.
      -     * Options:
      -     *   0: no output
      -     *   1: commands
      -     *   2: data and commands
      -     *   3: as 2 plus connection status
      -     *   4: low level data output
      -     * @type int
      -     * @see SMTP::$do_debug
      -     */
      -    public $SMTPDebug = 0;
      -
      -    /**
      -     * How to handle debug output.
      -     * Options:
      -     *   'echo': Output plain-text as-is, appropriate for CLI
      -     *   'html': Output escaped, line breaks converted to 
      , appropriate for browser output - * 'error_log': Output to error log as configured in php.ini - * @type string - * @see SMTP::$Debugoutput - */ - public $Debugoutput = 'echo'; - - /** - * Whether to keep SMTP connection open after each message. - * If this is set to true then to close the connection - * requires an explicit call to smtpClose(). - * @type bool - */ - public $SMTPKeepAlive = false; - - /** - * Whether to split multiple to addresses into multiple messages - * or send them all in one message. - * @type bool - */ - public $SingleTo = false; - - /** - * Storage for addresses when SingleTo is enabled. - * @type array - * @todo This should really not be public - */ - public $SingleToArray = array(); - - /** - * Whether to generate VERP addresses on send. - * Only applicable when sending via SMTP. - * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path - * @link http://www.postfix.org/VERP_README.html Postfix VERP info - * @type bool - */ - public $do_verp = false; - - /** - * Whether to allow sending messages with an empty body. - * @type bool - */ - public $AllowEmpty = false; - - /** - * The default line ending. - * @note The default remains "\n". We force CRLF where we know - * it must be used via self::CRLF. - * @type string - */ - public $LE = "\n"; - - /** - * DKIM selector. - * @type string - */ - public $DKIM_selector = ''; - - /** - * DKIM Identity. - * Usually the email address used as the source of the email - * @type string - */ - public $DKIM_identity = ''; - - /** - * DKIM passphrase. - * Used if your key is encrypted. - * @type string - */ - public $DKIM_passphrase = ''; - - /** - * DKIM signing domain name. - * @example 'example.com' - * @type string - */ - public $DKIM_domain = ''; - - /** - * DKIM private key file path. - * @type string - */ - public $DKIM_private = ''; - - /** - * Callback Action function name. - * - * The function that handles the result of the send email action. - * It is called out by send() for each email sent. - * - * Value can be any php callable: http://www.php.net/is_callable - * - * Parameters: - * bool $result result of the send action - * string $to email address of the recipient - * string $cc cc email addresses - * string $bcc bcc email addresses - * string $subject the subject - * string $body the email body - * string $from email address of sender - * @type string - */ - public $action_function = ''; - - /** - * What to use in the X-Mailer header. - * Options: null for default, whitespace for none, or a string to use - * @type string - */ - public $XMailer = ''; - - /** - * An instance of the SMTP sender class. - * @type SMTP - * @access protected - */ - protected $smtp = null; - - /** - * The array of 'to' addresses. - * @type array - * @access protected - */ - protected $to = array(); - - /** - * The array of 'cc' addresses. - * @type array - * @access protected - */ - protected $cc = array(); - - /** - * The array of 'bcc' addresses. - * @type array - * @access protected - */ - protected $bcc = array(); - - /** - * The array of reply-to names and addresses. - * @type array - * @access protected - */ - protected $ReplyTo = array(); - - /** - * An array of all kinds of addresses. - * Includes all of $to, $cc, $bcc, $replyto - * @type array - * @access protected - */ - protected $all_recipients = array(); - - /** - * The array of attachments. - * @type array - * @access protected - */ - protected $attachment = array(); - - /** - * The array of custom headers. - * @type array - * @access protected - */ - protected $CustomHeader = array(); - - /** - * The most recent Message-ID (including angular brackets). - * @type string - * @access protected - */ - protected $lastMessageID = ''; - - /** - * The message's MIME type. - * @type string - * @access protected - */ - protected $message_type = ''; - - /** - * The array of MIME boundary strings. - * @type array - * @access protected - */ - protected $boundary = array(); - - /** - * The array of available languages. - * @type array - * @access protected - */ - protected $language = array(); - - /** - * The number of errors encountered. - * @type integer - * @access protected - */ - protected $error_count = 0; - - /** - * The S/MIME certificate file path. - * @type string - * @access protected - */ - protected $sign_cert_file = ''; - - /** - * The S/MIME key file path. - * @type string - * @access protected - */ - protected $sign_key_file = ''; - - /** - * The S/MIME password for the key. - * Used only if the key is encrypted. - * @type string - * @access protected - */ - protected $sign_key_pass = ''; - - /** - * Whether to throw exceptions for errors. - * @type bool - * @access protected - */ - protected $exceptions = false; - - /** - * Error severity: message only, continue processing - */ - const STOP_MESSAGE = 0; - - /** - * Error severity: message, likely ok to continue processing - */ - const STOP_CONTINUE = 1; - - /** - * Error severity: message, plus full stop, critical error reached - */ - const STOP_CRITICAL = 2; - - /** - * SMTP RFC standard line ending - */ - const CRLF = "\r\n"; - - /** - * Constructor - * @param bool $exceptions Should we throw external exceptions? - */ - public function __construct($exceptions = false) - { - $this->exceptions = ($exceptions == true); - //Make sure our autoloader is loaded - if (version_compare(PHP_VERSION, '5.1.2', '>=')) { - $autoload = spl_autoload_functions(); - if ($autoload === false or !in_array('PHPMailerAutoload', $autoload)) { - require 'PHPMailerAutoload.php'; - } - } - } - - /** - * Destructor. - */ - public function __destruct() - { - if ($this->Mailer == 'smtp') { //close any open SMTP connection nicely - $this->smtpClose(); - } - } - - /** - * Call mail() in a safe_mode-aware fashion. - * Also, unless sendmail_path points to sendmail (or something that - * claims to be sendmail), don't pass params (not a perfect fix, - * but it will do) - * @param string $to To - * @param string $subject Subject - * @param string $body Message Body - * @param string $header Additional Header(s) - * @param string $params Params - * @access private - * @return bool - */ - private function mailPassthru($to, $subject, $body, $header, $params) - { - //Check overloading of mail function to avoid double-encoding - if (ini_get('mbstring.func_overload') & 1) { - $subject = $this->secureHeader($subject); - } else { - $subject = $this->encodeHeader($this->secureHeader($subject)); - } - if (ini_get('safe_mode') || !($this->UseSendmailOptions)) { - $result = @mail($to, $subject, $body, $header); - } else { - $result = @mail($to, $subject, $body, $header, $params); - } - return $result; - } - - /** - * Output debugging info via user-defined method. - * Only if debug output is enabled. - * @see PHPMailer::$Debugoutput - * @see PHPMailer::$SMTPDebug - * @param string $str - */ - protected function edebug($str) - { - if (!$this->SMTPDebug) { - return; - } - switch ($this->Debugoutput) { - case 'error_log': - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking display that's HTML-safe - echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, $this->CharSet) . "
      \n"; - break; - case 'echo': - default: - echo $str."\n"; - } - } - - /** - * Sets message type to HTML or plain. - * @param bool $ishtml True for HTML mode. - * @return void - */ - public function isHTML($ishtml = true) - { - if ($ishtml) { - $this->ContentType = 'text/html'; - } else { - $this->ContentType = 'text/plain'; - } - } - - /** - * Send messages using SMTP. - * @return void - */ - public function isSMTP() - { - $this->Mailer = 'smtp'; - } - - /** - * Send messages using PHP's mail() function. - * @return void - */ - public function isMail() - { - $this->Mailer = 'mail'; - } - - /** - * Send messages using $Sendmail. - * @return void - */ - public function isSendmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (!stristr($ini_sendmail_path, 'sendmail')) { - $this->Sendmail = '/usr/sbin/sendmail'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'sendmail'; - } - - /** - * Send messages using qmail. - * @return void - */ - public function isQmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (!stristr($ini_sendmail_path, 'qmail')) { - $this->Sendmail = '/var/qmail/bin/qmail-inject'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'qmail'; - } - - /** - * Add a "To" address. - * @param string $address - * @param string $name - * @return bool true on success, false if address already used - */ - public function addAddress($address, $name = '') - { - return $this->addAnAddress('to', $address, $name); - } - - /** - * Add a "CC" address. - * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address - * @param string $name - * @return bool true on success, false if address already used - */ - public function addCC($address, $name = '') - { - return $this->addAnAddress('cc', $address, $name); - } - - /** - * Add a "BCC" address. - * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address - * @param string $name - * @return bool true on success, false if address already used - */ - public function addBCC($address, $name = '') - { - return $this->addAnAddress('bcc', $address, $name); - } - - /** - * Add a "Reply-to" address. - * @param string $address - * @param string $name - * @return bool - */ - public function addReplyTo($address, $name = '') - { - return $this->addAnAddress('Reply-To', $address, $name); - } - - /** - * Add an address to one of the recipient arrays. - * Addresses that have been added already return false, but do not throw exceptions - * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' - * @param string $address The email address to send to - * @param string $name - * @throws phpmailerException - * @return bool true on success, false if address already used or invalid in some way - * @access protected - */ - protected function addAnAddress($kind, $address, $name = '') - { - if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { - $this->setError($this->lang('Invalid recipient array') . ': ' . $kind); - $this->edebug($this->lang('Invalid recipient array') . ': ' . $kind); - if ($this->exceptions) { - throw new phpmailerException('Invalid recipient array: ' . $kind); - } - return false; - } - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - if (!$this->validateAddress($address)) { - $this->setError($this->lang('invalid_address') . ': ' . $address); - $this->edebug($this->lang('invalid_address') . ': ' . $address); - if ($this->exceptions) { - throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); - } - return false; - } - if ($kind != 'Reply-To') { - if (!isset($this->all_recipients[strtolower($address)])) { - array_push($this->$kind, array($address, $name)); - $this->all_recipients[strtolower($address)] = true; - return true; - } - } else { - if (!array_key_exists(strtolower($address), $this->ReplyTo)) { - $this->ReplyTo[strtolower($address)] = array($address, $name); - return true; - } - } - return false; - } - - /** - * Set the From and FromName properties. - * @param string $address - * @param string $name - * @param bool $auto Whether to also set the Sender address, defaults to true - * @throws phpmailerException - * @return bool - */ - public function setFrom($address, $name = '', $auto = true) - { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - if (!$this->validateAddress($address)) { - $this->setError($this->lang('invalid_address') . ': ' . $address); - $this->edebug($this->lang('invalid_address') . ': ' . $address); - if ($this->exceptions) { - throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); - } - return false; - } - $this->From = $address; - $this->FromName = $name; - if ($auto) { - if (empty($this->Sender)) { - $this->Sender = $address; - } - } - return true; - } - - /** - * Return the Message-ID header of the last email. - * Technically this is the value from the last time the headers were created, - * but it's also the message ID of the last sent message except in - * pathological cases. - * @return string - */ - public function getLastMessageID() - { - return $this->lastMessageID; - } - - /** - * Check that a string looks like an email address. - * @param string $address The email address to check - * @param string $patternselect A selector for the validation pattern to use : - * * `auto` Pick strictest one automatically; - * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; - * * `pcre` Use old PCRE implementation; - * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains; - * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. - * * `noregex` Don't use a regex: super fast, really dumb. - * @return bool - * @static - * @access public - */ - public static function validateAddress($address, $patternselect = 'auto') - { - if (!$patternselect or $patternselect == 'auto') { - if (defined('PCRE_VERSION')) { //Check this constant so it works when extension_loaded() is disabled - if (version_compare(PCRE_VERSION, '8.0') >= 0) { - $patternselect = 'pcre8'; - } else { - $patternselect = 'pcre'; - } - } else { - //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension - if (version_compare(PHP_VERSION, '5.2.0') >= 0) { - $patternselect = 'php'; - } else { - $patternselect = 'noregex'; - } - } - } - switch ($patternselect) { - case 'pcre8': - /** - * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. - * @link http://squiloople.com/2009/12/20/email-address-validation/ - * @copyright 2009-2010 Michael Rushton - * Feel free to use and redistribute this code. But please keep this copyright notice. - */ - return (bool)preg_match( - '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . - '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . - '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . - '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . - '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . - '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . - '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . - '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', - $address - ); - break; - case 'pcre': - //An older regex that doesn't need a recent PCRE - return (bool)preg_match( - '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . - '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . - '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . - '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . - '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . - '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . - '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . - '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . - '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', - $address - ); - break; - case 'html5': - /** - * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. - * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) - */ - return (bool)preg_match('/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . - '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', $address); - break; - case 'php': - default: - return (bool)filter_var($address, FILTER_VALIDATE_EMAIL); - break; - case 'noregex': - //No PCRE! Do something _very_ approximate! - //Check the address is 3 chars or longer and contains an @ that's not the first or last char - return (strlen($address) >= 3 - and strpos($address, '@') >= 1 - and strpos($address, '@') != strlen($address) - 1); - break; - } - } - - /** - * Create a message and send it. - * Uses the sending method specified by $Mailer. - * @throws phpmailerException - * @return bool false on error - See the ErrorInfo property for details of the error. - */ - public function send() - { - try { - if (!$this->preSend()) { - return false; - } - return $this->postSend(); - } catch (phpmailerException $exc) { - $this->mailHeader = ''; - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - } - - /** - * Prepare a message for sending. - * @throws phpmailerException - * @return bool - */ - public function preSend() - { - try { - $this->mailHeader = ''; - if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { - throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); - } - - // Set whether the message is multipart/alternative - if (!empty($this->AltBody)) { - $this->ContentType = 'multipart/alternative'; - } - - $this->error_count = 0; // reset errors - $this->setMessageType(); - // Refuse to send an empty message unless we are specifically allowing it - if (!$this->AllowEmpty and empty($this->Body)) { - throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); - } - - $this->MIMEHeader = $this->createHeader(); - $this->MIMEBody = $this->createBody(); - - // To capture the complete message when using mail(), create - // an extra header list which createHeader() doesn't fold in - if ($this->Mailer == 'mail') { - if (count($this->to) > 0) { - $this->mailHeader .= $this->addrAppend('To', $this->to); - } else { - $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - $this->mailHeader .= $this->headerLine( - 'Subject', - $this->encodeHeader($this->secureHeader(trim($this->Subject))) - ); - } - - // Sign with DKIM if enabled - if (!empty($this->DKIM_domain) - && !empty($this->DKIM_private) - && !empty($this->DKIM_selector) - && !empty($this->DKIM_domain) - && file_exists($this->DKIM_private)) { - $header_dkim = $this->DKIM_Add( - $this->MIMEHeader . $this->mailHeader, - $this->encodeHeader($this->secureHeader($this->Subject)), - $this->MIMEBody - ); - $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . - str_replace("\r\n", "\n", $header_dkim) . self::CRLF; - } - return true; - - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - } - - /** - * Actually send a message. - * Send the email via the selected mechanism - * @throws phpmailerException - * @return bool - */ - public function postSend() - { - try { - // Choose the mailer and send through it - switch ($this->Mailer) { - case 'sendmail': - case 'qmail': - return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); - case 'smtp': - return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); - case 'mail': - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - default: - $sendMethod = $this->Mailer.'Send'; - if (method_exists($this, $sendMethod)) { - return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); - } - - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - } - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - } - return false; - } - - /** - * Send mail using the $Sendmail program. - * @param string $header The message headers - * @param string $body The message body - * @see PHPMailer::$Sendmail - * @throws phpmailerException - * @access protected - * @return bool - */ - protected function sendmailSend($header, $body) - { - if ($this->Sender != '') { - if ($this->Mailer == 'qmail') { - $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); - } else { - $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); - } - } else { - if ($this->Mailer == 'qmail') { - $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail)); - } else { - $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail)); - } - } - if ($this->SingleTo === true) { - foreach ($this->SingleToArray as $val) { - if (!@$mail = popen($sendmail, 'w')) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fputs($mail, 'To: ' . $val . "\n"); - fputs($mail, $header); - fputs($mail, $body); - $result = pclose($mail); - // implement call back function if it exists - $isSent = ($result == 0) ? 1 : 0; - $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body, $this->From); - if ($result != 0) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - } else { - if (!@$mail = popen($sendmail, 'w')) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fputs($mail, $header); - fputs($mail, $body); - $result = pclose($mail); - // implement call back function if it exists - $isSent = ($result == 0) ? 1 : 0; - $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); - if ($result != 0) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - return true; - } - - /** - * Send mail using the PHP mail() function. - * @param string $header The message headers - * @param string $body The message body - * @link http://www.php.net/manual/en/book.mail.php - * @throws phpmailerException - * @access protected - * @return bool - */ - protected function mailSend($header, $body) - { - $toArr = array(); - foreach ($this->to as $toaddr) { - $toArr[] = $this->addrFormat($toaddr); - } - $to = implode(', ', $toArr); - - if (empty($this->Sender)) { - $params = ' '; - } else { - $params = sprintf('-f%s', $this->Sender); - } - if ($this->Sender != '' and !ini_get('safe_mode')) { - $old_from = ini_get('sendmail_from'); - ini_set('sendmail_from', $this->Sender); - } - $result = false; - if ($this->SingleTo === true && count($toArr) > 1) { - foreach ($toArr as $val) { - $result = $this->mailPassthru($val, $this->Subject, $body, $header, $params); - // implement call back function if it exists - $isSent = ($result == 1) ? 1 : 0; - $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body, $this->From); - } - } else { - $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); - // implement call back function if it exists - $isSent = ($result == 1) ? 1 : 0; - $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); - } - if (isset($old_from)) { - ini_set('sendmail_from', $old_from); - } - if (!$result) { - throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); - } - return true; - } - - /** - * Get an instance to use for SMTP operations. - * Override this function to load your own SMTP implementation - * @return SMTP - */ - public function getSMTPInstance() - { - if (!is_object($this->smtp)) { - $this->smtp = new SMTP; - } - return $this->smtp; - } - - /** - * Send mail via SMTP. - * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. - * Uses the PHPMailerSMTP class by default. - * @see PHPMailer::getSMTPInstance() to use a different class. - * @param string $header The message headers - * @param string $body The message body - * @throws phpmailerException - * @uses SMTP - * @access protected - * @return bool - */ - protected function smtpSend($header, $body) - { - $bad_rcpt = array(); - - if (!$this->smtpConnect()) { - throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); - } - $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; - if (!$this->smtp->mail($smtp_from)) { - $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); - throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); - } - - // Attempt to send to all recipients - foreach ($this->to as $to) { - if (!$this->smtp->recipient($to[0])) { - $bad_rcpt[] = $to[0]; - $isSent = 0; - } else { - $isSent = 1; - } - $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body, $this->From); - } - foreach ($this->cc as $cc) { - if (!$this->smtp->recipient($cc[0])) { - $bad_rcpt[] = $cc[0]; - $isSent = 0; - } else { - $isSent = 1; - } - $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body, $this->From); - } - foreach ($this->bcc as $bcc) { - if (!$this->smtp->recipient($bcc[0])) { - $bad_rcpt[] = $bcc[0]; - $isSent = 0; - } else { - $isSent = 1; - } - $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body, $this->From); - } - - //Only send the DATA command if we have viable recipients - if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { - throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); - } - if ($this->SMTPKeepAlive == true) { - $this->smtp->reset(); - } else { - $this->smtp->quit(); - $this->smtp->close(); - } - if (count($bad_rcpt) > 0) { //Create error message for any bad addresses - throw new phpmailerException( - $this->lang('recipients_failed') . implode(', ', $bad_rcpt), - self::STOP_CONTINUE - ); - } - return true; - } - - /** - * Initiate a connection to an SMTP server. - * Returns false if the operation failed. - * @param array $options An array of options compatible with stream_context_create() - * @uses SMTP - * @access public - * @throws phpmailerException - * @return bool - */ - public function smtpConnect($options = array()) - { - if (is_null($this->smtp)) { - $this->smtp = $this->getSMTPInstance(); - } - - //Already connected? - if ($this->smtp->connected()) { - return true; - } - - $this->smtp->setTimeout($this->Timeout); - $this->smtp->setDebugLevel($this->SMTPDebug); - $this->smtp->setDebugOutput($this->Debugoutput); - $this->smtp->setVerp($this->do_verp); - $hosts = explode(';', $this->Host); - $lastexception = null; - - foreach ($hosts as $hostentry) { - $hostinfo = array(); - if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { - //Not a valid host entry - continue; - } - //$hostinfo[2]: optional ssl or tls prefix - //$hostinfo[3]: the hostname - //$hostinfo[4]: optional port number - //The host string prefix can temporarily override the current setting for SMTPSecure - //If it's not specified, the default value is used - $prefix = ''; - $tls = ($this->SMTPSecure == 'tls'); - if ($hostinfo[2] == 'ssl' or ($hostinfo[2] == '' and $this->SMTPSecure == 'ssl')) { - $prefix = 'ssl://'; - $tls = false; //Can't have SSL and TLS at once - } elseif ($hostinfo[2] == 'tls') { - $tls = true; - //tls doesn't use a prefix - } - $host = $hostinfo[3]; - $port = $this->Port; - $tport = (integer)$hostinfo[4]; - if ($tport > 0 and $tport < 65536) { - $port = $tport; - } - if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { - try { - if ($this->Helo) { - $hello = $this->Helo; - } else { - $hello = $this->serverHostname(); - } - $this->smtp->hello($hello); - - if ($tls) { - if (!$this->smtp->startTLS()) { - throw new phpmailerException($this->lang('connect_host')); - } - //We must resend HELO after tls negotiation - $this->smtp->hello($hello); - } - if ($this->SMTPAuth) { - if (!$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->Realm, - $this->Workstation - ) - ) { - throw new phpmailerException($this->lang('authenticate')); - } - } - return true; - } catch (phpmailerException $exc) { - $lastexception = $exc; - //We must have connected, but then failed TLS or Auth, so close connection nicely - $this->smtp->quit(); - } - } - } - //If we get here, all connection attempts have failed, so close connection hard - $this->smtp->close(); - //As we've caught all exceptions, just report whatever the last one was - if ($this->exceptions and !is_null($lastexception)) { - throw $lastexception; - } - return false; - } - - /** - * Close the active SMTP session if one exists. - * @return void - */ - public function smtpClose() - { - if ($this->smtp !== null) { - if ($this->smtp->connected()) { - $this->smtp->quit(); - $this->smtp->close(); - } - } - } - - /** - * Set the language for error messages. - * Returns false if it cannot load the language file. - * The default language is English. - * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") - * @param string $lang_path Path to the language file directory, with trailing separator (slash) - * @return bool - * @access public - */ - public function setLanguage($langcode = 'en', $lang_path = '') - { - //Define full set of translatable strings in English - $PHPMAILER_LANG = array( - 'authenticate' => 'SMTP Error: Could not authenticate.', - 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', - 'data_not_accepted' => 'SMTP Error: data not accepted.', - 'empty_message' => 'Message body empty', - 'encoding' => 'Unknown encoding: ', - 'execute' => 'Could not execute: ', - 'file_access' => 'Could not access file: ', - 'file_open' => 'File Error: Could not open file: ', - 'from_failed' => 'The following From address failed: ', - 'instantiate' => 'Could not instantiate mail function.', - 'invalid_address' => 'Invalid address', - 'mailer_not_supported' => ' mailer is not supported.', - 'provide_address' => 'You must provide at least one recipient email address.', - 'recipients_failed' => 'SMTP Error: The following recipients failed: ', - 'signing' => 'Signing Error: ', - 'smtp_connect_failed' => 'SMTP connect() failed.', - 'smtp_error' => 'SMTP server error: ', - 'variable_set' => 'Cannot set or reset variable: ' - ); - if (empty($lang_path)) { - //Calculate an absolute path so it can work if CWD is not here - $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; - } - $foundlang = true; - $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; - if ($langcode != 'en') { //There is no English translation file - //Make sure language file path is readable - if (!is_readable($lang_file)) { - $foundlang = false; - } else { - //Overwrite language-specific strings. - //This way we'll never have missing translations. - $foundlang = include $lang_file; - } - } - $this->language = $PHPMAILER_LANG; - return ($foundlang == true); //Returns false if language not found - } - - /** - * Get the array of strings for the current language. - * @return array - */ - public function getTranslations() - { - return $this->language; - } - - /** - * Create recipient headers. - * @access public - * @param string $type - * @param array $addr An array of recipient, - * where each recipient is a 2-element indexed array with element 0 containing an address - * and element 1 containing a name, like: - * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) - * @return string - */ - public function addrAppend($type, $addr) - { - $addresses = array(); - foreach ($addr as $address) { - $addresses[] = $this->addrFormat($address); - } - return $type . ': ' . implode(', ', $addresses) . $this->LE; - } - - /** - * Format an address for use in a message header. - * @access public - * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name - * like array('joe@example.com', 'Joe User') - * @return string - */ - public function addrFormat($addr) - { - if (empty($addr[1])) { // No name provided - return $this->secureHeader($addr[0]); - } else { - return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( - $addr[0] - ) . '>'; - } - } - - /** - * Word-wrap message. - * For use with mailers that do not automatically perform wrapping - * and for quoted-printable encoded messages. - * Original written by philippe. - * @param string $message The message to wrap - * @param integer $length The line length to wrap to - * @param bool $qp_mode Whether to run in Quoted-Printable mode - * @access public - * @return string - */ - public function wrapText($message, $length, $qp_mode = false) - { - $soft_break = ($qp_mode) ? sprintf(' =%s', $this->LE) : $this->LE; - // If utf-8 encoding is used, we will need to make sure we don't - // split multibyte characters when we wrap - $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); - $lelen = strlen($this->LE); - $crlflen = strlen(self::CRLF); - - $message = $this->fixEOL($message); - if (substr($message, -$lelen) == $this->LE) { - $message = substr($message, 0, -$lelen); - } - - $line = explode($this->LE, $message); // Magic. We know fixEOL uses $LE - $message = ''; - for ($i = 0; $i < count($line); $i++) { - $line_part = explode(' ', $line[$i]); - $buf = ''; - for ($e = 0; $e < count($line_part); $e++) { - $word = $line_part[$e]; - if ($qp_mode and (strlen($word) > $length)) { - $space_left = $length - strlen($buf) - $crlflen; - if ($e != 0) { - if ($space_left > 20) { - $len = $space_left; - if ($is_utf8) { - $len = $this->utf8CharBoundary($word, $len); - } elseif (substr($word, $len - 1, 1) == '=') { - $len--; - } elseif (substr($word, $len - 2, 1) == '=') { - $len -= 2; - } - $part = substr($word, 0, $len); - $word = substr($word, $len); - $buf .= ' ' . $part; - $message .= $buf . sprintf('=%s', self::CRLF); - } else { - $message .= $buf . $soft_break; - } - $buf = ''; - } - while (strlen($word) > 0) { - if ($length <= 0) { - break; - } - $len = $length; - if ($is_utf8) { - $len = $this->utf8CharBoundary($word, $len); - } elseif (substr($word, $len - 1, 1) == '=') { - $len--; - } elseif (substr($word, $len - 2, 1) == '=') { - $len -= 2; - } - $part = substr($word, 0, $len); - $word = substr($word, $len); - - if (strlen($word) > 0) { - $message .= $part . sprintf('=%s', self::CRLF); - } else { - $buf = $part; - } - } - } else { - $buf_o = $buf; - $buf .= ($e == 0) ? $word : (' ' . $word); - - if (strlen($buf) > $length and $buf_o != '') { - $message .= $buf_o . $soft_break; - $buf = $word; - } - } - } - $message .= $buf . self::CRLF; - } - - return $message; - } - - /** - * Find the last character boundary prior to $maxLength in a utf-8 - * quoted (printable) encoded string. - * Original written by Colin Brown. - * @access public - * @param string $encodedText utf-8 QP text - * @param int $maxLength find last character boundary prior to this length - * @return int - */ - public function utf8CharBoundary($encodedText, $maxLength) - { - $foundSplitPos = false; - $lookBack = 3; - while (!$foundSplitPos) { - $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); - $encodedCharPos = strpos($lastChunk, '='); - if ($encodedCharPos !== false) { - // Found start of encoded character byte within $lookBack block. - // Check the encoded byte value (the 2 chars after the '=') - $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); - $dec = hexdec($hex); - if ($dec < 128) { // Single byte character. - // If the encoded char was found at pos 0, it will fit - // otherwise reduce maxLength to start of the encoded char - $maxLength = ($encodedCharPos == 0) ? $maxLength : - $maxLength - ($lookBack - $encodedCharPos); - $foundSplitPos = true; - } elseif ($dec >= 192) { // First byte of a multi byte character - // Reduce maxLength to split at start of character - $maxLength = $maxLength - ($lookBack - $encodedCharPos); - $foundSplitPos = true; - } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back - $lookBack += 3; - } - } else { - // No encoded character found - $foundSplitPos = true; - } - } - return $maxLength; - } - - /** - * Set the body wrapping. - * @access public - * @return void - */ - public function setWordWrap() - { - if ($this->WordWrap < 1) { - return; - } - - switch ($this->message_type) { - case 'alt': - case 'alt_inline': - case 'alt_attach': - case 'alt_inline_attach': - $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); - break; - default: - $this->Body = $this->wrapText($this->Body, $this->WordWrap); - break; - } - } - - /** - * Assemble message headers. - * @access public - * @return string The assembled headers - */ - public function createHeader() - { - $result = ''; - - // Set the boundaries - $uniq_id = md5(uniqid(time())); - $this->boundary[1] = 'b1_' . $uniq_id; - $this->boundary[2] = 'b2_' . $uniq_id; - $this->boundary[3] = 'b3_' . $uniq_id; - - if ($this->MessageDate == '') { - $this->MessageDate = self::rfcDate(); - } - $result .= $this->headerLine('Date', $this->MessageDate); - - - // To be created automatically by mail() - if ($this->SingleTo === true) { - if ($this->Mailer != 'mail') { - foreach ($this->to as $toaddr) { - $this->SingleToArray[] = $this->addrFormat($toaddr); - } - } - } else { - if (count($this->to) > 0) { - if ($this->Mailer != 'mail') { - $result .= $this->addrAppend('To', $this->to); - } - } elseif (count($this->cc) == 0) { - $result .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - } - - $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); - - // sendmail and mail() extract Cc from the header before sending - if (count($this->cc) > 0) { - $result .= $this->addrAppend('Cc', $this->cc); - } - - // sendmail and mail() extract Bcc from the header before sending - if (( - $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' - ) - and count($this->bcc) > 0 - ) { - $result .= $this->addrAppend('Bcc', $this->bcc); - } - - if (count($this->ReplyTo) > 0) { - $result .= $this->addrAppend('Reply-To', $this->ReplyTo); - } - - // mail() sets the subject itself - if ($this->Mailer != 'mail') { - $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); - } - - if ($this->MessageID != '') { - $this->lastMessageID = $this->MessageID; - } else { - $this->lastMessageID = sprintf('<%s@%s>', $uniq_id, $this->ServerHostname()); - } - $result .= $this->HeaderLine('Message-ID', $this->lastMessageID); - $result .= $this->headerLine('X-Priority', $this->Priority); - if ($this->XMailer == '') { - $result .= $this->headerLine( - 'X-Mailer', - 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer/)' - ); - } else { - $myXmailer = trim($this->XMailer); - if ($myXmailer) { - $result .= $this->headerLine('X-Mailer', $myXmailer); - } - } - - if ($this->ConfirmReadingTo != '') { - $result .= $this->headerLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); - } - - // Add custom headers - for ($index = 0; $index < count($this->CustomHeader); $index++) { - $result .= $this->headerLine( - trim($this->CustomHeader[$index][0]), - $this->encodeHeader(trim($this->CustomHeader[$index][1])) - ); - } - if (!$this->sign_key_file) { - $result .= $this->headerLine('MIME-Version', '1.0'); - $result .= $this->getMailMIME(); - } - - return $result; - } - - /** - * Get the message MIME type headers. - * @access public - * @return string - */ - public function getMailMIME() - { - $result = ''; - $ismultipart = true; - switch ($this->message_type) { - case 'inline': - $result .= $this->headerLine('Content-Type', 'multipart/related;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - case 'attach': - case 'inline_attach': - case 'alt_attach': - case 'alt_inline_attach': - $result .= $this->headerLine('Content-Type', 'multipart/mixed;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - case 'alt': - case 'alt_inline': - $result .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - default: - // Catches case 'plain': and case '': - $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); - $ismultipart = false; - break; - } - //RFC1341 part 5 says 7bit is assumed if not specified - if ($this->Encoding != '7bit') { - //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE - if ($ismultipart) { - if ($this->Encoding == '8bit') { - $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); - } - //The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible - } else { - $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); - } - } - - if ($this->Mailer != 'mail') { - $result .= $this->LE; - } - - return $result; - } - - /** - * Returns the whole MIME message. - * Includes complete headers and body. - * Only valid post preSend(). - * @see PHPMailer::preSend() - * @access public - * @return string - */ - public function getSentMIMEMessage() - { - return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody; - } - - - /** - * Assemble the message body. - * Returns an empty string on failure. - * @access public - * @throws phpmailerException - * @return string The assembled message body - */ - public function createBody() - { - $body = ''; - - if ($this->sign_key_file) { - $body .= $this->getMailMIME() . $this->LE; - } - - $this->setWordWrap(); - - $bodyEncoding = $this->Encoding; - $bodyCharSet = $this->CharSet; - if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { - $bodyEncoding = '7bit'; - $bodyCharSet = 'us-ascii'; - } - $altBodyEncoding = $this->Encoding; - $altBodyCharSet = $this->CharSet; - if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { - $altBodyEncoding = '7bit'; - $altBodyCharSet = 'us-ascii'; - } - switch ($this->message_type) { - case 'inline': - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[1]); - break; - case 'attach': - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'inline_attach': - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'alt': - $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - if (!empty($this->Ical)) { - $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); - $body .= $this->encodeString($this->Ical, $this->Encoding); - $body .= $this->LE . $this->LE; - } - $body .= $this->endBoundary($this->boundary[1]); - break; - case 'alt_inline': - $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[2]); - $body .= $this->LE; - $body .= $this->endBoundary($this->boundary[1]); - break; - case 'alt_attach': - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->endBoundary($this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'alt_inline_attach': - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->textLine('--' . $this->boundary[2]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[3]); - $body .= $this->LE; - $body .= $this->endBoundary($this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - default: - // catch case 'plain' and case '' - $body .= $this->encodeString($this->Body, $bodyEncoding); - break; - } - - if ($this->isError()) { - $body = ''; - } elseif ($this->sign_key_file) { - try { - if (!defined('PKCS7_TEXT')) { - throw new phpmailerException($this->lang('signing') . ' OpenSSL extension missing.'); - } - //TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 - $file = tempnam(sys_get_temp_dir(), 'mail'); - file_put_contents($file, $body); //TODO check this worked - $signed = tempnam(sys_get_temp_dir(), 'signed'); - if (@openssl_pkcs7_sign( - $file, - $signed, - 'file://' . realpath($this->sign_cert_file), - array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), - null - ) - ) { - @unlink($file); - $body = file_get_contents($signed); - @unlink($signed); - } else { - @unlink($file); - @unlink($signed); - throw new phpmailerException($this->lang('signing') . openssl_error_string()); - } - } catch (phpmailerException $exc) { - $body = ''; - if ($this->exceptions) { - throw $exc; - } - } - } - return $body; - } - - /** - * Return the start of a message boundary. - * @access protected - * @param string $boundary - * @param string $charSet - * @param string $contentType - * @param string $encoding - * @return string - */ - protected function getBoundary($boundary, $charSet, $contentType, $encoding) - { - $result = ''; - if ($charSet == '') { - $charSet = $this->CharSet; - } - if ($contentType == '') { - $contentType = $this->ContentType; - } - if ($encoding == '') { - $encoding = $this->Encoding; - } - $result .= $this->textLine('--' . $boundary); - $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); - $result .= $this->LE; - //RFC1341 part 5 says 7bit is assumed if not specified - if ($encoding != '7bit') { - $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); - } - $result .= $this->LE; - - return $result; - } - - /** - * Return the end of a message boundary. - * @access protected - * @param string $boundary - * @return string - */ - protected function endBoundary($boundary) - { - return $this->LE . '--' . $boundary . '--' . $this->LE; - } - - /** - * Set the message type. - * PHPMailer only supports some preset message types, - * not arbitrary MIME structures. - * @access protected - * @return void - */ - protected function setMessageType() - { - $this->message_type = array(); - if ($this->alternativeExists()) { - $this->message_type[] = 'alt'; - } - if ($this->inlineImageExists()) { - $this->message_type[] = 'inline'; - } - if ($this->attachmentExists()) { - $this->message_type[] = 'attach'; - } - $this->message_type = implode('_', $this->message_type); - if ($this->message_type == '') { - $this->message_type = 'plain'; - } - } - - /** - * Format a header line. - * @access public - * @param string $name - * @param string $value - * @return string - */ - public function headerLine($name, $value) - { - return $name . ': ' . $value . $this->LE; - } - - /** - * Return a formatted mail line. - * @access public - * @param string $value - * @return string - */ - public function textLine($value) - { - return $value . $this->LE; - } - - /** - * Add an attachment from a path on the filesystem. - * Returns false if the file could not be found or read. - * @param string $path Path to the attachment. - * @param string $name Overrides the attachment name. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File extension (MIME) type. - * @param string $disposition Disposition to use - * @throws phpmailerException - * @return bool - */ - public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') - { - try { - if (!@is_file($path)) { - throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); - } - - //If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($path); - } - - $filename = basename($path); - if ($name == '') { - $name = $filename; - } - - $this->attachment[] = array( - 0 => $path, - 1 => $filename, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => false, // isStringAttachment - 6 => $disposition, - 7 => 0 - ); - - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - return true; - } - - /** - * Return the array of attachments. - * @return array - */ - public function getAttachments() - { - return $this->attachment; - } - - /** - * Attach all file, string, and binary attachments to the message. - * Returns an empty string on failure. - * @access protected - * @param string $disposition_type - * @param string $boundary - * @return string - */ - protected function attachAll($disposition_type, $boundary) - { - // Return text of body - $mime = array(); - $cidUniq = array(); - $incl = array(); - - // Add all attachments - foreach ($this->attachment as $attachment) { - // Check if it is a valid disposition_filter - if ($attachment[6] == $disposition_type) { - // Check for string attachment - $string = ''; - $path = ''; - $bString = $attachment[5]; - if ($bString) { - $string = $attachment[0]; - } else { - $path = $attachment[0]; - } - - $inclhash = md5(serialize($attachment)); - if (in_array($inclhash, $incl)) { - continue; - } - $incl[] = $inclhash; - $name = $attachment[2]; - $encoding = $attachment[3]; - $type = $attachment[4]; - $disposition = $attachment[6]; - $cid = $attachment[7]; - if ($disposition == 'inline' && isset($cidUniq[$cid])) { - continue; - } - $cidUniq[$cid] = true; - - $mime[] = sprintf('--%s%s', $boundary, $this->LE); - $mime[] = sprintf( - 'Content-Type: %s; name="%s"%s', - $type, - $this->encodeHeader($this->secureHeader($name)), - $this->LE - ); - //RFC1341 part 5 says 7bit is assumed if not specified - if ($encoding != '7bit') { - $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); - } - - if ($disposition == 'inline') { - $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); - } - - // If a filename contains any of these chars, it should be quoted, - // but not otherwise: RFC2183 & RFC2045 5.1 - // Fixes a warning in IETF's msglint MIME checker - // Allow for bypassing the Content-Disposition header totally - if (!(empty($disposition))) { - if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $name)) { - $mime[] = sprintf( - 'Content-Disposition: %s; filename="%s"%s', - $disposition, - $this->encodeHeader($this->secureHeader($name)), - $this->LE . $this->LE - ); - } else { - $mime[] = sprintf( - 'Content-Disposition: %s; filename=%s%s', - $disposition, - $this->encodeHeader($this->secureHeader($name)), - $this->LE . $this->LE - ); - } - } else { - $mime[] = $this->LE; - } - - // Encode as string attachment - if ($bString) { - $mime[] = $this->encodeString($string, $encoding); - if ($this->isError()) { - return ''; - } - $mime[] = $this->LE . $this->LE; - } else { - $mime[] = $this->encodeFile($path, $encoding); - if ($this->isError()) { - return ''; - } - $mime[] = $this->LE . $this->LE; - } - } - } - - $mime[] = sprintf('--%s--%s', $boundary, $this->LE); - - return implode('', $mime); - } - - /** - * Encode a file attachment in requested format. - * Returns an empty string on failure. - * @param string $path The full path to the file - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' - * @throws phpmailerException - * @see EncodeFile(encodeFile - * @access protected - * @return string - */ - protected function encodeFile($path, $encoding = 'base64') - { - try { - if (!is_readable($path)) { - throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); - } - $magic_quotes = get_magic_quotes_runtime(); - if ($magic_quotes) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime(0); - } else { - ini_set('magic_quotes_runtime', 0); - } - } - $file_buffer = file_get_contents($path); - $file_buffer = $this->encodeString($file_buffer, $encoding); - if ($magic_quotes) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime($magic_quotes); - } else { - ini_set('magic_quotes_runtime', $magic_quotes); - } - } - return $file_buffer; - } catch (Exception $exc) { - $this->setError($exc->getMessage()); - return ''; - } - } - - /** - * Encode a string in requested format. - * Returns an empty string on failure. - * @param string $str The text to encode - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' - * @access public - * @return string - */ - public function encodeString($str, $encoding = 'base64') - { - $encoded = ''; - switch (strtolower($encoding)) { - case 'base64': - $encoded = chunk_split(base64_encode($str), 76, $this->LE); - break; - case '7bit': - case '8bit': - $encoded = $this->fixEOL($str); - //Make sure it ends with a line break - if (substr($encoded, -(strlen($this->LE))) != $this->LE) { - $encoded .= $this->LE; - } - break; - case 'binary': - $encoded = $str; - break; - case 'quoted-printable': - $encoded = $this->encodeQP($str); - break; - default: - $this->setError($this->lang('encoding') . $encoding); - break; - } - return $encoded; - } - - /** - * Encode a header string optimally. - * Picks shortest of Q, B, quoted-printable or none. - * @access public - * @param string $str - * @param string $position - * @return string - */ - public function encodeHeader($str, $position = 'text') - { - $matchcount = 0; - switch (strtolower($position)) { - case 'phrase': - if (!preg_match('/[\200-\377]/', $str)) { - // Can't use addslashes as we don't know the value of magic_quotes_sybase - $encoded = addcslashes($str, "\0..\37\177\\\""); - if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { - return ($encoded); - } else { - return ("\"$encoded\""); - } - } - $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); - break; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'comment': - $matchcount = preg_match_all('/[()"]/', $str, $matches); - // Intentional fall-through - case 'text': - default: - $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); - break; - } - - if ($matchcount == 0) { //There are no chars that need encoding - return ($str); - } - - $maxlen = 75 - 7 - strlen($this->CharSet); - // Try to select the encoding which should produce the shortest output - if ($matchcount > strlen($str) / 3) { - //More than a third of the content will need encoding, so B encoding will be most efficient - $encoding = 'B'; - if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { - // Use a custom function which correctly encodes and wraps long - // multibyte strings without breaking lines within a character - $encoded = $this->base64EncodeWrapMB($str, "\n"); - } else { - $encoded = base64_encode($str); - $maxlen -= $maxlen % 4; - $encoded = trim(chunk_split($encoded, $maxlen, "\n")); - } - } else { - $encoding = 'Q'; - $encoded = $this->encodeQ($str, $position); - $encoded = $this->wrapText($encoded, $maxlen, true); - $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); - } - - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); - $encoded = trim(str_replace("\n", $this->LE, $encoded)); - - return $encoded; - } - - /** - * Check if a string contains multi-byte characters. - * @access public - * @param string $str multi-byte text to wrap encode - * @return bool - */ - public function hasMultiBytes($str) - { - if (function_exists('mb_strlen')) { - return (strlen($str) > mb_strlen($str, $this->CharSet)); - } else { // Assume no multibytes (we can't handle without mbstring functions anyway) - return false; - } - } - - /** - * Does a string contain any 8-bit chars (in any charset)? - * @param string $text - * @return bool - */ - public function has8bitChars($text) - { - return (bool)preg_match('/[\x80-\xFF]/', $text); - } - - /** - * Encode and wrap long multibyte strings for mail headers - * without breaking lines within a character. - * Adapted from a function by paravoid - * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 - * @access public - * @param string $str multi-byte text to wrap encode - * @param string $linebreak string to use as linefeed/end-of-line - * @return string - */ - public function base64EncodeWrapMB($str, $linebreak = null) - { - $start = '=?' . $this->CharSet . '?B?'; - $end = '?='; - $encoded = ''; - if ($linebreak === null) { - $linebreak = $this->LE; - } - - $mb_length = mb_strlen($str, $this->CharSet); - // Each line must have length <= 75, including $start and $end - $length = 75 - strlen($start) - strlen($end); - // Average multi-byte ratio - $ratio = $mb_length / strlen($str); - // Base64 has a 4:3 ratio - $avgLength = floor($length * $ratio * .75); - - for ($i = 0; $i < $mb_length; $i += $offset) { - $lookBack = 0; - do { - $offset = $avgLength - $lookBack; - $chunk = mb_substr($str, $i, $offset, $this->CharSet); - $chunk = base64_encode($chunk); - $lookBack++; - } while (strlen($chunk) > $length); - $encoded .= $chunk . $linebreak; - } - - // Chomp the last linefeed - $encoded = substr($encoded, 0, -strlen($linebreak)); - return $encoded; - } - - /** - * Encode a string in quoted-printable format. - * According to RFC2045 section 6.7. - * @access public - * @param string $string The text to encode - * @param integer $line_max Number of chars allowed on a line before wrapping - * @return string - * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment - */ - public function encodeQP($string, $line_max = 76) - { - if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) - return $this->fixEOL(quoted_printable_encode($string)); - } - //Fall back to a pure PHP implementation - $string = str_replace( - array('%20', '%0D%0A.', '%0D%0A', '%'), - array(' ', "\r\n=2E", "\r\n", '='), - rawurlencode($string) - ); - $string = preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); - return $this->fixEOL($string); - } - - /** - * Backward compatibility wrapper for an old QP encoding function that was removed. - * @see PHPMailer::encodeQP() - * @access public - * @param string $string - * @param integer $line_max - * @param bool $space_conv - * @return string - * @deprecated Use encodeQP instead. - */ - public function encodeQPphp( - $string, - $line_max = 76, - /** @noinspection PhpUnusedParameterInspection */ $space_conv = false - ) { - return $this->encodeQP($string, $line_max); - } - - /** - * Encode a string using Q encoding. - * @link http://tools.ietf.org/html/rfc2047 - * @param string $str the text to encode - * @param string $position Where the text is going to be used, see the RFC for what that means - * @access public - * @return string - */ - public function encodeQ($str, $position = 'text') - { - //There should not be any EOL in the string - $pattern = ''; - $encoded = str_replace(array("\r", "\n"), '', $str); - switch (strtolower($position)) { - case 'phrase': - //RFC 2047 section 5.3 - $pattern = '^A-Za-z0-9!*+\/ -'; - break; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'comment': - //RFC 2047 section 5.2 - $pattern = '\(\)"'; - //intentional fall-through - //for this reason we build the $pattern without including delimiters and [] - case 'text': - default: - //RFC 2047 section 5.1 - //Replace every high ascii, control, =, ? and _ characters - $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; - break; - } - $matches = array(); - if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { - //If the string contains an '=', make sure it's the first thing we replace - //so as to avoid double-encoding - $eqkey = array_search('=', $matches[0]); - if ($eqkey !== false) { - unset($matches[0][$eqkey]); - array_unshift($matches[0], '='); - } - foreach (array_unique($matches[0]) as $char) { - $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); - } - } - //Replace every spaces to _ (more readable than =20) - return str_replace(' ', '_', $encoded); - } - - - /** - * Add a string or binary attachment (non-filesystem). - * This method can be used to attach ascii or binary data, - * such as a BLOB record from a database. - * @param string $string String attachment data. - * @param string $filename Name of the attachment. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File extension (MIME) type. - * @param string $disposition Disposition to use - * @return void - */ - public function addStringAttachment( - $string, - $filename, - $encoding = 'base64', - $type = '', - $disposition = 'attachment' - ) { - //If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($filename); - } - // Append to $attachment array - $this->attachment[] = array( - 0 => $string, - 1 => $filename, - 2 => basename($filename), - 3 => $encoding, - 4 => $type, - 5 => true, // isStringAttachment - 6 => $disposition, - 7 => 0 - ); - } - - /** - * Add an embedded (inline) attachment from a file. - * This can include images, sounds, and just about any other document type. - * These differ from 'regular' attachmants in that they are intended to be - * displayed inline with the message, not just attached for download. - * This is used in HTML messages that embed the images - * the HTML refers to using the $cid value. - * @param string $path Path to the attachment. - * @param string $cid Content ID of the attachment; Use this to reference - * the content when using an embedded image in HTML. - * @param string $name Overrides the attachment name. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File MIME type. - * @param string $disposition Disposition to use - * @return bool True on successfully adding an attachment - */ - public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') - { - if (!@is_file($path)) { - $this->setError($this->lang('file_access') . $path); - return false; - } - - //If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($path); - } - - $filename = basename($path); - if ($name == '') { - $name = $filename; - } - - // Append to $attachment array - $this->attachment[] = array( - 0 => $path, - 1 => $filename, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => false, // isStringAttachment - 6 => $disposition, - 7 => $cid - ); - return true; - } - - /** - * Add an embedded stringified attachment. - * This can include images, sounds, and just about any other document type. - * Be sure to set the $type to an image type for images: - * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. - * @param string $string The attachment binary data. - * @param string $cid Content ID of the attachment; Use this to reference - * the content when using an embedded image in HTML. - * @param string $name - * @param string $encoding File encoding (see $Encoding). - * @param string $type MIME type. - * @param string $disposition Disposition to use - * @return bool True on successfully adding an attachment - */ - public function addStringEmbeddedImage( - $string, - $cid, - $name = '', - $encoding = 'base64', - $type = '', - $disposition = 'inline' - ) { - //If a MIME type is not specified, try to work it out from the name - if ($type == '') { - $type = self::filenameToType($name); - } - - // Append to $attachment array - $this->attachment[] = array( - 0 => $string, - 1 => $name, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => true, // isStringAttachment - 6 => $disposition, - 7 => $cid - ); - return true; - } - - /** - * Check if an inline attachment is present. - * @access public - * @return bool - */ - public function inlineImageExists() - { - foreach ($this->attachment as $attachment) { - if ($attachment[6] == 'inline') { - return true; - } - } - return false; - } - - /** - * Check if an attachment (non-inline) is present. - * @return bool - */ - public function attachmentExists() - { - foreach ($this->attachment as $attachment) { - if ($attachment[6] == 'attachment') { - return true; - } - } - return false; - } - - /** - * Check if this message has an alternative body set. - * @return bool - */ - public function alternativeExists() - { - return !empty($this->AltBody); - } - - /** - * Clear all To recipients. - * @return void - */ - public function clearAddresses() - { - foreach ($this->to as $to) { - unset($this->all_recipients[strtolower($to[0])]); - } - $this->to = array(); - } - - /** - * Clear all CC recipients. - * @return void - */ - public function clearCCs() - { - foreach ($this->cc as $cc) { - unset($this->all_recipients[strtolower($cc[0])]); - } - $this->cc = array(); - } - - /** - * Clear all BCC recipients. - * @return void - */ - public function clearBCCs() - { - foreach ($this->bcc as $bcc) { - unset($this->all_recipients[strtolower($bcc[0])]); - } - $this->bcc = array(); - } - - /** - * Clear all ReplyTo recipients. - * @return void - */ - public function clearReplyTos() - { - $this->ReplyTo = array(); - } - - /** - * Clear all recipient types. - * @return void - */ - public function clearAllRecipients() - { - $this->to = array(); - $this->cc = array(); - $this->bcc = array(); - $this->all_recipients = array(); - } - - /** - * Clear all filesystem, string, and binary attachments. - * @return void - */ - public function clearAttachments() - { - $this->attachment = array(); - } - - /** - * Clear all custom headers. - * @return void - */ - public function clearCustomHeaders() - { - $this->CustomHeader = array(); - } - - /** - * Add an error message to the error container. - * @access protected - * @param string $msg - * @return void - */ - protected function setError($msg) - { - $this->error_count++; - if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { - $lasterror = $this->smtp->getError(); - if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { - $msg .= '

      ' . $this->lang('smtp_error') . $lasterror['smtp_msg'] . "

      \n"; - } - } - $this->ErrorInfo = $msg; - } - - /** - * Return an RFC 822 formatted date. - * @access public - * @return string - * @static - */ - public static function rfcDate() - { - //Set the time zone to whatever the default is to avoid 500 errors - //Will default to UTC if it's not set properly in php.ini - date_default_timezone_set(@date_default_timezone_get()); - return date('D, j M Y H:i:s O'); - } - - /** - * Get the server hostname. - * Returns 'localhost.localdomain' if unknown. - * @access protected - * @return string - */ - protected function serverHostname() - { - $result = 'localhost.localdomain'; - if (!empty($this->Hostname)) { - $result = $this->Hostname; - } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { - $result = $_SERVER['SERVER_NAME']; - } elseif (function_exists('gethostname') && gethostname() !== false) { - $result = gethostname(); - } elseif (php_uname('n') !== false) { - $result = php_uname('n'); - } - return $result; - } - - /** - * Get an error message in the current language. - * @access protected - * @param string $key - * @return string - */ - protected function lang($key) - { - if (count($this->language) < 1) { - $this->setLanguage('en'); // set the default language - } - - if (isset($this->language[$key])) { - return $this->language[$key]; - } else { - return 'Language string failed to load: ' . $key; - } - } - - /** - * Check if an error occurred. - * @access public - * @return bool True if an error did occur. - */ - public function isError() - { - return ($this->error_count > 0); - } - - /** - * Ensure consistent line endings in a string. - * Changes every end of line from CRLF, CR or LF to $this->LE. - * @access public - * @param string $str String to fixEOL - * @return string - */ - public function fixEOL($str) - { - // Normalise to \n - $nstr = str_replace(array("\r\n", "\r"), "\n", $str); - // Now convert LE as needed - if ($this->LE !== "\n") { - $nstr = str_replace("\n", $this->LE, $nstr); - } - return $nstr; - } - - /** - * Add a custom header. - * $name value can be overloaded to contain - * both header name and value (name:value) - * @access public - * @param string $name Custom header name - * @param string $value Header value - * @return void - */ - public function addCustomHeader($name, $value = null) - { - if ($value === null) { - // Value passed in as name:value - $this->CustomHeader[] = explode(':', $name, 2); - } else { - $this->CustomHeader[] = array($name, $value); - } - } - - /** - * Create a message from an HTML string. - * Automatically makes modifications for inline images and backgrounds - * and creates a plain-text version by converting the HTML. - * Overwrites any existing values in $this->Body and $this->AltBody - * @access public - * @param string $message HTML message string - * @param string $basedir baseline directory for path - * @param bool $advanced Whether to use the advanced HTML to text converter - * @return string $message - */ - public function msgHTML($message, $basedir = '', $advanced = false) - { - preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); - if (isset($images[2])) { - foreach ($images[2] as $imgindex => $url) { - // do not change urls for absolute images (thanks to corvuscorax) - if (!preg_match('#^[A-z]+://#', $url)) { - $filename = basename($url); - $directory = dirname($url); - if ($directory == '.') { - $directory = ''; - } - $cid = md5($url) . '@phpmailer.0'; //RFC2392 S 2 - if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { - $basedir .= '/'; - } - if (strlen($directory) > 1 && substr($directory, -1) != '/') { - $directory .= '/'; - } - if ($this->addEmbeddedImage( - $basedir . $directory . $filename, - $cid, - $filename, - 'base64', - self::_mime_types(self::mb_pathinfo($filename, PATHINFO_EXTENSION)) - ) - ) { - $message = preg_replace( - '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); - } - } - } - } - $this->isHTML(true); - //Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better - $this->Body = $this->normalizeBreaks($message); - $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); - if (empty($this->AltBody)) { - $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . - self::CRLF . self::CRLF; - } - return $this->Body; - } - - /** - * Convert an HTML string into plain text. - * @param string $html The HTML text to convert - * @param bool $advanced Should this use the more complex html2text converter or just a simple one? - * @return string - */ - public function html2text($html, $advanced = false) - { - if ($advanced) { - require_once 'extras/class.html2text.php'; - $htmlconverter = new html2text($html); - return $htmlconverter->get_text(); - } - return html_entity_decode( - trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), - ENT_QUOTES, - $this->CharSet - ); - } - - /** - * Get the MIME type for a file extension. - * @param string $ext File extension - * @access public - * @return string MIME type of file. - * @static - */ - public static function _mime_types($ext = '') - { - $mimes = array( - 'xl' => 'application/excel', - 'hqx' => 'application/mac-binhex40', - 'cpt' => 'application/mac-compactpro', - 'bin' => 'application/macbinary', - 'doc' => 'application/msword', - 'word' => 'application/msword', - 'class' => 'application/octet-stream', - 'dll' => 'application/octet-stream', - 'dms' => 'application/octet-stream', - 'exe' => 'application/octet-stream', - 'lha' => 'application/octet-stream', - 'lzh' => 'application/octet-stream', - 'psd' => 'application/octet-stream', - 'sea' => 'application/octet-stream', - 'so' => 'application/octet-stream', - 'oda' => 'application/oda', - 'pdf' => 'application/pdf', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'mif' => 'application/vnd.mif', - 'xls' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - 'wbxml' => 'application/vnd.wap.wbxml', - 'wmlc' => 'application/vnd.wap.wmlc', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'dvi' => 'application/x-dvi', - 'gtar' => 'application/x-gtar', - 'php3' => 'application/x-httpd-php', - 'php4' => 'application/x-httpd-php', - 'php' => 'application/x-httpd-php', - 'phtml' => 'application/x-httpd-php', - 'phps' => 'application/x-httpd-php-source', - 'js' => 'application/x-javascript', - 'swf' => 'application/x-shockwave-flash', - 'sit' => 'application/x-stuffit', - 'tar' => 'application/x-tar', - 'tgz' => 'application/x-tar', - 'xht' => 'application/xhtml+xml', - 'xhtml' => 'application/xhtml+xml', - 'zip' => 'application/zip', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'mpga' => 'audio/mpeg', - 'aif' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'ram' => 'audio/x-pn-realaudio', - 'rm' => 'audio/x-pn-realaudio', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'ra' => 'audio/x-realaudio', - 'wav' => 'audio/x-wav', - 'bmp' => 'image/bmp', - 'gif' => 'image/gif', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'png' => 'image/png', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'eml' => 'message/rfc822', - 'css' => 'text/css', - 'html' => 'text/html', - 'htm' => 'text/html', - 'shtml' => 'text/html', - 'log' => 'text/plain', - 'text' => 'text/plain', - 'txt' => 'text/plain', - 'rtx' => 'text/richtext', - 'rtf' => 'text/rtf', - 'vcf' => 'text/vcard', - 'vcard' => 'text/vcard', - 'xml' => 'text/xml', - 'xsl' => 'text/xml', - 'mpeg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mov' => 'video/quicktime', - 'qt' => 'video/quicktime', - 'rv' => 'video/vnd.rn-realvideo', - 'avi' => 'video/x-msvideo', - 'movie' => 'video/x-sgi-movie' - ); - return (array_key_exists(strtolower($ext), $mimes) ? $mimes[strtolower($ext)]: 'application/octet-stream'); - } - - /** - * Map a file name to a MIME type. - * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. - * @param string $filename A file name or full path, does not need to exist as a file - * @return string - * @static - */ - public static function filenameToType($filename) - { - //In case the path is a URL, strip any query string before getting extension - $qpos = strpos($filename, '?'); - if ($qpos !== false) { - $filename = substr($filename, 0, $qpos); - } - $pathinfo = self::mb_pathinfo($filename); - return self::_mime_types($pathinfo['extension']); - } - - /** - * Multi-byte-safe pathinfo replacement. - * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. - * Works similarly to the one in PHP >= 5.2.0 - * @link http://www.php.net/manual/en/function.pathinfo.php#107461 - * @param string $path A filename or path, does not need to exist as a file - * @param integer|string $options Either a PATHINFO_* constant, - * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 - * @return string|array - * @static - */ - public static function mb_pathinfo($path, $options = null) - { - $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); - $pathinfo = array(); - preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo); - if (array_key_exists(1, $pathinfo)) { - $ret['dirname'] = $pathinfo[1]; - } - if (array_key_exists(2, $pathinfo)) { - $ret['basename'] = $pathinfo[2]; - } - if (array_key_exists(5, $pathinfo)) { - $ret['extension'] = $pathinfo[5]; - } - if (array_key_exists(3, $pathinfo)) { - $ret['filename'] = $pathinfo[3]; - } - switch ($options) { - case PATHINFO_DIRNAME: - case 'dirname': - return $ret['dirname']; - break; - case PATHINFO_BASENAME: - case 'basename': - return $ret['basename']; - break; - case PATHINFO_EXTENSION: - case 'extension': - return $ret['extension']; - break; - case PATHINFO_FILENAME: - case 'filename': - return $ret['filename']; - break; - default: - return $ret; - } - } - - /** - * Set or reset instance properties. - * - * Usage Example: - * $page->set('X-Priority', '3'); - * - * @access public - * @param string $name - * @param mixed $value - * NOTE: will not work with arrays, there are no arrays to set/reset - * @throws phpmailerException - * @return bool - * @todo Should this not be using __set() magic function? - */ - public function set($name, $value = '') - { - try { - if (isset($this->$name)) { - $this->$name = $value; - } else { - throw new phpmailerException($this->lang('variable_set') . $name, self::STOP_CRITICAL); - } - } catch (Exception $exc) { - $this->setError($exc->getMessage()); - if ($exc->getCode() == self::STOP_CRITICAL) { - return false; - } - } - return true; - } - - /** - * Strip newlines to prevent header injection. - * @access public - * @param string $str - * @return string - */ - public function secureHeader($str) - { - return trim(str_replace(array("\r", "\n"), '', $str)); - } - - /** - * Normalize line breaks in a string. - * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. - * Defaults to CRLF (for message bodies) and preserves consecutive breaks. - * @param string $text - * @param string $breaktype What kind of line break to use, defaults to CRLF - * @return string - * @access public - * @static - */ - public static function normalizeBreaks($text, $breaktype = "\r\n") - { - return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); - } - - - /** - * Set the public and private key files and password for S/MIME signing. - * @access public - * @param string $cert_filename - * @param string $key_filename - * @param string $key_pass Password for private key - */ - public function sign($cert_filename, $key_filename, $key_pass) - { - $this->sign_cert_file = $cert_filename; - $this->sign_key_file = $key_filename; - $this->sign_key_pass = $key_pass; - } - - /** - * Quoted-Printable-encode a DKIM header. - * @access public - * @param string $txt - * @return string - */ - public function DKIM_QP($txt) - { - $line = ''; - for ($i = 0; $i < strlen($txt); $i++) { - $ord = ord($txt[$i]); - if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { - $line .= $txt[$i]; - } else { - $line .= '=' . sprintf('%02X', $ord); - } - } - return $line; - } - - /** - * Generate a DKIM signature. - * @access public - * @param string $signheader Header - * @throws phpmailerException - * @return string - */ - public function DKIM_Sign($signheader) - { - if (!defined('PKCS7_TEXT')) { - if ($this->exceptions) { - throw new phpmailerException($this->lang('signing') . ' OpenSSL extension missing.'); - } - return ''; - } - $privKeyStr = file_get_contents($this->DKIM_private); - if ($this->DKIM_passphrase != '') { - $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); - } else { - $privKey = $privKeyStr; - } - if (openssl_sign($signheader, $signature, $privKey)) { - return base64_encode($signature); - } - return ''; - } - - /** - * Generate a DKIM canonicalization header. - * @access public - * @param string $signheader Header - * @return string - */ - public function DKIM_HeaderC($signheader) - { - $signheader = preg_replace('/\r\n\s+/', ' ', $signheader); - $lines = explode("\r\n", $signheader); - foreach ($lines as $key => $line) { - list($heading, $value) = explode(':', $line, 2); - $heading = strtolower($heading); - $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces - $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value - } - $signheader = implode("\r\n", $lines); - return $signheader; - } - - /** - * Generate a DKIM canonicalization body. - * @access public - * @param string $body Message Body - * @return string - */ - public function DKIM_BodyC($body) - { - if ($body == '') { - return "\r\n"; - } - // stabilize line endings - $body = str_replace("\r\n", "\n", $body); - $body = str_replace("\n", "\r\n", $body); - // END stabilize line endings - while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { - $body = substr($body, 0, strlen($body) - 2); - } - return $body; - } - - /** - * Create the DKIM header and body in a new message header. - * @access public - * @param string $headers_line Header lines - * @param string $subject Subject - * @param string $body Body - * @return string - */ - public function DKIM_Add($headers_line, $subject, $body) - { - $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms - $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body - $DKIMquery = 'dns/txt'; // Query method - $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) - $subject_header = "Subject: $subject"; - $headers = explode($this->LE, $headers_line); - $from_header = ''; - $to_header = ''; - $current = ''; - foreach ($headers as $header) { - if (strpos($header, 'From:') === 0) { - $from_header = $header; - $current = 'from_header'; - } elseif (strpos($header, 'To:') === 0) { - $to_header = $header; - $current = 'to_header'; - } else { - if ($current && strpos($header, ' =?') === 0) { - $current .= $header; - } else { - $current = ''; - } - } - } - $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); - $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); - $subject = str_replace( - '|', - '=7C', - $this->DKIM_QP($subject_header) - ); // Copied header fields (dkim-quoted-printable) - $body = $this->DKIM_BodyC($body); - $DKIMlen = strlen($body); // Length of body - $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body - $ident = ($this->DKIM_identity == '') ? '' : ' i=' . $this->DKIM_identity . ';'; - $dkimhdrs = 'DKIM-Signature: v=1; a=' . - $DKIMsignatureType . '; q=' . - $DKIMquery . '; l=' . - $DKIMlen . '; s=' . - $this->DKIM_selector . - ";\r\n" . - "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . - "\th=From:To:Subject;\r\n" . - "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . - "\tz=$from\r\n" . - "\t|$to\r\n" . - "\t|$subject;\r\n" . - "\tbh=" . $DKIMb64 . ";\r\n" . - "\tb="; - $toSign = $this->DKIM_HeaderC( - $from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs - ); - $signed = $this->DKIM_Sign($toSign); - return $dkimhdrs . $signed . "\r\n"; - } - - /** - * Allows for public read access to 'to' property. - * @access public - * @return array - */ - public function getToAddresses() - { - return $this->to; - } - - /** - * Allows for public read access to 'cc' property. - * @access public - * @return array - */ - public function getCcAddresses() - { - return $this->cc; - } - - /** - * Allows for public read access to 'bcc' property. - * @access public - * @return array - */ - public function getBccAddresses() - { - return $this->bcc; - } - - /** - * Allows for public read access to 'ReplyTo' property. - * @access public - * @return array - */ - public function getReplyToAddresses() - { - return $this->ReplyTo; - } - - /** - * Allows for public read access to 'all_recipients' property. - * @access public - * @return array - */ - public function getAllRecipientAddresses() - { - return $this->all_recipients; - } - - /** - * Perform a callback. - * @param bool $isSent - * @param string $to - * @param string $cc - * @param string $bcc - * @param string $subject - * @param string $body - * @param string $from - */ - protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from = null) - { - if (!empty($this->action_function) && is_callable($this->action_function)) { - $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); - call_user_func_array($this->action_function, $params); - } - } -} - -/** - * PHPMailer exception handler - * @package PHPMailer - */ -class phpmailerException extends Exception -{ - /** - * Prettify error message output - * @return string - */ - public function errorMessage() - { - $errorMsg = '' . $this->getMessage() . "
      \n"; - return $errorMsg; - } -} +// This file was removed for security purposes. \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.pop3.php b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.pop3.php deleted file mode 100644 index 71dffac2..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.pop3.php +++ /dev/null @@ -1,417 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailer POP-Before-SMTP Authentication Class. - * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. - * Does not support APOP. - * @package PHPMailer - * @author Richard Davey (original author) - * @author Marcus Bointon (Synchro/coolbru) - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - */ -class POP3 -{ - /** - * The POP3 PHPMailer Version number. - * @type string - * @access public - */ - public $Version = '5.2.8'; - - /** - * Default POP3 port number. - * @type int - * @access public - */ - public $POP3_PORT = 110; - - /** - * Default timeout in seconds. - * @type int - * @access public - */ - public $POP3_TIMEOUT = 30; - - /** - * POP3 Carriage Return + Line Feed. - * @type string - * @access public - * @deprecated Use the constant instead - */ - public $CRLF = "\r\n"; - - /** - * Debug display level. - * Options: 0 = no, 1+ = yes - * @type int - * @access public - */ - public $do_debug = 0; - - /** - * POP3 mail server hostname. - * @type string - * @access public - */ - public $host; - - /** - * POP3 port number. - * @type int - * @access public - */ - public $port; - - /** - * POP3 Timeout Value in seconds. - * @type int - * @access public - */ - public $tval; - - /** - * POP3 username - * @type string - * @access public - */ - public $username; - - /** - * POP3 password. - * @type string - * @access public - */ - public $password; - - /** - * Resource handle for the POP3 connection socket. - * @type resource - * @access private - */ - private $pop_conn; - - /** - * Are we connected? - * @type bool - * @access private - */ - private $connected; - - /** - * Error container. - * @type array - * @access private - */ - private $error; - - /** - * Line break constant - */ - const CRLF = "\r\n"; - - /** - * Constructor. - * @access public - */ - public function __construct() - { - $this->pop_conn = 0; - $this->connected = false; - $this->error = null; - } - - /** - * Simple static wrapper for all-in-one POP before SMTP - * @param $host - * @param bool $port - * @param bool $tval - * @param string $username - * @param string $password - * @param int $debug_level - * @return bool - */ - public static function popBeforeSmtp( - $host, - $port = false, - $tval = false, - $username = '', - $password = '', - $debug_level = 0 - ) { - $pop = new POP3; - return $pop->authorise($host, $port, $tval, $username, $password, $debug_level); - } - - /** - * Authenticate with a POP3 server. - * A connect, login, disconnect sequence - * appropriate for POP-before SMTP authorisation. - * @access public - * @param string $host - * @param bool|int $port - * @param bool|int $tval - * @param string $username - * @param string $password - * @param int $debug_level - * @return bool - */ - public function authorise($host, $port = false, $tval = false, $username = '', $password = '', $debug_level = 0) - { - $this->host = $host; - // If no port value provided, use default - if ($port === false) { - $this->port = $this->POP3_PORT; - } else { - $this->port = $port; - } - // If no timeout value provided, use default - if ($tval === false) { - $this->tval = $this->POP3_TIMEOUT; - } else { - $this->tval = $tval; - } - $this->do_debug = $debug_level; - $this->username = $username; - $this->password = $password; - // Refresh the error log - $this->error = null; - // connect - $result = $this->connect($this->host, $this->port, $this->tval); - if ($result) { - $login_result = $this->login($this->username, $this->password); - if ($login_result) { - $this->disconnect(); - return true; - } - } - // We need to disconnect regardless of whether the login succeeded - $this->disconnect(); - return false; - } - - /** - * Connect to a POP3 server. - * @access public - * @param string $host - * @param bool|int $port - * @param integer $tval - * @return boolean - */ - public function connect($host, $port = false, $tval = 30) - { - // Are we already connected? - if ($this->connected) { - return true; - } - - //On Windows this will raise a PHP Warning error if the hostname doesn't exist. - //Rather than suppress it with @fsockopen, capture it cleanly instead - set_error_handler(array($this, 'catchWarning')); - - // connect to the POP3 server - $this->pop_conn = fsockopen( - $host, // POP3 Host - $port, // Port # - $errno, // Error Number - $errstr, // Error Message - $tval - ); // Timeout (seconds) - // Restore the error handler - restore_error_handler(); - // Does the Error Log now contain anything? - if ($this->error && $this->do_debug >= 1) { - $this->displayErrors(); - } - // Did we connect? - if ($this->pop_conn == false) { - // It would appear not... - $this->error = array( - 'error' => "Failed to connect to server $host on port $port", - 'errno' => $errno, - 'errstr' => $errstr - ); - if ($this->do_debug >= 1) { - $this->displayErrors(); - } - return false; - } - - // Increase the stream time-out - // Check for PHP 4.3.0 or later - if (version_compare(phpversion(), '5.0.0', 'ge')) { - stream_set_timeout($this->pop_conn, $tval, 0); - } else { - // Does not work on Windows - if (substr(PHP_OS, 0, 3) !== 'WIN') { - socket_set_timeout($this->pop_conn, $tval, 0); - } - } - - // Get the POP3 server response - $pop3_response = $this->getResponse(); - // Check for the +OK - if ($this->checkResponse($pop3_response)) { - // The connection is established and the POP3 server is talking - $this->connected = true; - return true; - } - return false; - } - - /** - * Log in to the POP3 server. - * Does not support APOP (RFC 2828, 4949). - * @access public - * @param string $username - * @param string $password - * @return boolean - */ - public function login($username = '', $password = '') - { - if ($this->connected == false) { - $this->error = 'Not connected to POP3 server'; - - if ($this->do_debug >= 1) { - $this->displayErrors(); - } - } - if (empty($username)) { - $username = $this->username; - } - if (empty($password)) { - $password = $this->password; - } - - // Send the Username - $this->sendString("USER $username" . self::CRLF); - $pop3_response = $this->getResponse(); - if ($this->checkResponse($pop3_response)) { - // Send the Password - $this->sendString("PASS $password" . self::CRLF); - $pop3_response = $this->getResponse(); - if ($this->checkResponse($pop3_response)) { - return true; - } - } - return false; - } - - /** - * Disconnect from the POP3 server. - * @access public - */ - public function disconnect() - { - $this->sendString('QUIT'); - //The QUIT command may cause the daemon to exit, which will kill our connection - //So ignore errors here - @fclose($this->pop_conn); - } - - /** - * Get a response from the POP3 server. - * $size is the maximum number of bytes to retrieve - * @param integer $size - * @return string - * @access private - */ - private function getResponse($size = 128) - { - $response = fgets($this->pop_conn, $size); - if ($this->do_debug >= 1) { - echo "Server -> Client: $response"; - } - return $response; - } - - /** - * Send raw data to the POP3 server. - * @param string $string - * @return integer - * @access private - */ - private function sendString($string) - { - if ($this->pop_conn) { - if ($this->do_debug >= 2) { //Show client messages when debug >= 2 - echo "Client -> Server: $string"; - } - return fwrite($this->pop_conn, $string, strlen($string)); - } - return 0; - } - - /** - * Checks the POP3 server response. - * Looks for for +OK or -ERR. - * @param string $string - * @return boolean - * @access private - */ - private function checkResponse($string) - { - if (substr($string, 0, 3) !== '+OK') { - $this->error = array( - 'error' => "Server reported an error: $string", - 'errno' => 0, - 'errstr' => '' - ); - if ($this->do_debug >= 1) { - $this->displayErrors(); - } - return false; - } else { - return true; - } - } - - /** - * Display errors if debug is enabled. - * @access private - */ - private function displayErrors() - { - echo '
      ';
      -        foreach ($this->error as $single_error) {
      -            print_r($single_error);
      -        }
      -        echo '
      '; - } - - /** - * POP3 connection error handler. - * @param integer $errno - * @param string $errstr - * @param string $errfile - * @param integer $errline - * @access private - */ - private function catchWarning($errno, $errstr, $errfile, $errline) - { - $this->error[] = array( - 'error' => "Connecting to the POP3 server raised a PHP warning: ", - 'errno' => $errno, - 'errstr' => $errstr, - 'errfile' => $errfile, - 'errline' => $errline - ); - } -} diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.smtp.php b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.smtp.php deleted file mode 100644 index ec75ca54..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/class.smtp.php +++ /dev/null @@ -1,935 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailer RFC821 SMTP email transport class. - * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. - * @package PHPMailer - * @author Chris Ryan - * @author Marcus Bointon - */ -class SMTP -{ - /** - * The PHPMailer SMTP version number. - * @type string - */ - const VERSION = '5.2.8'; - - /** - * SMTP line break constant. - * @type string - */ - const CRLF = "\r\n"; - - /** - * The SMTP port to use if one is not specified. - * @type int - */ - const DEFAULT_SMTP_PORT = 25; - - /** - * The maximum line length allowed by RFC 2822 section 2.1.1 - * @type int - */ - const MAX_LINE_LENGTH = 998; - - /** - * The PHPMailer SMTP Version number. - * @type string - * @deprecated Use the constant instead - * @see SMTP::VERSION - */ - public $Version = '5.2.8'; - - /** - * SMTP server port number. - * @type int - * @deprecated This is only ever used as a default value, so use the constant instead - * @see SMTP::DEFAULT_SMTP_PORT - */ - public $SMTP_PORT = 25; - - /** - * SMTP reply line ending. - * @type string - * @deprecated Use the constant instead - * @see SMTP::CRLF - */ - public $CRLF = "\r\n"; - - /** - * Debug output level. - * Options: - * * `0` No output - * * `1` Commands - * * `2` Data and commands - * * `3` As 2 plus connection status - * * `4` Low-level data output - * @type int - */ - public $do_debug = 0; - - /** - * How to handle debug output. - * Options: - * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to
      , appropriate for browser output - * * `error_log` Output to error log as configured in php.ini - * @type string - */ - public $Debugoutput = 'echo'; - - /** - * Whether to use VERP. - * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path - * @link http://www.postfix.org/VERP_README.html Info on VERP - * @type bool - */ - public $do_verp = false; - - /** - * The timeout value for connection, in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. - * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 - * @type int - */ - public $Timeout = 300; - - /** - * The SMTP timelimit value for reads, in seconds. - * @type int - */ - public $Timelimit = 30; - - /** - * The socket for the server connection. - * @type resource - */ - protected $smtp_conn; - - /** - * Error message, if any, for the last call. - * @type string - */ - protected $error = ''; - - /** - * The reply the server sent to us for HELO. - * @type string - */ - protected $helo_rply = ''; - - /** - * The most recent reply received from the server. - * @type string - */ - protected $last_reply = ''; - - /** - * Constructor. - * @access public - */ - public function __construct() - { - $this->smtp_conn = 0; - $this->error = null; - $this->helo_rply = null; - $this->do_debug = 0; - } - - /** - * Output debugging info via a user-selected method. - * @param string $str Debug string to output - * @return void - */ - protected function edebug($str) - { - switch ($this->Debugoutput) { - case 'error_log': - //Don't output, just log - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking, HTML-safe output - echo htmlentities( - preg_replace('/[\r\n]+/', '', $str), - ENT_QUOTES, - 'UTF-8' - ) - . "
      \n"; - break; - case 'echo': - default: - echo gmdate('Y-m-d H:i:s')."\t".trim($str)."\n"; - } - } - - /** - * Connect to an SMTP server. - * @param string $host SMTP server IP or host name - * @param int $port The port number to connect to - * @param int $timeout How long to wait for the connection to open - * @param array $options An array of options for stream_context_create() - * @access public - * @return bool - */ - public function connect($host, $port = null, $timeout = 30, $options = array()) - { - // Clear errors to avoid confusion - $this->error = null; - // Make sure we are __not__ connected - if ($this->connected()) { - // Already connected, generate error - $this->error = array('error' => 'Already connected to a server'); - return false; - } - if (empty($port)) { - $port = self::DEFAULT_SMTP_PORT; - } - // Connect to the SMTP server - if ($this->do_debug >= 3) { - $this->edebug('Connection: opening'); - } - $errno = 0; - $errstr = ''; - $socket_context = stream_context_create($options); - //Suppress errors; connection failures are handled at a higher level - $this->smtp_conn = @stream_socket_client( - $host . ":" . $port, - $errno, - $errstr, - $timeout, - STREAM_CLIENT_CONNECT, - $socket_context - ); - // Verify we connected properly - if (empty($this->smtp_conn)) { - $this->error = array( - 'error' => 'Failed to connect to server', - 'errno' => $errno, - 'errstr' => $errstr - ); - if ($this->do_debug >= 1) { - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] - . ": $errstr ($errno)" - ); - } - return false; - } - if ($this->do_debug >= 3) { - $this->edebug('Connection: opened'); - } - // SMTP server can take longer to respond, give longer timeout for first read - // Windows does not have support for this timeout function - if (substr(PHP_OS, 0, 3) != 'WIN') { - $max = ini_get('max_execution_time'); - if ($max != 0 && $timeout > $max) { // Don't bother if unlimited - @set_time_limit($timeout); - } - stream_set_timeout($this->smtp_conn, $timeout, 0); - } - // Get any announcement - $announce = $this->get_lines(); - if ($this->do_debug >= 2) { - $this->edebug('SERVER -> CLIENT: ' . $announce); - } - return true; - } - - /** - * Initiate a TLS (encrypted) session. - * @access public - * @return bool - */ - public function startTLS() - { - if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { - return false; - } - // Begin encrypted connection - if (!stream_socket_enable_crypto( - $this->smtp_conn, - true, - STREAM_CRYPTO_METHOD_TLS_CLIENT - )) { - return false; - } - return true; - } - - /** - * Perform SMTP authentication. - * Must be run after hello(). - * @see hello() - * @param string $username The user name - * @param string $password The password - * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5) - * @param string $realm The auth realm for NTLM - * @param string $workstation The auth workstation for NTLM - * @access public - * @return bool True if successfully authenticated. - */ - public function authenticate( - $username, - $password, - $authtype = 'LOGIN', - $realm = '', - $workstation = '' - ) { - if (empty($authtype)) { - $authtype = 'LOGIN'; - } - switch ($authtype) { - case 'PLAIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { - return false; - } - // Send encoded username and password - if (!$this->sendCommand( - 'User & Password', - base64_encode("\0" . $username . "\0" . $password), - 235 - ) - ) { - return false; - } - break; - case 'LOGIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { - return false; - } - if (!$this->sendCommand("Username", base64_encode($username), 334)) { - return false; - } - if (!$this->sendCommand("Password", base64_encode($password), 235)) { - return false; - } - break; - case 'NTLM': - /* - * ntlm_sasl_client.php - * Bundled with Permission - * - * How to telnet in windows: - * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx - * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication - */ - require_once 'extras/ntlm_sasl_client.php'; - $temp = new stdClass(); - $ntlm_client = new ntlm_sasl_client_class; - //Check that functions are available - if (!$ntlm_client->Initialize($temp)) { - $this->error = array('error' => $temp->error); - if ($this->do_debug >= 1) { - $this->edebug( - 'You need to enable some modules in your php.ini file: ' - . $this->error['error'] - ); - } - return false; - } - //msg1 - $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1 - - if (!$this->sendCommand( - 'AUTH NTLM', - 'AUTH NTLM ' . base64_encode($msg1), - 334 - ) - ) { - return false; - } - //Though 0 based, there is a white space after the 3 digit number - //msg2 - $challenge = substr($this->last_reply, 3); - $challenge = base64_decode($challenge); - $ntlm_res = $ntlm_client->NTLMResponse( - substr($challenge, 24, 8), - $password - ); - //msg3 - $msg3 = $ntlm_client->TypeMsg3( - $ntlm_res, - $username, - $realm, - $workstation - ); - // send encoded username - return $this->sendCommand('Username', base64_encode($msg3), 235); - break; - case 'CRAM-MD5': - // Start authentication - if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { - return false; - } - // Get the challenge - $challenge = base64_decode(substr($this->last_reply, 4)); - - // Build the response - $response = $username . ' ' . $this->hmac($challenge, $password); - - // send encoded credentials - return $this->sendCommand('Username', base64_encode($response), 235); - break; - } - return true; - } - - /** - * Calculate an MD5 HMAC hash. - * Works like hash_hmac('md5', $data, $key) - * in case that function is not available - * @param string $data The data to hash - * @param string $key The key to hash with - * @access protected - * @return string - */ - protected function hmac($data, $key) - { - if (function_exists('hash_hmac')) { - return hash_hmac('md5', $data, $key); - } - - // The following borrowed from - // http://php.net/manual/en/function.mhash.php#27225 - - // RFC 2104 HMAC implementation for php. - // Creates an md5 HMAC. - // Eliminates the need to install mhash to compute a HMAC - // Hacked by Lance Rushing - - $bytelen = 64; // byte length for md5 - if (strlen($key) > $bytelen) { - $key = pack('H*', md5($key)); - } - $key = str_pad($key, $bytelen, chr(0x00)); - $ipad = str_pad('', $bytelen, chr(0x36)); - $opad = str_pad('', $bytelen, chr(0x5c)); - $k_ipad = $key ^ $ipad; - $k_opad = $key ^ $opad; - - return md5($k_opad . pack('H*', md5($k_ipad . $data))); - } - - /** - * Check connection state. - * @access public - * @return bool True if connected. - */ - public function connected() - { - if (!empty($this->smtp_conn)) { - $sock_status = stream_get_meta_data($this->smtp_conn); - if ($sock_status['eof']) { - // the socket is valid but we are not connected - if ($this->do_debug >= 1) { - $this->edebug( - 'SMTP NOTICE: EOF caught while checking if connected' - ); - } - $this->close(); - return false; - } - return true; // everything looks good - } - return false; - } - - /** - * Close the socket and clean up the state of the class. - * Don't use this function without first trying to use QUIT. - * @see quit() - * @access public - * @return void - */ - public function close() - { - $this->error = null; // so there is no confusion - $this->helo_rply = null; - if (!empty($this->smtp_conn)) { - // close the connection and cleanup - fclose($this->smtp_conn); - if ($this->do_debug >= 3) { - $this->edebug('Connection: closed'); - } - $this->smtp_conn = 0; - } - } - - /** - * Send an SMTP DATA command. - * Issues a data command and sends the msg_data to the server, - * finializing the mail transaction. $msg_data is the message - * that is to be send with the headers. Each header needs to be - * on a single line followed by a with the message headers - * and the message body being separated by and additional . - * Implements rfc 821: DATA - * @param string $msg_data Message data to send - * @access public - * @return bool - */ - public function data($msg_data) - { - if (!$this->sendCommand('DATA', 'DATA', 354)) { - return false; - } - /* The server is ready to accept data! - * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) - * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into - * smaller lines to fit within the limit. - * We will also look for lines that start with a '.' and prepend an additional '.'. - * NOTE: this does not count towards line-length limit. - */ - - // Normalize line breaks before exploding - $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); - - /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field - * of the first line (':' separated) does not contain a space then it _should_ be a header and we will - * process all lines before a blank line as headers. - */ - - $field = substr($lines[0], 0, strpos($lines[0], ':')); - $in_headers = false; - if (!empty($field) && strpos($field, ' ') === false) { - $in_headers = true; - } - - foreach ($lines as $line) { - $lines_out = array(); - if ($in_headers and $line == '') { - $in_headers = false; - } - // ok we need to break this line up into several smaller lines - //This is a small micro-optimisation: isset($str[$len]) is equivalent to (strlen($str) > $len) - while (isset($line[self::MAX_LINE_LENGTH])) { - //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on - //so as to avoid breaking in the middle of a word - $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); - if (!$pos) { //Deliberately matches both false and 0 - //No nice break found, add a hard break - $pos = self::MAX_LINE_LENGTH - 1; - $lines_out[] = substr($line, 0, $pos); - $line = substr($line, $pos); - } else { - //Break at the found point - $lines_out[] = substr($line, 0, $pos); - //Move along by the amount we dealt with - $line = substr($line, $pos + 1); - } - /* If processing headers add a LWSP-char to the front of new line - * RFC822 section 3.1.1 - */ - if ($in_headers) { - $line = "\t" . $line; - } - } - $lines_out[] = $line; - - // Send the lines to the server - foreach ($lines_out as $line_out) { - //RFC2821 section 4.5.2 - if (!empty($line_out) and $line_out[0] == '.') { - $line_out = '.' . $line_out; - } - $this->client_send($line_out . self::CRLF); - } - } - - // Message data has been sent, complete the command - return $this->sendCommand('DATA END', '.', 250); - } - - /** - * Send an SMTP HELO or EHLO command. - * Used to identify the sending server to the receiving server. - * This makes sure that client and server are in a known state. - * Implements RFC 821: HELO - * and RFC 2821 EHLO. - * @param string $host The host name or IP to connect to - * @access public - * @return bool - */ - public function hello($host = '') - { - // Try extended hello first (RFC 2821) - return (bool)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); - } - - /** - * Send an SMTP HELO or EHLO command. - * Low-level implementation used by hello() - * @see hello() - * @param string $hello The HELO string - * @param string $host The hostname to say we are - * @access protected - * @return bool - */ - protected function sendHello($hello, $host) - { - $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); - $this->helo_rply = $this->last_reply; - return $noerror; - } - - /** - * Send an SMTP MAIL command. - * Starts a mail transaction from the email address specified in - * $from. Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. - * Implements rfc 821: MAIL FROM: - * @param string $from Source address of this message - * @access public - * @return bool - */ - public function mail($from) - { - $useVerp = ($this->do_verp ? ' XVERP' : ''); - return $this->sendCommand( - 'MAIL FROM', - 'MAIL FROM:<' . $from . '>' . $useVerp, - 250 - ); - } - - /** - * Send an SMTP QUIT command. - * Closes the socket if there is no error or the $close_on_error argument is true. - * Implements from rfc 821: QUIT - * @param bool $close_on_error Should the connection close if an error occurs? - * @access public - * @return bool - */ - public function quit($close_on_error = true) - { - $noerror = $this->sendCommand('QUIT', 'QUIT', 221); - $err = $this->error; //Save any error - if ($noerror or $close_on_error) { - $this->close(); - $this->error = $err; //Restore any error from the quit command - } - return $noerror; - } - - /** - * Send an SMTP RCPT command. - * Sets the TO argument to $toaddr. - * Returns true if the recipient was accepted false if it was rejected. - * Implements from rfc 821: RCPT TO: - * @param string $toaddr The address the message is being sent to - * @access public - * @return bool - */ - public function recipient($toaddr) - { - return $this->sendCommand( - 'RCPT TO', - 'RCPT TO:<' . $toaddr . '>', - array(250, 251) - ); - } - - /** - * Send an SMTP RSET command. - * Abort any transaction that is currently in progress. - * Implements rfc 821: RSET - * @access public - * @return bool True on success. - */ - public function reset() - { - return $this->sendCommand('RSET', 'RSET', 250); - } - - /** - * Send a command to an SMTP server and check its return code. - * @param string $command The command name - not sent to the server - * @param string $commandstring The actual command to send - * @param int|array $expect One or more expected integer success codes - * @access protected - * @return bool True on success. - */ - protected function sendCommand($command, $commandstring, $expect) - { - if (!$this->connected()) { - $this->error = array( - 'error' => "Called $command without being connected" - ); - return false; - } - $this->client_send($commandstring . self::CRLF); - - $reply = $this->get_lines(); - $code = substr($reply, 0, 3); - - if ($this->do_debug >= 2) { - $this->edebug('SERVER -> CLIENT: ' . $reply); - } - - if (!in_array($code, (array)$expect)) { - $this->last_reply = null; - $this->error = array( - 'error' => "$command command failed", - 'smtp_code' => $code, - 'detail' => substr($reply, 4) - ); - if ($this->do_debug >= 1) { - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] . ': ' . $reply - ); - } - return false; - } - - $this->last_reply = $reply; - $this->error = null; - return true; - } - - /** - * Send an SMTP SAML command. - * Starts a mail transaction from the email address specified in $from. - * Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. This command - * will send the message to the users terminal if they are logged - * in and send them an email. - * Implements rfc 821: SAML FROM: - * @param string $from The address the message is from - * @access public - * @return bool - */ - public function sendAndMail($from) - { - return $this->sendCommand('SAML', "SAML FROM:$from", 250); - } - - /** - * Send an SMTP VRFY command. - * @param string $name The name to verify - * @access public - * @return bool - */ - public function verify($name) - { - return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); - } - - /** - * Send an SMTP NOOP command. - * Used to keep keep-alives alive, doesn't actually do anything - * @access public - * @return bool - */ - public function noop() - { - return $this->sendCommand('NOOP', 'NOOP', 250); - } - - /** - * Send an SMTP TURN command. - * This is an optional command for SMTP that this class does not support. - * This method is here to make the RFC821 Definition complete for this class - * and _may_ be implemented in future - * Implements from rfc 821: TURN - * @access public - * @return bool - */ - public function turn() - { - $this->error = array( - 'error' => 'The SMTP TURN command is not implemented' - ); - if ($this->do_debug >= 1) { - $this->edebug('SMTP NOTICE: ' . $this->error['error']); - } - return false; - } - - /** - * Send raw data to the server. - * @param string $data The data to send - * @access public - * @return int|bool The number of bytes sent to the server or false on error - */ - public function client_send($data) - { - if ($this->do_debug >= 1) { - $this->edebug("CLIENT -> SERVER: $data"); - } - return fwrite($this->smtp_conn, $data); - } - - /** - * Get the latest error. - * @access public - * @return array - */ - public function getError() - { - return $this->error; - } - - /** - * Get the last reply from the server. - * @access public - * @return string - */ - public function getLastReply() - { - return $this->last_reply; - } - - /** - * Read the SMTP server's response. - * Either before eof or socket timeout occurs on the operation. - * With SMTP we can tell if we have more lines to read if the - * 4th character is '-' symbol. If it is a space then we don't - * need to read anything else. - * @access protected - * @return string - */ - protected function get_lines() - { - // If the connection is bad, give up straight away - if (!is_resource($this->smtp_conn)) { - return ''; - } - $data = ''; - $endtime = 0; - stream_set_timeout($this->smtp_conn, $this->Timeout); - if ($this->Timelimit > 0) { - $endtime = time() + $this->Timelimit; - } - while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { - $str = @fgets($this->smtp_conn, 515); - if ($this->do_debug >= 4) { - $this->edebug("SMTP -> get_lines(): \$data was \"$data\""); - $this->edebug("SMTP -> get_lines(): \$str is \"$str\""); - } - $data .= $str; - if ($this->do_debug >= 4) { - $this->edebug("SMTP -> get_lines(): \$data is \"$data\""); - } - // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen - if ((isset($str[3]) and $str[3] == ' ')) { - break; - } - // Timed-out? Log and break - $info = stream_get_meta_data($this->smtp_conn); - if ($info['timed_out']) { - if ($this->do_debug >= 4) { - $this->edebug( - 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)' - ); - } - break; - } - // Now check if reads took too long - if ($endtime and time() > $endtime) { - if ($this->do_debug >= 4) { - $this->edebug( - 'SMTP -> get_lines(): timelimit reached ('. - $this->Timelimit . ' sec)' - ); - } - break; - } - } - return $data; - } - - /** - * Enable or disable VERP address generation. - * @param bool $enabled - */ - public function setVerp($enabled = false) - { - $this->do_verp = $enabled; - } - - /** - * Get VERP address generation mode. - * @return bool - */ - public function getVerp() - { - return $this->do_verp; - } - - /** - * Set debug output method. - * @param string $method The function/method to use for debugging output. - */ - public function setDebugOutput($method = 'echo') - { - $this->Debugoutput = $method; - } - - /** - * Get debug output method. - * @return string - */ - public function getDebugOutput() - { - return $this->Debugoutput; - } - - /** - * Set debug output level. - * @param int $level - */ - public function setDebugLevel($level = 0) - { - $this->do_debug = $level; - } - - /** - * Get debug output level. - * @return int - */ - public function getDebugLevel() - { - return $this->do_debug; - } - - /** - * Set SMTP timeout. - * @param int $timeout - */ - public function setTimeout($timeout = 0) - { - $this->Timeout = $timeout; - } - - /** - * Get SMTP timeout. - * @return int - */ - public function getTimeout() - { - return $this->Timeout; - } -} diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/composer.json b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/composer.json deleted file mode 100644 index 6e8880cb..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "phpmailer/phpmailer", - "type": "library", - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "authors": [ - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" - }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" - }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" - }, - { - "name": "Brent R. Matzelle" - } - ], - "require": { - "php": ">=5.0.0" - }, - "require-dev": { - "phpdocumentor/phpdocumentor": "*", - "phpunit/phpunit": "4.0.*" - }, - "autoload": { - "classmap": ["class.phpmailer.php", "class.pop3.php", "class.smtp.php"] - }, - "license": "LGPL-2.1" -} \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/Callback_function_notes.txt b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/Callback_function_notes.txt deleted file mode 100644 index 461ea508..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/Callback_function_notes.txt +++ /dev/null @@ -1,17 +0,0 @@ -NEW CALLBACK FUNCTION: -====================== - -We have had requests for a method to process the results of sending emails -through PHPMailer. In this new release, we have implemented a callback -function that passes the results of each email sent (to, cc, and/or bcc). -We have provided an example that echos the results back to the screen. The -callback function can be used for any purpose. With minor modifications, the -callback function can be used to create CSV logs, post results to databases, -etc. - -Please review the test.php script for the example. - -It's pretty straight forward. - -Enjoy! -Andy diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/DomainKeys_notes.txt b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/DomainKeys_notes.txt deleted file mode 100644 index 2ad10f15..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/DomainKeys_notes.txt +++ /dev/null @@ -1,55 +0,0 @@ -CREATE DKIM KEYS and DNS Resource Record: -========================================= - -To create DomainKeys Identified Mail keys, visit: -http://dkim.worxware.com/ -... read the information, fill in the form, and download the ZIP file -containing the public key, private key, DNS Resource Record and instructions -to add to your DNS Zone Record, and the PHPMailer code to enable DKIM -digital signing. - -/*** PROTECT YOUR PRIVATE & PUBLIC KEYS ***/ - -You need to protect your DKIM private and public keys from being viewed or -accessed. Add protection to your .htaccess file as in this example: - -# secure htkeyprivate file - - order allow,deny - deny from all - - -# secure htkeypublic file - - order allow,deny - deny from all - - -(the actual .htaccess additions are in the ZIP file sent back to you from -http://dkim.worxware.com/ - -A few notes on using DomainKey Identified Mail (DKIM): - -You do not need to use PHPMailer to DKIM sign emails IF: -- you enable DomainKey support and add the DNS resource record -- you use your outbound mail server - -If you are a third-party emailer that works on behalf of domain owners to -send their emails from your own server: -- you absolutely have to DKIM sign outbound emails -- the domain owner has to add the DNS resource record to match the - private key, public key, selector, identity, and domain that you create -- use caution with the "selector" ... at least one "selector" will already - exist in the DNS Zone Record of the domain at the domain owner's server - you need to ensure that the "selector" you use is unique -Note: since the IP address will not match the domain owner's DNS Zone record -you can be certain that email providers that validate based on DomainKey will -check the domain owner's DNS Zone record for your DNS resource record. Before -sending out emails on behalf of domain owners, ensure they have entered the -DNS resource record you provided them. - -Enjoy! -Andy - -PS. if you need additional information about DKIM, please see: -http://www.dkim.org/info/dkim-faq.html diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/Note_for_SMTP_debugging.txt b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/Note_for_SMTP_debugging.txt deleted file mode 100644 index 128b2d9d..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/Note_for_SMTP_debugging.txt +++ /dev/null @@ -1,17 +0,0 @@ -If you are having problems connecting or sending emails through your SMTP server, the SMTP class can provide more information about the processing/errors taking place. -Use the debug functionality of the class to see what's going on in your connections. To do that, set the debug level in your script. For example: - -$mail->SMTPDebug = 1; -$mail->isSMTP(); // telling the class to use SMTP -$mail->SMTPAuth = true; // enable SMTP authentication -$mail->Port = 26; // set the SMTP port -$mail->Host = "mail.yourhost.com"; // SMTP server -$mail->Username = "name@yourhost.com"; // SMTP account username -$mail->Password = "your password"; // SMTP account password - -Notes on this: -$mail->SMTPDebug = 0; ... will disable debugging (you can also leave this out completely, 0 is the default) -$mail->SMTPDebug = 1; ... will echo errors and server responses -$mail->SMTPDebug = 2; ... will echo errors, server responses and client messages - -And finally, don't forget to disable debugging before going into production. diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/extending.html b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/extending.html deleted file mode 100644 index ec2b8510..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/extending.html +++ /dev/null @@ -1,129 +0,0 @@ - - -Examples using phpmailer - - - - -

      Examples using PHPMailer

      - -

      1. Advanced Example

      -

      - -This demonstrates sending multiple email messages with binary attachments -from a MySQL database using multipart/alternative messages.

      - -

      -require 'PHPMailerAutoload.php';
      -
      -$mail = new PHPMailer();
      -
      -$mail->From     = 'list@example.com';
      -$mail->FromName = 'List manager';
      -$mail->Host     = 'smtp1.example.com;smtp2.example.com';
      -$mail->Mailer   = 'smtp';
      -
      -@mysqli_connect('localhost','root','password');
      -@mysqli_select_db("my_company");
      -$query = "SELECT full_name, email, photo FROM employee";
      -$result = @mysqli_query($query);
      -
      -while ($row = mysqli_fetch_assoc($result))
      -{
      -    // HTML body
      -    $body  = "Hello <font size=\"4\">" . $row['full_name'] . "</font>, <p>";
      -    $body .= "<i>Your</i> personal photograph to this message.<p>";
      -    $body .= "Sincerely, <br>";
      -    $body .= "phpmailer List manager";
      -
      -    // Plain text body (for mail clients that cannot read HTML)
      -    $text_body  = 'Hello ' . $row['full_name'] . ", \n\n";
      -    $text_body .= "Your personal photograph to this message.\n\n";
      -    $text_body .= "Sincerely, \n";
      -    $text_body .= 'phpmailer List manager';
      -
      -    $mail->Body    = $body;
      -    $mail->AltBody = $text_body;
      -    $mail->addAddress($row['email'], $row['full_name']);
      -    $mail->addStringAttachment($row['photo'], 'YourPhoto.jpg');
      -
      -    if(!$mail->send())
      -        echo "There has been a mail error sending to " . $row['email'] . "<br>";
      -
      -    // Clear all addresses and attachments for next loop
      -    $mail->clearAddresses();
      -    $mail->clearAttachments();
      -}
      -
      -

      - -

      2. Extending PHPMailer

      -

      - -Extending classes with inheritance is one of the most -powerful features of object-oriented programming. It allows you to make changes to the -original class for your own personal use without hacking the original -classes, and it's very easy to do: - -

      -Here's a class that extends the phpmailer class and sets the defaults -for the particular site:
      -PHP include file: my_phpmailer.php -

      - -

      -require 'PHPMailerAutoload.php';
      -
      -class my_phpmailer extends PHPMailer {
      -    // Set default variables for all new objects
      -    public $From     = 'from@example.com';
      -    public $FromName = 'Mailer';
      -    public $Host     = 'smtp1.example.com;smtp2.example.com';
      -    public $Mailer   = 'smtp';                         // Alternative to isSMTP()
      -    public $WordWrap = 75;
      -
      -    // Replace the default debug output function
      -    protected function edebug($msg) {
      -        print('My Site Error');
      -        print('Description:');
      -        printf('%s', $msg);
      -        exit;
      -    }
      -
      -    //Extend the send function
      -    public function send() {
      -        $this->Subject = '[Yay for me!] '.$this->Subject;
      -        return parent::send()
      -    }
      -
      -    // Create an additional function
      -    public function do_something($something) {
      -        // Place your new code here
      -    }
      -}
      -
      -
      -Now here's a normal PHP page in the site, which will have all the defaults set above:
      - -
      -require 'my_phpmailer.php';
      -
      -// Instantiate your new class
      -$mail = new my_phpmailer;
      -
      -// Now you only need to add the necessary stuff
      -$mail->addAddress('josh@example.com', 'Josh Adams');
      -$mail->Subject = 'Here is the subject';
      -$mail->Body    = 'This is the message body';
      -$mail->addAttachment('c:/temp/11-10-00.zip', 'new_name.zip');  // optional name
      -
      -if(!$mail->send())
      -{
      -   echo 'There was an error sending the message';
      -   exit;
      -}
      -
      -echo 'Message was sent successfully';
      -
      - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/faq.html b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/faq.html deleted file mode 100644 index 7033a142..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/faq.html +++ /dev/null @@ -1,28 +0,0 @@ - - -PHPMailer FAQ - - -

      PHPMailer FAQ

      -
        -
      • Q: I am concerned that using include files will take up too much - processing time on my computer. How can I make it run faster?
        - A: PHP by itself is fairly fast, but it recompiles scripts every time they are run, which takes up valuable - computer resources. You can bypass this by using an opcode cache which compiles - PHP code and store it in memory to reduce overhead immensely. APC - (Alternative PHP Cache) is a free opcode cache extension in the PECL library.
      • -
      • Q: Which mailer gives me the best performance?
        - A: On a single machine the sendmail (or Qmail) is fastest overall. - Next fastest is mail() to give you the best performance. Both do not have the overhead of SMTP. - If you do not have a local mail server (as is typical on Windows), SMTP is your only option.
      • -
      • Q: When I try to attach a file with on my server I get a - "Could not find {file} on filesystem error". Why is this?
        - A: If you are using a Unix machine this is probably because the user - running your web server does not have read access to the directory in question. If you are using Windows, - then the problem is probably that you have used single backslashes to denote directories (\). - A single backslash has a special meaning to PHP so these are not - valid. Instead use double backslashes ("\\") or a single forward - slash ("/").
      • -
      - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/generatedocs.sh b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/generatedocs.sh deleted file mode 100644 index 8f24269f..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/generatedocs.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -# Regenerate PHPMailer documentation -# Run from within the docs folder -rm -rf phpdoc/* -phpdoc --directory .. --target ./phpdoc --ignore test/,examples/,extras/,test_script/ --sourcecode --force --title PHPMailer --template="clean" diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/pop3_article.txt b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/pop3_article.txt deleted file mode 100644 index fb90b9c7..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/docs/pop3_article.txt +++ /dev/null @@ -1,50 +0,0 @@ -This is built for PHP Mailer 1.72 and was not tested with any previous version. It was developed under PHP 4.3.11 (E_ALL). It works under PHP 5 and 5.1 with E_ALL, but not in Strict mode due to var deprecation (but then neither does PHP Mailer either!). It follows the RFC 1939 standard explicitly and is fully commented. - -With that noted, here is how to implement it: - -I didn't want to modify the PHP Mailer classes at all, so you will have to include/require this class along with the base one. It can sit quite happily in the phpmailer directory. - -When you need it, create your POP3 object - -Right before I invoke PHP Mailer I activate the POP3 authorisation. POP3 before SMTP is a process whereby you login to your web hosts POP3 mail server BEFORE sending out any emails via SMTP. The POP3 logon 'verifies' your ability to send email by SMTP, which typically otherwise blocks you. On my web host (Pair Networks) a single POP3 logon is enough to 'verify' you for 90 minutes. Here is some sample PHP code that activates the POP3 logon and then sends an email via PHP Mailer: - -authorise('pop3.example.com', 110, 30, 'mailer', 'password', 1); -$mail = new PHPMailer(); $mail->SMTPDebug = 2; $mail->isSMTP(); -$mail->isHTML(false); $mail->Host = 'relay.example.com'; -$mail->From = 'mailer@example.com'; -$mail->FromName = 'Example Mailer'; -$mail->Subject = 'My subject'; -$mail->Body = 'Hello world'; -$mail->addAddress('rich@corephp.co.uk', 'Richard Davey'); -if (!$mail->send()) { - echo $mail->ErrorInfo; -} -?> - -The PHP Mailer parts of this code should be obvious to anyone who has used PHP Mailer before. One thing to note - you almost certainly will not need to use SMTP Authentication *and* POP3 before SMTP together. The Authorisation method is a proxy method to all of the others within that class. There are connect, Logon and disconnect methods available, but I wrapped them in the single Authorisation one to make things easier. -The Parameters - -The authorise parameters are as follows: - -$pop->authorise('pop3.example.com', 110, 30, 'mailer', 'password', 1); - - 1. pop3.example.com - The POP3 Mail Server Name (hostname or IP address) - 2. 110 - The POP3 Port on which to connect (default is usually 110, but check with your host) - 3. 30 - A connection time-out value (in seconds) - 4. mailer - The POP3 Username required to logon - 5. password - The POP3 Password required to logon - 6. 1 - The class debug level (0 = off, 1+ = debug output is echoed to the browser) - -Final Comments + the Download - -1) This class does not support APOP connections. This is only because I did not have an APOP server to test with, but if you'd like to see that added just contact me. - -2) Opening and closing lots of POP3 connections can be quite a resource/network drain. If you need to send a whole batch of emails then just perform the authentication once at the start, and then loop through your mail sending script. Providing this process doesn't take longer than the verification period lasts on your POP3 server, you should be fine. With my host that period is 90 minutes, i.e. plenty of time. - -3) If you have heavy requirements for this script (i.e. send a LOT of email on a frequent basis) then I would advise seeking out an alternative sending method (direct SMTP ideally). If this isn't possible then you could modify this class so the 'last authorised' date is recorded somewhere (MySQL, Flat file, etc) meaning you only open a new connection if the old one has expired, saving you precious overhead. - -4) There are lots of other POP3 classes for PHP available. However most of them implement the full POP3 command set, where-as this one is purely for authentication, and much lighter as a result. However using any of the other POP3 classes to just logon to your server would have the same net result. At the end of the day, use whatever method you feel most comfortable with. -Download - -My thanks to Chris Ryan for the inspiration (even if indirectly, via his SMTP class) diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/LGPLv3.txt b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/LGPLv3.txt deleted file mode 100644 index 3f9959fc..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/LGPLv3.txt +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/code_generator.phps b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/code_generator.phps deleted file mode 100644 index 49d6c888..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/code_generator.phps +++ /dev/null @@ -1,596 +0,0 @@ -CharSet = 'utf-8'; -$mail->Debugoutput = $CFG['smtp_debugoutput']; -$example_code .= "\n\n\$mail = new PHPMailer(true);"; -$example_code .= "\n\$mail->CharSet = 'utf-8';"; - -class phpmailerAppException extends phpmailerException -{ -} - -$example_code .= "\n\nclass phpmailerAppException extends phpmailerException {}"; -$example_code .= "\n\ntry {"; - -try { - if (isset($_POST["submit"]) && $_POST['submit'] == "Submit") { - $to = $_POST['To_Email']; - if (!PHPMailer::validateAddress($to)) { - throw new phpmailerAppException("Email address " . $to . " is invalid -- aborting!"); - } - - $example_code .= "\n\$to = '{$_POST['To_Email']}';"; - $example_code .= "\nif(!PHPMailer::validateAddress(\$to)) {"; - $example_code .= "\n throw new phpmailerAppException(\"Email address \" . " . - "\$to . \" is invalid -- aborting!\");"; - $example_code .= "\n}"; - - switch ($_POST['test_type']) { - case 'smtp': - $mail->isSMTP(); // telling the class to use SMTP - $mail->SMTPDebug = (integer)$_POST['smtp_debug']; - $mail->Host = $_POST['smtp_server']; // SMTP server - $mail->Port = (integer)$_POST['smtp_port']; // set the SMTP port - if ($_POST['smtp_secure']) { - $mail->SMTPSecure = strtolower($_POST['smtp_secure']); - } - $mail->SMTPAuth = array_key_exists('smtp_authenticate', $_POST); // enable SMTP authentication? - if (array_key_exists('smtp_authenticate', $_POST)) { - $mail->Username = $_POST['authenticate_username']; // SMTP account username - $mail->Password = $_POST['authenticate_password']; // SMTP account password - } - - $example_code .= "\n\$mail->isSMTP();"; - $example_code .= "\n\$mail->SMTPDebug = " . $_POST['smtp_debug'] . ";"; - $example_code .= "\n\$mail->Host = \"" . $_POST['smtp_server'] . "\";"; - $example_code .= "\n\$mail->Port = \"" . $_POST['smtp_port'] . "\";"; - $example_code .= "\n\$mail->SMTPSecure = \"" . strtolower($_POST['smtp_secure']) . "\";"; - $example_code .= "\n\$mail->SMTPAuth = " . (array_key_exists( - 'smtp_authenticate', - $_POST - ) ? 'true' : 'false') . ";"; - if (array_key_exists('smtp_authenticate', $_POST)) { - $example_code .= "\n\$mail->Username = \"" . $_POST['authenticate_username'] . "\";"; - $example_code .= "\n\$mail->Password = \"" . $_POST['authenticate_password'] . "\";"; - } - break; - case 'mail': - $mail->isMail(); // telling the class to use PHP's mail() - $example_code .= "\n\$mail->isMail();"; - break; - case 'sendmail': - $mail->isSendmail(); // telling the class to use Sendmail - $example_code .= "\n\$mail->isSendmail();"; - break; - case 'qmail': - $mail->isQmail(); // telling the class to use Qmail - $example_code .= "\n\$mail->isQmail();"; - break; - default: - throw new phpmailerAppException('Invalid test_type provided'); - } - - try { - if ($_POST['From_Name'] != '') { - $mail->addReplyTo($_POST['From_Email'], $_POST['From_Name']); - $mail->From = $_POST['From_Email']; - $mail->FromName = $_POST['From_Name']; - - $example_code .= "\n\$mail->addReplyTo(\"" . - $_POST['From_Email'] . "\", \"" . $_POST['From_Name'] . "\");"; - $example_code .= "\n\$mail->From = \"" . $_POST['From_Email'] . "\";"; - $example_code .= "\n\$mail->FromName = \"" . $_POST['From_Name'] . "\";"; - } else { - $mail->addReplyTo($_POST['From_Email']); - $mail->From = $_POST['From_Email']; - $mail->FromName = $_POST['From_Email']; - - $example_code .= "\n\$mail->addReplyTo(\"" . $_POST['From_Email'] . "\");"; - $example_code .= "\n\$mail->From = \"" . $_POST['From_Email'] . "\";"; - $example_code .= "\n\$mail->FromName = \"" . $_POST['From_Email'] . "\";"; - } - - if ($_POST['To_Name'] != '') { - $mail->addAddress($to, $_POST['To_Name']); - $example_code .= "\n\$mail->addAddress(\"$to\", \"" . $_POST['To_Name'] . "\");"; - } else { - $mail->addAddress($to); - $example_code .= "\n\$mail->addAddress(\"$to\");"; - } - - if ($_POST['bcc_Email'] != '') { - $indiBCC = explode(" ", $_POST['bcc_Email']); - foreach ($indiBCC as $key => $value) { - $mail->addBCC($value); - $example_code .= "\n\$mail->addBCC(\"$value\");"; - } - } - - if ($_POST['cc_Email'] != '') { - $indiCC = explode(" ", $_POST['cc_Email']); - foreach ($indiCC as $key => $value) { - $mail->addCC($value); - $example_code .= "\n\$mail->addCC(\"$value\");"; - } - } - } catch (phpmailerException $e) { //Catch all kinds of bad addressing - throw new phpmailerAppException($e->getMessage()); - } - $mail->Subject = $_POST['Subject'] . ' (PHPMailer test using ' . strtoupper($_POST['test_type']) . ')'; - $example_code .= "\n\$mail->Subject = \"" . $_POST['Subject'] . - '(PHPMailer test using ' . strtoupper($_POST['test_type']) . ')";'; - - if ($_POST['Message'] == '') { - $body = file_get_contents('contents.html'); - } else { - $body = $_POST['Message']; - } - - $example_code .= "\n\$body = <<<'EOT'\n" . htmlentities($body) . "\nEOT;"; - - $mail->WordWrap = 80; // set word wrap - $mail->msgHTML($body, dirname(__FILE__), true); //Create message bodies and embed images - - $example_code .= "\n\$mail->WordWrap = 80;"; - $example_code .= "\n\$mail->msgHTML(\$body, dirname(__FILE__), true); //Create message bodies and embed images"; - - $mail->addAttachment('images/phpmailer_mini.png', 'phpmailer_mini.png'); // optional name - $mail->addAttachment('images/phpmailer.png', 'phpmailer.png'); // optional name - $example_code .= "\n\$mail->addAttachment('images/phpmailer_mini.png'," . - "'phpmailer_mini.png'); // optional name"; - $example_code .= "\n\$mail->addAttachment('images/phpmailer.png', 'phpmailer.png'); // optional name"; - - try { - $mail->send(); - $results_messages[] = "Message has been sent using " . strtoupper($_POST["test_type"]); - } catch (phpmailerException $e) { - throw new phpmailerAppException("Unable to send to: " . $to . ': ' . $e->getMessage()); - } - - $example_code .= "\n\ntry {"; - $example_code .= "\n \$mail->send();"; - $example_code .= "\n \$results_messages[] = \"Message has been sent using " . - strtoupper($_POST['test_type']) . "\";"; - $example_code .= "\n}"; - $example_code .= "\ncatch (phpmailerException \$e) {"; - $example_code .= "\n throw new phpmailerAppException('Unable to send to: ' . \$to. ': '.\$e->getMessage());"; - $example_code .= "\n}"; - } -} catch (phpmailerAppException $e) { - $results_messages[] = $e->errorMessage(); -} -$example_code .= "\n}"; -$example_code .= "\ncatch (phpmailerAppException \$e) {"; -$example_code .= "\n \$results_messages[] = \$e->errorMessage();"; -$example_code .= "\n}"; -$example_code .= "\n\nif (count(\$results_messages) > 0) {"; -$example_code .= "\n echo \"

      Run results

      \\n\";"; -$example_code .= "\n echo \"
        \\n\";"; -$example_code .= "\nforeach (\$results_messages as \$result) {"; -$example_code .= "\n echo \"
      • \$result
      • \\n\";"; -$example_code .= "\n}"; -$example_code .= "\necho \"
      \\n\";"; -$example_code .= "\n}"; -?> - - - - PHPMailer Test Page - - - - - - - - -"; - echo exit("ERROR: Wrong PHP version. Must be PHP 5 or above."); -} - -if (count($results_messages) > 0) { - echo '

      Run results

      '; - echo '
        '; - foreach ($results_messages as $result) { - echo "
      • $result
      • "; - } - echo '
      '; -} - -if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") { - echo "
      \n"; - echo "
      Script:\n"; - echo "
      \n";
      -    echo $example_code;
      -    echo "\n
      \n"; - echo "\n
      \n"; -} -?> -
      -
      -
      -
      - Mail Details - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - - - -
      - - - -
      - - - -
      - - - -
      - - - -
      - - - -
      - - - -
      - - - -
      -
      Test will include two attachments.
      -
      -
      -
      -
      - Mail Test Specs - - - - - -
      Test Type -
      - - - required> -
      -
      - - - required> -
      -
      - - - required> -
      -
      - - - required> -
      -
      -
      "> - SMTP Specific Options: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - -
      - -
      - -
      - -
      - - value=""> -
      - -
      - -
      -
      -
      -
      -
      - -
      -
      - -
      - -
      -
      -
      - - \ No newline at end of file diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/contents.html b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/contents.html deleted file mode 100644 index 9257f6dd..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/contents.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - PHPMailer Test - - -
      -

      This is a test of PHPMailer.

      -
      - PHPMailer rocks -
      -

      This example uses HTML.

      -

      The PHPMailer image at the top has been embedded automatically.

      -
      - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/exceptions.phps b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/exceptions.phps deleted file mode 100644 index e2501903..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/exceptions.phps +++ /dev/null @@ -1,41 +0,0 @@ - - - - - PHPMailer - Exceptions test - - -setFrom('from@example.com', 'First Last'); - //Set an alternative reply-to address - $mail->addReplyTo('replyto@example.com', 'First Last'); - //Set who the message is to be sent to - $mail->addAddress('whoto@example.com', 'John Doe'); - //Set the subject line - $mail->Subject = 'PHPMailer Exceptions test'; - //Read an HTML message body from an external file, convert referenced images to embedded, - //and convert the HTML into a basic plain-text alternative body - $mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - //Replace the plain text body with one created manually - $mail->AltBody = 'This is a plain-text message body'; - //Attach an image file - $mail->addAttachment('images/phpmailer_mini.png'); - //send the message - //Note that we don't need check the response from this because it will throw an exception if it has trouble - $mail->send(); - echo "Message sent!"; -} catch (phpmailerException $e) { - echo $e->errorMessage(); //Pretty error messages from PHPMailer -} catch (Exception $e) { - echo $e->getMessage(); //Boring error messages from anything else! -} -?> - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/gmail.phps b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/gmail.phps deleted file mode 100644 index a6d41988..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/gmail.phps +++ /dev/null @@ -1,79 +0,0 @@ - - - - - PHPMailer - GMail SMTP test - - -isSMTP(); - -//Enable SMTP debugging -// 0 = off (for production use) -// 1 = client messages -// 2 = client and server messages -$mail->SMTPDebug = 2; - -//Ask for HTML-friendly debug output -$mail->Debugoutput = 'html'; - -//Set the hostname of the mail server -$mail->Host = 'smtp.gmail.com'; - -//Set the SMTP port number - 587 for authenticated TLS, a.k.a. RFC4409 SMTP submission -$mail->Port = 587; - -//Set the encryption system to use - ssl (deprecated) or tls -$mail->SMTPSecure = 'tls'; - -//Whether to use SMTP authentication -$mail->SMTPAuth = true; - -//Username to use for SMTP authentication - use full email address for gmail -$mail->Username = "username@gmail.com"; - -//Password to use for SMTP authentication -$mail->Password = "yourpassword"; - -//Set who the message is to be sent from -$mail->setFrom('from@example.com', 'First Last'); - -//Set an alternative reply-to address -$mail->addReplyTo('replyto@example.com', 'First Last'); - -//Set who the message is to be sent to -$mail->addAddress('whoto@example.com', 'John Doe'); - -//Set the subject line -$mail->Subject = 'PHPMailer GMail SMTP test'; - -//Read an HTML message body from an external file, convert referenced images to embedded, -//convert HTML into a basic plain-text alternative body -$mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - -//Replace the plain text body with one created manually -$mail->AltBody = 'This is a plain-text message body'; - -//Attach an image file -$mail->addAttachment('images/phpmailer_mini.png'); - -//send the message, check for errors -if (!$mail->send()) { - echo "Mailer Error: " . $mail->ErrorInfo; -} else { - echo "Message sent!"; -} -?> - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/images/phpmailer.png b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/images/phpmailer.png deleted file mode 100644 index 9bdd83c8..00000000 Binary files a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/images/phpmailer.png and /dev/null differ diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/images/phpmailer_mini.png b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/images/phpmailer_mini.png deleted file mode 100644 index e6915f43..00000000 Binary files a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/images/phpmailer_mini.png and /dev/null differ diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/index.html b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/index.html deleted file mode 100644 index 4c55023b..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - PHPMailer Examples - - -

      PHPMailer code examplesPHPMailer logo

      -

      This folder contains a collection of examples of using PHPMailer.

      -

      About testing email sending

      -

      When working on email sending code you'll find yourself worrying about what might happen if all these test emails got sent to your mailing list. The solution is to use a fake mail server, one that acts just like the real thing, but just doesn't actually send anything out. Some offer web interfaces, feedback, logging, the ability to return specific error codes, all things that are useful for testing error handling, authentication etc. Here's a selection of mail testing tools you might like to try:

      -
        -
      • FakeEmail, a Python-based fake mail server with a web interface.
      • -
      • smtp-sink, part of the Postfix mail server, so you probably already have this installed. This is used in the Travis-CI configuration to run PHPMailer's unit tests.
      • -
      • FakeSMTP, a Java desktop app with the ability to show an SMTP log and save messages to a folder.
      • -
      • smtp4dev, a dummy SMTP server for Windows.
      • -
      • fakesendmail.sh, part of PHPMailer's test setup, this is a shell script that emulates sendmail for testing 'mail' or 'sendmail' methods in PHPMailer.
      • -
      • msglint, not a mail server, the IETF's MIME structure analyser checks the formatting of your messages.
      • -
      -
      -

      Security note

      -

      Before running these examples you'll need to rename them with '.php' extensions. They are supplied as '.phps' files which will usually be displayed with syntax highlighting by PHP instead of running them. This prevents potential security issues with running potential spam-gateway code if you happen to deploy these code examples on a live site - please don't do that! Similarly, don't leave your passwords in these files as they will be visible to the world!

      -
      -

      code_generator.phps

      -

      This script is a simple code generator - fill in the form and hit submit, and it will use when you entered to email you a message, and will also generate PHP code using your settings that you can copy and paste to use in your own apps. If you need to get going quickly, this is probably the best place to start.

      -

      mail.phps

      -

      This script is a basic example which creates an email message from an external HTML file, creates a plain text body, sets various addresses, adds an attachment and sends the message. It uses PHP's built-in mail() function which is the simplest to use, but relies on the presence of a local mail server, something which is not usually available on Windows. If you find yourself in that sitution, either install a local mail server, or use a remote one and send using SMTP instead.

      -

      exceptions.phps

      -

      The same as the mail example, but shows how to use PHPMailer's optional exceptions for error handling.

      -

      smtp.phps

      -

      A simple example sending using SMTP with authentication.

      -

      smtp_no_auth.phps

      -

      A simple example sending using SMTP without authentication.

      -

      sendmail.phps

      -

      A simple example using sendmail. Sendmail is a program (usually found on Linux/BSD, OS X and other UNIX-alikes) that can be used to submit messages to a local mail server without a lengthy SMTP conversation. It's probably the fastest sending mechanism, but lacks some error reporting features. There are sendmail emulators for most popular mail servers including postfix, qmail, exim etc.

      -

      gmail.phps

      -

      Submitting email via Google's Gmail service is a popular use of PHPMailer. It's much the same as normal SMTP sending, just with some specific settings, namely using TLS encryption, authentication is enabled, and it connects to the SMTP submission port 587 on the smtp.gmail.com host. This example does all that.

      -

      pop_before_smtp.phps

      -

      Before effective SMTP authentication mechanisms were available, it was common for ISPs to use POP-before-SMTP authentication. As it implies, you authenticate using the POP3 protocol (an older protocol now mostly replaced by the far superior IMAP), and then the SMTP server will allow send access from your IP address for a short while, usually 5-15 minutes. PHPMailer includes a POP3 protocol client, so it can carry out this sequence - it's just like a normal SMTP conversation (without authentication), but connects via POP first.

      -

      mailing_list.phps

      -

      This is a somewhat naïve example of sending similar emails to a list of different addresses. It sets up a PHPMailer instance using SMTP, then connects to a MySQL database to retrieve a list of recipients. The code loops over this list, sending email to each person using their info and marks them as sent in the database. It makes use of SMTP keepalive which saves reconnecting and re-authenticating between each message.

      -
      -

      Most of these examples use the 'example.com' domain. This domain is reserved by IANA for illustrative purposes, as documented in RFC 2606. Don't use made-up domains like 'mydomain.com' or 'somedomain.com' in examples as someone, somewhere, probably owns them!

      - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/mail.phps b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/mail.phps deleted file mode 100644 index f12dbaf2..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/mail.phps +++ /dev/null @@ -1,37 +0,0 @@ - - - - - PHPMailer - mail() test - - -setFrom('from@example.com', 'First Last'); -//Set an alternative reply-to address -$mail->addReplyTo('replyto@example.com', 'First Last'); -//Set who the message is to be sent to -$mail->addAddress('whoto@example.com', 'John Doe'); -//Set the subject line -$mail->Subject = 'PHPMailer mail() test'; -//Read an HTML message body from an external file, convert referenced images to embedded, -//convert HTML into a basic plain-text alternative body -$mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); -//Replace the plain text body with one created manually -$mail->AltBody = 'This is a plain-text message body'; -//Attach an image file -$mail->addAttachment('images/phpmailer_mini.png'); - -//send the message, check for errors -if (!$mail->send()) { - echo "Mailer Error: " . $mail->ErrorInfo; -} else { - echo "Message sent!"; -} -?> - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/mailing_list.phps b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/mailing_list.phps deleted file mode 100644 index 070e82d3..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/mailing_list.phps +++ /dev/null @@ -1,55 +0,0 @@ -isSMTP(); -$mail->Host = 'smtp.example.com'; -$mail->SMTPAuth = true; -$mail->SMTPKeepAlive = true; // SMTP connection will not close after each email sent, reduces SMTP overhead -$mail->Port = 25; -$mail->Username = 'yourname@example.com'; -$mail->Password = 'yourpassword'; -$mail->setFrom('list@example.com', 'List manager'); -$mail->addReplyTo('list@example.com', 'List manager'); - -$mail->Subject = "PHPMailer Simple database mailing list test"; - -//Same body for all messages, so set this before the sending loop -//If you generate a different body for each recipient (e.g. you're using a templating system), -//set it inside the loop -$mail->msgHTML($body); -//msgHTML also sets AltBody, so if you want a custom one, set it afterwards -$mail->AltBody = 'To view the message, please use an HTML compatible email viewer!'; - -//Connect to the database and select the recipients from your mailing list that have not yet been sent to -//You'll need to alter this to match your database -$mysql = mysql_connect('localhost', 'username', 'password'); -mysql_select_db('mydb', $mysql); -$result = mysql_query("SELECT full_name, email, photo FROM mailinglist WHERE sent = false", $mysql); - -while ($row = mysql_fetch_array($result)) { - $mail->addAddress($row['email'], $row['full_name']); - $mail->addStringAttachment($row['photo'], 'YourPhoto.jpg'); //Assumes the image data is stored in the DB - - if (!$mail->send()) { - echo "Mailer Error (" . str_replace("@", "@", $row["email"]) . ') ' . $mail->ErrorInfo . '
      '; - break; //Abandon sending - } else { - echo "Message sent to :" . $row['full_name'] . ' (' . str_replace("@", "@", $row['email']) . ')
      '; - //Mark it as sent in the DB - mysql_query( - "UPDATE mailinglist SET sent = true WHERE email = '" . mysql_real_escape_string($row['email'], $mysql) . "'" - ); - } - // Clear all addresses and attachments for next loop - $mail->clearAddresses(); - $mail->clearAttachments(); -} diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/pop_before_smtp.phps b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/pop_before_smtp.phps deleted file mode 100644 index fc4c7387..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/pop_before_smtp.phps +++ /dev/null @@ -1,60 +0,0 @@ - - - - - PHPMailer - POP-before-SMTP test - - -isSMTP(); - //Enable SMTP debugging - // 0 = off (for production use) - // 1 = client messages - // 2 = client and server messages - $mail->SMTPDebug = 2; - //Ask for HTML-friendly debug output - $mail->Debugoutput = 'html'; - //Set the hostname of the mail server - $mail->Host = "mail.example.com"; - //Set the SMTP port number - likely to be 25, 465 or 587 - $mail->Port = 25; - //Whether to use SMTP authentication - $mail->SMTPAuth = false; - //Set who the message is to be sent from - $mail->setFrom('from@example.com', 'First Last'); - //Set an alternative reply-to address - $mail->addReplyTo('replyto@example.com', 'First Last'); - //Set who the message is to be sent to - $mail->addAddress('whoto@example.com', 'John Doe'); - //Set the subject line - $mail->Subject = 'PHPMailer POP-before-SMTP test'; - //Read an HTML message body from an external file, convert referenced images to embedded, - //and convert the HTML into a basic plain-text alternative body - $mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - //Replace the plain text body with one created manually - $mail->AltBody = 'This is a plain-text message body'; - //Attach an image file - $mail->addAttachment('images/phpmailer_mini.png'); - //send the message - //Note that we don't need check the response from this because it will throw an exception if it has trouble - $mail->send(); - echo "Message sent!"; -} catch (phpmailerException $e) { - echo $e->errorMessage(); //Pretty error messages from PHPMailer -} catch (Exception $e) { - echo $e->getMessage(); //Boring error messages from anything else! -} -?> - - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/scripts/XRegExp.js b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/scripts/XRegExp.js deleted file mode 100644 index ebdb9c94..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/scripts/XRegExp.js +++ /dev/null @@ -1,664 +0,0 @@ -// XRegExp 1.5.1 -// (c) 2007-2012 Steven Levithan -// MIT License -// -// Provides an augmented, extensible, cross-browser implementation of regular expressions, -// including support for additional syntax, flags, and methods - -var XRegExp; - -if (XRegExp) { - // Avoid running twice, since that would break references to native globals - throw Error("can't load XRegExp twice in the same frame"); -} - -// Run within an anonymous function to protect variables and avoid new globals -(function (undefined) { - - //--------------------------------- - // Constructor - //--------------------------------- - - // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native - // regular expression in that additional syntax and flags are supported and cross-browser - // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and - // converts to type XRegExp - XRegExp = function (pattern, flags) { - var output = [], - currScope = XRegExp.OUTSIDE_CLASS, - pos = 0, - context, tokenResult, match, chr, regex; - - if (XRegExp.isRegExp(pattern)) { - if (flags !== undefined) - throw TypeError("can't supply flags when constructing one RegExp from another"); - return clone(pattern); - } - // Tokens become part of the regex construction process, so protect against infinite - // recursion when an XRegExp is constructed within a token handler or trigger - if (isInsideConstructor) - throw Error("can't call the XRegExp constructor within token definition functions"); - - flags = flags || ""; - context = { // `this` object for custom tokens - hasNamedCapture: false, - captureNames: [], - hasFlag: function (flag) {return flags.indexOf(flag) > -1;}, - setFlag: function (flag) {flags += flag;} - }; - - while (pos < pattern.length) { - // Check for custom tokens at the current position - tokenResult = runTokens(pattern, pos, currScope, context); - - if (tokenResult) { - output.push(tokenResult.output); - pos += (tokenResult.match[0].length || 1); - } else { - // Check for native multicharacter metasequences (excluding character classes) at - // the current position - if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) { - output.push(match[0]); - pos += match[0].length; - } else { - chr = pattern.charAt(pos); - if (chr === "[") - currScope = XRegExp.INSIDE_CLASS; - else if (chr === "]") - currScope = XRegExp.OUTSIDE_CLASS; - // Advance position one character - output.push(chr); - pos++; - } - } - } - - regex = RegExp(output.join(""), nativ.replace.call(flags, flagClip, "")); - regex._xregexp = { - source: pattern, - captureNames: context.hasNamedCapture ? context.captureNames : null - }; - return regex; - }; - - - //--------------------------------- - // Public properties - //--------------------------------- - - XRegExp.version = "1.5.1"; - - // Token scope bitflags - XRegExp.INSIDE_CLASS = 1; - XRegExp.OUTSIDE_CLASS = 2; - - - //--------------------------------- - // Private variables - //--------------------------------- - - var replacementToken = /\$(?:(\d\d?|[$&`'])|{([$\w]+)})/g, - flagClip = /[^gimy]+|([\s\S])(?=[\s\S]*\1)/g, // Nonnative and duplicate flags - quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/, - isInsideConstructor = false, - tokens = [], - // Copy native globals for reference ("native" is an ES3 reserved keyword) - nativ = { - exec: RegExp.prototype.exec, - test: RegExp.prototype.test, - match: String.prototype.match, - replace: String.prototype.replace, - split: String.prototype.split - }, - compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups - compliantLastIndexIncrement = function () { - var x = /^/g; - nativ.test.call(x, ""); - return !x.lastIndex; - }(), - hasNativeY = RegExp.prototype.sticky !== undefined, - nativeTokens = {}; - - // `nativeTokens` match native multicharacter metasequences only (including deprecated octals, - // excluding character classes) - nativeTokens[XRegExp.INSIDE_CLASS] = /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/; - nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/; - - - //--------------------------------- - // Public methods - //--------------------------------- - - // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by - // the XRegExp library and can be used to create XRegExp plugins. This function is intended for - // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can - // be disabled by `XRegExp.freezeTokens` - XRegExp.addToken = function (regex, handler, scope, trigger) { - tokens.push({ - pattern: clone(regex, "g" + (hasNativeY ? "y" : "")), - handler: handler, - scope: scope || XRegExp.OUTSIDE_CLASS, - trigger: trigger || null - }); - }; - - // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag - // combination has previously been cached, the cached copy is returned; otherwise the newly - // created regex is cached - XRegExp.cache = function (pattern, flags) { - var key = pattern + "/" + (flags || ""); - return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags)); - }; - - // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh - // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global` - // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve - // special properties required for named capture - XRegExp.copyAsGlobal = function (regex) { - return clone(regex, "g"); - }; - - // Accepts a string; returns the string with regex metacharacters escaped. The returned string - // can safely be used at any point within a regex to match the provided literal string. Escaped - // characters are [ ] { } ( ) * + ? - . , \ ^ $ | # and whitespace - XRegExp.escape = function (str) { - return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); - }; - - // Accepts a string to search, regex to search with, position to start the search within the - // string (default: 0), and an optional Boolean indicating whether matches must start at-or- - // after the position or at the specified position only. This function ignores the `lastIndex` - // of the provided regex in its own handling, but updates the property for compatibility - XRegExp.execAt = function (str, regex, pos, anchored) { - var r2 = clone(regex, "g" + ((anchored && hasNativeY) ? "y" : "")), - match; - r2.lastIndex = pos = pos || 0; - match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.) - if (anchored && match && match.index !== pos) - match = null; - if (regex.global) - regex.lastIndex = match ? r2.lastIndex : 0; - return match; - }; - - // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing - // syntax and flag changes. Should be run after XRegExp and any plugins are loaded - XRegExp.freezeTokens = function () { - XRegExp.addToken = function () { - throw Error("can't run addToken after freezeTokens"); - }; - }; - - // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object. - // Note that this is also `true` for regex literals and regexes created by the `XRegExp` - // constructor. This works correctly for variables created in another frame, when `instanceof` - // and `constructor` checks would fail to work as intended - XRegExp.isRegExp = function (o) { - return Object.prototype.toString.call(o) === "[object RegExp]"; - }; - - // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to - // iterate over regex matches compared to the traditional approaches of subverting - // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop - XRegExp.iterate = function (str, regex, callback, context) { - var r2 = clone(regex, "g"), - i = -1, match; - while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.) - if (regex.global) - regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback` - callback.call(context, match, ++i, str, regex); - if (r2.lastIndex === match.index) - r2.lastIndex++; - } - if (regex.global) - regex.lastIndex = 0; - }; - - // Accepts a string and an array of regexes; returns the result of using each successive regex - // to search within the matches of the previous regex. The array of regexes can also contain - // objects with `regex` and `backref` properties, in which case the named or numbered back- - // references specified are passed forward to the next regex or returned. E.g.: - // var xregexpImgFileNames = XRegExp.matchChain(html, [ - // {regex: /]+)>/i, backref: 1}, // tag attributes - // {regex: XRegExp('(?ix) \\s src=" (? [^"]+ )'), backref: "src"}, // src attribute values - // {regex: XRegExp("^http://xregexp\\.com(/[^#?]+)", "i"), backref: 1}, // xregexp.com paths - // /[^\/]+$/ // filenames (strip directory paths) - // ]); - XRegExp.matchChain = function (str, chain) { - return function recurseChain (values, level) { - var item = chain[level].regex ? chain[level] : {regex: chain[level]}, - regex = clone(item.regex, "g"), - matches = [], i; - for (i = 0; i < values.length; i++) { - XRegExp.iterate(values[i], regex, function (match) { - matches.push(item.backref ? (match[item.backref] || "") : match[0]); - }); - } - return ((level === chain.length - 1) || !matches.length) ? - matches : recurseChain(matches, level + 1); - }([str], 0); - }; - - - //--------------------------------- - // New RegExp prototype methods - //--------------------------------- - - // Accepts a context object and arguments array; returns the result of calling `exec` with the - // first value in the arguments array. the context is ignored but is accepted for congruity - // with `Function.prototype.apply` - RegExp.prototype.apply = function (context, args) { - return this.exec(args[0]); - }; - - // Accepts a context object and string; returns the result of calling `exec` with the provided - // string. the context is ignored but is accepted for congruity with `Function.prototype.call` - RegExp.prototype.call = function (context, str) { - return this.exec(str); - }; - - - //--------------------------------- - // Overriden native methods - //--------------------------------- - - // Adds named capture support (with backreferences returned as `result.name`), and fixes two - // cross-browser issues per ES3: - // - Captured values for nonparticipating capturing groups should be returned as `undefined`, - // rather than the empty string. - // - `lastIndex` should not be incremented after zero-length matches. - RegExp.prototype.exec = function (str) { - var match, name, r2, origLastIndex; - if (!this.global) - origLastIndex = this.lastIndex; - match = nativ.exec.apply(this, arguments); - if (match) { - // Fix browsers whose `exec` methods don't consistently return `undefined` for - // nonparticipating capturing groups - if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) { - r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", "")); - // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed - // matching due to characters outside the match - nativ.replace.call((str + "").slice(match.index), r2, function () { - for (var i = 1; i < arguments.length - 2; i++) { - if (arguments[i] === undefined) - match[i] = undefined; - } - }); - } - // Attach named capture properties - if (this._xregexp && this._xregexp.captureNames) { - for (var i = 1; i < match.length; i++) { - name = this._xregexp.captureNames[i - 1]; - if (name) - match[name] = match[i]; - } - } - // Fix browsers that increment `lastIndex` after zero-length matches - if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) - this.lastIndex--; - } - if (!this.global) - this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) - return match; - }; - - // Fix browser bugs in native method - RegExp.prototype.test = function (str) { - // Use the native `exec` to skip some processing overhead, even though the altered - // `exec` would take care of the `lastIndex` fixes - var match, origLastIndex; - if (!this.global) - origLastIndex = this.lastIndex; - match = nativ.exec.call(this, str); - // Fix browsers that increment `lastIndex` after zero-length matches - if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) - this.lastIndex--; - if (!this.global) - this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) - return !!match; - }; - - // Adds named capture support and fixes browser bugs in native method - String.prototype.match = function (regex) { - if (!XRegExp.isRegExp(regex)) - regex = RegExp(regex); // Native `RegExp` - if (regex.global) { - var result = nativ.match.apply(this, arguments); - regex.lastIndex = 0; // Fix IE bug - return result; - } - return regex.exec(this); // Run the altered `exec` - }; - - // Adds support for `${n}` tokens for named and numbered backreferences in replacement text, - // and provides named backreferences to replacement functions as `arguments[0].name`. Also - // fixes cross-browser differences in replacement text syntax when performing a replacement - // using a nonregex search value, and the value of replacement regexes' `lastIndex` property - // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary - // third (`flags`) parameter - String.prototype.replace = function (search, replacement) { - var isRegex = XRegExp.isRegExp(search), - captureNames, result, str, origLastIndex; - - // There are too many combinations of search/replacement types/values and browser bugs that - // preclude passing to native `replace`, so don't try - //if (...) - // return nativ.replace.apply(this, arguments); - - if (isRegex) { - if (search._xregexp) - captureNames = search._xregexp.captureNames; // Array or `null` - if (!search.global) - origLastIndex = search.lastIndex; - } else { - search = search + ""; // Type conversion - } - - if (Object.prototype.toString.call(replacement) === "[object Function]") { - result = nativ.replace.call(this + "", search, function () { - if (captureNames) { - // Change the `arguments[0]` string primitive to a String object which can store properties - arguments[0] = new String(arguments[0]); - // Store named backreferences on `arguments[0]` - for (var i = 0; i < captureNames.length; i++) { - if (captureNames[i]) - arguments[0][captureNames[i]] = arguments[i + 1]; - } - } - // Update `lastIndex` before calling `replacement` (fix browsers) - if (isRegex && search.global) - search.lastIndex = arguments[arguments.length - 2] + arguments[0].length; - return replacement.apply(null, arguments); - }); - } else { - str = this + ""; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`) - result = nativ.replace.call(str, search, function () { - var args = arguments; // Keep this function's `arguments` available through closure - return nativ.replace.call(replacement + "", replacementToken, function ($0, $1, $2) { - // Numbered backreference (without delimiters) or special variable - if ($1) { - switch ($1) { - case "$": return "$"; - case "&": return args[0]; - case "`": return args[args.length - 1].slice(0, args[args.length - 2]); - case "'": return args[args.length - 1].slice(args[args.length - 2] + args[0].length); - // Numbered backreference - default: - // What does "$10" mean? - // - Backreference 10, if 10 or more capturing groups exist - // - Backreference 1 followed by "0", if 1-9 capturing groups exist - // - Otherwise, it's the string "$10" - // Also note: - // - Backreferences cannot be more than two digits (enforced by `replacementToken`) - // - "$01" is equivalent to "$1" if a capturing group exists, otherwise it's the string "$01" - // - There is no "$0" token ("$&" is the entire match) - var literalNumbers = ""; - $1 = +$1; // Type conversion; drop leading zero - if (!$1) // `$1` was "0" or "00" - return $0; - while ($1 > args.length - 3) { - literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers; - $1 = Math.floor($1 / 10); // Drop the last digit - } - return ($1 ? args[$1] || "" : "$") + literalNumbers; - } - // Named backreference or delimited numbered backreference - } else { - // What does "${n}" mean? - // - Backreference to numbered capture n. Two differences from "$n": - // - n can be more than two digits - // - Backreference 0 is allowed, and is the entire match - // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture - // - Otherwise, it's the string "${n}" - var n = +$2; // Type conversion; drop leading zeros - if (n <= args.length - 3) - return args[n]; - n = captureNames ? indexOf(captureNames, $2) : -1; - return n > -1 ? args[n + 1] : $0; - } - }); - }); - } - - if (isRegex) { - if (search.global) - search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows) - else - search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) - } - - return result; - }; - - // A consistent cross-browser, ES3 compliant `split` - String.prototype.split = function (s /* separator */, limit) { - // If separator `s` is not a regex, use the native `split` - if (!XRegExp.isRegExp(s)) - return nativ.split.apply(this, arguments); - - var str = this + "", // Type conversion - output = [], - lastLastIndex = 0, - match, lastLength; - - // Behavior for `limit`: if it's... - // - `undefined`: No limit - // - `NaN` or zero: Return an empty array - // - A positive number: Use `Math.floor(limit)` - // - A negative number: No limit - // - Other: Type-convert, then use the above rules - if (limit === undefined || +limit < 0) { - limit = Infinity; - } else { - limit = Math.floor(+limit); - if (!limit) - return []; - } - - // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero - // and restore it to its original value when we're done using the regex - s = XRegExp.copyAsGlobal(s); - - while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.) - if (s.lastIndex > lastLastIndex) { - output.push(str.slice(lastLastIndex, match.index)); - - if (match.length > 1 && match.index < str.length) - Array.prototype.push.apply(output, match.slice(1)); - - lastLength = match[0].length; - lastLastIndex = s.lastIndex; - - if (output.length >= limit) - break; - } - - if (s.lastIndex === match.index) - s.lastIndex++; - } - - if (lastLastIndex === str.length) { - if (!nativ.test.call(s, "") || lastLength) - output.push(""); - } else { - output.push(str.slice(lastLastIndex)); - } - - return output.length > limit ? output.slice(0, limit) : output; - }; - - - //--------------------------------- - // Private helper functions - //--------------------------------- - - // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp` - // instance with a fresh `lastIndex` (set to zero), preserving properties required for named - // capture. Also allows adding new flags in the process of copying the regex - function clone (regex, additionalFlags) { - if (!XRegExp.isRegExp(regex)) - throw TypeError("type RegExp expected"); - var x = regex._xregexp; - regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || "")); - if (x) { - regex._xregexp = { - source: x.source, - captureNames: x.captureNames ? x.captureNames.slice(0) : null - }; - } - return regex; - } - - function getNativeFlags (regex) { - return (regex.global ? "g" : "") + - (regex.ignoreCase ? "i" : "") + - (regex.multiline ? "m" : "") + - (regex.extended ? "x" : "") + // Proposed for ES4; included in AS3 - (regex.sticky ? "y" : ""); - } - - function runTokens (pattern, index, scope, context) { - var i = tokens.length, - result, match, t; - // Protect against constructing XRegExps within token handler and trigger functions - isInsideConstructor = true; - // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws - try { - while (i--) { // Run in reverse order - t = tokens[i]; - if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) { - t.pattern.lastIndex = index; - match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc. - if (match && match.index === index) { - result = { - output: t.handler.call(context, match, scope), - match: match - }; - break; - } - } - } - } catch (err) { - throw err; - } finally { - isInsideConstructor = false; - } - return result; - } - - function indexOf (array, item, from) { - if (Array.prototype.indexOf) // Use the native array method if available - return array.indexOf(item, from); - for (var i = from || 0; i < array.length; i++) { - if (array[i] === item) - return i; - } - return -1; - } - - - //--------------------------------- - // Built-in tokens - //--------------------------------- - - // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the - // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS` - - // Comment pattern: (?# ) - XRegExp.addToken( - /\(\?#[^)]*\)/, - function (match) { - // Keep tokens separated unless the following token is a quantifier - return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; - } - ); - - // Capturing group (match the opening parenthesis only). - // Required for support of named capturing groups - XRegExp.addToken( - /\((?!\?)/, - function () { - this.captureNames.push(null); - return "("; - } - ); - - // Named capturing group (match the opening delimiter only): (? - XRegExp.addToken( - /\(\?<([$\w]+)>/, - function (match) { - this.captureNames.push(match[1]); - this.hasNamedCapture = true; - return "("; - } - ); - - // Named backreference: \k - XRegExp.addToken( - /\\k<([\w$]+)>/, - function (match) { - var index = indexOf(this.captureNames, match[1]); - // Keep backreferences separate from subsequent literal numbers. Preserve back- - // references to named groups that are undefined at this point as literal strings - return index > -1 ? - "\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? "" : "(?:)") : - match[0]; - } - ); - - // Empty character class: [] or [^] - XRegExp.addToken( - /\[\^?]/, - function (match) { - // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S]. - // (?!) should work like \b\B, but is unreliable in Firefox - return match[0] === "[]" ? "\\b\\B" : "[\\s\\S]"; - } - ); - - // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx) - // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc. - XRegExp.addToken( - /^\(\?([imsx]+)\)/, - function (match) { - this.setFlag(match[1]); - return ""; - } - ); - - // Whitespace and comments, in free-spacing (aka extended) mode only - XRegExp.addToken( - /(?:\s+|#.*)+/, - function (match) { - // Keep tokens separated unless the following token is a quantifier - return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; - }, - XRegExp.OUTSIDE_CLASS, - function () {return this.hasFlag("x");} - ); - - // Dot, in dotall (aka singleline) mode only - XRegExp.addToken( - /\./, - function () {return "[\\s\\S]";}, - XRegExp.OUTSIDE_CLASS, - function () {return this.hasFlag("s");} - ); - - - //--------------------------------- - // Backward compatibility - //--------------------------------- - - // Uncomment the following block for compatibility with XRegExp 1.0-1.2: - /* - XRegExp.matchWithinChain = XRegExp.matchChain; - RegExp.prototype.addFlags = function (s) {return clone(this, s);}; - RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;}; - RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);}; - RegExp.prototype.validate = function (s) {var r = RegExp("^(?:" + this.source + ")$(?!\\s)", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;}; - */ - -})(); - diff --git a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/scripts/shAutoloader.js b/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/scripts/shAutoloader.js deleted file mode 100644 index 9f5942ee..00000000 --- a/profiles/commerce_kickstart/modules/contrib/mailjet/includes/phpmailer/examples/scripts/shAutoloader.js +++ /dev/null @@ -1,122 +0,0 @@ -(function() { - -var sh = SyntaxHighlighter; - -/** - * Provides functionality to dynamically load only the brushes that a needed to render the current page. - * - * There are two syntaxes that autoload understands. For example: - * - * SyntaxHighlighter.autoloader( - * [ 'applescript', 'Scripts/shBrushAppleScript.js' ], - * [ 'actionscript3', 'as3', 'Scripts/shBrushAS3.js' ] - * ); - * - * or a more easily comprehendable one: - * - * SyntaxHighlighter.autoloader( - * 'applescript Scripts/shBrushAppleScript.js', - * 'actionscript3 as3 Scripts/shBrushAS3.js' - * ); - */ -sh.autoloader = function() -{ - var list = arguments, - elements = sh.findElements(), - brushes = {}, - scripts = {}, - all = SyntaxHighlighter.all, - allCalled = false, - allParams = null, - i - ; - - SyntaxHighlighter.all = function(params) - { - allParams = params; - allCalled = true; - }; - - function addBrush(aliases, url) - { - for (var i = 0; i < aliases.length; i++) - brushes[aliases[i]] = url; - }; - - function getAliases(item) - { - return item.pop - ? item - : item.split(/\s+/) - ; - } - - // create table of aliases and script urls - for (i = 0; i < list.length; i++) - { - var aliases = getAliases(list[i]), - url = aliases.pop() - ; - - addBrush(aliases, url); - } - - // dynamically add