Bläddra i källkod

added example for developpers module

Bachir Soussi Chiadmi 7 år sedan
förälder
incheckning
5ec2f4311c
100 ändrade filer med 6835 tillägg och 0 borttagningar
  1. 95 0
      sites/all/modules/examples/examples/.eslintrc
  2. 63 0
      sites/all/modules/examples/examples/CONTRIBUTING.md
  3. 339 0
      sites/all/modules/examples/examples/LICENSE.txt
  4. 79 0
      sites/all/modules/examples/examples/README.md
  5. 86 0
      sites/all/modules/examples/examples/STANDARDS.md
  6. 114 0
      sites/all/modules/examples/examples/TESTING.md
  7. 14 0
      sites/all/modules/examples/examples/ajax_example/ajax_example.info.yml
  8. 7 0
      sites/all/modules/examples/examples/ajax_example/ajax_example.libraries.yml
  9. 72 0
      sites/all/modules/examples/examples/ajax_example/ajax_example.links.menu.yml
  10. 23 0
      sites/all/modules/examples/examples/ajax_example/ajax_example.module
  11. 96 0
      sites/all/modules/examples/examples/ajax_example/ajax_example.routing.yml
  12. 9 0
      sites/all/modules/examples/examples/ajax_example/css/ajax-example-base.css
  13. 19 0
      sites/all/modules/examples/examples/ajax_example/js/ajax-example.js
  14. 112 0
      sites/all/modules/examples/examples/ajax_example/src/Controller/AjaxExampleController.php
  15. 124 0
      sites/all/modules/examples/examples/ajax_example/src/Form/Autotextfields.php
  16. 269 0
      sites/all/modules/examples/examples/ajax_example/src/Form/DependentDropdown.php
  17. 222 0
      sites/all/modules/examples/examples/ajax_example/src/Form/DynamicFormSections.php
  18. 126 0
      sites/all/modules/examples/examples/ajax_example/src/Form/EntityAutocomplete.php
  19. 80 0
      sites/all/modules/examples/examples/ajax_example/src/Form/Simplest.php
  20. 73 0
      sites/all/modules/examples/examples/ajax_example/src/Form/SubmitDriven.php
  21. 229 0
      sites/all/modules/examples/examples/ajax_example/src/Form/Wizard.php
  22. 29 0
      sites/all/modules/examples/examples/ajax_example/templates/description.html.twig
  23. 79 0
      sites/all/modules/examples/examples/ajax_example/tests/src/Functional/AjaxExampleMenuTest.php
  24. 74 0
      sites/all/modules/examples/examples/ajax_example/tests/src/Functional/DependentDropdownTest.php
  25. 70 0
      sites/all/modules/examples/examples/ajax_example/tests/src/Functional/DynamicFormSectionsTest.php
  26. 80 0
      sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/AutotextfieldsTest.php
  27. 75 0
      sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/DependentDropdownTest.php
  28. 67 0
      sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/DynamicFormSectionsTest.php
  29. 69 0
      sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/EntityAutocompleteTest.php
  30. 60 0
      sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/SimplestTest.php
  31. 45 0
      sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/SubmitDrivenTest.php
  32. 14 0
      sites/all/modules/examples/examples/batch_example/batch_example.info.yml
  33. 83 0
      sites/all/modules/examples/examples/batch_example/batch_example.install
  34. 5 0
      sites/all/modules/examples/examples/batch_example/batch_example.links.menu.yml
  35. 133 0
      sites/all/modules/examples/examples/batch_example/batch_example.module
  36. 7 0
      sites/all/modules/examples/examples/batch_example/batch_example.routing.yml
  37. 148 0
      sites/all/modules/examples/examples/batch_example/src/Form/BatchExampleForm.php
  38. 40 0
      sites/all/modules/examples/examples/batch_example/tests/src/functional/BatchExampleWebTest.php
  39. 15 0
      sites/all/modules/examples/examples/block_example/block_example.info.yml
  40. 3 0
      sites/all/modules/examples/examples/block_example/block_example.links.menu.yml
  41. 53 0
      sites/all/modules/examples/examples/block_example/block_example.module
  42. 7 0
      sites/all/modules/examples/examples/block_example/block_example.routing.yml
  43. 7 0
      sites/all/modules/examples/examples/block_example/config/schema/block_example.schema.yml
  44. 19 0
      sites/all/modules/examples/examples/block_example/src/Controller/BlockExampleController.php
  45. 82 0
      sites/all/modules/examples/examples/block_example/src/Plugin/Block/ExampleConfigurableTextBlock.php
  46. 30 0
      sites/all/modules/examples/examples/block_example/src/Plugin/Block/ExampleEmptyBlock.php
  47. 26 0
      sites/all/modules/examples/examples/block_example/src/Plugin/Block/ExampleUppercaseBlock.php
  48. 15 0
      sites/all/modules/examples/examples/block_example/templates/description.html.twig
  49. 50 0
      sites/all/modules/examples/examples/block_example/tests/src/Functional/BlockExampleMenuTest.php
  50. 87 0
      sites/all/modules/examples/examples/block_example/tests/src/Functional/BlockExampleTest.php
  51. 14 0
      sites/all/modules/examples/examples/cache_example/cache_example.info.yml
  52. 4 0
      sites/all/modules/examples/examples/cache_example/cache_example.links.menu.yml
  53. 36 0
      sites/all/modules/examples/examples/cache_example/cache_example.module
  54. 6 0
      sites/all/modules/examples/examples/cache_example/cache_example.routing.yml
  55. 306 0
      sites/all/modules/examples/examples/cache_example/src/Form/CacheExampleForm.php
  56. 110 0
      sites/all/modules/examples/examples/cache_example/tests/src/Functional/CacheExampleTest.php
  57. 12 0
      sites/all/modules/examples/examples/composer.json
  58. 20 0
      sites/all/modules/examples/examples/config_entity_example/config/install/config_entity_example.robot.marvin.yml
  59. 28 0
      sites/all/modules/examples/examples/config_entity_example/config/schema/config_entity_example.schema.yml
  60. 13 0
      sites/all/modules/examples/examples/config_entity_example/config_entity_example.info.yml
  61. 14 0
      sites/all/modules/examples/examples/config_entity_example/config_entity_example.links.action.yml
  62. 3 0
      sites/all/modules/examples/examples/config_entity_example/config_entity_example.links.menu.yml
  63. 42 0
      sites/all/modules/examples/examples/config_entity_example/config_entity_example.module
  64. 4 0
      sites/all/modules/examples/examples/config_entity_example/config_entity_example.permissions.yml
  65. 56 0
      sites/all/modules/examples/examples/config_entity_example/config_entity_example.routing.yml
  66. 87 0
      sites/all/modules/examples/examples/config_entity_example/src/Controller/RobotListBuilder.php
  67. 89 0
      sites/all/modules/examples/examples/config_entity_example/src/Entity/Robot.php
  68. 35 0
      sites/all/modules/examples/examples/config_entity_example/src/Form/RobotAddForm.php
  69. 89 0
      sites/all/modules/examples/examples/config_entity_example/src/Form/RobotDeleteForm.php
  70. 35 0
      sites/all/modules/examples/examples/config_entity_example/src/Form/RobotEditForm.php
  71. 221 0
      sites/all/modules/examples/examples/config_entity_example/src/Form/RobotFormBase.php
  72. 34 0
      sites/all/modules/examples/examples/config_entity_example/src/RobotAccessController.php
  73. 17 0
      sites/all/modules/examples/examples/config_entity_example/templates/description.html.twig
  74. 172 0
      sites/all/modules/examples/examples/config_entity_example/tests/src/Functional/ConfigEntityExampleTest.php
  75. 15 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.info.yml
  76. 11 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.links.action.yml
  77. 12 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.links.menu.yml
  78. 22 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.links.task.yml
  79. 45 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.module
  80. 10 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.permissions.yml
  81. 58 0
      sites/all/modules/examples/examples/content_entity_example/content_entity_example.routing.yml
  82. 47 0
      sites/all/modules/examples/examples/content_entity_example/src/ContactAccessControlHandler.php
  83. 18 0
      sites/all/modules/examples/examples/content_entity_example/src/ContactInterface.php
  84. 350 0
      sites/all/modules/examples/examples/content_entity_example/src/Entity/Contact.php
  85. 99 0
      sites/all/modules/examples/examples/content_entity_example/src/Entity/Controller/ContactListBuilder.php
  86. 56 0
      sites/all/modules/examples/examples/content_entity_example/src/Form/ContactDeleteForm.php
  87. 42 0
      sites/all/modules/examples/examples/content_entity_example/src/Form/ContactForm.php
  88. 39 0
      sites/all/modules/examples/examples/content_entity_example/src/Form/ContactSettingsForm.php
  89. 249 0
      sites/all/modules/examples/examples/content_entity_example/tests/src/Functional/ContentEntityExampleTest.php
  90. 4 0
      sites/all/modules/examples/examples/cron_example/config/install/cron_example.settings.yml
  91. 12 0
      sites/all/modules/examples/examples/cron_example/config/schema/cron_example.schema.yml
  92. 14 0
      sites/all/modules/examples/examples/cron_example/cron_example.info.yml
  93. 3 0
      sites/all/modules/examples/examples/cron_example/cron_example.links.menu.yml
  94. 56 0
      sites/all/modules/examples/examples/cron_example/cron_example.module
  95. 7 0
      sites/all/modules/examples/examples/cron_example/cron_example.routing.yml
  96. 256 0
      sites/all/modules/examples/examples/cron_example/src/Form/CronExampleForm.php
  97. 92 0
      sites/all/modules/examples/examples/cron_example/src/Plugin/QueueWorker/ReportWorkerBase.php
  98. 29 0
      sites/all/modules/examples/examples/cron_example/src/Plugin/QueueWorker/ReportWorkerOne.php
  99. 29 0
      sites/all/modules/examples/examples/cron_example/src/Plugin/QueueWorker/ReportWorkerTwo.php
  100. 90 0
      sites/all/modules/examples/examples/cron_example/tests/src/Functional/CronExampleTest.php

+ 95 - 0
sites/all/modules/examples/examples/.eslintrc

@@ -0,0 +1,95 @@
+{
+  "extends": "eslint:recommended",
+  "env": {
+    "browser": true
+  },
+  "globals": {
+    "Drupal": true,
+    "drupalSettings": true,
+    "drupalTranslations": true,
+    "domready": true,
+    "jQuery": true,
+    "_": true,
+    "matchMedia": true,
+    "Backbone": true,
+    "Modernizr": true,
+    "CKEDITOR": true
+  },
+  "rules": {
+    // Errors.
+    "array-bracket-spacing": [2, "never"],
+    "block-scoped-var": 2,
+    "brace-style": [2, "stroustrup", {"allowSingleLine": true}],
+    "comma-dangle": [2, "never"],
+    "comma-spacing": 2,
+    "comma-style": [2, "last"],
+    "computed-property-spacing": [2, "never"],
+    "curly": [2, "all"],
+    "eol-last": 2,
+    "eqeqeq": [2, "smart"],
+    "guard-for-in": 2,
+    "indent": [2, 2, {"SwitchCase": 1}],
+    "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
+    "keyword-spacing": [2, {"before": true, "after": true}],
+    "linebreak-style": [2, "unix"],
+    "lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
+    "new-parens": 2,
+    "no-array-constructor": 2,
+    "no-caller": 2,
+    "no-catch-shadow": 2,
+    "no-eval": 2,
+    "no-extend-native": 2,
+    "no-extra-bind": 2,
+    "no-extra-parens": [2, "functions"],
+    "no-implied-eval": 2,
+    "no-iterator": 2,
+    "no-label-var": 2,
+    "no-labels": 2,
+    "no-lone-blocks": 2,
+    "no-loop-func": 2,
+    "no-multi-spaces": 2,
+    "no-multi-str": 2,
+    "no-native-reassign": 2,
+    "no-nested-ternary": 2,
+    "no-new-func": 2,
+    "no-new-object": 2,
+    "no-new-wrappers": 2,
+    "no-octal-escape": 2,
+    "no-process-exit": 2,
+    "no-proto": 2,
+    "no-return-assign": 2,
+    "no-script-url": 2,
+    "no-sequences": 2,
+    "no-shadow-restricted-names": 2,
+    "no-spaced-func": 2,
+    "no-trailing-spaces": 2,
+    "no-undef-init": 2,
+    "no-undefined": 2,
+    "no-unused-expressions": 2,
+    "no-unused-vars": [2, {"vars": "all", "args": "none"}],
+    "no-with": 2,
+    "object-curly-spacing": [2, "never"],
+    "one-var": [2, "never"],
+    "quote-props": [2, "consistent-as-needed"],
+    "quotes": [2, "single", "avoid-escape"],
+    "semi": [2, "always"],
+    "semi-spacing": [2, {"before": false, "after": true}],
+    "space-before-blocks": [2, "always"],
+    "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
+    "space-in-parens": [2, "never"],
+    "space-infix-ops": 2,
+    "space-unary-ops": [2, { "words": true, "nonwords": false }],
+    "spaced-comment": [2, "always"],
+    "strict": 2,
+    "yoda": [2, "never"],
+    // Warnings.
+    "max-nested-callbacks": [1, 3],
+    "valid-jsdoc": [1, {
+      "prefer": {
+        "returns": "return",
+        "property": "prop"
+      },
+      "requireReturn": false
+    }]
+  }
+}

+ 63 - 0
sites/all/modules/examples/examples/CONTRIBUTING.md

@@ -0,0 +1,63 @@
+Drupal Examples For Developers: Contributor's Guide
+===================================================
+
+Examples for Developers is a community project.
+
+If you'd like to participate in Examples development, thank you!
+
+If you are new to Drupal or open source in general, have no fear. Examples is
+an easy-going project where you can learn some things about how to work on a
+collaborative project in a friendly environment.
+
+
+Policies
+--------
+
+Examples follows the Drupal core process as much as possible.
+
+Contributions thus need to be similar in quality to Drupal core patches.
+Contributions will need to meet the following minimum standards:
+
+### Normal Drupal issue process
+
+Drupal projects use patches related to issues. You should know how to make a
+patch and an interdiff using git. It's fine to develop on github or
+what-have-you, but eventually it has to be a patch that can be reviewed in the
+normal Drupal issue process. See the list of resources for some information on
+how to do do this.
+
+Your patch will also need to be reviewed by someone other than yourself. Learn
+about the review process in the resources section.
+
+### DrupalCI
+
+Examples uses the Drupal automated testing system to verify the applicability of
+patches. See `TESTING.md` for details.
+
+### Drupal coding standards
+
+All code in Examples should adhere to the Drupal core coding standards. Examples
+uses the Drupal Coder project and PHP_CodeSniffer to enforce coding standards.
+Think of this as another test your code must pass. See `STANDARDS.md` for
+details.
+
+
+Resources
+---------
+
+### Novice
+
+Drupal novice contribution guide: https://www.drupal.org/novice
+
+Drupal contribution guide: https://www.drupal.org/contribute
+
+What's a patch? https://www.drupal.org/patch
+
+How to make a patch with git: https://www.drupal.org/node/707484
+
+### Everyone
+
+How to review a patch: https://www.drupal.org/patch/review
+
+See `STANDARDS.md` and `TESTING.md` for information on how to run a coding
+standards test, and also how to run the tests themselves.

+ 339 - 0
sites/all/modules/examples/examples/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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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.

+ 79 - 0
sites/all/modules/examples/examples/README.md

@@ -0,0 +1,79 @@
+Examples for Developers
+=======================
+
+Project site: http://drupal.org/project/examples
+
+Code: https://drupal.org/project/examples/git-instructions
+
+Issues: https://drupal.org/project/issues/examples
+
+What Is This?
+-------------
+
+This set of modules is intended to provide working examples of Drupal 8's
+features and APIs.  The modules strive to be simple, well documented and
+modification friendly, in order to help developers quickly learn their inner
+workings.
+
+These examples are meant to teach you about code-level development for Drupal
+8. Some solutions might be better served using a contributed module, so that
+you don't end up having to re-invent the wheel in PHP. When in doubt, look for
+an existing contrib project that already does what you want, and contribute to
+that project.
+
+
+How To Use The Examples
+-----------------------
+
+There are three main ways to interact with the examples in this project:
+
+1. Enable the modules and use them within Drupal. Not all modules will have
+obvious things to see within your Drupal installation. For instance, while the
+Page and Form API examples will display forms, the Database API example does not
+have much that is visible within Drupal.
+
+2. Read the code. Much effort has gone into making the example code readable,
+not only in terms of the code itself, but also the extensive inline comments
+and documentation blocks.
+
+3. Browse the code and documentation on the web. There are two main places to
+do this:
+
+* https://api.drupal.org/api/examples is the main API site for all of Drupal.
+It has all manner of cross-linked references between the example code and the
+APIs being demonstrated. All of the Doxygen-based comments in the code are
+parsed and made browseable here.
+
+* http://drupalcode.org/project/examples.git allows you to browse the git
+repository for the Examples project.
+
+This project ships with a composer.json file. This is meant to illustrate how
+to provide a composer.json file for a Drupal contrib project. You can read more
+about how to use Composer with Drupal here: https://www.drupal.org/node/2718229
+
+How To Install The Modules
+--------------------------
+
+1. The Examples project installs like any other Drupal module. There is extensive
+documentation on how to do this here:
+https://drupal.org/documentation/install/modules-themes/modules-8 But essentially:
+Download the tarball and expand it into the modules/ directory in your Drupal 8
+installation.
+
+2. Within Drupal, enable any Example sub-module you wish to explore in Admin
+menu > Extend.
+
+3. Rebuild access permissions if you are prompted to.
+
+4. Profit!  The links for Examples material will appear in your Tools menu. This
+menu appears on the left sidebar by default. You'll need to reenable it if you
+removed it.
+
+Having seen the behavior of the various example modules, you can move on to
+reading the code, experimenting with it, and hopefully grasp how things work.
+
+If you find a problem, incorrect comment, obsolete or improper code or such,
+please search for an issue about it at http://drupal.org/project/issues/examples
+If there isn't already an issue for it, please create a new one.
+
+Thanks.

+ 86 - 0
sites/all/modules/examples/examples/STANDARDS.md

@@ -0,0 +1,86 @@
+Developing with Coding Standards for Examples for Developers
+============================================================
+
+Examples uses mostly the same coding standards as Drupal core.
+
+If you see a discrepancy between the coding standards tools used by core and
+those used by Examples, please file an issue so that Examples can follow core.
+
+Examples uses the `phpcs` tool to allow for checking PHP coding standards. We
+use the `drupal/coder` project for Drupal-specific coding standards.
+
+We also use `eslint` for JavaScript coding standards, and `csslint` for CSS.
+
+Examples has a `phpcs.xml.dist` file at the root of the project. phpcs uses this
+file to specify the current coding standards 'sniffs' which code in the project
+must pass.
+
+Contributors should install `phpcs` in their local Drupal installation, and then
+use that to run `phpcs` against Examples as part of their development and review
+process. (See details below on how to install and run this tool.)
+
+Contributors can also patch the `phpcs.xml.dist` file itself, in order to fix
+the codebase to pass a given rule or sniff. Patches which do this should be
+limited to a single rule or sniff, in order make the patch easier to review.
+
+Examples also uses the Coder project (`drupal/coder`), which adds additional
+Drupal-specific coding standards. We're currently locked to Coder version
+8.2.8, but this should change to reflect the state of core's coding standards.
+
+Installing phpcs
+----------------
+
+Current versions of Drupal 8 core require phpcs and Coder as dev dependencies.
+That means they're already probably installed in your core vendor/ directory.
+
+We need to tell `phpcs` to use the Drupal coding standard provided by Coder,
+because it isn't configured that way by default.
+
+Like this:
+
+    $ cd my/drupal/root/
+    $ ./vendor/bin/phpcs --config-set installed_paths /full/path/to/drupal/vendor/drupal/coder/coder_sniffer/
+    // phpcs now knows how to find the Drupal standard. You can test it:
+    $ cd core
+    $ ../vendor/bin/phpcs -e --standard=Drupal
+    // Shows you a bunch of Drupal-related sniffs.
+
+Running phpcs
+-------------
+
+Now you can run phpcs:
+
+    $ cd modules/examples
+    $ ../../vendor/bin/phpcs -ps
+    // phpcs uses Examples' phpcs.xml.dist to verify coding standards.
+    // -p shows you progress dots.
+    // -s shows you sniff errors in the report.
+
+If there are errors, they can sometimes be fixed with `phpcbf`, which is
+part of the `phpcs` package.
+
+    $ ../../vendor/bin/phpcbf
+    // phpcbf now performs automated fixes.
+
+Always look at the changes to see what `phpcbf` did.
+
+And always re-run `phpcs` in order to discover whether `phpcbf` handled all the
+errors.
+
+Installing eslint
+-----------------
+
+`eslint` is a node.js tool. You can and probably should install it globally,
+since installing it locally would add files to the examples project.
+Instructions available here: https://www.npmjs.com/package/eslint
+
+Examples has an `.eslintrc` file which defines the JavaScript coding standard.
+This file should be identical to the current Drupal core standard.
+
+Running eslint
+--------------
+
+You can run eslint this way:
+
+    $ cd /path/to/examples
+    $ eslint .

+ 114 - 0
sites/all/modules/examples/examples/TESTING.md

@@ -0,0 +1,114 @@
+Testing Drupal Examples for Developers
+======================================
+
+The Drupal Examples for Developers project uses DrupalCI testing on drupal.org.
+
+That means: It runs the testbot on every patch that is marked as 'Needs Review.'
+
+Your patch might not get reviewed, and certainly won't get committed unless it
+passes the testbot.
+
+The testbot runs a script that's in your Drupal installation called
+`core/scripts/run-tests.sh`. You can run `run-tests.sh` manually and approximate
+the testbot's behavior.
+
+You can find information on how to run `run-tests.sh` locally here:
+https://www.drupal.org/node/645286
+
+Examples is always targeted to the dev branch of Drupal core for the latest
+release. As of this writing, the latest release of Drupal core is 8.2.5, which
+means development for Examples should be against the Drupal 8.2.x development
+branch. When Drupal 8.3.0 is released, we'll start targeting Examples to 8.3.x,
+and so on.
+
+You should at least run `run-tests.sh` locally against all the changes in your
+patch before uploading it.
+
+Keep in mind that unless you know you're changing behavior that is being tested
+for, the tests are not at fault. :-)
+
+Note also that, currently, using the `phpunit` tool under Drupal 8 will not find
+PHPUnit-based tests in submodules, such as phpunit_example. There is no
+suggested workaround for this, since there is no best practice to demonstrate as
+an example. There is, however, this issue in core:
+https://www.drupal.org/node/2499239
+
+How To Run The Tests In The Drupal UI
+-------------------------------------
+
+Generally, you should run tests from the command line. This is generally easier
+than using Drupal's testing UI. However, here's how you can do it that way:
+
+Enable the Testing module.
+
+Visit the test list page at `admin/config/development/testing`.
+
+Since the tests are organized by module, you can search for a module name and
+get all the tests for that module. For instance, type in 'node_type_example' for
+all the tests related to that module.
+
+Click the check boxes next to the tests you want to run. If you find this
+tedious, it's time to learn to use the command line. :-)
+
+Click 'Run Tests.' You're now running the tests.
+
+Step-by-step: How To Run The Tests.
+-----------------------------------
+
+Begin with an installed Drupal codebase. Make a codebase, set up the database,
+etc. Note that you can use an existing Drupal instance but the best practice is
+to start fresh. Something not working right? Try a new installation.
+
+Use the dev branch of core for the latest release of Drupal. As of this writing,
+it's 8.2.x. When Drupal 8.3.0 is released, we'll target 8.3.x.
+
+Open the terminal window and move to the root directory of the Drupal
+installation:
+
+	$ cd path/to/drupal
+
+Put Examples into the `modules/` folder of the Drupal installation. If you are
+doing development on Examples, you should have already checked out the git
+repository into `modules/`, like this:
+
+	$ git clone --branch 8.x-1.x https://git.drupal.org/project/examples.git modules/examples
+
+Now you can run `run-tests.sh`, which, despite having a `.sh` suffix is not a
+shell script. It's a PHP script.
+
+You'll use the `--directory` option to have the test runner scan the Examples
+module directory for tests.
+
+Also, importantly, if your test site has its own URL, you'll need to supply that
+with the `--url` option. For instance, under MAMP, you must specify
+`--url http://localhost:8888/`.
+
+You can also use `--concurrency` to speed up the test run, and `--browser` to
+see detailed test results in a web browser instead of just text output in the
+terminal.
+
+	$ php ./core/scripts/run-tests.sh --browser --concurrency 10 --url http://localhost:8888/ --directory modules/examples
+
+This should run all the tests present in Examples. If you add a test and it
+doesn't appear in the list of tests to run, then you'll need to double-check
+that it's in the proper test namespace and that the class name (and thus the
+file name) ends in Test.
+
+What Tests Should An Example Module Have?
+------------------------------------------
+
+Examples has a checklist for each module:
+https://www.drupal.org/node/2209627
+
+The reason we care about these tests is that we want the documentation
+of these APIs to be correct. If Core changes APIs, we want our tests to
+fail so that we know our documentation is incorrect.
+
+Our list of required tests includes:
+* Functional tests which verifies a 200 result for each route/path defined by
+    the module.
+* Functional tests of permission-based restrictions.
+* Functional tests which submit forms and verify that they behave as
+    expected.
+* Unit tests of unit-testable code.
+* Other. More. Better.

+ 14 - 0
sites/all/modules/examples/examples/ajax_example/ajax_example.info.yml

@@ -0,0 +1,14 @@
+name: 'AJAX Example'
+type: module
+description: 'An example module showing how to use Drupal AJAX forms.'
+package: 'Example modules'
+# core: 8.x
+dependencies:
+  - drupal:node
+  - examples:examples
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 7 - 0
sites/all/modules/examples/examples/ajax_example/ajax_example.libraries.yml

@@ -0,0 +1,7 @@
+ajax_example.library:
+  version: 1.x
+  css:
+    base:
+      css/ajax-example-base.css: {}
+  js:
+    js/ajax-example.js: {}

+ 72 - 0
sites/all/modules/examples/examples/ajax_example/ajax_example.links.menu.yml

@@ -0,0 +1,72 @@
+ajax_example.description:
+  title: 'AJAX Example'
+  route_name: 'ajax_example.description'
+  expanded: TRUE
+
+ajax_example.simplest:
+  title: 'Simplest AJAX example'
+  route_name: 'ajax_example.simplest'
+  parent: ajax_example.description
+  weight: 0
+
+ajax_example.submit-driven:
+  title: 'Submit-driven AJAX'
+  route_name: 'ajax_example.submit_driven_ajax'
+  parent: ajax_example.description
+  weight: 1
+
+ajax_example.render-link:
+  title: 'AJAX link in a render array'
+  route_name: 'ajax_example.ajax_link_render'
+  parent: ajax_example.description
+  weight: 2
+
+ajax_example.wizard-example:
+  title: 'Wizard example'
+  route_name: 'ajax_example.wizard'
+  parent: ajax_example.description
+  weight: 2
+
+ajax_example.wizard-examplenojs:
+  title: 'Wizard example w/JS turned off'
+  route_name: 'ajax_example.wizardnojs'
+  parent: ajax_example.description
+  weight: 3
+
+ajax_example.autocomplete-user:
+  title: 'Autocomplete user with entity_autocomplete'
+  route_name: 'ajax_example.autocomplete_user'
+  parent: ajax_example.description
+  weight: 4
+
+ajax_example.autotextfields:
+  title: 'Generate textfields'
+  route_name: 'ajax_example.autotextfields'
+  parent: ajax_example.description
+  weight: 5
+
+ajax_example.dependent-dropdown:
+  title: 'Dependent dropdown'
+  route_name: 'ajax_example.dependent_dropdown'
+  parent: ajax_example.description
+  weight: 6
+ajax_example.dependent-dropdown-nojs:
+  title: 'Dependent dropdown w/ no JS'
+  route_name: 'ajax_example.dependent_dropdown'
+  route_parameters:
+    nojs: nojs
+  parent: ajax_example.description
+  weight: 6
+
+ajax_example.dynamic-form-sections:
+  title: 'Dynamic form sections'
+  route_name: 'ajax_example.dynamic_form_sections'
+  parent: ajax_example.description
+  weight: 10
+ajax_example.dynamic-form-sections-nojs:
+  title: 'Dynamic form sections w/ no JS'
+  route_name: 'ajax_example.dynamic_form_sections'
+  route_parameters:
+    nojs: nojs
+  parent: ajax_example.description
+  weight: 10

+ 23 - 0
sites/all/modules/examples/examples/ajax_example/ajax_example.module

@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * AJAX Examples module file with basic examples.
+ */
+
+/**
+ * @defgroup ajax_example Example: AJAX
+ * @ingroup examples
+ * @{
+ * These examples show basic AJAX concepts.
+ *
+ * General documentation is available at
+ * @link ajax AJAX Framework documentation @endlink and at the
+ * @link http://drupal.org/node/752056 AJAX Forms handbook page @endlink.
+ *
+ * The several examples here demonstrate basic AJAX usage.
+ */
+
+/**
+ * @} End of "defgroup ajax_example".
+ */

+ 96 - 0
sites/all/modules/examples/examples/ajax_example/ajax_example.routing.yml

@@ -0,0 +1,96 @@
+ajax_example.description:
+  path: 'examples/ajax-example'
+  defaults:
+    _controller: '\Drupal\ajax_example\Controller\AjaxExampleController::description'
+    _title: 'AJAX Example'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.simplest:
+  path: 'examples/ajax-example/simplest'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Simplest'
+    _title: 'Simplest AJAX example'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.autotextfields:
+  path: 'examples/ajax-example/autotextfields'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Autotextfields'
+    _title: 'Generate textfields'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.submit_driven_ajax:
+  path: 'examples/ajax-example/submit-driven-ajax'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\SubmitDriven'
+    _title: 'Submit-driven AJAX'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dependent_dropdown:
+  path: 'examples/ajax-example/dependent-dropdown/{nojs}'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DependentDropdown'
+    _title: 'Dependent dropdown'
+    nojs: ajax
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dynamic_form_sections:
+  path: 'examples/ajax-example/dynamic-form-sections/{nojs}'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DynamicFormSections'
+    _title: 'Dynamic form sections'
+    nojs: 'ajax'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.wizard:
+  path: 'examples/ajax-example/wizard'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Wizard'
+    _title: 'Wizard with graceful degradation'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.wizardnojs:
+  path: 'examples/ajax-example/wizard-nojs/{no_js_use}'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Wizard'
+    _title: 'Wizard with graceful degradation, w/JS turned off'
+    no_js_use: TRUE
+  requirements:
+    _permission: 'access content'
+
+ajax_example.ajax_link_render:
+  path: 'examples/ajax-example/ajax-link-renderable'
+  defaults:
+    _controller: '\Drupal\ajax_example\Controller\AjaxExampleController::renderLinkRenderableArray'
+    _title: 'AJAX link from a render array'
+  requirements:
+    _permission: 'access content'
+
+# This route is for an AJAX callback. It is used by the AJAX system on
+# ajax_example.ajax_link_render. It has a {nojs} parameter, which gives us
+# a way to know whether the request is an AJAX request or is from some other
+# source.
+ajax_example.ajax_link_callback:
+  path: 'examples/ajax-example/ajax-link-callback/{nojs}'
+  defaults:
+    _controller: '\Drupal\ajax_example\Controller\AjaxExampleController::ajaxLinkCallback'
+    # We provide a default value for {nojs}, so that it can be an optional
+    # parameter.
+    nojs: 'nojs'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.autocomplete_user:
+  path: 'examples/ajax_example/user_autocomplete'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\EntityAutocomplete'
+    _title: 'Autocomplete users with entity_autocomplete.'
+  requirements:
+    _permission: 'access content'

+ 9 - 0
sites/all/modules/examples/examples/ajax_example/css/ajax-example-base.css

@@ -0,0 +1,9 @@
+/*
+ * @file
+ * CSS for ajax_example.
+ */
+
+/* Put some buttons inline, for nicer formatting. */
+.ajax-example-inline {
+  display: inline-block;
+}

+ 19 - 0
sites/all/modules/examples/examples/ajax_example/js/ajax-example.js

@@ -0,0 +1,19 @@
+/**
+ * @file
+ * JavaScript for ajax_example.
+ */
+
+(function ($) {
+
+  // Re-enable form elements that are disabled for non-ajax situations.
+  Drupal.behaviors.enableFormItemsForAjaxForms = {
+    attach: function () {
+      // If ajax is enabled, we want to hide items that are marked as hidden in
+      // our example.
+      if (Drupal.ajax) {
+        $('.ajax-example-hide').hide();
+      }
+    }
+  };
+
+})(jQuery);

+ 112 - 0
sites/all/modules/examples/examples/ajax_example/src/Controller/AjaxExampleController.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\ajax_example\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\AppendCommand;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+use Drupal\examples\Utility\DescriptionTemplateTrait;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller routines for AJAX example routes.
+ */
+class AjaxExampleController extends ControllerBase {
+
+  use DescriptionTemplateTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getModuleName() {
+    return 'ajax_example';
+  }
+
+  /**
+   * Demonstrates a clickable AJAX-enabled link using the 'use-ajax' class.
+   *
+   * Because of the 'use-ajax' class applied here, the link submission is done
+   * without a page refresh.
+   *
+   * When using the AJAX framework outside the context of a form or a renderable
+   * array of type 'link', you have to include ajax.js explicitly.
+   *
+   * @return array
+   *   Form API array.
+   *
+   * @ingroup ajax_example
+   */
+  public function renderLinkRenderableArray() {
+    $build['my_div'] = [
+      '#markup' => $this->t('
+The link below has been rendered as an element with the #ajax property, so if
+javascript is enabled, ajax.js will try to submit it via an AJAX call instead
+of a normal page load. The URL also contains the "/nojs/" magic string, which
+is stripped if javascript is enabled, allowing the server code to tell by the
+URL whether JS was enabled or not, letting it do different things based on that.'),
+    ];
+    // We'll add a nice border element for our demo.
+    $build['ajax_link'] = [
+      '#type' => 'details',
+      '#title' => $this->t('This is the AJAX link'),
+      '#open' => TRUE,
+    ];
+    // We build the AJAX link.
+    $build['ajax_link']['link'] = [
+      '#type' => 'link',
+      '#title' => $this->t('Click me'),
+      // We have to ensure that Drupal's Ajax system is loaded.
+      '#attached' => ['library' => ['core/drupal.ajax']],
+      // We add the 'use-ajax' class so that Drupal's AJAX system can spring
+      // into action.
+      '#attributes' => ['class' => ['use-ajax']],
+      // The URL for this link element is the callback. In our case, it's route
+      // ajax_example.ajax_link_callback, which maps to ajaxLinkCallback()
+      // below. The route has a /{nojs} section, which is how the callback can
+      // know whether the request was made by AJAX or some other means where
+      // JavaScript won't be able to handle the result. If the {nojs} part of
+      // the path is replaced with 'ajax', then the request was made by AJAX.
+      '#url' => Url::fromRoute('ajax_example.ajax_link_callback', ['nojs' => 'ajax']),
+    ];
+    // We provide a DIV that AJAX can append some text into.
+    $build['ajax_link']['destination'] = [
+      '#type' => 'container',
+      '#attributes' => ['id' => ['ajax-example-destination-div']],
+    ];
+    return $build;
+  }
+
+  /**
+   * Callback for link example.
+   *
+   * Takes different logic paths based on whether Javascript was enabled.
+   * If $type == 'ajax', it tells this function that ajax.js has rewritten
+   * the URL and thus we are doing an AJAX and can return an array of commands.
+   *
+   * @param string $nojs
+   *   Either 'ajax' or 'nojs. Type is simply the normal URL argument to this
+   *   URL.
+   *
+   * @return string|array
+   *   If $type == 'ajax', returns an array of AJAX Commands.
+   *   Otherwise, just returns the content, which will end up being a page.
+   */
+  public function ajaxLinkCallback($nojs = 'ajax') {
+    // Determine whether the request is coming from AJAX or not.
+    if ($nojs == 'ajax') {
+      $output = $this->t("This is some content delivered via AJAX");
+      $response = new AjaxResponse();
+      $response->addCommand(new AppendCommand('#ajax-example-destination-div', $output));
+
+      // See ajax_example_advanced.inc for more details on the available
+      // commands and how to use them.
+      // $page = array('#type' => 'ajax', '#commands' => $commands);
+      // ajax_deliver($response);
+      return $response;
+    }
+    $response = new Response($this->t("This is some content delivered via a page load."));
+    return $response;
+  }
+
+}

+ 124 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/Autotextfields.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Show textfields based on AJAX-enabled checkbox clicks.
+ *
+ * @ingroup ajax_example
+ */
+class Autotextfields extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_autotextfields';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * This form has two checkboxes which the user can check in order to then
+   * reveal the first and/or last name text fields.
+   *
+   * We could perform this behavior with #states. We might not want to if, for
+   * instance, we wanted to require a name, but let the user choose whether
+   * to enter first or last or both.
+   *
+   * For all the requests this class gets, the buildForm() method will always be
+   * called. If an AJAX request comes in, the form state will be set to the
+   * state the user changed that caused the AJAX request. So if the user enabled
+   * one of our checkboxes, it will be checked in $form_state.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['description'] = [
+      '#type' => 'item',
+      '#markup' => $this->t('This form demonstrates changing the status of form elements through AJAX requests.'),
+    ];
+    $form['ask_first_name'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Ask me my first name'),
+      '#ajax' => [
+        'callback' => '::textfieldsCallback',
+        'wrapper' => 'textfields-container',
+        'effect' => 'fade',
+      ],
+    ];
+    $form['ask_last_name'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Ask me my last name'),
+      '#ajax' => [
+        'callback' => '::textfieldsCallback',
+        'wrapper' => 'textfields-container',
+        'effect' => 'fade',
+      ],
+    ];
+
+    // Wrap textfields in a container. This container will be replaced through
+    // AJAX.
+    $form['textfields_container'] = [
+      '#type' => 'container',
+      '#attributes' => ['id' => 'textfields-container'],
+    ];
+    $form['textfields_container']['textfields'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t("Generated text fields for first and last name"),
+      '#description' => t('This is where we put automatically generated textfields'),
+    ];
+
+    // This form is rebuilt on all requests, so whether or not the request comes
+    // from AJAX, we should rebuild everything based on the form state.
+    // Checkbox values are expressed as 1 or 0, so we have to be sure to compare
+    // type as well as value.
+    if ($form_state->getValue('ask_first_name', NULL) === 1) {
+      $form['textfields_container']['textfields']['first_name'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('First Name'),
+        '#required' => TRUE,
+      ];
+    }
+    if ($form_state->getValue('ask_last_name', NULL) === 1) {
+      $form['textfields_container']['textfields']['last_name'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('Last Name'),
+        '#required' => TRUE,
+      ];
+    }
+
+    $form['textfields_container']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Click Me'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    drupal_set_message(
+      $this->t('Submit handler: First name: @first_name Last name: @last_name',
+        [
+          '@first_name' => $form_state->getValue('first_name', 'n/a'),
+          '@last_name' => $form_state->getValue('last_name', 'n/a'),
+        ]
+      )
+    );
+  }
+
+  /**
+   * Callback for ajax_example_autotextfields.
+   *
+   * Selects the piece of the form we want to use as replacement markup and
+   * returns it as a form (renderable array).
+   */
+  public function textfieldsCallback($form, FormStateInterface $form_state) {
+    return $form['textfields_container'];
+  }
+
+}

+ 269 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/DependentDropdown.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+
+/**
+ * Re-populate a dropdown based on form state.
+ */
+class DependentDropdown extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_dependentdropdown';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * The $nojs parameter is specified as a path parameter on the route.
+   *
+   * @see ajax_example.routing.yml
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
+    // Add our CSS and tiny JS to hide things when they should be hidden.
+    $form['#attached']['library'][] = 'ajax_example/ajax_example.library';
+
+    // Explanatory text with helpful links.
+    $form['info'] = [
+      '#markup' =>
+      $this->t('<p>Like other examples in this module, this form has a path that
+          can be modified with /nojs to simulate its behavior without JavaScript.
+        </p><ul>
+        <li>@try_it_without_ajax</li>
+        <li>@try_it_with_ajax</li>
+      </ul>',
+        [
+          '@try_it_without_ajax' => Link::createFromRoute(
+            $this->t('Try it without AJAX'),
+            'ajax_example.dependent_dropdown', ['nojs' => 'nojs'])
+            ->toString(),
+          '@try_it_with_ajax' => Link::createFromRoute(
+            $this->t('Try it with AJAX'),
+            'ajax_example.dependent_dropdown')
+            ->toString(),
+        ]
+      ),
+    ];
+
+    // Our first dropdown lets us select a family of instruments: String,
+    // Woodwind, Brass, or Percussion.
+    $instrument_family_options = static::getFirstDropdownOptions();
+    // When the AJAX request occurs, this form will be build in order to process
+    // form state before the AJAX callback is called. We can use this
+    // opportunity to populate the form as we wish based on the changes to the
+    // form that caused the AJAX request. If the user caused the AJAX request,
+    // then it would have been setting a value for instrument_family_options.
+    // So if there's a value in that dropdown before we build it here, we grab
+    // it's value to help us build the specific instrument dropdown. Otherwise
+    // we can just use the value of the first item as the default value.
+    if (empty($form_state->getValue('instrument_family_dropdown'))) {
+      // Use a default value.
+      $selected_family = key($instrument_family_options);
+    }
+    else {
+      // Get the value if it already exists.
+      $selected_family = $form_state->getValue('instrument_family_dropdown');
+    }
+
+    $form['instrument_family_fieldset'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Choose an instrument family'),
+    ];
+    $form['instrument_family_fieldset']['instrument_family_dropdown'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Instrument Type'),
+      '#options' => $instrument_family_options,
+      '#default_value' => $selected_family,
+      // Bind an ajax callback to the change event (which is the default for the
+      // select form type) of the first dropdown. It will replace the second
+      // dropdown when rebuilt.
+      '#ajax' => [
+        // When 'event' occurs, Drupal will perform an ajax request in the
+        // background. Usually the default value is sufficient (eg. change for
+        // select elements), but valid values include any jQuery event,
+        // most notably 'mousedown', 'blur', and 'submit'.
+        'callback' => '::instrumentDropdownCallback',
+        'wrapper' => 'instrument-fieldset-container',
+      ],
+    ];
+    // Since we don't know if the user has js or not, we always need to output
+    // this element, then hide it with with css if javascript is enabled.
+    $form['instrument_family_fieldset']['choose_family'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Choose'),
+      '#attributes' => ['class' => ['ajax-example-hide', 'ajax-example-inline']],
+    ];
+    // We are using the path parameter $nojs to signal when to simulate the
+    // the user turning off JavaScript. We'll remove all the AJAX elements. This
+    // is not required, and is here so that we can demonstrate a graceful
+    // fallback without having to turn off JavaScript.
+    if ($nojs == 'nojs') {
+      // Removing the #ajax element tells the system not to use AJAX.
+      unset($form['instrument_family_fieldset']['instrument_family_dropdown']['#ajax']);
+      // Removing the ajax-example-hide class from the Choose button ensures
+      // that our JavaScript won't hide it.
+      unset($form['instrument_family_fieldset']['choose_family']['#attributes']);
+    }
+
+    // Since we're managing state for this whole fieldset (both the dropdown
+    // and enabling the Submit button), we want to replace the whole thing
+    // on AJAX requests. That's why we put it in this container.
+    $form['instrument_fieldset_container'] = [
+      '#type' => 'container',
+      '#attributes' => ['id' => 'instrument-fieldset-container'],
+    ];
+    // Build the instrument field set.
+    $form['instrument_fieldset_container']['instrument_fieldset'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Choose an instrument'),
+    ];
+    $form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown'] = [
+      '#type' => 'select',
+      '#title' => $instrument_family_options[$selected_family] . ' ' . $this->t('Instruments'),
+      // When the form is rebuilt during ajax processing, the $selected_family
+      // variable will now have the new value and so the options will change.
+      '#options' => static::getSecondDropdownOptions($selected_family),
+      '#default_value' => !empty($form_state->getValue('instrument_dropdown')) ? $form_state->getValue('instrument_dropdown') : '',
+    ];
+    $form['instrument_fieldset_container']['instrument_fieldset']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Submit'),
+    ];
+    // We might normally use #state to disable the instrument fields based on
+    // the instrument family fields. But since the premise is that we don't have
+    // JavaScript running, #state won't work either. We have to set up the state
+    // of the instrument fieldset here, based on the selected instrument family.
+    if ($selected_family == 'none') {
+      $form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#title'] =
+        $this->t('You must choose an instrument family.');
+      $form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#disabled'] = TRUE;
+      $form['instrument_fieldset_container']['instrument_fieldset']['submit']['#disabled'] = TRUE;
+    }
+    else {
+      $form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#disabled'] = FALSE;
+      $form['instrument_fieldset_container']['instrument_fieldset']['submit']['#disabled'] = FALSE;
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $trigger = (string) $form_state->getTriggeringElement()['#value'];
+    switch ($trigger) {
+      case 'Submit':
+        // Submit: We're done.
+        drupal_set_message($this->t('Your values have been submitted. Instrument family: @family, Instrument: @instrument', [
+          '@family' => $form_state->getValue('instrument_family_dropdown'),
+          '@instrument' => $form_state->getValue('instrument_dropdown'),
+        ]));
+        return;
+    }
+    // 'Choose' or anything else will cause rebuild of the form and present
+    // it again.
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Provide a new dropdown based on the AJAX call.
+   *
+   * This callback will occur *after* the form has been rebuilt by buildForm().
+   * Since that's the case, the form should contain the right values for
+   * instrument_dropdown.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   The portion of the render structure that will replace the
+   *   instrument-dropdown-replace form element.
+   */
+  public function instrumentDropdownCallback(array $form, FormStateInterface $form_state) {
+    return $form['instrument_fieldset_container'];
+  }
+
+  /**
+   * Helper function to populate the first dropdown.
+   *
+   * This would normally be pulling data from the database.
+   *
+   * @return array
+   *   Dropdown options.
+   */
+  public static function getFirstDropdownOptions() {
+    return [
+      'none' => 'none',
+      'String' => 'String',
+      'Woodwind' => 'Woodwind',
+      'Brass' => 'Brass',
+      'Percussion' => 'Percussion',
+    ];
+  }
+
+  /**
+   * Helper function to populate the second dropdown.
+   *
+   * This would normally be pulling data from the database.
+   *
+   * @param string $key
+   *   This will determine which set of options is returned.
+   *
+   * @return array
+   *   Dropdown options
+   */
+  public static function getSecondDropdownOptions($key = '') {
+    switch ($key) {
+      case 'String':
+        $options = [
+          'Violin' => 'Violin',
+          'Viola' => 'Viola',
+          'Cello' => 'Cello',
+          'Double Bass' => 'Double Bass',
+        ];
+        break;
+
+      case 'Woodwind':
+        $options = [
+          'Flute' => 'Flute',
+          'Clarinet' => 'Clarinet',
+          'Oboe' => 'Oboe',
+          'Bassoon' => 'Bassoon',
+        ];
+        break;
+
+      case 'Brass':
+        $options = [
+          'Trumpet' => 'Trumpet',
+          'Trombone' => 'Trombone',
+          'French Horn' => 'French Horn',
+          'Euphonium' => 'Euphonium',
+        ];
+        break;
+
+      case 'Percussion':
+        $options = [
+          'Bass Drum' => 'Bass Drum',
+          'Timpani' => 'Timpani',
+          'Snare Drum' => 'Snare Drum',
+          'Tambourine' => 'Tambourine',
+        ];
+        break;
+
+      default:
+        $options = ['none' => 'none'];
+        break;
+    }
+    return $options;
+  }
+
+}

+ 222 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/DynamicFormSections.php

@@ -0,0 +1,222 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+
+/**
+ * Dynamically-enabled form with graceful no-JS degradation.
+ *
+ * Example of a form with portions dynamically enabled or disabled, but with
+ * graceful degradation in the case of no JavaScript.
+ *
+ * The idea here is that certain parts of the form don't need to be displayed
+ * unless a given option is selected, but then they should be displayed and
+ * configured.
+ *
+ * The third $no_js_use argument is strictly for demonstrating operation
+ * without JavaScript, without making the user/developer turn off JavaScript.
+ */
+class DynamicFormSections extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_dynamicsectiondegrades';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
+    // Add our CSS and tiny JS to hide things when they should be hidden.
+    $form['#attached']['library'][] = 'ajax_example/ajax_example.library';
+
+    // Explanatory text with helpful links.
+    $form['info'] = [
+      '#markup' =>
+      $this->t('<p>Like other examples in this module, this form has a path that
+        can be modified with /nojs to simulate its behavior without JavaScript.
+        </p><ul>
+        <li>@try_it_without_ajax</li>
+        <li>@try_it_with_ajax</li>
+      </ul>',
+        [
+          '@try_it_without_ajax' => Link::createFromRoute(
+            $this->t('Try it without AJAX'),
+            'ajax_example.dynamic_form_sections', ['nojs' => 'nojs'])
+            ->toString(),
+          '@try_it_with_ajax' => Link::createFromRoute(
+            $this->t('Try it with AJAX'),
+            'ajax_example.dynamic_form_sections')
+            ->toString(),
+        ]
+      ),
+    ];
+
+    $form['question_type_select'] = [
+      // This is our select dropdown.
+      '#type' => 'select',
+      '#title' => t('Question style'),
+      // We have a variety of form items you can use to get input from the user.
+      '#options' => [
+        'Choose question style' => 'Choose question style',
+        'Multiple Choice' => 'Multiple Choice',
+        'True/False' => 'True/False',
+        'Fill-in-the-blanks' => 'Fill-in-the-blanks',
+      ],
+      // The #ajax section tells the AJAX system that whenever this dropdown
+      // emits an event, it should call the callback and put the resulting
+      // content into the wrapper we specify. The questions-fieldset-wrapper is
+      // defined below.
+      '#ajax' => [
+        'wrapper' => 'questions-fieldset-wrapper',
+        'callback' => '::promptCallback',
+      ],
+    ];
+    // The CSS for this module hides this next button if JS is enabled.
+    $form['question_type_submit'] = [
+      '#type' => 'submit',
+      '#value' => t('Choose'),
+      '#attributes' => ['class' => ['ajax-example-inline']],
+      // No need to validate when submitting this.
+      '#limit_validation_errors' => [],
+      '#validate' => [],
+    ];
+
+    // This section allows us to demonstrate no-AJAX use without turning off
+    // javascript in the browser.
+    if ($nojs != 'nojs') {
+      // Allow JavaScript to hide the choose button if we're using AJAX.
+      $form['question_type_submit']['#attributes']['class'][] = 'ajax-example-hide';
+    }
+    else {
+      // Remove #ajax from the above, so it won't perform AJAX behaviors.
+      unset($form['question_type_select']['#ajax']);
+    }
+
+    // This fieldset just serves as a container for the part of the form
+    // that gets rebuilt. It has a nice line around it so you can see it.
+    $form['questions_fieldset'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Stuff will appear here'),
+      '#open' => TRUE,
+      // We set the ID of this fieldset to questions-fieldset-wrapper so the
+      // AJAX command can replace it.
+      '#attributes' => ['id' => 'questions-fieldset-wrapper'],
+    ];
+
+    // When the AJAX request comes in, or when the user hit 'Submit' if there is
+    // no JavaScript, the form state will tell us what the user has selected
+    // from the dropdown. We can look at the value of the dropdown to determine
+    // which secondary form to display.
+    $question_type = $form_state->getValue('question_type_select');
+    if (!empty($question_type) && $question_type !== 'Choose question style') {
+
+      $form['questions_fieldset']['question'] = [
+        '#markup' => t('Who was the first president of the U.S.?'),
+      ];
+
+      // Build up a secondary form, based on the type of question the user
+      // chose.
+      switch ($question_type) {
+        case 'Multiple Choice':
+          $form['questions_fieldset']['question'] = [
+            '#type' => 'radios',
+            '#title' => t('Who was the first president of the United States'),
+            '#options' => [
+              'George Bush' => 'George Bush',
+              'Adam McGuire' => 'Adam McGuire',
+              'Abraham Lincoln' => 'Abraham Lincoln',
+              'George Washington' => 'George Washington',
+            ],
+
+          ];
+          break;
+
+        case 'True/False':
+          $form['questions_fieldset']['question'] = [
+            '#type' => 'radios',
+            '#title' => $this->t('Was George Washington the first president of the United States?'),
+            '#options' => [
+              'George Washington' => 'True',
+              0 => 'False',
+            ],
+            '#description' => $this->t('Click "True" if you think George Washington was the first president of the United States.'),
+          ];
+          break;
+
+        case 'Fill-in-the-blanks':
+          $form['questions_fieldset']['question'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Who was the first president of the United States'),
+            '#description' => $this->t('Please type the correct answer to the question.'),
+          ];
+          break;
+      }
+
+      $form['questions_fieldset']['submit'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Submit your answer'),
+      ];
+    }
+    return $form;
+  }
+
+  /**
+   * Final submit handler.
+   *
+   * Reports what values were finally set.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // This is only executed when a button is pressed, not when the AJAXfield
+    // select is changed.
+    // Now handle the case of the next, previous, and submit buttons.
+    // Only submit will result in actual submission, all others rebuild.
+    if ($form_state->getValue('question_type_submit') == 'Choose') {
+      $form_state->setValue('question_type_select', $form_state->getUserInput()['question_type_select']);
+      $form_state->setRebuild();
+    }
+
+    if ($form_state->getValue('submit') == 'Submit your answer') {
+      $form_state->setRebuild(FALSE);
+      $answer = $form_state->getValue('question');
+      // Special handling for the checkbox.
+      if ($answer == 1 && $form['questions_fieldset']['question']['#type'] == 'checkbox') {
+        $answer = $form['questions_fieldset']['question']['#title'];
+      }
+      if ($answer == $this->t('George Washington')) {
+        drupal_set_message($this->t('You got the right answer: @answer', ['@answer' => $answer]));
+      }
+      else {
+        drupal_set_message($this->t('Sorry, your answer (@answer) is wrong', ['@answer' => $answer]));
+      }
+      return;
+    }
+    // Sets the form to be rebuilt after processing.
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Callback for the select element.
+   *
+   * Since the questions_fieldset part of the form has already been built during
+   * the AJAX request, we can return only that part of the form to the AJAX
+   * request, and it will insert that part into questions-fieldset-wrapper.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   The form structure.
+   */
+  public function promptCallback(array $form, FormStateInterface $form_state) {
+    return $form['questions_fieldset'];
+  }
+
+}

+ 126 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/EntityAutocomplete.php

@@ -0,0 +1,126 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * A simple autocomplete form which looks up usernames.
+ *
+ * @ingroup ajax_example
+ */
+class EntityAutocomplete implements FormInterface, ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity type manager service.
+   *
+   * We need this for the submit handler.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Container injection factory.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service discovery container.
+   *
+   * @return self
+   *   The form object.
+   */
+  public static function create(ContainerInterface $container) {
+    $form = new static(
+      $container->get('entity_type.manager')
+    );
+    $form->setStringTranslation($container->get('string_translation'));
+    return $form;
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_autocomplete_user';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['info'] = [
+      '#markup' => '<div>' . t("This example uses the entity_autocomplete form "
+        . "element to select users. You'll need a few users on your system for "
+        . "it to make sense.") . '</div>',
+    ];
+
+    // Here we use the delightful entity_autocomplete form element. It allows us
+    // to consistently select entities. See https://www.drupal.org/node/2418529.
+    $form['users'] = [
+      // A type of entity_autocomplete lets Drupal know it should autocomplete
+      // entities.
+      '#type' => 'entity_autocomplete',
+      // We can specify entity types to autocomplete.
+      '#target_type' => 'user',
+      // Specifying #tags as TRUE allows for multiple selections, separated by
+      // commas.
+      '#tags' => TRUE,
+      '#title' => t('Choose a user. Separate with commas.'),
+    ];
+
+    $form['actions'] = [
+      '#type' => 'actions',
+    ];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Submit'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Here we validate and signal an error if there are no users selected.
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $state_users = $form_state->getValue('users');
+    if (empty($state_users)) {
+      $form_state->setErrorByName('users', 'There were no users selected.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * On submit, show the user the names of the users they selected.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $state_users = $form_state->getValue('users');
+    $users = [];
+    foreach ($state_users as $state_user) {
+      $uid = $state_user['target_id'];
+      $users[] = $this->entityTypeManager->getStorage('user')->load($uid)->getUsername();
+    }
+    drupal_set_message('These are your users: ' . implode(' ', $users));
+  }
+
+}

+ 80 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/Simplest.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * A relatively simple AJAX demonstration form.
+ */
+class Simplest extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_simplest';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['changethis'] = [
+      '#title' => $this->t("Choose something and explain why"),
+      '#type' => 'select',
+      '#options' => [
+        'one' => 'one',
+        'two' => 'two',
+        'three' => 'three',
+      ],
+      '#ajax' => [
+        // #ajax has two required keys: callback and wrapper.
+        // 'callback' is a function that will be called when this element
+        // changes.
+        'callback' => '::promptCallback',
+        // 'wrapper' is the HTML id of the page element that will be replaced.
+        'wrapper' => 'replace-textfield-container',
+      ],
+    ];
+
+    // The 'replace-textfield-container' container will be replaced whenever
+    // 'changethis' is updated.
+    $form['replace_textfield_container'] = [
+      '#type' => 'container',
+      '#attributes' => ['id' => 'replace-textfield-container'],
+    ];
+    $form['replace_textfield_container']['replace_textfield'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t("Why"),
+    ];
+
+    // An AJAX request calls the form builder function for every change.
+    // We can change how we build the form based on $form_state.
+    $value = $form_state->getValue('changethis');
+    // The getValue() method returns NULL by default if the form element does
+    // not exist. It won't exist yet if we're building it for the first time.
+    if ($value !== NULL) {
+      $form['replace_textfield_container']['replace_textfield']['#description'] =
+        $this->t("Say why you chose '@value'", ['@value' => $value]);
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // No-op. Our form doesn't need a submit handler, because the form is never
+    // submitted. We add the method here so we fulfill FormInterface.
+  }
+
+  /**
+   * Handles switching the available regions based on the selected theme.
+   */
+  public function promptCallback($form, FormStateInterface $form_state) {
+    return $form['replace_textfield_container'];
+  }
+
+}

+ 73 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/SubmitDriven.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Submit a form without a page reload.
+ */
+class SubmitDriven extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_autotextfields';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // This container wil be replaced by AJAX.
+    $form['container'] = [
+      '#type' => 'container',
+      '#attributes' => ['id' => 'box-container'],
+    ];
+    // The box contains some markup that we can change on a submit request.
+    $form['container']['box'] = [
+      '#type' => 'markup',
+      '#markup' => '<h1>Initial markup for box</h1>',
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      // The AJAX handler will call our callback, and will replace whatever page
+      // element has id box-container.
+      '#ajax' => [
+        'callback' => '::promptCallback',
+        'wrapper' => 'box-container',
+      ],
+      '#value' => $this->t('Submit'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Callback for submit_driven example.
+   *
+   * Select the 'box' element, change the markup in it, and return it as a
+   * renderable array.
+   *
+   * @return array
+   *   Renderable array (the box element)
+   */
+  public function promptCallback(array &$form, FormStateInterface $form_state) {
+    // In most cases, it is recommended that you put this logic in form
+    // generation rather than the callback. Submit driven forms are an
+    // exception, because you may not want to return the form at all.
+    $element = $form['container'];
+    $element['box']['#markup'] = "Clicked submit ({$form_state->getValue('op')}): " . date('c');
+    return $element;
+  }
+
+}

+ 229 - 0
sites/all/modules/examples/examples/ajax_example/src/Form/Wizard.php

@@ -0,0 +1,229 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Link;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\HtmlCommand;
+
+/**
+ * AJAX example wizard.
+ */
+class Wizard extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_wizard';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $no_js_use = FALSE) {
+    $url = Url::fromUri('internal:/examples/ajax-example/wizard-nojs');
+    $link = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard-nojs'), $url)
+      ->toString();
+
+    // Prepare link for multiple arguments.
+    $urltwo = Url::fromUri('internal:/examples/ajax-example/wizard');
+    $linktwo = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard'), $urltwo)
+      ->toString();
+
+    // We want to deal with hierarchical form values.
+    $form['#tree'] = TRUE;
+    $form['description'] = [
+      '#markup' => t('This example is a step-by-step wizard. The @link does it without page reloads; the @link1 is the same code but simulates a non-javascript environment, showing it with page reloads.', [
+        '@link' => $linktwo,
+        '@link1' => $link,
+      ]),
+    ];
+
+    $form['step'] = [
+      '#type' => 'hidden',
+      '#value' => !empty($form_state->getValue('step')) ? $form_state->getValue('step') : 1,
+    ];
+    print_r($form_state->getValue('step'));
+
+    if ($form['step']['#value'] == 1) {
+      $form['step1'] = [
+        '#type' => 'fieldset',
+        '#title' => $this->t('Step 1: Personal details'),
+      ];
+      $form['step1']['name'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('Your name'),
+        '#default_value' => empty($form_state->getValue([
+          'step1',
+          'name',
+        ]) ? '' : $form_state->getValue(['step1', 'name'])),
+        '#required' => TRUE,
+      ];
+
+      $form['next'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Next step'),
+        '#ajax' => [
+          'wrapper' => 'ajax-example-wizard',
+          'callback' => '::prompt',
+        ],
+      ];
+    }
+
+    // This simply allows us to demonstrate no-javascript use without
+    // actually turning off javascript in the browser. Removing the #ajax
+    // element turns off AJAX behaviors on that element and as a result
+    // ajax.js doesn't get loaded.
+    // For demonstration only! You don't need this.
+    if ($no_js_use) {
+      // Remove the #ajax from the above, so ajax.js won't be loaded.
+      // For demonstration only.
+      unset($form['next']['#ajax']);
+      unset($form['prev']['#ajax']);
+    }
+
+    return $form;
+  }
+
+  /**
+   * Wizard callback function.
+   *
+   * @param array $form
+   *   Form API form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form API form.
+   *
+   * @return array
+   *   Form array.
+   */
+  public function prompt(array $form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * Save away the current information.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    if ($form_state->getTriggeringElement()['#value'] == $this->t('Submit your information')) {
+      $value_message = $this->t('Your information has been submitted:') . ' ';
+      foreach ($form_state->getValue('value') as $step => $values) {
+        $value_message .= "$step: ";
+        foreach ($values as $key => $value) {
+          $value_message .= "$key=$value, ";
+        }
+      }
+      drupal_set_message($value_message);
+      $form_state->setRebuild(FALSE);
+      // Redirect to #action, else return.
+      return;
+    }
+    else {
+      $step = $form_state->getValue('step');
+      // Increment or decrement the step as needed. Recover values if they
+      // exist.
+      if ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Next step')) {
+        $step++;
+      }
+      elseif ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Previous step')) {
+        $step--;
+      }
+
+      switch ($step) {
+        case 1:
+          $form['step1'] = [
+            '#type' => 'fieldset',
+            '#title' => $this->t('Step 1: Personal details'),
+          ];
+          $form['step1']['name'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Your name'),
+            '#default_value' => empty($form_state->getValue([
+              'step1',
+              'name',
+            ]) ? '' : $form_state->getValue(['step1', 'name'])),
+            '#required' => TRUE,
+          ];
+          $form_state->setValue('step', 1);
+          break;
+
+        case 2:
+          unset($form['step1']);
+          unset($form['next']);
+          $form['step2'] = [
+            '#type' => 'fieldset',
+            '#title' => t('Step 2: Street address info'),
+          ];
+          $form['step2']['address'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Your street address'),
+            '#default_value' => empty($form_state->getValue([
+              'step2',
+              'address',
+            ]) ? '' : $form_state->getValue(['step2', 'address'])),
+            '#required' => TRUE,
+          ];
+          $form_state->setValue('step', $step);
+          break;
+
+        case 3:
+
+          $form['step3'] = [
+            '#type' => 'fieldset',
+            '#title' => $this->t('Step 3: City info'),
+          ];
+          $form['step3']['city'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Your city'),
+            '#default_value' => empty($form_state->getValue([
+              'step3',
+              'city',
+            ]) ? '' : $form_state->getValue(['step3', 'city'])),
+            '#required' => TRUE,
+          ];
+          $form_state->setValue('step', $step);
+          break;
+      }
+      if ($step == 3) {
+
+        $form['submit'] = [
+          '#type' => 'submit',
+          '#value' => $this->t("Submit your information"),
+        ];
+      }
+      if ($step > 1 && !isset($form['prev'])) {
+        $form['prev'] = [
+          '#type' => 'submit',
+          '#value' => t("Previous step"),
+          // Since all info will be discarded, don't validate on 'prev'.
+          '#limit_validation_errors' => [],
+          // #submit is required to use #limit_validation_errors.
+          '#submit' => ['ajax_example_wizard_submit'],
+          '#ajax' => [
+            'wrapper' => 'ajax-example-wizard',
+            'callback' => '::prompt',
+          ],
+        ];
+      }
+      if ($step < 3 && !isset($form['next'])) {
+        $form['next'] = [
+          '#type' => 'submit',
+          '#value' => $this->t('Next step'),
+          '#limit_validation_errors' => [],
+          '#ajax' => [
+            'wrapper' => 'ajax-example-wizard',
+            'callback' => '::prompt',
+          ],
+        ];
+      }
+      $response = new AjaxResponse();
+      $response->addCommand(new HtmlCommand('#ajax-example-wizard', $form));
+      return $response;
+    }
+
+  }
+
+}

+ 29 - 0
sites/all/modules/examples/examples/ajax_example/templates/description.html.twig

@@ -0,0 +1,29 @@
+{#
+
+Description text for the Ajax Example.
+
+#}
+
+{% set simple_ajax_example = path('ajax_example.simplest') %}
+{% set ajax_generate_textfields = path('ajax_example.autotextfields') %}
+{% set ajax_submit = path('ajax_example.submit_driven_ajax') %}
+{% set ajax_dependent_dropdown = path('ajax_example.dependent_dropdown') %}
+{% set ajax_dependent_dropdown_nojs = path('ajax_example.dependent_dropdown', {'nojs': 'nojs'}) %}
+{% set ajax_dynamic_form = path('ajax_example.dynamic_form_sections') %}
+{% set ajax_dynamic_form_nojs = path('ajax_example.dynamic_form_sections', {'nojs': 'nojs'}) %}
+{% set ajax_wizard_example = path('ajax_example.wizard') %}
+{% set ajax_wizard_example_nojs = path('ajax_example.wizardnojs') %}
+
+{% trans %}
+
+<p>The AJAX example module provides many examples of AJAX including forms, links, and AJAX commands.</p>
+<p><a href={{ simple_ajax_example }}>Simplest AJAX Example</a></p>
+<p><a href={{ ajax_generate_textfields }}>Generate textfields</a></p>
+<p><a href={{ ajax_submit }}>Submit-driven AJAX</a></p>
+<p><a href={{ ajax_dependent_dropdown }}>Dependent dropdown</a></p>
+<p><a href={{ ajax_dependent_dropdown_nojs }}>Dependent dropdown w/ no JS</a></p>
+<p><a href={{ ajax_dynamic_form }}>Dynamic form sections</a></p>
+<p><a href={{ ajax_dynamic_form_nojs }}>Dynamic form sections w/ no JS</a></p>
+<p><a href={{ ajax_wizard_example }}>AJAX Wizard Example</a></p>
+<p><a href={{ ajax_wizard_example_nojs }}>AJAX Wizard Example w/JS turned off</a></p>
+{% endtrans %}

+ 79 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/Functional/AjaxExampleMenuTest.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Verify tool block menu items and operability of all our routes.
+ *
+ * @group ajax_example
+ * @group examples
+ *
+ * @ingroup ajax_example
+ */
+class AjaxExampleMenuTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * Tests links.
+   */
+  public function testAjaxExampleLinks() {
+    // Login a user that can access content.
+    $this->drupalLogin(
+      $this->createUser(['access content', 'access user profiles'])
+    );
+
+    $assertion = $this->assertSession();
+
+    // Routes with menu links, and their form buttons.
+    $routes_with_menu_links = [
+      'ajax_example.description' => [],
+      'ajax_example.simplest' => [],
+      'ajax_example.autotextfields' => ['Click Me'],
+      'ajax_example.submit_driven_ajax' => ['Submit'],
+      'ajax_example.dependent_dropdown' => ['Submit'],
+      'ajax_example.dynamic_form_sections' => ['Choose'],
+      'ajax_example.wizard' => ['Next step'],
+      'ajax_example.wizardnojs' => ['Next step'],
+      'ajax_example.ajax_link_render' => [],
+      'ajax_example.autocomplete_user' => ['Submit'],
+    ];
+
+    // Ensure the links appear in the tools menu sidebar.
+    $this->drupalGet('');
+    foreach (array_keys($routes_with_menu_links) as $route) {
+      $assertion->linkByHrefExists(Url::fromRoute($route)->getInternalPath());
+    }
+
+    // All our routes with their form buttons.
+    $routes = [
+      'ajax_example.ajax_link_callback' => [],
+    ];
+
+    // Go to all the routes and click all the buttons.
+    $routes = array_merge($routes_with_menu_links, $routes);
+    foreach ($routes as $route => $buttons) {
+      $url = Url::fromRoute($route);
+      $this->drupalGet($url);
+      $assertion->statusCodeEquals(200);
+      foreach ($buttons as $button) {
+        $this->drupalPostForm($url, [], $button);
+        $assertion->statusCodeEquals(200);
+      }
+    }
+  }
+
+}

+ 74 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/Functional/DependentDropdownTest.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Functional test of non-AJAX dependent dropdown example.
+ *
+ * @group ajax_example
+ * @group examples
+ *
+ * @ingroup ajax_example
+ *
+ * @see \Drupal\Tests\ajax_example\FunctionalJavascript\DependentDropdownTest
+ */
+class DependentDropdownTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the dependent dropdown form without AJAX.
+   */
+  public function testDependentDropdown() {
+    // Get the Mink stuff.
+    $session = $this->getSession();
+    $assert = $this->assertSession();
+    $page = $session->getPage();
+
+    // Get a URL object for the form, specifying no JS.
+    $dropdown_url = Url::fromRoute('ajax_example.dependent_dropdown', ['nojs' => 'nojs']);
+
+    // Get the form.
+    $this->drupalGet($dropdown_url);
+    // Check for the initial state.
+    $assert->fieldDisabled('instrument_dropdown');
+    $assert->fieldValueEquals('instrument_dropdown', 'none');
+    $submit_button = $page->findButton('edit-submit');
+    $this->assertTrue($submit_button->hasAttribute('disabled'));
+
+    // Run through the matrix of form submissions.
+    $families = [
+      'String' => ['Violin', 'Viola', 'Cello', 'Double Bass'],
+      'Woodwind' => ['Flute', 'Clarinet', 'Oboe', 'Bassoon'],
+      'Brass' => ['Trumpet', 'Trombone', 'French Horn', 'Euphonium'],
+      'Percussion' => ['Bass Drum', 'Timpani', 'Snare Drum', 'Tambourine'],
+    ];
+
+    foreach ($families as $family => $instruments) {
+      // Post the form for the instrument family.
+      $this->drupalPostForm($dropdown_url, ['instrument_family_dropdown' => $family], 'Choose');
+      // Get the instrument dropdown elements.
+      $instrument_options = $page->findAll('css', '#edit-instrument-dropdown option');
+      $this->assertCount(count($instruments), $instrument_options);
+      // Make sure all the instruments are in the select dropdown.
+      foreach ($instrument_options as $instrument) {
+        $this->assertContains($instrument->getAttribute('value'), $instruments);
+      }
+      // Post each instrument. We have to 'choose' again in order to unlock the
+      // instrument dropdown.
+      foreach ($instruments as $instrument) {
+        $this->drupalPostForm($dropdown_url, ['instrument_family_dropdown' => $family], 'Choose');
+        $this->drupalPostForm(NULL, ['instrument_dropdown' => $instrument], 'Submit');
+        $assert->pageTextContains("Your values have been submitted. Instrument family: $family, Instrument: $instrument");
+      }
+    }
+
+  }
+
+}

+ 70 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/Functional/DynamicFormSectionsTest.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Functional test of non-AJAX dependent dropdown example.
+ *
+ * @group ajax_example
+ * @group examples
+ *
+ * @ingroup ajax_example
+ *
+ * @see Drupal\Tests\ajax_example\FunctionalJavascript\DynamicFormSectionsTest
+ */
+class DynamicFormSectionsTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the dynamic sections form without AJAX.
+   */
+  public function testDynamicFormSections() {
+    // Get the Mink stuff.
+    $assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Get a URL object for the form, specifying no JS.
+    $dropdown_url = Url::fromRoute('ajax_example.dynamic_form_sections', ['nojs' => 'nojs']);
+
+    // Get the form.
+    $this->drupalGet($dropdown_url);
+    // Check for the initial state.
+    $detail_children = $page->findAll('css', 'div.details-wrapper *');
+    $this->assertEmpty($detail_children);
+
+    // Go through the dropdown options. First outlier is 'Choose question style'
+    // which should have an empty details section.
+    $this->drupalPostForm($dropdown_url, ['question_type_select' => 'Choose question style'], 'Choose');
+    $detail_children = $page->findAll('css', 'div.details-wrapper *');
+    $this->assertEmpty($detail_children);
+
+    // Cycle through the other dropdown values.
+    $question_styles = [
+      'Multiple Choice',
+      'True/False',
+      'Fill-in-the-blanks',
+    ];
+    // These all add stuff to the details wrapper.
+    foreach ($question_styles as $question_style) {
+      $this->drupalPostForm($dropdown_url, ['question_type_select' => $question_style], 'Choose');
+      $detail_children = $page->findAll('css', 'div.details-wrapper *');
+      $this->assertNotEmpty($detail_children);
+      $this->drupalPostForm(NULL, ['question' => 'George Washington'], 'Submit your answer');
+      $assert->pageTextContains('You got the right answer: George Washington');
+    }
+    // One wrong answer to exercise that code path.
+    $this->drupalPostForm($dropdown_url, ['question_type_select' => 'Multiple Choice'], 'Choose');
+    $detail_children = $page->findAll('css', 'div.details-wrapper *');
+    $this->assertNotEmpty($detail_children);
+    $this->drupalPostForm(NULL, ['question' => 'Abraham Lincoln'], 'Submit your answer');
+    $assert->pageTextContains('Sorry, your answer (Abraham Lincoln) is wrong');
+  }
+
+}

+ 80 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/AutotextfieldsTest.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Test the user interactions for the Autotextfields example.
+ *
+ * @group ajax_example
+ */
+class AutofieldsTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the user interactions for the Autotextfields example.
+   */
+  public function testAutotextfields() {
+    // Get our Mink stuff.
+    $session = $this->getSession();
+    $page = $session->getPage();
+    $assert = $this->assertSession();
+
+    // Get the page.
+    $form_url = Url::fromRoute('ajax_example.autotextfields');
+    $this->drupalGet($form_url);
+
+    // Check our initial state.
+    $assert->checkboxNotChecked('ask_first_name');
+    $assert->checkboxNotChecked('ask_last_name');
+    $assert->fieldNotExists('first_name');
+    $assert->fieldNotExists('last_name');
+    // Submit the form. This tests what happens when there are no user
+    // interactions because drupalPostForm() reloads the form.
+    $this->drupalPostForm($form_url, [], 'Click Me');
+    $assert->pageTextContains('Submit handler: First name: n/a Last name: n/a');
+
+    // Ask for the first name.
+    $page->checkField('ask_first_name');
+    $assert->assertWaitOnAjaxRequest();
+    $assert->fieldExists('first_name');
+    $assert->fieldNotExists('last_name');
+    // Submit the form. We have to find the field and set its value rather than
+    // use drupalPostForm(), because when we post the form, it will be rebuilt.
+    // We are testing the form state after AJAX has modified it, so we must
+    // preserve that.
+    $page->findField('first_name')->setValue('Dries');
+    $page->pressButton('Click Me');
+    $assert->pageTextContains('Submit handler: First name: Dries Last name: n/a');
+
+    // Ask for the first and last name.
+    $page->checkField('ask_first_name');
+    $assert->assertWaitOnAjaxRequest();
+    $assert->fieldExists('first_name');
+    $page->checkField('ask_last_name');
+    $assert->assertWaitOnAjaxRequest();
+    $assert->fieldExists('last_name');
+    // Submit the form.
+    $page->findField('first_name')->setValue('Dries');
+    $page->findField('last_name')->setValue('Buytaert');
+    $page->pressButton('Click Me');
+    $assert->pageTextContains('Submit handler: First name: Dries Last name: Buytaert');
+
+    // Ask for only the last name.
+    $page->checkField('ask_last_name');
+    $assert->assertWaitOnAjaxRequest();
+    $assert->fieldNotExists('first_name');
+    $assert->fieldExists('last_name');
+    // Submit the form.
+    $page->findField('last_name')->setValue('Buytaert');
+    $page->pressButton('Click Me');
+    $assert->pageTextContains('Submit handler: First name: n/a Last name: Buytaert');
+  }
+
+}

+ 75 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/DependentDropdownTest.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Functional test of dependent dropdown example.
+ *
+ * @group ajax_example
+ *
+ * @see \Drupal\Tests\ajax_example\FunctionalJavascript\DependentDropdownTest
+ */
+class DependentDropdownTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the dependent dropdown form with AJAX.
+   */
+  public function testDependentDropdown() {
+    // Get the Mink stuff.
+    $assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Get a URL object for the form, specifying AJAX.
+    $dropdown_url = Url::fromRoute('ajax_example.dependent_dropdown', ['nojs' => 'ajax']);
+
+    // Get the form.
+    $this->drupalGet($dropdown_url);
+    // Check for the initial state.
+    $assert->fieldDisabled('instrument_dropdown');
+    $assert->fieldValueEquals('instrument_dropdown', 'none');
+    $submit_button = $page->findButton('edit-submit');
+    $this->assertTrue($submit_button->hasAttribute('disabled'));
+
+    // Run through the matrix of families.
+    $families = [
+      'String' => ['Violin', 'Viola', 'Cello', 'Double Bass'],
+      'Woodwind' => ['Flute', 'Clarinet', 'Oboe', 'Bassoon'],
+      'Brass' => ['Trumpet', 'Trombone', 'French Horn', 'Euphonium'],
+      'Percussion' => ['Bass Drum', 'Timpani', 'Snare Drum', 'Tambourine'],
+    ];
+
+    foreach ($families as $family => $instruments) {
+      // Select a family.
+      $family_dropdown = $assert->fieldExists('instrument_family_dropdown');
+      $family_dropdown->setValue($family);
+      $assert->assertWaitOnAjaxRequest();
+
+      // Get the instrument dropdown elements.
+      $instrument_options = $page->findAll('css', 'select[name="instrument_dropdown"] option');
+      $this->assertCount(count($instruments), $instrument_options);
+      // Make sure all the instruments are in the select dropdown.
+      foreach ($instrument_options as $instrument) {
+        $this->assertContains($instrument->getAttribute('value'), $instruments);
+      }
+
+      // Post each instrument.
+      foreach ($instruments as $instrument) {
+        $this->drupalGet($dropdown_url);
+        $family_dropdown->setValue($family);
+        $assert->assertWaitOnAjaxRequest();
+        $this->drupalPostForm(NULL, ['instrument_dropdown' => $instrument], 'Submit');
+        $assert->pageTextContains("Your values have been submitted. Instrument family: $family, Instrument: $instrument");
+      }
+    }
+
+  }
+
+}

+ 67 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/DynamicFormSectionsTest.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Functional test of dependent dropdown example.
+ *
+ * @group ajax_example
+ *
+ * @see Drupal\Tests\ajax_example\Functional\DynamicFormSectionsTest
+ */
+class DynamicFormSectionsTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the dependent dropdown form with AJAX.
+   */
+  public function testDynamicFormSections() {
+    // Get the Mink stuff.
+    $assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Get a URL object for the form, specifying no JS.
+    $dropdown_url = Url::fromRoute('ajax_example.dynamic_form_sections', ['nojs' => 'ajax']);
+
+    // Get the form.
+    $this->drupalGet($dropdown_url);
+    // Check for the initial state.
+    $this->assertEmpty($page->findAll('css', 'div.details-wrapper *'));
+
+    // Cycle through the other dropdown values.
+    $question_styles = [
+      'Multiple Choice',
+      'True/False',
+      'Fill-in-the-blanks',
+    ];
+
+    // Check expectations against the details wrapper.
+    $question_type_dropdown = $page->findField('question_type_select');
+    foreach ($question_styles as $question_style) {
+      $question_type_dropdown->setValue($question_style);
+      $assert->assertWaitOnAjaxRequest();
+      $this->assertNotEmpty($page->findAll('css', 'div.details-wrapper *'));
+    }
+    // Prompt to choose question should remove the question.
+    $question_type_dropdown->setValue('Choose question style');
+    $assert->assertWaitOnAjaxRequest();
+    $this->assertEmpty($page->findAll('css', 'div.details-wrapper *'));
+
+    // Submit the correct answers.
+    foreach ($question_styles as $question_style) {
+      $this->drupalGet($dropdown_url);
+      $question_type_dropdown->setValue($question_style);
+      $assert->assertWaitOnAjaxRequest();
+      $this->drupalPostForm(NULL, ['question' => 'George Washington'], 'Submit your answer');
+      $assert->pageTextContains('You got the right answer: George Washington');
+    }
+  }
+
+}

+ 69 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/EntityAutocompleteTest.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the behavior of the entity_autocomplete example.
+ *
+ * @group ajax_example
+ */
+class EntityAutocompleteTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the behavior of the submit-driven AJAX example.
+   *
+   * Behaviors to test:
+   * - GET the route ajax_example.autocomplete_user.
+   * - Examine the DOM to make sure our change hasn't happened yet.
+   * - Send an event to the DOM to trigger the autocomplete.
+   * - Wait for the autocomplete request to complete.
+   * - Examine the DOM to see if our expected change happened.
+   * - Submit some names to see if our form processed the user properly.
+   */
+  public function testSubmitDriven() {
+    // Set up some accounts with known names.
+    $names = ['bb', 'bc'];
+    foreach ($names as $name) {
+      $this->createUser([], $name);
+    }
+
+    // Get our various Mink elements.
+    $assert = $this->assertSession();
+    $session = $this->getSession();
+    $page = $session->getPage();
+    // We'll be using the users field quite a bit, so let's make it a variable.
+    $users_field_name = 'edit-users';
+
+    // Get the form.
+    $this->drupalGet(Url::fromRoute('ajax_example.autocomplete_user'));
+    // Examine the DOM to make sure our change hasn't happened yet.
+    $assert->fieldValueEquals($users_field_name, '');
+
+    // Send an event to the DOM. This will start the autocomplete process.
+    $autocomplete_field = $page->findField($users_field_name);
+    $session->getDriver()->keyDown($autocomplete_field->getXpath(), 'b');
+
+    // Wait for the autocomplete request to complete.
+    $assert->waitOnAutocomplete();
+
+    // Examine the DOM to see if our expected change happened.
+    $results = $page->findAll('css', '.ui-autocomplete li');
+    $this->assertCount(2, $results);
+    foreach ($results as $result) {
+      $this->assertContains($result->getText(), $names);
+    }
+
+    // Submit to see if our form processed the user properly.
+    $this->submitForm([$users_field_name => 'bb, bc'], 'Submit');
+    $assert->pageTextContains('These are your users: bb bc');
+  }
+
+}

+ 60 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/SimplestTest.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Test the user interactions for the Simplest example.
+ *
+ * @group ajax_example
+ */
+class SimplestTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the user interactions for the Autotextfields example.
+   */
+  public function testAutotextfields() {
+    // Get our Mink stuff.
+    $session = $this->getSession();
+    $page = $session->getPage();
+    $assert = $this->assertSession();
+
+    // Get the page.
+    $form_url = Url::fromRoute('ajax_example.simplest');
+    $this->drupalGet($form_url);
+
+    // Don't repeat ourselves. This makes it easier if we change the markup
+    // later.
+    $description_selector = '#replace-textfield-container div.description';
+
+    // Check our initial state.
+    $assert->elementExists('css', '#replace-textfield-container');
+    $assert->elementNotExists('css', $description_selector);
+
+    // Select values on the dropdown. Start with three so the change event is
+    // triggered.
+    foreach (['three', 'two', 'one'] as $value) {
+      // Select the dropdown value.
+      $page->selectFieldOption('changethis', $value);
+      // Wait for AJAX to happen.
+      $assert->assertWaitOnAjaxRequest();
+      // Assert that the description exists.
+      $assert->elementExists('css', $description_selector);
+      // Get the description element from the page.
+      $prompt_element = $page->find('css', $description_selector);
+      // Assert that the description element says what we expect it to say.
+      $this->assertEquals(
+        "Say why you chose '$value'",
+        $prompt_element->getText()
+      );
+    }
+  }
+
+}

+ 45 - 0
sites/all/modules/examples/examples/ajax_example/tests/src/FunctionalJavascript/SubmitDrivenTest.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the behavior of the submit-driven AJAX example.
+ *
+ * @group ajax_example
+ */
+class SubmitDrivenTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the behavior of the submit-driven AJAX example.
+   *
+   * Behaviors to test:
+   * - GET the route ajax_example.submit_driven_ajax.
+   * - Examine the DOM to make sure our change hasn't happened yet.
+   * - Submit the form.
+   * - Wait for the AJAX request to complete.
+   * - Examine the DOM to see if our expected change happened.
+   */
+  public function testSubmitDriven() {
+    // Get the session assertion object.
+    $assert = $this->assertSession();
+    // Get the page.
+    $this->drupalGet(Url::fromRoute('ajax_example.submit_driven_ajax'));
+    // Examine the DOM to make sure our change hasn't happened yet.
+    $assert->pageTextNotContains('Clicked submit (Submit):');
+    // Submit the form.
+    $this->submitForm([], 'Submit');
+    // Wait on the AJAX request.
+    $assert->assertWaitOnAjaxRequest();
+    // Compare DOM to our expectations.
+    $assert->pageTextContains('Clicked submit (Submit):');
+  }
+
+}

+ 14 - 0
sites/all/modules/examples/examples/batch_example/batch_example.info.yml

@@ -0,0 +1,14 @@
+name: Batch Example
+type: module
+description: An example outlining how a module can define batch operations.
+package: Example modules
+# core: 8.x
+dependencies:
+  - examples
+  - toolbar
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 83 - 0
sites/all/modules/examples/examples/batch_example/batch_example.install

@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the batch_example module.
+ */
+
+/**
+ * Example of batch-driven update function.
+ *
+ * Because some update functions may require the batch API, the $sandbox
+ * provides a place to store state. When $sandbox['#finished'] == TRUE,
+ * calls to this update function are completed.
+ *
+ * The $sandbox param provides a way to store data during multiple invocations.
+ * When the $sandbox['#finished'] == 1, execution is complete.
+ *
+ * This dummy 'update' function changes no state in the system. It simply
+ * loads each node.
+ *
+ * To make this update function run again and again, execute the query
+ * "update system set schema_version = 0 where name = 'batch_example';"
+ * and then run /update.php.
+ *
+ * @ingroup batch_example
+ */
+function batch_example_update_8001(&$sandbox) {
+  // Use the sandbox at your convenience to store the information needed
+  // to track progression between successive calls to the function.
+  if (!isset($sandbox['progress'])) {
+    // The count of nodes visited so far.
+    $sandbox['progress'] = 0;
+    // Total nodes that must be visited.
+    $sandbox['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
+    // A place to store messages during the run.
+    $sandbox['messages'] = [];
+    // Last node read via the query.
+    $sandbox['current_node'] = -1;
+  }
+
+  // Process nodes by groups of 10 (arbitrary value).
+  // When a group is processed, the batch update engine determines
+  // whether it should continue processing in the same request or provide
+  // progress feedback to the user and wait for the next request.
+  $limit = 10;
+
+  // Retrieve the next group of nids.
+  $query = db_select('node', 'n');
+  $query->fields('n', ['nid']);
+  $result = $query
+    ->where('n.nid > :nid', [':nid' => $sandbox['current_node']])
+    ->range(0, $limit)
+    ->orderBy('n.nid', 'ASC')
+    ->execute();
+  foreach ($result as $row) {
+    // Here we actually perform a dummy 'update' on the current node.
+    $node = db_query('SELECT nid FROM {node} WHERE nid = :nid', [':nid' => $row->nid])->fetchField();
+
+    // Update our progress information.
+    $sandbox['progress']++;
+    $sandbox['current_node'] = $row->nid;
+  }
+
+  // Set the "finished" status, to tell batch engine whether this function
+  // needs to run again. If you set a float, this will indicate the progress
+  // of the batch so the progress bar will update.
+  $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']);
+
+  // Set up a per-run message; Make a copy of $sandbox so we can change it.
+  // This is simply a debugging stanza to illustrate how to capture status
+  // from each pass through hook_update_N().
+  $sandbox_status = $sandbox;
+  // Don't want them in the output.
+  unset($sandbox_status['messages']);
+  $sandbox['messages'][] = t('$sandbox=') . print_r($sandbox_status, TRUE);
+
+  if ($sandbox['#finished']) {
+    // hook_update_N() may optionally return a string which will be displayed
+    // to the user.
+    $final_message = '<ul><li>' . implode('</li><li>', $sandbox['messages']) . "</li></ul>";
+    return t('The batch_example demonstration update did what it was supposed to do: @message', ['@message' => $final_message]);
+  }
+}

+ 5 - 0
sites/all/modules/examples/examples/batch_example/batch_example.links.menu.yml

@@ -0,0 +1,5 @@
+# Define default links for this module.
+batch_example.form:
+  title: Batch API Examples
+  description: Batch examples using Drupal Batch API.
+  route_name: batch_example.form

+ 133 - 0
sites/all/modules/examples/examples/batch_example/batch_example.module

@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Outlines how a module can use the Batch API.
+ */
+
+/**
+ * @defgroup batch_example Example: Batch API
+ * @ingroup examples
+ * @{
+ * Outlines how a module can use the Batch API.
+ *
+ * Batches allow heavy processing to be spread out over several page
+ * requests, ensuring that the processing does not get interrupted
+ * because of a PHP timeout, while allowing the user to receive feedback
+ * on the progress of the ongoing operations. It also can reduce out of memory
+ * situations.
+ *
+ * The @link batch_example.install .install file @endlink also shows how the
+ * Batch API can be used to handle long-running hook_update_N() functions.
+ *
+ * Two harmless batches are defined:
+ * - batch 1: Load the node with the lowest nid 100 times.
+ * - batch 2: Load all nodes, 20 times and uses a progressive op, loading nodes
+ *   by groups of 5.
+ *
+ * @see batch
+ */
+
+/**
+ * Batch operation for batch 1: one at a time.
+ *
+ * This is the function that is called on each operation in batch 1.
+ */
+function batch_example_op_1($id, $operation_details, &$context) {
+  // Simulate long process by waiting 1/50th of a second.
+  usleep(20000);
+
+  // Store some results for post-processing in the 'finished' callback.
+  // The contents of 'results' will be available as $results in the
+  // 'finished' function (in this example, batch_example_finished()).
+  $context['results'][] = $id;
+
+  // Optional message displayed under the progressbar.
+  $context['message'] = t('Running Batch "@id" @details',
+    ['@id' => $id, '@details' => $operation_details]
+  );
+}
+
+/**
+ * Batch operation for batch 2: five at a time.
+ *
+ * This is the function that is called on each operation in batch 2.
+ *
+ * After each group of 5 control is returned to the batch API for later
+ * continuation.
+ */
+function batch_example_op_2($operation_details, &$context) {
+  // Use the $context['sandbox'] at your convenience to store the
+  // information needed to track progression between successive calls.
+  if (empty($context['sandbox'])) {
+    $context['sandbox'] = [];
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['current_node'] = 0;
+
+    // Save node count for the termination message.
+    $context['sandbox']['max'] = 30;
+  }
+
+  // Process in groups of 5 (arbitrary value).
+  // When a group of five is processed, the batch update engine determines
+  // whether it should continue processing in the same request or provide
+  // progress feedback to the user and wait for the next request.
+  // That way even though we're already processing at the operation level
+  // the operation itself is interruptible.
+  $limit = 5;
+
+  // Retrieve the next group.
+  $result = range($context['sandbox']['current_node'] + 1, $context['sandbox']['current_node'] + 1 + $limit);
+
+  foreach ($result as $row) {
+    // Here we actually perform our dummy 'processing' on the current node.
+    usleep(20000);
+
+    // Store some results for post-processing in the 'finished' callback.
+    // The contents of 'results' will be available as $results in the
+    // 'finished' function (in this example, batch_example_finished()).
+    $context['results'][] = $row . ' ' . $operation_details;
+
+    // Update our progress information.
+    $context['sandbox']['progress']++;
+    $context['sandbox']['current_node'] = $row;
+    $context['message'] = t('Running Batch "@id" @details',
+      ['@id' => $row, '@details' => $operation_details]
+    );
+  }
+
+  // Inform the batch engine that we are not finished,
+  // and provide an estimation of the completion level we reached.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
+  }
+}
+
+/**
+ * Batch 'finished' callback used by both batch 1 and batch 2.
+ */
+function batch_example_finished($success, $results, $operations) {
+  if ($success) {
+    // Here we could do something meaningful with the results.
+    // We just display the number of nodes we processed...
+    drupal_set_message(t('@count results processed.', ['@count' => count($results)]));
+    drupal_set_message(t('The final result was "%final"', ['%final' => end($results)]));
+  }
+  else {
+    // An error occurred.
+    // $operations contains the operations that remained unprocessed.
+    $error_operation = reset($operations);
+    drupal_set_message(
+      t('An error occurred while processing @operation with arguments : @args',
+        [
+          '@operation' => $error_operation[0],
+          '@args' => print_r($error_operation[0], TRUE),
+        ]
+      )
+    );
+  }
+}
+
+/**
+ * @} End of "defgroup batch_example".
+ */

+ 7 - 0
sites/all/modules/examples/examples/batch_example/batch_example.routing.yml

@@ -0,0 +1,7 @@
+batch_example.form:
+  path: 'examples/batch_example'
+  defaults:
+    _form: '\Drupal\batch_example\Form\BatchExampleForm'
+    _title: 'Demo of batch processing'
+  requirements:
+    _permission: 'access content'

+ 148 - 0
sites/all/modules/examples/examples/batch_example/src/Form/BatchExampleForm.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace Drupal\batch_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form with examples on how to use cache.
+ */
+class BatchExampleForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'batch_example_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+
+    $form['description'] = [
+      '#type' => 'markup',
+      '#markup' => t('This example offers two different batches. The first does 1000 identical operations, each completed in on run; the second does 20 operations, but each takes more than one run to operate if there are more than 5 nodes.'),
+    ];
+    $form['batch'] = [
+      '#type' => 'select',
+      '#title' => 'Choose batch',
+      '#options' => [
+        'batch_1' => t('batch 1 - 1000 operations'),
+        'batch_2' => t('batch 2 - 20 operations.'),
+      ],
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => 'Go',
+    ];
+
+    return $form;
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Gather our form value.
+    $value = $form_state->getValues()['batch'];
+    // Set the batch, using convenience methods.
+    $batch = [];
+    switch ($value) {
+      case 'batch_1':
+        $batch = $this->generateBatch1();
+        break;
+
+      case 'batch_2':
+        $batch = $this->generateBatch2();
+        break;
+    }
+    batch_set($batch);
+  }
+
+  /**
+   * Generate Batch 1.
+   *
+   * Batch 1 will process one item at a time.
+   *
+   * This creates an operations array defining what batch 1 should do, including
+   * what it should do when it's finished. In this case, each operation is the
+   * same and by chance even has the same $nid to operate on, but we could have
+   * a mix of different types of operations in the operations array.
+   */
+  public function generateBatch1() {
+    $num_operations = 1000;
+    drupal_set_message(t('Creating an array of @num operations', ['@num' => $num_operations]));
+
+    $operations = [];
+    // Set up an operations array with 1000 elements, each doing function
+    // batch_example_op_1.
+    // Each operation in the operations array means at least one new HTTP
+    // request, running Drupal from scratch to accomplish the operation. If the
+    // operation returns with $context['finished'] != TRUE, then it will be
+    // called again.
+    // In this example, $context['finished'] is always TRUE.
+    for ($i = 0; $i < $num_operations; $i++) {
+      // Each operation is an array consisting of
+      // - The function to call.
+      // - An array of arguments to that function.
+      $operations[] = [
+        'batch_example_op_1',
+        [
+          $i + 1,
+          t('(Operation @operation)', ['@operation' => $i]),
+        ],
+      ];
+    }
+    $batch = [
+      'title' => t('Creating an array of @num operations', ['@num' => $num_operations]),
+      'operations' => $operations,
+      'finished' => 'batch_example_finished',
+    ];
+    return $batch;
+  }
+
+  /**
+   * Generate Batch 2.
+   *
+   * Batch 2 will process five items at a time.
+   *
+   * This creates an operations array defining what batch 2 should do, including
+   * what it should do when it's finished. In this case, each operation is the
+   * same and by chance even has the same $nid to operate on, but we could have
+   * a mix of different types of operations in the operations array.
+   */
+  public function generateBatch2() {
+    $num_operations = 20;
+
+    $operations = [];
+    // 20 operations, each one loads all nodes.
+    for ($i = 0; $i < $num_operations; $i++) {
+      $operations[] = [
+        'batch_example_op_2',
+        [t('(Operation @operation)', ['@operation' => $i])],
+      ];
+    }
+    $batch = [
+      'operations' => $operations,
+      'finished' => 'batch_example_finished',
+      // @current, @remaining, @total, @percentage, @estimate and @elapsed.
+      // These placeholders are replaced with actual values in _batch_process(),
+      // using strtr() instead of t(). The values are determined based on the
+      // number of operations in the 'operations' array (above), NOT by the
+      // number of nodes that will be processed. In this example, there are 20
+      // operations, so @total will always be 20, even though there are multiple
+      // nodes per operation.
+      // Defaults to t('Completed @current of @total.').
+      'title' => t('Processing batch 2'),
+      'init_message' => t('Batch 2 is starting.'),
+      'progress_message' => t('Processed @current out of @total.'),
+      'error_message' => t('Batch 2 has encountered an error.'),
+    ];
+    return $batch;
+  }
+
+}

+ 40 - 0
sites/all/modules/examples/examples/batch_example/tests/src/functional/BatchExampleWebTest.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\batch_example\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Functional tests for the Batch Example module.
+ *
+ * @group fapi_example
+ *
+ * @ingroup batch_example
+ */
+class BatchExampleWebTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  static public $modules = ['node', 'batch_example'];
+
+  /**
+   * Login user and test both batch examples.
+   */
+  public function testBatchExampleBasic() {
+    // Login the admin user.
+    $web_user = $this->drupalCreateUser(['access content']);
+    $this->drupalLogin($web_user);
+
+    // Launch Batch 1.
+    $this->drupalPostForm('examples/batch_example', ['batch' => 'batch_1'], 'Go');
+    // Check that 1000 operations were performed.
+    $this->assertText('1000 results processed');
+
+    // Launch Batch 2.
+    $this->drupalPostForm('examples/batch_example', ['batch' => 'batch_2'], 'Go');
+    // Check that 600 operations were performed.
+    $this->assertText('600 results processed');
+  }
+
+}

+ 15 - 0
sites/all/modules/examples/examples/block_example/block_example.info.yml

@@ -0,0 +1,15 @@
+name: Block Example
+type: module
+description: Demonstrates how a module can define blocks.
+package: Example modules
+# core: 8.x
+dependencies:
+  - drupal:block
+  - drupal:node
+  - examples:examples
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 3 - 0
sites/all/modules/examples/examples/block_example/block_example.links.menu.yml

@@ -0,0 +1,3 @@
+block_example.description:
+  title: Block Example
+  route_name: block_example.description

+ 53 - 0
sites/all/modules/examples/examples/block_example/block_example.module

@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Module file for block_example.
+ */
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Block\BlockPluginInterface;
+
+/**
+ * @defgroup block_example Example: Block
+ * @ingroup examples
+ * @{
+ * Demonstrates code creation of blocks.
+ *
+ * This example demonstrates how a module can define blocks that can be
+ * displayed on various pages of a site, and how to alter blocks provided by
+ * other modules.
+ */
+
+/**
+ * Implements hook_block_view_alter().
+ *
+ * This hook allows you to modify the output of any block in the system.
+ *
+ * We are going to change the block label to uppercase if it contains the string
+ * "uppercase" or if the default block label contains this string. The default
+ * block label is set programmatically in the subject key of the block's plugin
+ * definition. The configurable block label, which can be overridden through the
+ * UI, is found in the "#configuration" key of the block's build definition.
+ * This module creates a block that demonstrates the uppercase effect in the
+ * "Example: uppercase this please" block. You can also demonstrate the effect
+ * of this hook by editing the title of an existing block or by creating a new
+ * block which where the default label has the string "uppercase" in it.
+ *
+ * Instead of hook_block_view_alter(), which is called for all blocks, you can
+ * also use hook_block_view_BASE_BLOCK_ID_alter() to alter a specific block. To
+ * only change the "example_uppercase" block we would use the function:
+ * hook_block_view_example_uppercase_alter().
+ */
+function block_example_block_view_alter(array &$build, BlockPluginInterface $block) {
+  // We'll search for the string 'uppercase'.
+  $definition = $block->getPluginDefinition();
+  if ((!empty($build['#configuration']['label']) && stristr($build['#configuration']['label'], 'uppercase')) || (!empty($definition['subject']) && stristr($definition['subject'], 'uppercase'))) {
+    // This will uppercase the block title.
+    $build['#configuration']['label'] = Unicode::strtoupper($build['#configuration']['label']);
+  }
+}
+
+/**
+ * @} End of "defgroup block_example".
+ */

+ 7 - 0
sites/all/modules/examples/examples/block_example/block_example.routing.yml

@@ -0,0 +1,7 @@
+block_example.description:
+  path: '/examples/block-example'
+  defaults:
+    _controller: '\Drupal\block_example\Controller\BlockExampleController::description'
+    _title: 'Block Example'
+  requirements:
+    _permission: 'access content'

+ 7 - 0
sites/all/modules/examples/examples/block_example/config/schema/block_example.schema.yml

@@ -0,0 +1,7 @@
+block.settings.example_configurable_text:
+  type: block_settings
+  label: 'Example configurable text block configuration'
+  mapping:
+    block_example_string:
+      type: text
+      label: 'Block contents'

+ 19 - 0
sites/all/modules/examples/examples/block_example/src/Controller/BlockExampleController.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\block_example\Controller;
+
+use Drupal\examples\Utility\DescriptionTemplateTrait;
+/**
+ * Controller routines for block example routes.
+ */
+class BlockExampleController {
+  use DescriptionTemplateTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getModuleName() {
+    return 'block_example';
+  }
+
+}

+ 82 - 0
sites/all/modules/examples/examples/block_example/src/Plugin/Block/ExampleConfigurableTextBlock.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\block_example\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a 'Example: configurable text string' block.
+ *
+ * Drupal\Core\Block\BlockBase gives us a very useful set of basic functionality
+ * for this configurable block. We can just fill in a few of the blanks with
+ * defaultConfiguration(), blockForm(), blockSubmit(), and build().
+ *
+ * @Block(
+ *   id = "example_configurable_text",
+ *   admin_label = @Translation("Example: configurable text")
+ * )
+ */
+class ExampleConfigurableTextBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * This method sets the block default configuration. This configuration
+   * determines the block's behavior when a block is initially placed in a
+   * region. Default values for the block configuration form should be added to
+   * the configuration array. System default configurations are assembled in
+   * BlockBase::__construct() e.g. cache setting and block title visibility.
+   *
+   * @see \Drupal\block\BlockBase::__construct()
+   */
+  public function defaultConfiguration() {
+    return [
+      'block_example_string' => $this->t('A default value. This block was created at %time', ['%time' => date('c')]),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * This method defines form elements for custom block configuration. Standard
+   * block configuration fields are added by BlockBase::buildConfigurationForm()
+   * (block title and title visibility) and BlockFormController::form() (block
+   * visibility settings).
+   *
+   * @see \Drupal\block\BlockBase::buildConfigurationForm()
+   * @see \Drupal\block\BlockFormController::form()
+   */
+  public function blockForm($form, FormStateInterface $form_state) {
+    $form['block_example_string_text'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('Block contents'),
+      '#description' => $this->t('This text will appear in the example block.'),
+      '#default_value' => $this->configuration['block_example_string'],
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * This method processes the blockForm() form fields when the block
+   * configuration form is submitted.
+   *
+   * The blockValidate() method can be used to validate the form submission.
+   */
+  public function blockSubmit($form, FormStateInterface $form_state) {
+    $this->configuration['block_example_string']
+      = $form_state->getValue('block_example_string_text');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->configuration['block_example_string'],
+    ];
+  }
+
+}

+ 30 - 0
sites/all/modules/examples/examples/block_example/src/Plugin/Block/ExampleEmptyBlock.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\block_example\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Provides a 'Example: empty block' block.
+ *
+ * @Block(
+ *   id = "example_empty",
+ *   admin_label = @Translation("Example: empty block")
+ * )
+ */
+class ExampleEmptyBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * The return value of the build() method is a renderable array. Returning an
+   * empty array will result in empty block contents. The front end will not
+   * display empty blocks.
+   */
+  public function build() {
+    // We return an empty array on purpose. The block will thus not be rendered
+    // on the site. See BlockExampleTest::testBlockExampleBasic().
+    return [];
+  }
+
+}

+ 26 - 0
sites/all/modules/examples/examples/block_example/src/Plugin/Block/ExampleUppercaseBlock.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\block_example\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Provides a 'Example: uppercase this please' block.
+ *
+ * @Block(
+ *   id = "example_uppercase",
+ *   admin_label = @Translation("Example: uppercase this please")
+ * )
+ */
+class ExampleUppercaseBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => t("This block's title is changed to uppercase. Any block title which contains 'uppercase' will also be changed to uppercase."),
+    ];
+  }
+
+}

+ 15 - 0
sites/all/modules/examples/examples/block_example/templates/description.html.twig

@@ -0,0 +1,15 @@
+{#
+
+Description text for the Block Example.
+
+#}
+
+{% set block_admin_page = url('block.admin_display')['#markup'] %}
+
+{% trans %}
+
+<p>The Block Example provides three sample blocks which demonstrate the various
+  block APIs. To experiment with the blocks, enable and configure them on
+  <a href="{{ block_admin_page }}">the block admin page</a>.</p>
+
+{% endtrans %}

+ 50 - 0
sites/all/modules/examples/examples/block_example/tests/src/Functional/BlockExampleMenuTest.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\Tests\block_example\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Test the user-facing menus in Block Example.
+ *
+ * @ingroup block_example
+ *
+ * @group block_example
+ * @group examples
+ */
+class BlockExampleMenuTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['block', 'block_example'];
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * This test class requires the "Tools" block.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * Test for a link to the block example in the Tools menu.
+   */
+  public function testBlockExampleLink() {
+    $this->drupalGet('');
+    $this->assertLinkByHref('examples/block-example');
+
+    $this->drupalGet('examples/block-example');
+    $this->assertResponse(200, 'Description page exists.');
+
+    // Verify that the block admin page link works.
+    $this->clickLink('the block admin page');
+    // Since it links to the admin page, we should get a permissions error and
+    // not 404.
+    $this->assertResponse(403);
+  }
+
+}

+ 87 - 0
sites/all/modules/examples/examples/block_example/tests/src/Functional/BlockExampleTest.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\Tests\block_example\Functional;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Test the configuration options and block created by Block Example module.
+ *
+ * @ingroup block_example
+ *
+ * @group block_example
+ * @group examples
+ */
+class BlockExampleTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['block', 'block_example'];
+
+  /**
+   * Tests block_example functionality.
+   */
+  public function testBlockExampleBasic() {
+    $assert = $this->assertSession();
+
+    // Create user.
+    $web_user = $this->drupalCreateUser(['administer blocks']);
+    // Login the admin user.
+    $this->drupalLogin($web_user);
+
+    $theme_name = \Drupal::config('system.theme')->get('default');
+
+    // Verify the blocks are listed to be added.
+    $this->drupalGet('/admin/structure/block/library/' . $theme_name, ['query' => ['region' => 'content']]);
+    $assert->pageTextContains('Example: configurable text');
+    $assert->pageTextContains('Example: empty block');
+    $assert->pageTextContains('Example: uppercase this please');
+
+    // Define and place blocks.
+    $settings_configurable = [
+      'label' => 'Configurable text',
+      'id' => 'block_example_example_configurable_text',
+      'theme' => $theme_name,
+    ];
+    $this->drupalPlaceBlock('example_configurable_text', $settings_configurable);
+
+    $settings_uppercase = [
+      'label' => 'Configurable block to be uppercased',
+      'id' => 'block_example_example_uppercased',
+      'theme' => $theme_name,
+    ];
+    $this->drupalPlaceBlock('example_uppercase', $settings_uppercase);
+
+    $settings_empty = [
+      'label' => 'Empty block',
+      'id' => 'block_example_example_empty',
+      'theme' => $theme_name,
+    ];
+    $this->drupalPlaceBlock('example_empty', $settings_empty);
+
+    // Verify that blocks are there. Empty block will not be shown, because it
+    // holds an empty array.
+    $this->drupalGet('');
+    $assert->pageTextContains($settings_configurable['label']);
+    $assert->pageTextContains($settings_uppercase['label']);
+    $assert->pageTextContains(Unicode::strtoupper($settings_uppercase['label']));
+    $assert->pageTextNotContains($settings_empty['label']);
+
+    // Change content of configurable text block.
+    $edit = [
+      'settings[block_example_string_text]' => $this->randomMachineName(),
+    ];
+    $this->drupalPostForm('/admin/structure/block/manage/' . $settings_configurable['id'], $edit, 'Save block');
+    $assert->statusCodeEquals(200);
+
+    // Verify that new content is shown.
+    $this->drupalGet('');
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains($edit['settings[block_example_string_text]']);
+  }
+
+}

+ 14 - 0
sites/all/modules/examples/examples/cache_example/cache_example.info.yml

@@ -0,0 +1,14 @@
+name: Cache Example
+description: Demonstrates how to use Cache API.
+package: Example modules
+# core: 8.x
+type: module
+dependencies:
+  - drupal:node
+  - examples:examples
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 4 - 0
sites/all/modules/examples/examples/cache_example/cache_example.links.menu.yml

@@ -0,0 +1,4 @@
+cache_example.description:
+  title: 'Cache Example'
+  description: 'Example of Drupal Cache API'
+  route_name: cache_example.description

+ 36 - 0
sites/all/modules/examples/examples/cache_example/cache_example.module

@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Outlines how a module can use the Cache API.
+ *
+ * @todo: Demonstrate allowing invalid cache items.
+ * @todo: Demonstrate deleteing cache entries.
+ * @todo: Demonstrate deleteing cache tags.
+ * @todo: Demonstrate deleteing all cache items.
+ * @todo: Demonstrate invalidating cache entries.
+ * @todo: Demonstrate invalidating cache tags.
+ * @todo: Demonstrate invalidating all cache items.
+ */
+
+/**
+ * @defgroup cache_example Example: Cache API
+ * @ingroup examples
+ * @{
+ * Outlines how a module can use the Cache API.
+ *
+ * Cache API allows us to cache data that is heavy to calculate. As this can
+ * significantly speed up the Drupal site, it is recommended to use cache
+ * mechanism when it is appropriate.
+ *
+ * Cache in Drupal is very easy to use. This example will search entire Drupal
+ * folder and display all files. Since this operation includes filesystem it can
+ * take a while. This list will not change much on production
+ * websites, so we decide to cache it.
+ *
+ * @see \Drupal\Core\Cache\CacheBackendInterface
+ */
+
+/**
+ * @} End of "defgroup cache_example".
+ */

+ 6 - 0
sites/all/modules/examples/examples/cache_example/cache_example.routing.yml

@@ -0,0 +1,6 @@
+cache_example.description:
+  path: 'examples/cache-example'
+  defaults:
+    _form: '\Drupal\cache_example\Form\CacheExampleForm'
+  requirements:
+    _permission: 'access content'

+ 306 - 0
sites/all/modules/examples/examples/cache_example/src/Form/CacheExampleForm.php

@@ -0,0 +1,306 @@
+<?php
+
+namespace Drupal\cache_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+
+/**
+ * Form with examples on how to use cache.
+ */
+class CacheExampleForm extends FormBase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The cache.default cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * Dependency injection through the constructor.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack service.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The string translation service.
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   The current active user service.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache object associated with the default bin.
+   */
+  public function __construct(
+    RequestStack $request_stack,
+    TranslationInterface $translation,
+    AccountProxyInterface $current_user,
+    CacheBackendInterface $cache_backend
+  ) {
+    $this->setRequestStack($request_stack);
+    $this->setStringTranslation($translation);
+    $this->currentUser = $current_user;
+    $this->cacheBackend = $cache_backend;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    // Forms that require a Drupal service or a custom service should access
+    // the service using dependency injection.
+    // @link https://www.drupal.org/node/2203931.
+    // Those services are passed in the $container through the static create
+    // method.
+    return new static(
+      $container->get('request_stack'),
+      $container->get('string_translation'),
+      $container->get('current_user'),
+      $container->get('cache.default')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'cron_cache';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Log execution time.
+    $start_time = microtime(TRUE);
+
+    // Try to load the files count from cache. This function will accept two
+    // arguments:
+    // - cache object name (cid)
+    // - cache bin, the (optional) cache bin (most often a database table) where
+    //   the object is to be saved.
+    //
+    // cache_get() returns the cached object or FALSE if object does not exist.
+    if ($cache = $this->cacheBackend->get('cache_example_files_count')) {
+      /*
+       * Get cached data. Complex data types will be unserialized automatically.
+       */
+      $files_count = $cache->data;
+    }
+    else {
+      // If there was no cached data available we have to search filesystem.
+      // Recursively get all .PHP files from Drupal's core folder.
+      $files_count = count(file_scan_directory('core', '/.php/'));
+
+      // Since we have recalculated, we now need to store the new data into
+      // cache. Complex data types will be automatically serialized before
+      // being saved into cache.
+      // Here we use the default setting and create an unexpiring cache item.
+      // See below for an example that creates an expiring cache item.
+      $this->cacheBackend->set('cache_example_files_count', $files_count, CacheBackendInterface::CACHE_PERMANENT);
+    }
+
+    $end_time = microtime(TRUE);
+    $duration = $end_time - $start_time;
+
+    // Format intro message.
+    $intro_message = '<p>' . $this->t("This example will search Drupal's core folder and display a count of the PHP files in it.") . ' ';
+    $intro_message .= $this->t('This can take a while, since there are a lot of files to be searched.') . ' ';
+    $intro_message .= $this->t('We will search filesystem just once and save output to the cache. We will use cached data for later requests.') . '</p>';
+    $intro_message .= '<p>'
+      . $this->t(
+        '<a href="@url">Reload this page</a> to see cache in action.',
+        ['@url' => $this->getRequest()->getRequestUri()]
+      )
+      . ' ';
+    $intro_message .= $this->t('You can use the button below to remove cached data.') . '</p>';
+
+    $form['file_search'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('File search caching'),
+    ];
+    $form['file_search']['introduction'] = [
+      '#markup' => $intro_message,
+    ];
+
+    $color = empty($cache) ? 'red' : 'green';
+    $retrieval = empty($cache) ? $this->t('calculated by traversing the filesystem') : $this->t('retrieved from cache');
+
+    $form['file_search']['statistics'] = [
+      '#type' => 'item',
+      '#markup' => $this->t('%count files exist in this Drupal installation; @retrieval in @time ms. <br/>(Source: <span style="color:@color;">@source</span>)', [
+        '%count' => $files_count,
+        '@retrieval' => $retrieval,
+        '@time' => number_format($duration * 1000, 2),
+        '@color' => $color,
+        '@source' => empty($cache) ? $this->t('actual file search') : $this->t('cached'),
+      ]
+      ),
+    ];
+    $form['file_search']['remove_file_count'] = [
+      '#type' => 'submit',
+      '#submit' => [[$this, 'expireFiles']],
+      '#value' => $this->t('Explicitly remove cached file count'),
+    ];
+
+    $form['expiration_demo'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Cache expiration settings'),
+    ];
+    $form['expiration_demo']['explanation'] = [
+      '#markup' => $this->t('A cache item can be set as CACHE_PERMANENT, meaning that it will only be removed when explicitly cleared, or it can have an expiration time (a Unix timestamp).'),
+    ];
+
+    $item = $this->cacheBackend->get('cache_example_expiring_item', TRUE);
+    if ($item == FALSE) {
+      $item_status = $this->t('Cache item does not exist');
+    }
+    else {
+      $item_status = $item->valid ? $this->t('Cache item exists and is set to expire at %time', ['%time' => $item->data]) :
+      $this->t('Cache_item is invalid');
+    }
+
+    $form['expiration_demo']['current_status'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Current status of cache item "cache_example_expiring_item"'),
+      '#markup' => $item_status,
+    ];
+    $form['expiration_demo']['expiration'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Time before cache expiration'),
+      '#options' => [
+        'never_remove' => $this->t('CACHE_PERMANENT'),
+        -10 => $this->t('Immediate expiration'),
+        10 => $this->t('10 seconds from form submission'),
+        60 => $this->t('1 minute from form submission'),
+        300 => $this->t('5 minutes from form submission'),
+      ],
+      '#default_value' => -10,
+      '#description' => $this->t('Any cache item can be set to only expire when explicitly cleared, or to expire at a given time.'),
+    ];
+    $form['expiration_demo']['create_cache_item'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Create a cache item with this expiration'),
+      '#submit' => [[$this, 'createExpiringItem']],
+    ];
+
+    $form['cache_clearing'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Expire and remove options'),
+      '#description' => $this->t("We have APIs to expire cached items and also to just remove them. Unfortunately, they're all the same API, cache_clear_all"),
+    ];
+    $form['cache_clearing']['cache_clear_type'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Type of cache clearing to do'),
+      '#options' => [
+        'expire' => $this->t('Remove items from the "cache" bin that have expired'),
+        'remove_all' => $this->t('Remove all items from the "cache" bin regardless of expiration'),
+        'remove_tag' => $this->t('Remove all items in the "cache" bin with the tag "cache_example" set to 1'),
+      ],
+      '#default_value' => 'expire',
+    ];
+    // Submit button to clear cached data.
+    $form['cache_clearing']['clear_expired'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Clear or expire cache'),
+      '#submit' => [[$this, 'cacheClearing']],
+      '#access' => $this->currentUser->hasPermission('administer site configuration'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * Submit handler that explicitly clears cache_example_files_count from cache.
+   */
+  public function expireFiles($form, &$form_state) {
+    // Clear cached data. This function will delete cached object from cache
+    // bin.
+    //
+    // The first argument is cache id to be deleted. Since we've provided it
+    // explicitly, it will be removed whether or not it has an associated
+    // expiration time. The second argument (required here) is the cache bin.
+    // Using cache_clear_all() explicitly in this way
+    // forces removal of the cached item.
+    $this->cacheBackend->delete('cache_example_files_count');
+
+    // Display message to the user.
+    drupal_set_message($this->t('Cached data key "cache_example_files_count" was cleared.'), 'status');
+  }
+
+  /**
+   * Submit handler to create a new cache item with specified expiration.
+   */
+  public function createExpiringItem($form, &$form_state) {
+
+    $tags = [
+      'cache_example:1',
+    ];
+
+    $interval = $form_state->getValue('expiration');
+    if ($interval == 'never_remove') {
+      $expiration = CacheBackendInterface::CACHE_PERMANENT;
+      $expiration_friendly = $this->t('Never expires');
+    }
+    else {
+      $expiration = time() + $interval;
+      $expiration_friendly = format_date($expiration);
+    }
+    // Set the expiration to the actual Unix timestamp of the end of the
+    // required interval. Also add a tag to it to be able to clear caches more
+    // precise.
+    $this->cacheBackend->set('cache_example_expiring_item', $expiration_friendly, $expiration, $tags);
+    drupal_set_message($this->t('cache_example_expiring_item was set to expire at %time', ['%time' => $expiration_friendly]));
+  }
+
+  /**
+   * Submit handler to demonstrate the various uses of cache_clear_all().
+   */
+  public function cacheClearing($form, &$form_state) {
+    switch ($form_state->getValue('cache_clear_type')) {
+      case 'expire':
+        // Here we'll remove all cache keys in the 'cache' bin that have
+        // expired.
+        $this->cacheBackend->garbageCollection();
+        drupal_set_message($this->t('\Drupal::cache()->garbageCollection() was called, removing any expired cache items.'));
+        break;
+
+      case 'remove_all':
+        // This removes all keys in a bin using a super-wildcard. This
+        // has nothing to do with expiration. It's just brute-force removal.
+        $this->cacheBackend->deleteAll();
+        drupal_set_message($this->t('ALL entries in the "cache" bin were removed with \Drupal::cache()->deleteAll().'));
+        break;
+
+      case 'remove_tag':
+        // This removes cache entries with the tag "cache_example" set to 1 in
+        // the "cache".
+        $tags = [
+          'cache_example:1',
+        ];
+        Cache::invalidateTags($tags);
+        drupal_set_message($this->t('Cache entries with the tag "cache_example" set to 1 in the "cache" bin were invalidated with \Drupal\Core\Cache\Cache::invalidateTags($tags).'));
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+
+  }
+
+}

+ 110 - 0
sites/all/modules/examples/examples/cache_example/tests/src/Functional/CacheExampleTest.php

@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\Tests\cache_example\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests for the cache_example module.
+ *
+ * @ingroup cache_example
+ *
+ * @group cache_example
+ * @group examples
+ */
+class CacheExampleTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['cache_example'];
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * Test menu links and routes.
+   *
+   * Test the following:
+   * - A link to the cache_example in the Tools menu.
+   * - That you can successfully access the cache_example form.
+   */
+  public function testCacheExampleMenu() {
+
+    $assert = $this->assertSession();
+
+    // Test for a link to the cache_example in the Tools menu.
+    $this->drupalGet('');
+    $assert->statusCodeEquals(200);
+
+    $assert->linkByHrefExists('examples/cache-example');
+
+    // Verify if the can successfully access the cache_example form.
+    $this->drupalGet('examples/cache-example');
+    $assert->statusCodeEquals(200);
+  }
+
+  /**
+   * Test that our caches function.
+   *
+   * Does the following:
+   * - Load cache example page and test if displaying uncached version.
+   * - Reload once again and test if displaying cached version.
+   * - Find reload link and click on it.
+   * - Clear cache at the end and test if displaying uncached version again.
+   */
+  public function testCacheExampleBasic() {
+    $assert = $this->assertSession();
+
+    // We need administrative privileges to clear the cache.
+    $admin_user = $this->drupalCreateUser(['administer site configuration']);
+    $this->drupalLogin($admin_user);
+
+    // Get initial page cache example page, first time accessed,
+    // and assert uncached output.
+    $this->drupalGet('examples/cache-example');
+    $assert->pageTextContains('Source: actual file search');
+
+    // Reload the page; the number should be cached.
+    $this->drupalGet('examples/cache-example');
+    $assert->pageTextContains('Source: cached');
+
+    // Now push the button to remove the count.
+    $this->drupalPostForm('examples/cache-example', [], 'Explicitly remove cached file count');
+    $assert->pageTextContains('Source: actual file search');
+
+    // Create a cached item. First make sure it doesn't already exist.
+    $assert->pageTextContains('Cache item does not exist');
+    $this->drupalPostForm('examples/cache-example', ['expiration' => -10], 'Create a cache item with this expiration');
+    // We should now have an already-expired item. Automatically invalid.
+    $assert->pageTextContains('Cache_item is invalid');
+    // Now do the expiration operation.
+    $this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'expire'], 'Clear or expire cache');
+    // And verify that it was removed.
+    $assert->pageTextContains('Cache item does not exist');
+
+    // Create a cached item. This time we'll make it not expire.
+    $this->drupalPostForm('examples/cache-example', ['expiration' => 'never_remove'], 'Create a cache item with this expiration');
+    // We should now have an never-remove item.
+    $assert->pageTextContains('Cache item exists and is set to expire at Never expires');
+    // Now do the expiration operation.
+    $this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'expire'], 'Clear or expire cache');
+    // And verify that it was not removed.
+    $assert->pageTextContains('Cache item exists and is set to expire at Never expires');
+    // Now do tag invalidation.
+    $this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'remove_tag'], 'Clear or expire cache');
+    // And verify that it was invalidated.
+    $assert->pageTextContains('Cache_item is invalid');
+    // Do the hard delete.
+    $this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'remove_all'], 'Clear or expire cache');
+    // And verify that it was removed.
+    $assert->pageTextContains('Cache item does not exist');
+  }
+
+}

+ 12 - 0
sites/all/modules/examples/examples/composer.json

@@ -0,0 +1,12 @@
+{
+  "name": "drupal/examples",
+  "description": "The Examples for Developers project aims to provide high-quality, well-documented API examples for a broad range of Drupal core functionality.",
+  "type": "drupal-module",
+  "homepage": "https://www.drupal.org/project/examples",
+  "support": {
+    "issues": "https://www.drupal.org/project/issues/examples",
+    "documentation": "https://api.drupal.org/api/examples",
+    "source": "http://cgit.drupalcode.org/examples"
+  },
+  "license": "GPL-2.0+"
+}

+ 20 - 0
sites/all/modules/examples/examples/config_entity_example/config/install/config_entity_example.robot.marvin.yml

@@ -0,0 +1,20 @@
+# This file defines a default config entity. This allows the module to include
+# config entities that are present 'out of the box'. Default config entities
+# are created in Drupal when the module is enabled. They are removed when the
+# module is uninstalled.
+
+# Default config entities can be edited by the user within Drupal. This edited
+# entity wil become exportable through the configuration system. This file,
+# however, will remain untouched.
+
+# For our config entity to be added at installation time, we have to place it in
+# the config/install directory of our module. Thus our file is located at:
+# config/install/config_entity_example.robot.marvin.yml.
+
+# You can see where these properties are defined in the annotation of
+# Drupal\config_entity_example\Entity\Robot.
+
+# The id of the config entity.
+id: marvin
+# Our properties follow.
+label: 'Marvin, the paranoid android'

+ 28 - 0
sites/all/modules/examples/examples/config_entity_example/config/schema/config_entity_example.schema.yml

@@ -0,0 +1,28 @@
+# Schema for the configuration files of the Config Entity Example module.
+
+# This schema tells the config system how to read our config YML files.
+# See for example the file config/config_entity_example.robot.marvin.yml, which
+# contains our default config entity.
+
+# Documentation for schema files like this one is located here:
+# https://drupal.org/node/1905070
+
+config_entity_example.robot.*:
+  type: mapping
+  label: 'Robot'
+  mapping:
+    id:
+      type: string
+      label: 'Robot id'
+    uuid:
+      type: string
+      label: 'UUID'
+    label:
+      type: label
+      label: 'Label'
+    floopy:
+      type: boolean
+      label: 'Floopy'
+    langcode:
+      type: string
+      label: 'Default language'

+ 13 - 0
sites/all/modules/examples/examples/config_entity_example/config_entity_example.info.yml

@@ -0,0 +1,13 @@
+name: 'Config Entity Example'
+type: module
+description: 'Demonstrates how to create a config entity type.'
+package: Example modules
+# core: 8.x
+dependencies:
+  - examples:examples
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 14 - 0
sites/all/modules/examples/examples/config_entity_example/config_entity_example.links.action.yml

@@ -0,0 +1,14 @@
+# Add some local task links to facilitate navigation.
+
+config_entity_example.add_action:
+  route_name: entity.robot.add_form
+  title: 'Add robot'
+  appears_on:
+    - entity.robot.list
+
+config_entity_example.list_action:
+  route_name: entity.robot.list
+  title: 'List robots'
+  appears_on:
+    - entity.robot.add_form
+    - entity.robot.edit_form

+ 3 - 0
sites/all/modules/examples/examples/config_entity_example/config_entity_example.links.menu.yml

@@ -0,0 +1,3 @@
+config_entity_example.menu:
+  title: Config Entity Example
+  route_name: entity.robot.list

+ 42 - 0
sites/all/modules/examples/examples/config_entity_example/config_entity_example.module

@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Demonstrates how to define a new configuration entity type.
+ */
+
+/**
+ * @defgroup config_entity_example Example: Config Entity
+ * @ingroup examples
+ * @{
+ * Implement a Config Entity.
+ *
+ * This module demonstrates implementing a Config Entity.
+ *
+ * This is an example of a simple configuration entity, the kind you might
+ * create to store administrator-defined objects like blocks or views.
+ *
+ * In this module we define a configuration entity named 'Robot'. The entity
+ * has a unique ID (also called a machine name), a human-readable label used
+ * for display, and a universally unique identifier. You can create new robots
+ * by navigating to &lt;your_site_root&gt;/examples/config_entity_example. A
+ * default robot, "marvin", is included with the module.
+ *
+ * What's special about a Configuration Entity?
+ *
+ * Configuration entities are entities just like content entities. The key
+ * difference is where the data is stored. Content entities are stored in the
+ * database. Configuration entities are stored in *.yml files, typically under
+ * &lt;your_site_root&gt;/sites/default/files/config_&lt;unique_id&gt;.
+ *
+ * Another key difference with configuration entities is the expectation they
+ * are created by administrators, and not end users. As files, configuration
+ * entities can be added to a version control system.
+ *
+ * Originally based on code from blog post at
+ * http://previousnext.com.au/blog/understanding-drupal-8s-config-entities
+ */
+
+/**
+ * @} End of "defgroup config_entity_example".
+ */

+ 4 - 0
sites/all/modules/examples/examples/config_entity_example/config_entity_example.permissions.yml

@@ -0,0 +1,4 @@
+#Define the administer permission for the robot config entity.
+'administer robots':
+  title: 'Administer robots'
+  description: Create and edit robots.

+ 56 - 0
sites/all/modules/examples/examples/config_entity_example/config_entity_example.routing.yml

@@ -0,0 +1,56 @@
+# The routing.yml file defines the paths for our module.
+# Here we define the paths for our entity type's admin UI.
+
+# This is the router item for listing all entities.
+entity.robot.list:
+  path: '/examples/config-entity-example'
+  defaults:
+    # '_entity_list' tells Drupal to use an entity list controller.
+    # We give the entity ID here. Drupal then looks in the entity's annotation
+    # and looks for the "list" entry under "controllers" for the class to load.
+    # @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
+    _entity_list: 'robot'
+    _title: 'Config Entity Example'
+  requirements:
+    _permission: 'administer robots'
+
+# This is the router item for adding our entity.
+entity.robot.add_form:
+  path: '/examples/config-entity-example/add'
+  defaults:
+    _title: 'Add robot'
+    # Like _entity_list above, _entity_form gives the entity type ID, only this
+    # time also lists the form separated by a period. Drupal looks in the
+    # annotation for the entity and locates the "add" entry under "form" for
+    # the form class to load.
+    # @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
+    _entity_form: robot.add
+  requirements:
+    _entity_create_access: robot
+
+# This is the router item for editing our entity.
+entity.robot.edit_form:
+  # Parameters may be passed to the form via the URL path. We name the
+  # parameter in the path by enclosing it in curly braces. For entity forms,
+  # we include the entity ID in the path by including a parameter with the
+  # same name as the entity type ID.
+  path: '/examples/config-entity-example/manage/{robot}'
+  defaults:
+    _title: 'Edit robot'
+    # List our add entry above, this _entity_form entry instructs Drupal to
+    # read our entity type's annonation, and look for the "edit" entry under
+    # "form".
+    _entity_form: robot.edit
+  requirements:
+    # This uses our entity access controller.
+    # @see \Drupal\Core\Entity\EntityAccessCheck
+    _entity_access: robot.update
+
+# This is the router item for deleting an instance of our entity.
+entity.robot.delete_form:
+  path: '/examples/config-entity-example/manage/{robot}/delete'
+  defaults:
+    _title: 'Delete robot'
+    _entity_form: robot.delete
+  requirements:
+    _entity_access: robot.delete

+ 87 - 0
sites/all/modules/examples/examples/config_entity_example/src/Controller/RobotListBuilder.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\config_entity_example\Controller;
+
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\examples\Utility\DescriptionTemplateTrait;
+
+/**
+ * Provides a listing of robot entities.
+ *
+ * List Controllers provide a list of entities in a tabular form. The base
+ * class provides most of the rendering logic for us. The key functions
+ * we need to override are buildHeader() and buildRow(). These control what
+ * columns are displayed in the table, and how each row is displayed
+ * respectively.
+ *
+ * Drupal locates the list controller by looking for the "list" entry under
+ * "controllers" in our entity type's annotation. We define the path on which
+ * the list may be accessed in our module's *.routing.yml file. The key entry
+ * to look for is "_entity_list". In *.routing.yml, "_entity_list" specifies
+ * an entity type ID. When a user navigates to the URL for that router item,
+ * Drupal loads the annotation for that entity type. It looks for the "list"
+ * entry under "controllers" for the class to load.
+ *
+ * @ingroup config_entity_example
+ */
+class RobotListBuilder extends ConfigEntityListBuilder {
+  use DescriptionTemplateTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getModuleName() {
+    return 'config_entity_example';
+  }
+
+  /**
+   * Builds the header row for the entity listing.
+   *
+   * @return array
+   *   A render array structure of header strings.
+   *
+   * @see \Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildHeader() {
+    $header['label'] = $this->t('Robot');
+    $header['machine_name'] = $this->t('Machine Name');
+    $header['floopy'] = $this->t('Floopy');
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * Builds a row for an entity in the entity listing.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which to build the row.
+   *
+   * @return array
+   *   A render array of the table row for displaying the entity.
+   *
+   * @see \Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['label'] = $entity->label();
+    $row['machine_name'] = $entity->id();
+    $row['floopy'] = $entity->floopy;
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * Adds some descriptive text to our entity list.
+   *
+   * Typically, there's no need to override render(). You may wish to do so,
+   * however, if you want to add markup before or after the table.
+   *
+   * @return array
+   *   Renderable array.
+   */
+  public function render() {
+    $build = $this->description();
+    $build[] = parent::render();
+    return $build;
+  }
+
+}

+ 89 - 0
sites/all/modules/examples/examples/config_entity_example/src/Entity/Robot.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\config_entity_example\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+
+/**
+ * Defines the robot entity.
+ *
+ * The lines below, starting with '@ConfigEntityType,' are a plugin annotation.
+ * These define the entity type to the entity type manager.
+ *
+ * The properties in the annotation are as follows:
+ *  - id: The machine name of the entity type.
+ *  - label: The human-readable label of the entity type. We pass this through
+ *    the "@Translation" wrapper so that the multilingual system may
+ *    translate it in the user interface.
+ *  - handlers: An array of entity handler classes, keyed by handler type.
+ *    - access: The class that is used for access checks.
+ *    - list_builder: The class that provides listings of the entity.
+ *    - form: An array of entity form classes keyed by their operation.
+ *  - entity_keys: Specifies the class properties in which unique keys are
+ *    stored for this entity type. Unique keys are properties which you know
+ *    will be unique, and which the entity manager can use as unique in database
+ *    queries.
+ *  - links: entity URL definitions. These are mostly used for Field UI.
+ *    Arbitrary keys can set here. For example, User sets cancel-form, while
+ *    Node uses delete-form.
+ *
+ * @see http://previousnext.com.au/blog/understanding-drupal-8s-config-entities
+ * @see annotation
+ * @see Drupal\Core\Annotation\Translation
+ *
+ * @ingroup config_entity_example
+ *
+ * @ConfigEntityType(
+ *   id = "robot",
+ *   label = @Translation("Robot"),
+ *   admin_permission = "administer robots",
+ *   handlers = {
+ *     "access" = "Drupal\config_entity_example\RobotAccessController",
+ *     "list_builder" = "Drupal\config_entity_example\Controller\RobotListBuilder",
+ *     "form" = {
+ *       "add" = "Drupal\config_entity_example\Form\RobotAddForm",
+ *       "edit" = "Drupal\config_entity_example\Form\RobotEditForm",
+ *       "delete" = "Drupal\config_entity_example\Form\RobotDeleteForm"
+ *     }
+ *   },
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label"
+ *   },
+ *   links = {
+ *     "edit-form" = "/examples/config_entity_example/manage/{robot}",
+ *     "delete-form" = "/examples/config_entity_example/manage/{robot}/delete"
+ *   }
+ * )
+ */
+class Robot extends ConfigEntityBase {
+
+  /**
+   * The robot ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The robot UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The robot label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The robot floopy flag.
+   *
+   * @var string
+   */
+  public $floopy;
+
+}

+ 35 - 0
sites/all/modules/examples/examples/config_entity_example/src/Form/RobotAddForm.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\config_entity_example\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class RobotAddForm.
+ *
+ * Provides the add form for our Robot entity.
+ *
+ * @ingroup config_entity_example
+ */
+class RobotAddForm extends RobotFormBase {
+
+  /**
+   * Returns the actions provided by this form.
+   *
+   * For our add form, we only need to change the text of the submit button.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An array of supported actions for the current entity form.
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = $this->t('Create Robot');
+    return $actions;
+  }
+
+}

+ 89 - 0
sites/all/modules/examples/examples/config_entity_example/src/Form/RobotDeleteForm.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\config_entity_example\Form;
+
+use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class RobotDeleteForm.
+ *
+ * Provides a confirm form for deleting the entity. This is different from the
+ * add and edit forms as it does not inherit from RobotFormBase. The reason for
+ * this is that we do not need to build the same form. Instead, we present the
+ * user with a simple yes/no question. For this reason, we derive from
+ * EntityConfirmFormBase instead.
+ *
+ * @ingroup config_entity_example
+ */
+class RobotDeleteForm extends EntityConfirmFormBase {
+
+  /**
+   * Gathers a confirmation question.
+   *
+   * The question is used as a title in our confirm form. For delete confirm
+   * forms, this typically takes the form of "Are you sure you want to
+   * delete...", including the entity label.
+   *
+   * @return string
+   *   Translated string.
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete robot %label?', [
+      '%label' => $this->entity->label(),
+    ]);
+  }
+
+  /**
+   * Gather the confirmation text.
+   *
+   * The confirm text is used as the text in the button that confirms the
+   * question posed by getQuestion().
+   *
+   * @return string
+   *   Translated string.
+   */
+  public function getConfirmText() {
+    return $this->t('Delete Robot');
+  }
+
+  /**
+   * Gets the cancel URL.
+   *
+   * Provides the URL to go to if the user cancels the action. For entity
+   * delete forms, this is typically the route that points at the list
+   * controller.
+   *
+   * @return \Drupal\Core\Url
+   *   The URL to go to if the user cancels the deletion.
+   */
+  public function getCancelUrl() {
+    return new Url('entity.robot.list');
+  }
+
+  /**
+   * The submit handler for the confirm form.
+   *
+   * For entity delete forms, you use this to delete the entity in
+   * $this->entity.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Delete the entity.
+    $this->entity->delete();
+
+    // Set a message that the entity was deleted.
+    drupal_set_message($this->t('Robot %label was deleted.', [
+      '%label' => $this->entity->label(),
+    ]));
+
+    // Redirect the user to the list controller when complete.
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}

+ 35 - 0
sites/all/modules/examples/examples/config_entity_example/src/Form/RobotEditForm.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\config_entity_example\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class RobotEditForm.
+ *
+ * Provides the edit form for our Robot entity.
+ *
+ * @ingroup config_entity_example
+ */
+class RobotEditForm extends RobotFormBase {
+
+  /**
+   * Returns the actions provided by this form.
+   *
+   * For the edit form, we only need to change the text of the submit button.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An array of supported actions for the current entity form.
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = $this->t('Update Robot');
+    return $actions;
+  }
+
+}

+ 221 - 0
sites/all/modules/examples/examples/config_entity_example/src/Form/RobotFormBase.php

@@ -0,0 +1,221 @@
+<?php
+
+namespace Drupal\config_entity_example\Form;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class RobotFormBase.
+ *
+ * Typically, we need to build the same form for both adding a new entity,
+ * and editing an existing entity. Instead of duplicating our form code,
+ * we create a base class. Drupal never routes to this class directly,
+ * but instead through the child classes of RobotAddForm and RobotEditForm.
+ *
+ * @ingroup config_entity_example
+ */
+class RobotFormBase extends EntityForm {
+
+  /**
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $entityQueryFactory;
+
+  /**
+   * Construct the RobotFormBase.
+   *
+   * For simple entity forms, there's no need for a constructor. Our robot form
+   * base, however, requires an entity query factory to be injected into it
+   * from the container. We later use this query factory to build an entity
+   * query for the exists() method.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   An entity query factory for the robot entity type.
+   */
+  public function __construct(QueryFactory $query_factory) {
+    $this->entityQueryFactory = $query_factory;
+  }
+
+  /**
+   * Factory method for RobotFormBase.
+   *
+   * When Drupal builds this class it does not call the constructor directly.
+   * Instead, it relies on this method to build the new object. Why? The class
+   * constructor may take multiple arguments that are unknown to Drupal. The
+   * create() method always takes one parameter -- the container. The purpose
+   * of the create() method is twofold: It provides a standard way for Drupal
+   * to construct the object, meanwhile it provides you a place to get needed
+   * constructor parameters from the container.
+   *
+   * In this case, we ask the container for an entity query factory. We then
+   * pass the factory to our class as a constructor parameter.
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity.query'));
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   *
+   * Builds the entity add/edit form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An associative array containing the robot add/edit form.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Get anything we need from the base class.
+    $form = parent::buildForm($form, $form_state);
+
+    // Drupal provides the entity to us as a class variable. If this is an
+    // existing entity, it will be populated with existing values as class
+    // variables. If this is a new entity, it will be a new object with the
+    // class of our entity. Drupal knows which class to call from the
+    // annotation on our Robot class.
+    $robot = $this->entity;
+
+    // Build the form.
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $robot->label(),
+      '#required' => TRUE,
+    ];
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#title' => $this->t('Machine name'),
+      '#default_value' => $robot->id(),
+      '#machine_name' => [
+        'exists' => [$this, 'exists'],
+        'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
+        'error' => 'The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".',
+      ],
+      '#disabled' => !$robot->isNew(),
+    ];
+    $form['floopy'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Floopy'),
+      '#default_value' => $robot->floopy,
+    ];
+
+    // Return the form.
+    return $form;
+  }
+
+  /**
+   * Checks for an existing robot.
+   *
+   * @param string|int $entity_id
+   *   The entity ID.
+   * @param array $element
+   *   The form element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return bool
+   *   TRUE if this format already exists, FALSE otherwise.
+   */
+  public function exists($entity_id, array $element, FormStateInterface $form_state) {
+    // Use the query factory to build a new robot entity query.
+    $query = $this->entityQueryFactory->get('robot');
+
+    // Query the entity ID to see if its in use.
+    $result = $query->condition('id', $element['#field_prefix'] . $entity_id)
+      ->execute();
+
+    // We don't need to return the ID, only if it exists or not.
+    return (bool) $result;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::actions().
+   *
+   * To set the submit button text, we need to override actions().
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An array of supported actions for the current entity form.
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    // Get the basic actins from the base class.
+    $actions = parent::actions($form, $form_state);
+
+    // Change the submit button text.
+    $actions['submit']['#value'] = $this->t('Save');
+
+    // Return the result.
+    return $actions;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::validate().
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function validate(array $form, FormStateInterface $form_state) {
+    parent::validate($form, $form_state);
+
+    // Add code here to validate your config entity's form elements.
+    // Nothing to do here.
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   *
+   * Saves the entity. This is called after submit() has built the entity from
+   * the form values. Do not override submit() as save() is the preferred
+   * method for entity form controllers.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    // EntityForm provides us with the entity we're working on.
+    $robot = $this->getEntity();
+
+    // Drupal already populated the form values in the entity object. Each
+    // form field was saved as a public variable in the entity class. PHP
+    // allows Drupal to do this even if the method is not defined ahead of
+    // time.
+    $status = $robot->save();
+
+    // Grab the URL of the new entity. We'll use it in the message.
+    $url = $robot->urlInfo();
+
+    // Create an edit link.
+    $edit_link = Link::fromTextAndUrl($this->t('Edit'), $url)->toString();
+
+    if ($status == SAVED_UPDATED) {
+      // If we edited an existing entity...
+      drupal_set_message($this->t('Robot %label has been updated.', ['%label' => $robot->label()]));
+      $this->logger('contact')->notice('Robot %label has been updated.', ['%label' => $robot->label(), 'link' => $edit_link]);
+    }
+    else {
+      // If we created a new entity...
+      drupal_set_message($this->t('Robot %label has been added.', ['%label' => $robot->label()]));
+      $this->logger('contact')->notice('Robot %label has been added.', ['%label' => $robot->label(), 'link' => $edit_link]);
+    }
+
+    // Redirect the user back to the listing route after the save operation.
+    $form_state->setRedirect('entity.robot.list');
+  }
+
+}

+ 34 - 0
sites/all/modules/examples/examples/config_entity_example/src/RobotAccessController.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\config_entity_example;
+
+use Drupal\Core\Entity\EntityAccessControlHandler;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines an access controller for the robot entity.
+ *
+ * We set this class to be the access controller in Robot's entity annotation.
+ *
+ * @see \Drupal\config_entity_example\Entity\Robot
+ *
+ * @ingroup config_entity_example
+ */
+class RobotAccessController extends EntityAccessControlHandler {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+    // The $opereration parameter tells you what sort of operation access is
+    // being checked for.
+    if ($operation == 'view') {
+      return TRUE;
+    }
+    // Other than the view operation, we're going to be insanely lax about
+    // access. Don't try this at home!
+    return parent::checkAccess($entity, $operation, $account);
+  }
+
+}

+ 17 - 0
sites/all/modules/examples/examples/config_entity_example/templates/description.html.twig

@@ -0,0 +1,17 @@
+{#
+
+Description text for the Config Entity Example.
+
+#}
+
+{% trans %}
+
+<p>The Config Entity Example module defines a Robot entity type. This is a list
+  of the Robot entities currently in your Drupal site.</p><p>By default, when
+  you enable this module, one entity is created from configuration. This is
+  why we call them Config Entities. Marvin, the paranoid android, is created
+  in the database when the module is enabled.</p><p>You can view a list of
+  Robots here. You can also use the 'Operations' column to edit and delete
+  Robots.</p>
+
+{% endtrans %}

+ 172 - 0
sites/all/modules/examples/examples/config_entity_example/tests/src/Functional/ConfigEntityExampleTest.php

@@ -0,0 +1,172 @@
+<?php
+
+namespace Drupal\Tests\config_entity_example\Functional;
+use Drupal\config_entity_example\Entity\Robot;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Test the Config Entity Example module.
+ *
+ * @group config_entity_example
+ * @group examples
+ *
+ * @ingroup config_entity_example
+ */
+class ConfigEntityExampleTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['config_entity_example'];
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * We need the 'minimal' profile in order to make sure the Tool block is
+   * available.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * Various functional test of the Config Entity Example module.
+   *
+   * 1) Verify that the Marvin entity was created when the module was installed.
+   *
+   * 2) Verify that permissions are applied to the various defined paths.
+   *
+   * 3) Verify that we can manage entities through the user interface.
+   *
+   * 4) Verify that the entity we add can be re-edited.
+   *
+   * 5) Verify that the label is shown in the list.
+   */
+  public function testConfigEntityExample() {
+    $assert = $this->assertSession();
+
+    // 1) Verify that the Marvin entity was created when the module was
+    // installed.
+    $entity = Robot::load('marvin');
+    $this->assertNotNull($entity, 'Marvin was created during installation.');
+
+    // 2) Verify that permissions are applied to the various defined paths.
+    // Define some paths. Since the Marvin entity is defined, we can use it
+    // in our management paths.
+    $forbidden_paths = [
+      '/examples/config-entity-example',
+      '/examples/config-entity-example/add',
+      '/examples/config-entity-example/manage/marvin',
+      '/examples/config-entity-example/manage/marvin/delete',
+    ];
+    // Check each of the paths to make sure we don't have access. At this point
+    // we haven't logged in any users, so the client is anonymous.
+    foreach ($forbidden_paths as $path) {
+      $this->drupalGet($path);
+      $assert->statusCodeEquals(403);
+    }
+
+    // Create a user with no permissions.
+    $noperms_user = $this->drupalCreateUser();
+    $this->drupalLogin($noperms_user);
+    // Should be the same result for forbidden paths, since the user needs
+    // special permissions for these paths.
+    foreach ($forbidden_paths as $path) {
+      $this->drupalGet($path);
+      $assert->statusCodeEquals(403);
+    }
+
+    // Create a user who can administer robots.
+    $admin_user = $this->drupalCreateUser(['administer robots']);
+    $this->drupalLogin($admin_user);
+    // Forbidden paths aren't forbidden any more.
+    foreach ($forbidden_paths as $unforbidden) {
+      $this->drupalGet($unforbidden);
+      $assert->statusCodeEquals(200);
+    }
+
+    // Now that we have the admin user logged in, check the menu links.
+    $this->drupalGet('');
+    $assert->linkByHrefExists('examples/config-entity-example');
+
+    // 3) Verify that we can manage entities through the user interface.
+    // We still have the admin user logged in, so we'll create, update, and
+    // delete an entity.
+    // Go to the list page.
+    $this->drupalGet('/examples/config-entity-example');
+    $this->clickLink('Add robot');
+    $robot_machine_name = 'roboname';
+    $this->drupalPostForm(
+      NULL,
+      [
+        'label' => $robot_machine_name,
+        'id' => $robot_machine_name,
+        'floopy' => TRUE,
+      ],
+      'Create Robot'
+    );
+
+    // 4) Verify that our robot appears when we edit it.
+    $this->drupalGet('/examples/config-entity-example/manage/' . $robot_machine_name);
+    $assert->fieldExists('label');
+    $assert->checkboxChecked('edit-floopy');
+
+    // 5) Verify that the label and machine name are shown in the list.
+    $this->drupalGet('/examples/config-entity-example');
+    $this->clickLink('Add robot');
+    $robby_machine_name = 'robby_machine_name';
+    $robby_label = 'Robby label';
+    $this->drupalPostForm(
+      NULL,
+      [
+        'label' => $robby_label,
+        'id' => $robby_machine_name,
+        'floopy' => TRUE,
+      ],
+      'Create Robot'
+    );
+    $this->drupalGet('/examples/config-entity-example');
+    $assert->pageTextContains($robby_label);
+    $assert->pageTextContains($robby_machine_name);
+
+    // 6) Verify that required links are present on respective paths.
+    $this->assertLinkByHref('/examples/config-entity-example/add');
+    $this->assertLinkByHref('/examples/config-entity-example/manage/robby_machine_name');
+    $this->assertLinkByHref('/examples/config-entity-example/manage/robby_machine_name/delete');
+
+    // Verify links on Add Robot.
+    $this->drupalGet('/examples/config-entity-example/add');
+    $this->assertActionButton('examples/config-entity-example');
+
+    // Verify links on Edit Robot.
+    $this->drupalGet('/examples/config-entity-example/manage/robby_machine_name');
+    $this->assertLinkByHref('/examples/config-entity-example/manage/robby_machine_name/delete');
+    $this->assertActionButton('examples/config-entity-example');
+
+    // Verify links on Delete Robot.
+    $this->drupalGet('/examples/config-entity-example/manage/robby_machine_name/delete');
+    // List page will be the destination of the cancel link.
+    $cancel_button = $this->xpath(
+      '//a[@id="edit-cancel" and contains(@href, :path)]',
+      [':path' => '/examples/config-entity-example']
+    );
+    $this->assertEqual(count($cancel_button), 1, 'Found cancel button linking to list page.');
+  }
+
+  /**
+   * Wrap an assertion for the action button.
+   *
+   * @param string $path
+   *   Drupal path to a page.
+   */
+  protected function assertActionButton($path) {
+    $button_element = $this->xpath(
+      '//a[contains(@class, "button-action") and contains(@data-drupal-link-system-path, :path)]',
+      [':path' => $path]
+    );
+    $this->assertEqual(count($button_element), 1, 'Found action button for path: ' . $path);
+  }
+
+}

+ 15 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.info.yml

@@ -0,0 +1,15 @@
+name: Content Entity Example
+type: module
+description: Demonstrates how to create a content entity.
+package: Example modules
+# core: 8.x
+# These modules are required by the tests, must be available at bootstrap time
+dependencies:
+  - drupal:options
+  - examples:examples
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 11 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.links.action.yml

@@ -0,0 +1,11 @@
+# All action links for this module
+
+content_entity_example.contact_add:
+  # Which route will be called by the link
+  route_name: content_entity_example.contact_add
+  title: 'Add contact'
+
+  # Where will the link appear, defined by route name.
+  appears_on:
+    - entity.content_entity_example_contact.collection
+    - entity.content_entity_example_contact.canonical

+ 12 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.links.menu.yml

@@ -0,0 +1,12 @@
+# Define the menu links for this module
+
+entity.content_entity_example_contact.collection:
+  title: 'Content Entity Example: Contacts listing'
+  route_name: entity.content_entity_example_contact.collection
+  description: 'List contacts'
+  weight: 10
+content_entity_example_contact.admin.structure.settings:
+  title: 'Contact settings'
+  description: 'Configure contact entity'
+  route_name:  content_entity_example.contact_settings
+  parent: system.admin_structure

+ 22 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.links.task.yml

@@ -0,0 +1,22 @@
+# Define the 'local' links for the module
+
+contact.settings_tab:
+  route_name: content_entity_example.contact_settings
+  title: Settings
+  base_route: content_entity_example.contact_settings
+
+contact.view:
+  route_name: entity.content_entity_example_contact.canonical
+  base_route: entity.content_entity_example_contact.canonical
+  title: View
+
+contact.page_edit:
+  route_name: entity.content_entity_example_contact.edit_form
+  base_route: entity.content_entity_example_contact.canonical
+  title: Edit
+
+contact.delete_confirm:
+  route_name:  entity.content_entity_example_contact.delete_form
+  base_route:  entity.content_entity_example_contact.canonical
+  title: Delete
+  weight: 10

+ 45 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.module

@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\content_entity_example\content_entity_example.module.
+ */
+
+/**
+ * @defgroup content_entity_example Example: Content Entity
+ * @ingroup examples
+ * @{
+ * Implement a content entity.
+ *
+ * This module demonstrates implementing a content entity.
+ *
+ * Entity API is the API that stores pieces of content and configuration for
+ * Drupal core. For instance, if you've encountered node content types, you've
+ * encountered entities of type 'node.'
+ *
+ * This example implements the Entity API so that we have an entity type usable
+ * by the user which you might think of as specialized nodes, but which are
+ * different from nodes. These entities are called Contact, and are known
+ * internally by the machine name content_entity_example_contact.
+ *
+ * Contact is a fieldable content entity used to hold structured information
+ * without the overhead of using a node content type. 'Fieldable' means you can
+ * attach fields to it, like you can with nodes. It is defined programmatically
+ * (completely in code). We will show the main techniques to handle and expose
+ * the contents of this entity type.
+ *
+ * The Contact entity will demonstrate the main tasks for an entity:
+ * - define
+ * - save
+ * - load
+ * - view
+ * - edit
+ * - delete
+ * - control access
+ *
+ * Where ever possible, we use the amazing tools built into D8 natively.
+ *
+ * @see Drupal\content_entity_example\Entity\Contact
+ * @see config_entity_example
+ * }
+ */

+ 10 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.permissions.yml

@@ -0,0 +1,10 @@
+'delete contact entity':
+  title: Delete entity content.
+'add contact entity':
+  title: Add entity content
+'view contact entity':
+  title: View entity content
+'edit contact entity':
+  title: Edit entity content
+'administer contact entity':
+  title: Administer settings

+ 58 - 0
sites/all/modules/examples/examples/content_entity_example/content_entity_example.routing.yml

@@ -0,0 +1,58 @@
+# This file brings everything together. Very nifty!
+
+# Route name can be used in several places; e.g. links, redirects, and local
+# actions.
+entity.content_entity_example_contact.canonical:
+  path: '/content_entity_example_contact/{content_entity_example_contact}'
+  defaults:
+  # Calls the view controller, defined in the annotation of the contact entity
+    _entity_view: 'content_entity_example_contact'
+    _title: 'Contact content'
+  requirements:
+  # Calls the access controller of the entity, $operation 'view'
+    _entity_access: 'content_entity_example_contact.view'
+
+entity.content_entity_example_contact.collection:
+  path: '/content_entity_example_contact/list'
+  defaults:
+  # Calls the list controller, defined in the annotation of the contact entity.
+    _entity_list: 'content_entity_example_contact'
+    _title: 'Contact list'
+  requirements:
+  # Checks for permission directly.
+    _permission: 'view contact entity'
+
+content_entity_example.contact_add:
+  path: '/content_entity_example_contact/add'
+  defaults:
+  # Calls the form.add controller, defined in the contact entity.
+    _entity_form: content_entity_example_contact.add
+    _title: 'Add contact'
+  requirements:
+    _entity_create_access: 'content_entity_example_contact'
+
+entity.content_entity_example_contact.edit_form:
+  path: '/content_entity_example_contact/{content_entity_example_contact}/edit'
+  defaults:
+  # Calls the form.edit controller, defined in the contact entity.
+    _entity_form: content_entity_example_contact.edit
+    _title: 'Edit contact'
+  requirements:
+    _entity_access: 'content_entity_example_contact.edit'
+
+entity.content_entity_example_contact.delete_form:
+  path: '/contact/{content_entity_example_contact}/delete'
+  defaults:
+    # Calls the form.delete controller, defined in the contact entity.
+    _entity_form: content_entity_example_contact.delete
+    _title: 'Delete contact'
+  requirements:
+    _entity_access: 'content_entity_example_contact.delete'
+
+content_entity_example.contact_settings:
+  path: 'admin/structure/content_entity_example_contact_settings'
+  defaults:
+    _form: '\Drupal\content_entity_example\Form\ContactSettingsForm'
+    _title: 'Contact settings'
+  requirements:
+    _permission: 'administer contact entity'

+ 47 - 0
sites/all/modules/examples/examples/content_entity_example/src/ContactAccessControlHandler.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\content_entity_example;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityAccessControlHandler;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Access controller for the comment entity.
+ *
+ * @see \Drupal\comment\Entity\Comment.
+ */
+class ContactAccessControlHandler extends EntityAccessControlHandler {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Link the activities to the permissions. checkAccess is called with the
+   * $operation as defined in the routing.yml file.
+   */
+  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+    switch ($operation) {
+      case 'view':
+        return AccessResult::allowedIfHasPermission($account, 'view contact entity');
+
+      case 'edit':
+        return AccessResult::allowedIfHasPermission($account, 'edit contact entity');
+
+      case 'delete':
+        return AccessResult::allowedIfHasPermission($account, 'delete contact entity');
+    }
+    return AccessResult::allowed();
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Separate from the checkAccess because the entity does not yet exist, it
+   * will be created during the 'add' process.
+   */
+  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
+    return AccessResult::allowedIfHasPermission($account, 'add contact entity');
+  }
+
+}

+ 18 - 0
sites/all/modules/examples/examples/content_entity_example/src/ContactInterface.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\content_entity_example;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\user\EntityOwnerInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
+
+/**
+ * Provides an interface defining a Contact entity.
+ *
+ * We have this interface so we can join the other interfaces it extends.
+ *
+ * @ingroup content_entity_example
+ */
+interface ContactInterface extends ContentEntityInterface, EntityOwnerInterface, EntityChangedInterface {
+
+}

+ 350 - 0
sites/all/modules/examples/examples/content_entity_example/src/Entity/Contact.php

@@ -0,0 +1,350 @@
+<?php
+
+namespace Drupal\content_entity_example\Entity;
+
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\content_entity_example\ContactInterface;
+use Drupal\user\UserInterface;
+use Drupal\Core\Entity\EntityChangedTrait;
+
+/**
+ * Defines the ContentEntityExample entity.
+ *
+ * @ingroup content_entity_example
+ *
+ * This is the main definition of the entity type. From it, an entityType is
+ * derived. The most important properties in this example are listed below.
+ *
+ * id: The unique identifier of this entityType. It follows the pattern
+ * 'moduleName_xyz' to avoid naming conflicts.
+ *
+ * label: Human readable name of the entity type.
+ *
+ * handlers: Handler classes are used for different tasks. You can use
+ * standard handlers provided by D8 or build your own, most probably derived
+ * from the standard class. In detail:
+ *
+ * - view_builder: we use the standard controller to view an instance. It is
+ *   called when a route lists an '_entity_view' default for the entityType
+ *   (see routing.yml for details. The view can be manipulated by using the
+ *   standard drupal tools in the settings.
+ *
+ * - list_builder: We derive our own list builder class from the
+ *   entityListBuilder to control the presentation.
+ *   If there is a view available for this entity from the views module, it
+ *   overrides the list builder. @todo: any view? naming convention?
+ *
+ * - form: We derive our own forms to add functionality like additional fields,
+ *   redirects etc. These forms are called when the routing list an
+ *   '_entity_form' default for the entityType. Depending on the suffix
+ *   (.add/.edit/.delete) in the route, the correct form is called.
+ *
+ * - access: Our own accessController where we determine access rights based on
+ *   permissions.
+ *
+ * More properties:
+ *
+ *  - base_table: Define the name of the table used to store the data. Make sure
+ *    it is unique. The schema is automatically determined from the
+ *    BaseFieldDefinitions below. The table is automatically created during
+ *    installation.
+ *
+ *  - fieldable: Can additional fields be added to the entity via the GUI?
+ *    Analog to content types.
+ *
+ *  - entity_keys: How to access the fields. Analog to 'nid' or 'uid'.
+ *
+ *  - links: Provide links to do standard tasks. The 'edit-form' and
+ *    'delete-form' links are added to the list built by the
+ *    entityListController. They will show up as action buttons in an additional
+ *    column.
+ *
+ * There are many more properties to be used in an entity type definition. For
+ * a complete overview, please refer to the '\Drupal\Core\Entity\EntityType'
+ * class definition.
+ *
+ * The following construct is the actual definition of the entity type which
+ * is read and cached. Don't forget to clear cache after changes.
+ *
+ * @ContentEntityType(
+ *   id = "content_entity_example_contact",
+ *   label = @Translation("Contact entity"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
+ *     "list_builder" = "Drupal\content_entity_example\Entity\Controller\ContactListBuilder",
+ *     "form" = {
+ *       "add" = "Drupal\content_entity_example\Form\ContactForm",
+ *       "edit" = "Drupal\content_entity_example\Form\ContactForm",
+ *       "delete" = "Drupal\content_entity_example\Form\ContactDeleteForm",
+ *     },
+ *     "access" = "Drupal\content_entity_example\ContactAccessControlHandler",
+ *   },
+ *   list_cache_contexts = { "user" },
+ *   base_table = "contact",
+ *   admin_permission = "administer content_entity_example entity",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "name",
+ *     "uuid" = "uuid"
+ *   },
+ *   links = {
+ *     "canonical" = "/content_entity_example_contact/{content_entity_example_contact}",
+ *     "edit-form" = "/content_entity_example_contact/{content_entity_example_contact}/edit",
+ *     "delete-form" = "/contact/{content_entity_example_contact}/delete",
+ *     "collection" = "/content_entity_example_contact/list"
+ *   },
+ *   field_ui_base_route = "content_entity_example.contact_settings",
+ * )
+ *
+ * The 'links' above are defined by their path. For core to find the
+ * corresponding route, the route name must follow the correct pattern:
+ *
+ * entity.<entity-name>.<link-name> (replace dashes with underscores)
+ * Example: 'entity.content_entity_example_contact.canonical'
+ *
+ * See routing file above for the corresponding implementation
+ *
+ * The Contact class defines methods and fields for the contact entity.
+ *
+ * Being derived from the ContentEntityBase class, we can override the methods
+ * we want. In our case we want to provide access to the standard fields about
+ * creation and changed time stamps.
+ *
+ * Our interface (see ContactInterface) also exposes the EntityOwnerInterface.
+ * This allows us to provide methods for setting and providing ownership
+ * information.
+ *
+ * The most important part is the definitions of the field properties for this
+ * entity type. These are of the same type as fields added through the GUI, but
+ * they can by changed in code. In the definition we can define if the user with
+ * the rights privileges can influence the presentation (view, edit) of each
+ * field.
+ *
+ * The class also uses the EntityChangedTrait trait which allows it to record
+ * timestamps of save operations.
+ */
+class Contact extends ContentEntityBase implements ContactInterface {
+
+  use EntityChangedTrait;
+
+  /**
+   * {@inheritdoc}
+   *
+   * When a new entity instance is added, set the user_id entity reference to
+   * the current user as the creator of the instance.
+   */
+  public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
+    parent::preCreate($storage_controller, $values);
+    $values += [
+      'user_id' => \Drupal::currentUser()->id(),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreatedTime() {
+    return $this->get('created')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChangedTime() {
+    return $this->get('changed')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    return $this->get('user_id')->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->get('user_id')->target_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($uid) {
+    $this->set('user_id', $uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwner(UserInterface $account) {
+    $this->set('user_id', $account->id());
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Define the field properties here.
+   *
+   * Field name, type and size determine the table structure.
+   *
+   * In addition, we can define how the field and its content can be manipulated
+   * in the GUI. The behaviour of the widgets used can be determined here.
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+
+    // Standard field, used as unique if primary index.
+    $fields['id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('ID'))
+      ->setDescription(t('The ID of the Contact entity.'))
+      ->setReadOnly(TRUE);
+
+    // Standard field, unique outside of the scope of the current project.
+    $fields['uuid'] = BaseFieldDefinition::create('uuid')
+      ->setLabel(t('UUID'))
+      ->setDescription(t('The UUID of the Contact entity.'))
+      ->setReadOnly(TRUE);
+
+    // Name field for the contact.
+    // We set display options for the view as well as the form.
+    // Users with correct privileges can change the view and edit configuration.
+    $fields['name'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Name'))
+      ->setDescription(t('The name of the Contact entity.'))
+      ->setSettings([
+        'max_length' => 255,
+        'text_processing' => 0,
+      ])
+      // Set no default value.
+      ->setDefaultValue(NULL)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -6,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -6,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['first_name'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('First Name'))
+      ->setDescription(t('The first name of the Contact entity.'))
+      ->setSettings([
+        'max_length' => 255,
+        'text_processing' => 0,
+      ])
+      // Set no default value.
+      ->setDefaultValue(NULL)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -5,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -5,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    // Gender field for the contact.
+    // ListTextType with a drop down menu widget.
+    // The values shown in the menu are 'male' and 'female'.
+    // In the view the field content is shown as string.
+    // In the form the choices are presented as options list.
+    $fields['gender'] = BaseFieldDefinition::create('list_string')
+      ->setLabel(t('Gender'))
+      ->setDescription(t('The gender of the Contact entity.'))
+      ->setSettings([
+        'allowed_values' => [
+          'female' => 'female',
+          'male' => 'male',
+        ],
+      ])
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -4,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'options_select',
+        'weight' => -4,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    // Owner field of the contact.
+    // Entity reference field, holds the reference to the user object.
+    // The view shows the user name field of the user.
+    // The form presents a auto complete field for the user name.
+    $fields['user_id'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('User Name'))
+      ->setDescription(t('The Name of the associated user.'))
+      ->setSetting('target_type', 'user')
+      ->setSetting('handler', 'default')
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'author',
+        'weight' => -3,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'entity_reference_autocomplete',
+        'settings' => [
+          'match_operator' => 'CONTAINS',
+          'size' => 60,
+          'placeholder' => '',
+        ],
+        'weight' => -3,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    // Role field for the contact.
+    // The values shown in options are 'administrator' and 'user'.
+    $fields['role'] = BaseFieldDefinition::create('list_string')
+      ->setLabel(t('Role'))
+      ->setDescription(t('The role of the Contact entity.'))
+      ->setSettings([
+        'allowed_values' => [
+          'administrator' => 'administrator',
+          'user' => 'user',
+        ],
+      ])
+      // Set the default value of this field to 'user'.
+      ->setDefaultValue('user')
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -2,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'options_select',
+        'weight' => -2,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['langcode'] = BaseFieldDefinition::create('language')
+      ->setLabel(t('Language code'))
+      ->setDescription(t('The language code of ContentEntityExample entity.'));
+    $fields['created'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Created'))
+      ->setDescription(t('The time that the entity was created.'));
+
+    $fields['changed'] = BaseFieldDefinition::create('changed')
+      ->setLabel(t('Changed'))
+      ->setDescription(t('The time that the entity was last edited.'));
+
+    return $fields;
+  }
+
+}

+ 99 - 0
sites/all/modules/examples/examples/content_entity_example/src/Entity/Controller/ContactListBuilder.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\content_entity_example\Entity\Controller;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityListBuilder;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a list controller for content_entity_example entity.
+ *
+ * @ingroup content_entity_example
+ */
+class ContactListBuilder extends EntityListBuilder {
+
+  /**
+   * The url generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getStorage($entity_type->id()),
+      $container->get('url_generator')
+    );
+  }
+
+  /**
+   * Constructs a new ContactListBuilder object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
+   *   The entity storage class.
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
+   *   The url generator.
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, UrlGeneratorInterface $url_generator) {
+    parent::__construct($entity_type, $storage);
+    $this->urlGenerator = $url_generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * We override ::render() so that we can add our own content above the table.
+   * parent::render() is where EntityListBuilder creates the table using our
+   * buildHeader() and buildRow() implementations.
+   */
+  public function render() {
+    $build['description'] = [
+      '#markup' => $this->t('Content Entity Example implements a Contacts model. These contacts are fieldable entities. You can manage the fields on the <a href="@adminlink">Contacts admin page</a>.', [
+        '@adminlink' => $this->urlGenerator->generateFromRoute('content_entity_example.contact_settings'),
+      ]),
+    ];
+    $build['table'] = parent::render();
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Building the header and content lines for the contact list.
+   *
+   * Calling the parent::buildHeader() adds a column for the possible actions
+   * and inserts the 'edit' and 'delete' links as defined for the entity type.
+   */
+  public function buildHeader() {
+    $header['id'] = $this->t('ContactID');
+    $header['name'] = $this->t('Name');
+    $header['first_name'] = $this->t('First Name');
+    $header['gender'] = $this->t('Gender');
+    $header['role'] = $this->t('Role');
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    /* @var $entity \Drupal\content_entity_example\Entity\Contact */
+    $row['id'] = $entity->id();
+    $row['name'] = $entity->link();
+    $row['first_name'] = $entity->first_name->value;
+    $row['gender'] = $entity->gender->value;
+    $row['role'] = $entity->role->value;
+    return $row + parent::buildRow($entity);
+  }
+
+}

+ 56 - 0
sites/all/modules/examples/examples/content_entity_example/src/Form/ContactDeleteForm.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\content_entity_example\Form;
+
+use Drupal\Core\Entity\ContentEntityConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Provides a form for deleting a content_entity_example entity.
+ *
+ * @ingroup content_entity_example
+ */
+class ContactDeleteForm extends ContentEntityConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete entity %name?', ['%name' => $this->entity->label()]);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * If the delete command is canceled, return to the contact list.
+   */
+  public function getCancelUrl() {
+    return new Url('entity.content_entity_example_contact.collection');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Delete the entity and log the event. logger() replaces the watchdog.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity = $this->getEntity();
+    $entity->delete();
+
+    $this->logger('content_entity_example')->notice('@type: deleted %title.',
+      [
+        '@type' => $this->entity->bundle(),
+        '%title' => $this->entity->label(),
+      ]);
+    $form_state->setRedirect('entity.content_entity_example_contact.collection');
+  }
+
+}

+ 42 - 0
sites/all/modules/examples/examples/content_entity_example/src/Form/ContactForm.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\content_entity_example\Form;
+
+use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form controller for the content_entity_example entity edit forms.
+ *
+ * @ingroup content_entity_example
+ */
+class ContactForm extends ContentEntityForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    /* @var $entity \Drupal\content_entity_example\Entity\Contact */
+    $form = parent::buildForm($form, $form_state);
+    $entity = $this->entity;
+
+    $form['langcode'] = [
+      '#title' => $this->t('Language'),
+      '#type' => 'language_select',
+      '#default_value' => $entity->getUntranslated()->language()->getId(),
+      '#languages' => Language::STATE_ALL,
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $form_state->setRedirect('entity.content_entity_example_contact.collection');
+    $entity = $this->getEntity();
+    $entity->save();
+  }
+
+}

+ 39 - 0
sites/all/modules/examples/examples/content_entity_example/src/Form/ContactSettingsForm.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\content_entity_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class ContentEntityExampleSettingsForm.
+ *
+ * @ingroup content_entity_example
+ */
+class ContactSettingsForm extends FormBase {
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    return 'content_entity_example_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Empty implementation of the abstract submit class.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['contact_settings']['#markup'] = 'Settings form for ContentEntityExample. Manage field settings here.';
+    return $form;
+  }
+
+}

+ 249 - 0
sites/all/modules/examples/examples/content_entity_example/tests/src/Functional/ContentEntityExampleTest.php

@@ -0,0 +1,249 @@
+<?php
+
+namespace Drupal\content_entity_example\Tests;
+
+use Drupal\content_entity_example\Entity\Contact;
+use Drupal\Tests\examples\Functional\ExamplesBrowserTestBase;
+
+/**
+ * Tests the basic functions of the Content Entity Example module.
+ *
+ * @ingroup content_entity_example
+ *
+ * @group content_entity_example
+ * @group examples
+ */
+class ContentEntityExampleTest extends ExamplesBrowserTestBase {
+
+  public static $modules = ['content_entity_example', 'block', 'field_ui'];
+
+  /**
+   * Basic tests for Content Entity Example.
+   */
+  public function testContentEntityExample() {
+    $assert = $this->assertSession();
+
+    $web_user = $this->drupalCreateUser([
+      'add contact entity',
+      'edit contact entity',
+      'view contact entity',
+      'delete contact entity',
+      'administer contact entity',
+      'administer content_entity_example_contact display',
+      'administer content_entity_example_contact fields',
+      'administer content_entity_example_contact form display',
+    ]);
+
+    // Anonymous User should not see the link to the listing.
+    $assert->pageTextNotContains('Content Entity Example: Contacts Listing');
+
+    $this->drupalLogin($web_user);
+
+    // Web_user user has the right to view listing.
+    $assert->linkExists('Content Entity Example: Contacts Listing');
+
+    $this->clickLink('Content Entity Example: Contacts Listing');
+
+    // WebUser can add entity content.
+    $assert->linkExists('Add Contact');
+
+    $this->clickLink(t('Add Contact'));
+
+    $assert->fieldValueEquals('name[0][value]', '');
+    $assert->fieldValueEquals('name[0][value]', '');
+    $assert->fieldValueEquals('name[0][value]', '');
+    $assert->fieldValueEquals('name[0][value]', '');
+
+    $user_ref = $web_user->name->value . ' (' . $web_user->id() . ')';
+    $assert->fieldValueEquals('user_id[0][target_id]', $user_ref);
+
+    // Post content, save an instance. Go back to list after saving.
+    $edit = [
+      'name[0][value]' => 'test name',
+      'first_name[0][value]' => 'test first name',
+      'gender' => 'male',
+      'role' => 'administrator',
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+
+    // Entity listed.
+    $assert->linkExists('Edit');
+    $assert->linkExists('Delete');
+
+    $this->clickLink('test name');
+
+    // Entity shown.
+    $assert->pageTextContains('test name');
+    $assert->pageTextContains('test first name');
+    $assert->pageTextContains('administrator');
+    $assert->pageTextContains('male');
+    $assert->linkExists('Add Contact');
+    $assert->linkExists('Edit');
+    $assert->linkExists('Delete');
+
+    // Delete the entity.
+    $this->clickLink('Delete');
+
+    // Confirm deletion.
+    $assert->linkExists('Cancel');
+    $this->drupalPostForm(NULL, [], 'Delete');
+
+    // Back to list, must be empty.
+    $assert->pageTextNotContains('test name');
+
+    // Settings page.
+    $this->drupalGet('admin/structure/content_entity_example_contact_settings');
+    $assert->pageTextContains('Contact Settings');
+
+    // Make sure the field manipulation links are available.
+    $assert->linkExists('Settings');
+    $assert->linkExists('Manage fields');
+    $assert->linkExists('Manage form display');
+    $assert->linkExists('Manage display');
+  }
+
+  /**
+   * Test all paths exposed by the module, by permission.
+   */
+  public function testPaths() {
+    $assert = $this->assertSession();
+
+    // Generate a contact so that we can test the paths against it.
+    $contact = Contact::create(
+      [
+        'name' => 'somename',
+        'first_name' => 'Joe',
+        'gender' => 'female',
+        'role' => 'administrator',
+      ]
+    );
+    $contact->save();
+
+    // Gather the test data.
+    $data = $this->providerTestPaths($contact->id());
+
+    // Run the tests.
+    foreach ($data as $datum) {
+      // drupalCreateUser() doesn't know what to do with an empty permission
+      // array, so we help it out.
+      if ($datum[2]) {
+        $user = $this->drupalCreateUser([$datum[2]]);
+        $this->drupalLogin($user);
+      }
+      else {
+        $user = $this->drupalCreateUser();
+        $this->drupalLogin($user);
+      }
+      $this->drupalGet($datum[1]);
+      $assert->statusCodeEquals($datum[0]);
+    }
+  }
+
+  /**
+   * Data provider for testPaths.
+   *
+   * @param int $contact_id
+   *   The id of an existing Contact entity.
+   *
+   * @return array
+   *   Nested array of testing data. Arranged like this:
+   *   - Expected response code.
+   *   - Path to request.
+   *   - Permission for the user.
+   */
+  protected function providerTestPaths($contact_id) {
+    return [
+      [
+        200,
+        '/content_entity_example_contact/' . $contact_id,
+        'view contact entity',
+      ],
+      [
+        403,
+        '/content_entity_example_contact/' . $contact_id,
+        '',
+      ],
+      [
+        200,
+        '/content_entity_example_contact/list',
+        'view contact entity',
+      ],
+      [
+        403,
+        '/content_entity_example_contact/list',
+        '',
+      ],
+      [
+        200,
+        '/content_entity_example_contact/add',
+        'add contact entity',
+      ],
+      [
+        403,
+        '/content_entity_example_contact/add',
+        '',
+      ],
+      [
+        200,
+        '/content_entity_example_contact/' . $contact_id . '/edit',
+        'edit contact entity',
+      ],
+      [
+        403,
+        '/content_entity_example_contact/' . $contact_id . '/edit',
+        '',
+      ],
+      [
+        200,
+        '/contact/' . $contact_id . '/delete',
+        'delete contact entity',
+      ],
+      [
+        403,
+        '/contact/' . $contact_id . '/delete',
+        '',
+      ],
+      [
+        200,
+        'admin/structure/content_entity_example_contact_settings',
+        'administer contact entity',
+      ],
+      [
+        403,
+        'admin/structure/content_entity_example_contact_settings',
+        '',
+      ],
+    ];
+  }
+
+  /**
+   * Test add new fields to the contact entity.
+   */
+  public function testAddFields() {
+    $web_user = $this->drupalCreateUser([
+      'administer contact entity',
+      'administer content_entity_example_contact display',
+      'administer content_entity_example_contact fields',
+      'administer content_entity_example_contact form display',
+    ]);
+
+    $this->drupalLogin($web_user);
+    $entity_name = 'content_entity_example_contact';
+    $add_field_url = 'admin/structure/' . $entity_name . '_settings/fields/add-field';
+    $this->drupalGet($add_field_url);
+    $field_name = 'test_name';
+    $edit = [
+      'new_storage_type' => 'list_string',
+      'label' => 'test name',
+      'field_name' => $field_name,
+    ];
+
+    $this->drupalPostForm(NULL, $edit, 'Save and continue');
+    $expected_path = $this->buildUrl('admin/structure/' . $entity_name . '_settings/fields/' . $entity_name . '.' . $entity_name . '.field_' . $field_name . '/storage');
+
+    // Fetch url without query parameters.
+    $current_path = strtok($this->getUrl(), '?');
+    $this->assertEquals($expected_path, $current_path);
+  }
+
+}

+ 4 - 0
sites/all/modules/examples/examples/cron_example/config/install/cron_example.settings.yml

@@ -0,0 +1,4 @@
+# Default form values for \Drupal\cron_example\Form\CronExampleForm.
+# @see examples/cron_example/config/schema/cron_example.schema.yml
+# @see \Drupal\cron_example\Form\CronExampleForm::getEditableConfigNames()
+interval: 300

+ 12 - 0
sites/all/modules/examples/examples/cron_example/config/schema/cron_example.schema.yml

@@ -0,0 +1,12 @@
+# Set configuration defaults. This schema describes the cron_example.settings
+# config. The defaults for this config are set in
+# config/install/cron_example.settings.yml and then used by
+# \Drupal\cron_example\Form\CronExampleForm.
+# @see https://www.drupal.org/node/1905070
+cron_example.settings:
+  type: config_object
+  label: 'Cron Example settings'
+  mapping:
+    interval:
+      type: integer
+      label: 'Period between cron runs'

+ 14 - 0
sites/all/modules/examples/examples/cron_example/cron_example.info.yml

@@ -0,0 +1,14 @@
+name: Cron Example
+type: module
+description: 'Demonstrates hook_cron() and related features'
+package: Example modules
+# core: 8.x
+dependencies:
+  - drupal:node
+  - examples:examples
+
+# Information added by Drupal.org packaging script on 2017-12-17
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'examples'
+datestamp: 1513537386

+ 3 - 0
sites/all/modules/examples/examples/cron_example/cron_example.links.menu.yml

@@ -0,0 +1,3 @@
+cron_example.description:
+  title: Cron Example
+  route_name: cron_example

+ 56 - 0
sites/all/modules/examples/examples/cron_example/cron_example.module

@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Demonstrates use of the Cron API in Drupal - hook_cron().
+ */
+
+/**
+ * @defgroup cron_example Example: Cron
+ * @ingroup examples
+ * @{
+ * Example using Cron API, including hook_cron() and @QueueWorker plugins
+ *
+ * This example is part of the Examples for Developers Project
+ * which you can download and experiment with at
+ * http://drupal.org/project/examples
+ */
+
+/**
+ * Implements hook_cron().
+ *
+ * We implement hook_cron() to do "background" processing. It gets called every
+ * time the Drupal cron runs. We then decide what has to happen in response.
+ *
+ * In this example, we log a message after the time given in the state value
+ * 'cron_example.next_execution'. Then we update that variable to a time in the
+ * future.
+ */
+function cron_example_cron() {
+  // We access our configuration.
+  $cron_config = \Drupal::configFactory()->getEditable('cron_example.settings');
+  // Default to an hourly interval. Of course, cron has to be running at least
+  // hourly for this to work.
+  $interval = $cron_config->get('interval');
+  $interval = !empty($interval) ? $interval : 3600;
+
+  // We usually don't want to act every time cron runs (which could be every
+  // minute) so keep a time for the next run in the site state.
+  $next_execution = \Drupal::state()->get('cron_example.next_execution');
+  $next_execution = !empty($next_execution) ? $next_execution : 0;
+  if (REQUEST_TIME >= $next_execution) {
+    // This is a silly example of a cron job.
+    // It just makes it obvious that the job has run without
+    // making any changes to your database.
+    \Drupal::logger('cron_example')->notice('cron_example ran');
+    if (\Drupal::state()->get('cron_example_show_status_message')) {
+      drupal_set_message(t('cron_example executed at %time', ['%time' => date_iso8601(REQUEST_TIME)]));
+      \Drupal::state()->set('cron_example_show_status_message', FALSE);
+    }
+    \Drupal::state()->set('cron_example.next_execution', REQUEST_TIME + $interval);
+  }
+}
+
+/**
+ * @} End of "defgroup cron_example".
+ */

+ 7 - 0
sites/all/modules/examples/examples/cron_example/cron_example.routing.yml

@@ -0,0 +1,7 @@
+cron_example:
+  path: '/examples/cron-example'
+  defaults:
+    _form: '\Drupal\cron_example\Form\CronExampleForm'
+    _title: 'Cron Example'
+  requirements:
+    _permission: 'access content'

+ 256 - 0
sites/all/modules/examples/examples/cron_example/src/Form/CronExampleForm.php

@@ -0,0 +1,256 @@
+<?php
+
+namespace Drupal\cron_example\Form;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\CronInterface;
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Queue\QueueFactory;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Form with examples on how to use cron.
+ */
+class CronExampleForm extends ConfigFormBase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The cron service.
+   *
+   * @var \Drupal\Core\CronInterface
+   */
+  protected $cron;
+
+  /**
+   * The queue object.
+   *
+   * @var \Drupal\Core\Queue\QueueFactory
+   */
+  protected $queue;
+
+  /**
+   * The state keyvalue collection.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $current_user, CronInterface $cron, QueueFactory $queue, StateInterface $state) {
+    parent::__construct($config_factory);
+    $this->currentUser = $current_user;
+    $this->cron = $cron;
+    $this->queue = $queue;
+    $this->state = $state;
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('current_user'),
+      $container->get('cron'),
+      $container->get('queue'),
+      $container->get('state')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'cron_example';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->configFactory->get('cron_example.settings');
+
+    $form['status'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Cron status information'),
+      '#open' => TRUE,
+    ];
+    $form['status']['intro'] = [
+      '#type' => 'item',
+      '#markup' => $this->t('The cron example demonstrates hook_cron() and hook_queue_info() processing. If you have administrative privileges you can run cron from this page and see the results.'),
+    ];
+
+    $next_execution = \Drupal::state()->get('cron_example.next_execution');
+    $next_execution = !empty($next_execution) ? $next_execution : REQUEST_TIME;
+
+    $args = [
+      '%time' => date_iso8601(\Drupal::state()->get('cron_example.next_execution')),
+      '%seconds' => $next_execution - REQUEST_TIME,
+    ];
+    $form['status']['last'] = [
+      '#type' => 'item',
+      '#markup' => $this->t('cron_example_cron() will next execute the first time cron runs after %time (%seconds seconds from now)', $args),
+    ];
+
+    if ($this->currentUser->hasPermission('administer site configuration')) {
+      $form['cron_run'] = [
+        '#type' => 'details',
+        '#title' => $this->t('Run cron manually'),
+        '#open' => TRUE,
+      ];
+      $form['cron_run']['cron_reset'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t("Run cron_example's cron regardless of whether interval has expired."),
+        '#default_value' => FALSE,
+      ];
+      $form['cron_run']['cron_trigger']['actions'] = ['#type' => 'actions'];
+      $form['cron_run']['cron_trigger']['actions']['sumbit'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Run cron now'),
+        '#submit' => [[$this, 'cronRun']],
+      ];
+    }
+
+    $form['cron_queue_setup'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Cron queue setup (for hook_cron_queue_info(), etc.)'),
+      '#open' => TRUE,
+    ];
+
+    $queue_1 = $this->queue->get('cron_example_queue_1');
+    $queue_2 = $this->queue->get('cron_example_queue_2');
+
+    $args = [
+      '%queue_1' => $queue_1->numberOfItems(),
+      '%queue_2' => $queue_2->numberOfItems(),
+    ];
+    $form['cron_queue_setup']['current_cron_queue_status'] = [
+      '#type' => 'item',
+      '#markup' => $this->t('There are currently %queue_1 items in queue 1 and %queue_2 items in queue 2', $args),
+    ];
+    $form['cron_queue_setup']['num_items'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Number of items to add to queue'),
+      '#options' => array_combine([1, 5, 10, 100, 1000], [1, 5, 10, 100, 1000]),
+      '#default_value' => 5,
+    ];
+    $form['cron_queue_setup']['queue'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Queue to add items to'),
+      '#options' => [
+        'cron_example_queue_1' => $this->t('Queue 1'),
+        'cron_example_queue_2' => $this->t('Queue 2'),
+      ],
+      '#default_value' => 'cron_example_queue_1',
+    ];
+    $form['cron_queue_setup']['actions'] = ['#type' => 'actions'];
+    $form['cron_queue_setup']['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Add jobs to queue'),
+      '#submit' => [[$this, 'addItems']],
+    ];
+
+    $form['configuration'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Configuration of cron_example_cron()'),
+      '#open' => TRUE,
+    ];
+    $form['configuration']['cron_example_interval'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Cron interval'),
+      '#description' => $this->t('Time after which cron_example_cron will respond to a processing request.'),
+      '#default_value' => $config->get('interval'),
+      '#options' => [
+        60 => $this->t('1 minute'),
+        300 => $this->t('5 minutes'),
+        3600 => $this->t('1 hour'),
+        86400 => $this->t('1 day'),
+      ],
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * Allow user to directly execute cron, optionally forcing it.
+   */
+  public function cronRun(array &$form, FormStateInterface &$form_state) {
+    $config = $this->configFactory->getEditable('cron_example.settings');
+
+    $cron_reset = $form_state->getValue('cron_reset');
+    if (!empty($cron_reset)) {
+      \Drupal::state()->set('cron_example.next_execution', 0);
+    }
+
+    // Use a state variable to signal that cron was run manually from this form.
+    $this->state->set('cron_example_show_status_message', TRUE);
+    if ($this->cron->run()) {
+      drupal_set_message($this->t('Cron ran successfully.'));
+    }
+    else {
+      drupal_set_message($this->t('Cron run failed.'), 'error');
+    }
+  }
+
+  /**
+   * Add the items to the queue when signaled by the form.
+   */
+  public function addItems(array &$form, FormStateInterface &$form_state) {
+    $values = $form_state->getValues();
+    $queue_name = $form['cron_queue_setup']['queue'][$values['queue']]['#title'];
+    $num_items = $form_state->getValue('num_items');
+    // Queues are defined by a QueueWorker Plugin which are selected by their
+    // id attritbute.
+    // @see \Drupal\cron_example\Plugin\QueueWorker\ReportWorkerOne
+    $queue = $this->queue->get($values['queue']);
+
+    for ($i = 1; $i <= $num_items; $i++) {
+      // Create a new item, a new data object, which is passed to the
+      // QueueWorker's processItem() method.
+      $item = new \stdClass();
+      $item->created = REQUEST_TIME;
+      $item->sequence = $i;
+      $queue->createItem($item);
+    }
+
+    $args = [
+      '%num' => $num_items,
+      '%queue' => $queue_name,
+    ];
+    drupal_set_message($this->t('Added %num items to %queue', $args));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Update the interval as stored in configuration. This will be read when
+    // this modules hook_cron function fires and will be used to ensure that
+    // action is taken only after the appropiate time has elapsed.
+    $this->configFactory->getEditable('cron_example.settings')
+      ->set('interval', $form_state->getValue('cron_example_interval'))
+      ->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['cron_example.settings'];
+  }
+
+}

+ 92 - 0
sites/all/modules/examples/examples/cron_example/src/Plugin/QueueWorker/ReportWorkerBase.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\cron_example\Plugin\QueueWorker;
+
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\Queue\QueueWorkerBase;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+
+/**
+ * Provides base functionality for the ReportWorkers.
+ */
+abstract class ReportWorkerBase extends QueueWorkerBase implements ContainerFactoryPluginInterface {
+
+  use StringTranslationTrait;
+
+
+  /**
+   * The state.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * ReportWorkerBase constructor.
+   *
+   * @param array $configuration
+   *   The configuration of the instance.
+   * @param string $plugin_id
+   *   The plugin id.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service the instance should use.
+   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
+   *   The logger service the instance should use.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, StateInterface $state, LoggerChannelFactoryInterface $logger) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->state = $state;
+    $this->logger = $logger;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('state'),
+      $container->get('logger.factory')
+    );
+  }
+
+  /**
+   * Simple reporter log and display information about the queue.
+   *
+   * @param int $worker
+   *   Worker number.
+   * @param object $item
+   *   The $item which was stored in the cron queue.
+   */
+  protected function reportWork($worker, $item) {
+    if ($this->state->get('cron_example_show_status_message')) {
+      drupal_set_message(
+        $this->t('Queue @worker worker processed item with sequence @sequence created at @time', [
+          '@worker' => $worker,
+          '@sequence' => $item->sequence,
+          '@time' => date_iso8601($item->created),
+        ])
+      );
+    }
+    $this->logger->get('cron_example')->info('Queue @worker worker processed item with sequence @sequence created at @time', [
+      '@worker' => $worker,
+      '@sequence' => $item->sequence,
+      '@time' => date_iso8601($item->created),
+    ]);
+  }
+
+}

+ 29 - 0
sites/all/modules/examples/examples/cron_example/src/Plugin/QueueWorker/ReportWorkerOne.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\cron_example\Plugin\QueueWorker;
+
+/**
+ * A report worker.
+ *
+ * @QueueWorker(
+ *   id = "cron_example_queue_1",
+ *   title = @Translation("First worker in cron_example"),
+ *   cron = {"time" = 1}
+ * )
+ *
+ * QueueWorkers are new in Drupal 8. They define a queue, which in this case
+ * is identified as cron_example_queue_1 and contain a process that operates on
+ * all the data given to the queue.
+ *
+ * @see queue_example.module
+ */
+class ReportWorkerOne extends ReportWorkerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processItem($data) {
+    $this->reportWork(1, $data);
+  }
+
+}

+ 29 - 0
sites/all/modules/examples/examples/cron_example/src/Plugin/QueueWorker/ReportWorkerTwo.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\cron_example\Plugin\QueueWorker;
+
+/**
+ * A report worker.
+ *
+ * @QueueWorker(
+ *   id = "cron_example_queue_2",
+ *   title = @Translation("Second worker in cron_example"),
+ *   cron = {"time" = 20}
+ * )
+ *
+ * QueueWorkers are new in Drupal 8. They define a queue, which in this case
+ * is identified as cron_example_queue_2 and contain a process that operates on
+ * all the data given to the queue.
+ *
+ * @see queue_example.module
+ */
+class ReportWorkerTwo extends ReportWorkerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processItem($data) {
+    $this->reportWork(2, $data);
+  }
+
+}

+ 90 - 0
sites/all/modules/examples/examples/cron_example/tests/src/Functional/CronExampleTest.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\Tests\cron_example\Functional;
+
+use Drupal\Tests\examples\Functional\ExamplesBrowserTestBase;
+
+/**
+ * Test the functionality for the Cron Example.
+ *
+ * @ingroup cron_example
+ *
+ * @group cron_example
+ * @group examples
+ */
+class CronExampleTest extends ExamplesBrowserTestBase {
+
+  /**
+   * An editable config object for access to 'cron_example.settings'.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $cronConfig;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['cron_example', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    // Create user. Search content permission granted for the search block to
+    // be shown.
+    $this->drupalLogin($this->drupalCreateUser(['administer site configuration', 'access content']));
+
+    $this->cronConfig = \Drupal::configFactory()->getEditable('cron_example.settings');
+  }
+
+  /**
+   * Create an example node, test block through admin and user interfaces.
+   */
+  public function testCronExampleBasic() {
+    $assert = $this->assertSession();
+
+    // Pretend that cron has never been run (even though simpletest seems to
+    // run it once...).
+    \Drupal::state()->set('cron_example.next_execution', 0);
+    $this->drupalGet('examples/cron-example');
+
+    // Initial run should cause cron_example_cron() to fire.
+    $post = [];
+    $this->drupalPostForm('examples/cron-example', $post, 'Run cron now');
+    $assert->pageTextContains('cron_example executed at');
+
+    // Forcing should also cause cron_example_cron() to fire.
+    $post['cron_reset'] = TRUE;
+    $this->drupalPostForm(NULL, $post, 'Run cron now');
+    $assert->pageTextContains('cron_example executed at');
+
+    // But if followed immediately and not forced, it should not fire.
+    $post['cron_reset'] = FALSE;
+    $this->drupalPostForm(NULL, $post, 'Run cron now');
+    $assert->statusCodeEquals(200);
+    $assert->pageTextNotContains('cron_example executed at');
+    $assert->pageTextContains('There are currently 0 items in queue 1 and 0 items in queue 2');
+
+    $post = [
+      'num_items' => 5,
+      'queue' => 'cron_example_queue_1',
+    ];
+    $this->drupalPostForm(NULL, $post, 'Add jobs to queue');
+    $assert->pageTextContains('There are currently 5 items in queue 1 and 0 items in queue 2');
+
+    $post = [
+      'num_items' => 100,
+      'queue' => 'cron_example_queue_2',
+    ];
+    $this->drupalPostForm(NULL, $post, 'Add jobs to queue');
+    $assert->pageTextContains('There are currently 5 items in queue 1 and 100 items in queue 2');
+
+    $this->drupalPostForm('examples/cron-example', [], 'Run cron now');
+    $assert->responseMatches('/Queue 1 worker processed item with sequence 5 /');
+    $assert->responseMatches('/Queue 2 worker processed item with sequence 100 /');
+  }
+
+}

Vissa filer visades inte eftersom för många filer har ändrats