Commit ef05e610245a91e6d501d32a6d24f620331d9e76

Authored by 陈晓峰
1 parent 1cae1ec7

examples

Showing 70 changed files with 4629 additions and 0 deletions

Too many changes to show.

To preserve performance only 70 of 190 files are displayed.

  1 + GNU GENERAL PUBLIC LICENSE
  2 + Version 2, June 1991
  3 +
  4 + Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
  5 + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  6 + Everyone is permitted to copy and distribute verbatim copies
  7 + of this license document, but changing it is not allowed.
  8 +
  9 + Preamble
  10 +
  11 + The licenses for most software are designed to take away your
  12 +freedom to share and change it. By contrast, the GNU General Public
  13 +License is intended to guarantee your freedom to share and change free
  14 +software--to make sure the software is free for all its users. This
  15 +General Public License applies to most of the Free Software
  16 +Foundation's software and to any other program whose authors commit to
  17 +using it. (Some other Free Software Foundation software is covered by
  18 +the GNU Lesser General Public License instead.) You can apply it to
  19 +your programs, too.
  20 +
  21 + When we speak of free software, we are referring to freedom, not
  22 +price. Our General Public Licenses are designed to make sure that you
  23 +have the freedom to distribute copies of free software (and charge for
  24 +this service if you wish), that you receive source code or can get it
  25 +if you want it, that you can change the software or use pieces of it
  26 +in new free programs; and that you know you can do these things.
  27 +
  28 + To protect your rights, we need to make restrictions that forbid
  29 +anyone to deny you these rights or to ask you to surrender the rights.
  30 +These restrictions translate to certain responsibilities for you if you
  31 +distribute copies of the software, or if you modify it.
  32 +
  33 + For example, if you distribute copies of such a program, whether
  34 +gratis or for a fee, you must give the recipients all the rights that
  35 +you have. You must make sure that they, too, receive or can get the
  36 +source code. And you must show them these terms so they know their
  37 +rights.
  38 +
  39 + We protect your rights with two steps: (1) copyright the software, and
  40 +(2) offer you this license which gives you legal permission to copy,
  41 +distribute and/or modify the software.
  42 +
  43 + Also, for each author's protection and ours, we want to make certain
  44 +that everyone understands that there is no warranty for this free
  45 +software. If the software is modified by someone else and passed on, we
  46 +want its recipients to know that what they have is not the original, so
  47 +that any problems introduced by others will not reflect on the original
  48 +authors' reputations.
  49 +
  50 + Finally, any free program is threatened constantly by software
  51 +patents. We wish to avoid the danger that redistributors of a free
  52 +program will individually obtain patent licenses, in effect making the
  53 +program proprietary. To prevent this, we have made it clear that any
  54 +patent must be licensed for everyone's free use or not licensed at all.
  55 +
  56 + The precise terms and conditions for copying, distribution and
  57 +modification follow.
  58 +
  59 + GNU GENERAL PUBLIC LICENSE
  60 + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
  61 +
  62 + 0. This License applies to any program or other work which contains
  63 +a notice placed by the copyright holder saying it may be distributed
  64 +under the terms of this General Public License. The "Program", below,
  65 +refers to any such program or work, and a "work based on the Program"
  66 +means either the Program or any derivative work under copyright law:
  67 +that is to say, a work containing the Program or a portion of it,
  68 +either verbatim or with modifications and/or translated into another
  69 +language. (Hereinafter, translation is included without limitation in
  70 +the term "modification".) Each licensee is addressed as "you".
  71 +
  72 +Activities other than copying, distribution and modification are not
  73 +covered by this License; they are outside its scope. The act of
  74 +running the Program is not restricted, and the output from the Program
  75 +is covered only if its contents constitute a work based on the
  76 +Program (independent of having been made by running the Program).
  77 +Whether that is true depends on what the Program does.
  78 +
  79 + 1. You may copy and distribute verbatim copies of the Program's
  80 +source code as you receive it, in any medium, provided that you
  81 +conspicuously and appropriately publish on each copy an appropriate
  82 +copyright notice and disclaimer of warranty; keep intact all the
  83 +notices that refer to this License and to the absence of any warranty;
  84 +and give any other recipients of the Program a copy of this License
  85 +along with the Program.
  86 +
  87 +You may charge a fee for the physical act of transferring a copy, and
  88 +you may at your option offer warranty protection in exchange for a fee.
  89 +
  90 + 2. You may modify your copy or copies of the Program or any portion
  91 +of it, thus forming a work based on the Program, and copy and
  92 +distribute such modifications or work under the terms of Section 1
  93 +above, provided that you also meet all of these conditions:
  94 +
  95 + a) You must cause the modified files to carry prominent notices
  96 + stating that you changed the files and the date of any change.
  97 +
  98 + b) You must cause any work that you distribute or publish, that in
  99 + whole or in part contains or is derived from the Program or any
  100 + part thereof, to be licensed as a whole at no charge to all third
  101 + parties under the terms of this License.
  102 +
  103 + c) If the modified program normally reads commands interactively
  104 + when run, you must cause it, when started running for such
  105 + interactive use in the most ordinary way, to print or display an
  106 + announcement including an appropriate copyright notice and a
  107 + notice that there is no warranty (or else, saying that you provide
  108 + a warranty) and that users may redistribute the program under
  109 + these conditions, and telling the user how to view a copy of this
  110 + License. (Exception: if the Program itself is interactive but
  111 + does not normally print such an announcement, your work based on
  112 + the Program is not required to print an announcement.)
  113 +
  114 +These requirements apply to the modified work as a whole. If
  115 +identifiable sections of that work are not derived from the Program,
  116 +and can be reasonably considered independent and separate works in
  117 +themselves, then this License, and its terms, do not apply to those
  118 +sections when you distribute them as separate works. But when you
  119 +distribute the same sections as part of a whole which is a work based
  120 +on the Program, the distribution of the whole must be on the terms of
  121 +this License, whose permissions for other licensees extend to the
  122 +entire whole, and thus to each and every part regardless of who wrote it.
  123 +
  124 +Thus, it is not the intent of this section to claim rights or contest
  125 +your rights to work written entirely by you; rather, the intent is to
  126 +exercise the right to control the distribution of derivative or
  127 +collective works based on the Program.
  128 +
  129 +In addition, mere aggregation of another work not based on the Program
  130 +with the Program (or with a work based on the Program) on a volume of
  131 +a storage or distribution medium does not bring the other work under
  132 +the scope of this License.
  133 +
  134 + 3. You may copy and distribute the Program (or a work based on it,
  135 +under Section 2) in object code or executable form under the terms of
  136 +Sections 1 and 2 above provided that you also do one of the following:
  137 +
  138 + a) Accompany it with the complete corresponding machine-readable
  139 + source code, which must be distributed under the terms of Sections
  140 + 1 and 2 above on a medium customarily used for software interchange; or,
  141 +
  142 + b) Accompany it with a written offer, valid for at least three
  143 + years, to give any third party, for a charge no more than your
  144 + cost of physically performing source distribution, a complete
  145 + machine-readable copy of the corresponding source code, to be
  146 + distributed under the terms of Sections 1 and 2 above on a medium
  147 + customarily used for software interchange; or,
  148 +
  149 + c) Accompany it with the information you received as to the offer
  150 + to distribute corresponding source code. (This alternative is
  151 + allowed only for noncommercial distribution and only if you
  152 + received the program in object code or executable form with such
  153 + an offer, in accord with Subsection b above.)
  154 +
  155 +The source code for a work means the preferred form of the work for
  156 +making modifications to it. For an executable work, complete source
  157 +code means all the source code for all modules it contains, plus any
  158 +associated interface definition files, plus the scripts used to
  159 +control compilation and installation of the executable. However, as a
  160 +special exception, the source code distributed need not include
  161 +anything that is normally distributed (in either source or binary
  162 +form) with the major components (compiler, kernel, and so on) of the
  163 +operating system on which the executable runs, unless that component
  164 +itself accompanies the executable.
  165 +
  166 +If distribution of executable or object code is made by offering
  167 +access to copy from a designated place, then offering equivalent
  168 +access to copy the source code from the same place counts as
  169 +distribution of the source code, even though third parties are not
  170 +compelled to copy the source along with the object code.
  171 +
  172 + 4. You may not copy, modify, sublicense, or distribute the Program
  173 +except as expressly provided under this License. Any attempt
  174 +otherwise to copy, modify, sublicense or distribute the Program is
  175 +void, and will automatically terminate your rights under this License.
  176 +However, parties who have received copies, or rights, from you under
  177 +this License will not have their licenses terminated so long as such
  178 +parties remain in full compliance.
  179 +
  180 + 5. You are not required to accept this License, since you have not
  181 +signed it. However, nothing else grants you permission to modify or
  182 +distribute the Program or its derivative works. These actions are
  183 +prohibited by law if you do not accept this License. Therefore, by
  184 +modifying or distributing the Program (or any work based on the
  185 +Program), you indicate your acceptance of this License to do so, and
  186 +all its terms and conditions for copying, distributing or modifying
  187 +the Program or works based on it.
  188 +
  189 + 6. Each time you redistribute the Program (or any work based on the
  190 +Program), the recipient automatically receives a license from the
  191 +original licensor to copy, distribute or modify the Program subject to
  192 +these terms and conditions. You may not impose any further
  193 +restrictions on the recipients' exercise of the rights granted herein.
  194 +You are not responsible for enforcing compliance by third parties to
  195 +this License.
  196 +
  197 + 7. If, as a consequence of a court judgment or allegation of patent
  198 +infringement or for any other reason (not limited to patent issues),
  199 +conditions are imposed on you (whether by court order, agreement or
  200 +otherwise) that contradict the conditions of this License, they do not
  201 +excuse you from the conditions of this License. If you cannot
  202 +distribute so as to satisfy simultaneously your obligations under this
  203 +License and any other pertinent obligations, then as a consequence you
  204 +may not distribute the Program at all. For example, if a patent
  205 +license would not permit royalty-free redistribution of the Program by
  206 +all those who receive copies directly or indirectly through you, then
  207 +the only way you could satisfy both it and this License would be to
  208 +refrain entirely from distribution of the Program.
  209 +
  210 +If any portion of this section is held invalid or unenforceable under
  211 +any particular circumstance, the balance of the section is intended to
  212 +apply and the section as a whole is intended to apply in other
  213 +circumstances.
  214 +
  215 +It is not the purpose of this section to induce you to infringe any
  216 +patents or other property right claims or to contest validity of any
  217 +such claims; this section has the sole purpose of protecting the
  218 +integrity of the free software distribution system, which is
  219 +implemented by public license practices. Many people have made
  220 +generous contributions to the wide range of software distributed
  221 +through that system in reliance on consistent application of that
  222 +system; it is up to the author/donor to decide if he or she is willing
  223 +to distribute software through any other system and a licensee cannot
  224 +impose that choice.
  225 +
  226 +This section is intended to make thoroughly clear what is believed to
  227 +be a consequence of the rest of this License.
  228 +
  229 + 8. If the distribution and/or use of the Program is restricted in
  230 +certain countries either by patents or by copyrighted interfaces, the
  231 +original copyright holder who places the Program under this License
  232 +may add an explicit geographical distribution limitation excluding
  233 +those countries, so that distribution is permitted only in or among
  234 +countries not thus excluded. In such case, this License incorporates
  235 +the limitation as if written in the body of this License.
  236 +
  237 + 9. The Free Software Foundation may publish revised and/or new versions
  238 +of the General Public License from time to time. Such new versions will
  239 +be similar in spirit to the present version, but may differ in detail to
  240 +address new problems or concerns.
  241 +
  242 +Each version is given a distinguishing version number. If the Program
  243 +specifies a version number of this License which applies to it and "any
  244 +later version", you have the option of following the terms and conditions
  245 +either of that version or of any later version published by the Free
  246 +Software Foundation. If the Program does not specify a version number of
  247 +this License, you may choose any version ever published by the Free Software
  248 +Foundation.
  249 +
  250 + 10. If you wish to incorporate parts of the Program into other free
  251 +programs whose distribution conditions are different, write to the author
  252 +to ask for permission. For software which is copyrighted by the Free
  253 +Software Foundation, write to the Free Software Foundation; we sometimes
  254 +make exceptions for this. Our decision will be guided by the two goals
  255 +of preserving the free status of all derivatives of our free software and
  256 +of promoting the sharing and reuse of software generally.
  257 +
  258 + NO WARRANTY
  259 +
  260 + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
  261 +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
  262 +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
  263 +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
  264 +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  265 +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
  266 +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
  267 +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
  268 +REPAIR OR CORRECTION.
  269 +
  270 + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
  271 +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
  272 +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
  273 +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
  274 +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
  275 +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
  276 +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
  277 +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
  278 +POSSIBILITY OF SUCH DAMAGES.
  279 +
  280 + END OF TERMS AND CONDITIONS
  281 +
  282 + How to Apply These Terms to Your New Programs
  283 +
  284 + If you develop a new program, and you want it to be of the greatest
  285 +possible use to the public, the best way to achieve this is to make it
  286 +free software which everyone can redistribute and change under these terms.
  287 +
  288 + To do so, attach the following notices to the program. It is safest
  289 +to attach them to the start of each source file to most effectively
  290 +convey the exclusion of warranty; and each file should have at least
  291 +the "copyright" line and a pointer to where the full notice is found.
  292 +
  293 + <one line to give the program's name and a brief idea of what it does.>
  294 + Copyright (C) <year> <name of author>
  295 +
  296 + This program is free software; you can redistribute it and/or modify
  297 + it under the terms of the GNU General Public License as published by
  298 + the Free Software Foundation; either version 2 of the License, or
  299 + (at your option) any later version.
  300 +
  301 + This program is distributed in the hope that it will be useful,
  302 + but WITHOUT ANY WARRANTY; without even the implied warranty of
  303 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  304 + GNU General Public License for more details.
  305 +
  306 + You should have received a copy of the GNU General Public License along
  307 + with this program; if not, write to the Free Software Foundation, Inc.,
  308 + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  309 +
  310 +Also add information on how to contact you by electronic and paper mail.
  311 +
  312 +If the program is interactive, make it output a short notice like this
  313 +when it starts in an interactive mode:
  314 +
  315 + Gnomovision version 69, Copyright (C) year name of author
  316 + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
  317 + This is free software, and you are welcome to redistribute it
  318 + under certain conditions; type `show c' for details.
  319 +
  320 +The hypothetical commands `show w' and `show c' should show the appropriate
  321 +parts of the General Public License. Of course, the commands you use may
  322 +be called something other than `show w' and `show c'; they could even be
  323 +mouse-clicks or menu items--whatever suits your program.
  324 +
  325 +You should also get your employer (if you work as a programmer) or your
  326 +school, if any, to sign a "copyright disclaimer" for the program, if
  327 +necessary. Here is a sample; alter the names:
  328 +
  329 + Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  330 + `Gnomovision' (which makes passes at compilers) written by James Hacker.
  331 +
  332 + <signature of Ty Coon>, 1 April 1989
  333 + Ty Coon, President of Vice
  334 +
  335 +This General Public License does not permit incorporating your program into
  336 +proprietary programs. If your program is a subroutine library, you may
  337 +consider it more useful to permit linking proprietary applications with the
  338 +library. If this is what you want to do, use the GNU Lesser General
  339 +Public License instead of this License.
  1 +Examples for Developers
  2 +=======================
  3 +
  4 +Project site: http://drupal.org/project/examples
  5 +
  6 +Code: https://drupal.org/project/examples/git-instructions
  7 +
  8 +Issues: https://drupal.org/project/issues/examples
  9 +
  10 +What Is This?
  11 +-------------
  12 +
  13 +This set of modules is intended to provide working examples of Drupal 8's
  14 +features and APIs. The modules strive to be simple, well documented and
  15 +modification friendly, in order to help developers quickly learn their inner
  16 +workings.
  17 +
  18 +These examples are meant to teach you about code-level development for Drupal
  19 +8. Some solutions might be better served using a contributed module, so that
  20 +you don't end up having to re-invent the wheel in PHP.
  21 +
  22 +
  23 +How To Use The Examples
  24 +-----------------------
  25 +
  26 +There are three main ways to interact with the examples in this project:
  27 +
  28 +1. Enable the modules and use them within Drupal. Not all modules will have
  29 +obvious things to see within your Drupal installation. For instance, while the
  30 +Page and Form API examples will display forms, the Database API example does not
  31 +have much that is visible within Drupal.
  32 +
  33 +2. Read the code. Much effort has gone into making the example code readable,
  34 +not only in terms of the code itself, but also the extensive inline comments
  35 +and documentation blocks.
  36 +
  37 +3. Browse the code and documentation on the web. There are two main places to
  38 +do this:
  39 +
  40 +* https://api.drupal.org/api/examples is the main API site for all of Drupal.
  41 +It has all manner of cross-linked references between the example code and the
  42 +APIs being demonstrated. All of the Doxygen-based comments in the code are
  43 +parsed and made browseable here.
  44 +
  45 +* http://drupalcode.org/project/examples.git allows you to browse the git
  46 +repository for the Examples project.
  47 +
  48 +Note also that in the future, many modules from the Examples project will be
  49 +moved into Drupal 8 core. This means some examples will come with Drupal
  50 +itself. As of this writing, none have been migrated to Drupal 8 yet.
  51 +
  52 +This collection of modules ship with a composer.json file outlining the name,
  53 +description, type and license of the package. Although not used by Drupal itself
  54 +it allows the module to be installed via composer, therfore all contrib modules
  55 +should ship with one.
  56 +
  57 +How To Install The Modules
  58 +--------------------------
  59 +
  60 +1. The Examples project installs like any other Drupal module. There is extensive
  61 +documentation on how to do this here: https://drupal.org/documentation/install/modules-themes/modules-8
  62 +
  63 +2. Enable any Example sub-module you wish to explore in Admin menu > Extend.
  64 +
  65 +3. Rebuild access permissions if you are prompted to.
  66 +
  67 +4. Profit! The links for Examples material will appear in your Tools menu. This
  68 +menu appears on the left sidebar by default. You'll need to reenable it if you
  69 +removed it.
  70 +
  71 +Having seen the behavior of the various example modules, you can move on to
  72 +reading the code, experimenting with it, and hopefully grasp how things work.
  73 +
  74 +If you find a problem, incorrect comment, obsolete or improper code or such,
  75 +please search for an issue about it at http://drupal.org/project/issues/examples
  76 +If there isn't already an issue for it, please create a new one.
  77 +
  78 +Thanks.
  79 +
  1 +name: Block Example
  2 +type: module
  3 +description: An example outlining how a module can define blocks.
  4 +package: Example modules
  5 +# core: 8.x
  6 +dependencies:
  7 + - block
  8 + - examples
  9 +
  10 +# Information added by Drupal.org packaging script on 2015-11-11
  11 +version: '8.x-1.x-dev'
  12 +core: '8.x'
  13 +project: 'examples'
  14 +datestamp: 1447204146
  1 +block_example.description:
  2 + title: Block Example
  3 + route_name: block_example.description
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Module file for block_example.
  6 + */
  7 +
  8 +use Drupal\Component\Utility\Unicode;
  9 +use Drupal\Core\Block\BlockPluginInterface;
  10 +
  11 +/**
  12 + * @defgroup block_example Example: Block
  13 + * @ingroup examples
  14 + * @{
  15 + * Demonstrates code creation of blocks.
  16 + *
  17 + * This is an example outlining how a module can define blocks that can be
  18 + * displayed on various pages of a site, or how to alter blocks provided by
  19 + * other modules.
  20 + */
  21 +
  22 +/**
  23 + * Implements hook_block_view_alter().
  24 + *
  25 + * This hook allows you to modify the output of any block in the system.
  26 + *
  27 + * In addition, instead of hook_block_view_alter(), which is called for all
  28 + * blocks, you can also use hook_block_view_MODULE_DELTA_alter() to alter a
  29 + * specific block. To change only our block using
  30 + * hook_block_view_MODULE_DELTA_alter, we would use the function:
  31 + * block_example_block_view_block_example_example_configurable_text_alter()
  32 + *
  33 + * We are going to uppercase the subject (the title of the block as shown to the
  34 + * user) of any block if the string "uppercase" appears in the block title or
  35 + * subject. Default block titles are set programmatically in the subject key;
  36 + * titles created through the UI are saved in the title key. This module creates
  37 + * an example block to demonstrate this effect (default title set
  38 + * programmatically as subject). You can also demonstrate the effect of this
  39 + * hook by creating a new block whose title has the string 'uppercase' in it
  40 + * (set as title through the UI).
  41 + */
  42 +function block_example_block_view_alter(array &$build, BlockPluginInterface $block) {
  43 + // We'll search for the string 'uppercase'.
  44 + $definition = $block->getPluginDefinition();
  45 + if ((!empty($build['#configuration']['label']) && stristr($build['#configuration']['label'], 'uppercase')) || (!empty($definition['subject']) && stristr($definition['subject'], 'uppercase'))) {
  46 + // This will uppercase the block title.
  47 + $build['#configuration']['label'] = Unicode::strtoupper($build['#configuration']['label']);
  48 + }
  49 +}
  50 +
  51 +/**
  52 + * @} End of "defgroup block_example".
  53 + */
  1 +block_example.description:
  2 + path: '/examples/block_example'
  3 + defaults:
  4 + _controller: '\Drupal\block_example\Controller\BlockExampleController::description'
  5 + _title: 'Block Example'
  6 + requirements:
  7 + _permission: 'access content'
  1 +block.settings.example_configurable_text:
  2 + type: block_settings
  3 + label: 'Example configurable text block configuration'
  4 + mapping:
  5 + block_example_string:
  6 + type: text
  7 + label: 'Block contents'
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\block_example\Controller\BlockExampleController.
  6 + */
  7 +
  8 +namespace Drupal\block_example\Controller;
  9 +
  10 +use Drupal\Core\Controller\ControllerBase;
  11 +use Drupal\Core\Url;
  12 +
  13 +/**
  14 + * Controller routines for block example routes.
  15 + */
  16 +class BlockExampleController extends ControllerBase {
  17 +
  18 + /**
  19 + * A simple controller method to explain what the block example is about.
  20 + */
  21 + public function description() {
  22 + // Make a link from a route to the block admin page.
  23 + $url = Url::fromRoute('block.admin_display');
  24 + $block_admin_link = $this->l($this->t('the block admin page'), $url);
  25 +
  26 + // Put the link into the content.
  27 + $build = array(
  28 + '#markup' => $this->t('The Block Example provides three sample blocks which demonstrate the various block APIs. To experiment with the blocks, enable and configure them on @block_admin_link.', ['@block_admin_link' => $block_admin_link]),
  29 + );
  30 +
  31 + return $build;
  32 + }
  33 +
  34 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\block_example\Plugin\Block\ExampleConfigurableTextBlock.
  6 + */
  7 +
  8 +namespace Drupal\block_example\Plugin\Block;
  9 +
  10 +use Drupal\Core\Block\BlockBase;
  11 +use Drupal\Core\Form\FormStateInterface;
  12 +
  13 +/**
  14 + * Provides a 'Example: configurable text string' block.
  15 + *
  16 + * Drupal\Core\Block\BlockBase gives us a very useful set of basic functionality
  17 + * for this configurable block. We can just fill in a few of the blanks with
  18 + * defaultConfiguration(), blockForm(), blockSubmit(), and build().
  19 + *
  20 + * @Block(
  21 + * id = "example_configurable_text",
  22 + * admin_label = @Translation("Title of first block (example_configurable_text)")
  23 + * )
  24 + */
  25 +class ExampleConfigurableTextBlock extends BlockBase {
  26 +
  27 + /**
  28 + * {@inheritdoc}
  29 + */
  30 + public function defaultConfiguration() {
  31 + return array(
  32 + 'block_example_string' => $this->t('A default value. This block was created at %time', array('%time' => date('c'))),
  33 + );
  34 + }
  35 +
  36 + /**
  37 + * {@inheritdoc}
  38 + */
  39 + public function blockForm($form, FormStateInterface $form_state) {
  40 + $form['block_example_string_text'] = array(
  41 + '#type' => 'textarea',
  42 + '#title' => $this->t('Block contents'),
  43 + '#description' => $this->t('This text will appear in the example block.'),
  44 + '#default_value' => $this->configuration['block_example_string'],
  45 + );
  46 + return $form;
  47 + }
  48 +
  49 + /**
  50 + * {@inheritdoc}
  51 + */
  52 + public function blockSubmit($form, FormStateInterface $form_state) {
  53 + $this->configuration['block_example_string']
  54 + = $form_state->getValue('block_example_string_text');
  55 + }
  56 +
  57 + /**
  58 + * {@inheritdoc}
  59 + */
  60 + public function build() {
  61 + return array(
  62 + '#type' => 'markup',
  63 + '#markup' => $this->configuration['block_example_string'],
  64 + );
  65 + }
  66 +
  67 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\block_example\Plugin\Block\ExampleEmptyBlock.
  6 + */
  7 +
  8 +namespace Drupal\block_example\Plugin\Block;
  9 +
  10 +use Drupal\Core\Block\BlockBase;
  11 +
  12 +/**
  13 + * Provides a 'Example: empty block' block.
  14 + *
  15 + * @Block(
  16 + * id = "example_empty",
  17 + * admin_label = @Translation("Example: empty block")
  18 + * )
  19 + */
  20 +class ExampleEmptyBlock extends BlockBase {
  21 +
  22 + /**
  23 + * {@inheritdoc}
  24 + */
  25 + public function build() {
  26 + // We return an empty array on purpose. The block will thus not be rendered
  27 + // on the site. See BlockExampleTest::testBlockExampleBasic().
  28 + return array();
  29 + }
  30 +
  31 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\block_example\Plugin\Block\ExampleUppercaseBlock.
  6 + */
  7 +
  8 +namespace Drupal\block_example\Plugin\Block;
  9 +
  10 +use Drupal\Core\Block\BlockBase;
  11 +
  12 +/**
  13 + * Provides a 'Example: uppercase this please' block.
  14 + *
  15 + * @Block(
  16 + * id = "example_uppercase",
  17 + * admin_label = @Translation("Example: uppercase this please")
  18 + * )
  19 + */
  20 +class ExampleUppercaseBlock extends BlockBase {
  21 +
  22 + /**
  23 + * {@inheritdoc}
  24 + */
  25 + public function build() {
  26 + return array(
  27 + '#type' => 'markup',
  28 + '#markup' => $this->t("This block's title will be changed to uppercase. Any other block with 'uppercase' in the subject or title will also be altered. If you change this block's title through the UI to omit the word 'uppercase', it will still be altered to uppercase as the subject key has not been changed."),
  29 + );
  30 + }
  31 +
  32 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Definition of Drupal\block_example\Tests\BlockExampleMenuTest.
  6 + */
  7 +
  8 +namespace Drupal\block_example\Tests;
  9 +
  10 +use Drupal\simpletest\WebTestBase;
  11 +
  12 +/**
  13 + * Test the user-facing menus in Block Example.
  14 + *
  15 + * @ingroup block_example
  16 + * @group block_example
  17 + * @group examples
  18 + */
  19 +class BlockExampleMenuTest extends WebTestBase {
  20 +
  21 + /**
  22 + * Modules to enable.
  23 + *
  24 + * @var array
  25 + */
  26 + public static $modules = array('block', 'block_example');
  27 +
  28 + /**
  29 + * The installation profile to use with this test.
  30 + *
  31 + * This test class requires the "Tools" block.
  32 + *
  33 + * @var string
  34 + */
  35 + protected $profile = 'minimal';
  36 +
  37 + /**
  38 + * Test for a link to the block example in the Tools menu.
  39 + */
  40 + public function testBlockExampleLink() {
  41 + $this->drupalGet('');
  42 + $this->assertLinkByHref('examples/block_example');
  43 + }
  44 +
  45 + /**
  46 + * Tests block_example menus.
  47 + */
  48 + public function testBlockExampleMenu() {
  49 + $this->drupalGet('examples/block_example');
  50 + $this->assertResponse(200, 'Description page exists.');
  51 + }
  52 +
  53 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Definition of Drupal\block_example\Tests\BlockExampleTest.
  6 + */
  7 +
  8 +namespace Drupal\block_example\Tests;
  9 +
  10 +use Drupal\Component\Utility\Unicode;
  11 +use Drupal\simpletest\WebTestBase;
  12 +
  13 +/**
  14 + * Test the configuration options and block created by Block Example module.
  15 + *
  16 + * @ingroup block_example
  17 + * @group block_example
  18 + * @group examples
  19 + */
  20 +class BlockExampleTest extends WebTestBase {
  21 +
  22 + /**
  23 + * Modules to enable.
  24 + *
  25 + * @var array
  26 + */
  27 + public static $modules = array('block', 'block_example');
  28 +
  29 + /**
  30 + * Tests block_example functionality.
  31 + */
  32 + public function testBlockExampleBasic() {
  33 + // Create user.
  34 + $web_user = $this->drupalCreateUser(array('administer blocks'));
  35 + // Login the admin user.
  36 + $this->drupalLogin($web_user);
  37 +
  38 + $theme_name = \Drupal::config('system.theme')->get('default');
  39 +
  40 + // Verify the blocks are listed to be added.
  41 + $this->drupalGet('/admin/structure/block/library/' . $theme_name, ['query' => ['region' => 'content']]);
  42 + $this->assertRaw(t('Title of first block (example_configurable_text)'), 'Block configurable-string found.');
  43 + $this->assertRaw(t('Example: empty block'), 'Block empty-block found.');
  44 + $this->assertRaw(t('Example: uppercase this please'), 'Block uppercase found.');
  45 +
  46 + // Define and place blocks.
  47 + $settings_configurable = array(
  48 + 'label' => t('Title of first block (example_configurable_text)'),
  49 + 'id' => 'block_example_example_configurable_text',
  50 + 'theme' => $theme_name,
  51 + );
  52 + $this->drupalPlaceBlock('example_configurable_text', $settings_configurable);
  53 +
  54 + $settings_uppercase = array(
  55 + 'label' => t('Configurable block to be uppercased'),
  56 + 'id' => 'block_example_example_uppercased',
  57 + 'theme' => $theme_name,
  58 + );
  59 + $this->drupalPlaceBlock('example_uppercase', $settings_uppercase);
  60 +
  61 + $settings_empty = array(
  62 + 'label' => t('Example: empty block'),
  63 + 'id' => 'block_example_example_empty',
  64 + 'theme' => $theme_name,
  65 + );
  66 + $this->drupalPlaceBlock('example_empty', $settings_empty);
  67 +
  68 + // Verify that blocks are there. Empty block will not be shown, because it
  69 + // holds an empty array.
  70 + $this->drupalGet('');
  71 + $this->assertRaw($settings_configurable['label'], 'Block configurable test not found.');
  72 + $this->assertNoRaw($settings_uppercase['label'], 'Block uppercase with normal label not found.');
  73 + $this->assertRaw(Unicode::strtoupper($settings_uppercase['label']), 'Block uppercase with uppercased label found.');
  74 + $this->assertNoRaw($settings_empty['label'], 'Block empty not found.');
  75 +
  76 + // Change content of configurable text block.
  77 + $edit = array(
  78 + 'settings[block_example_string_text]' => $this->randomMachineName(),
  79 + );
  80 + $this->drupalPostForm('/admin/structure/block/manage/' . $settings_configurable['id'], $edit, t('Save block'));
  81 +
  82 + // Verify that new content is shown.
  83 + $this->drupalGet('');
  84 + $this->assertRaw($edit['settings[block_example_string_text]'], 'Content of configurable text block successfully verified.');
  85 + }
  86 +
  87 +}
  1 +name: Cache example
  2 +description: 'An example outlining how to use Cache API.'
  3 +package: Example modules
  4 +# core: 8.x
  5 +type: module
  6 +dependencies:
  7 + - examples
  8 +
  9 +# Information added by Drupal.org packaging script on 2015-11-11
  10 +version: '8.x-1.x-dev'
  11 +core: '8.x'
  12 +project: 'examples'
  13 +datestamp: 1447204146
  1 +cache_example.description:
  2 + title: Cache example
  3 + description: 'Example of Drupal Cache API'
  4 + route_name: cache_example.description
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Outlines how a module can use the Cache API.
  6 + *
  7 + * @todo: Demonstrate allowing invalid cache items.
  8 + * @todo: Demonstrate deleteing cache entries.
  9 + * @todo: Demonstrate deleteing cache tags.
  10 + * @todo: Demonstrate deleteing all cache items.
  11 + * @todo: Demonstrate invalidating cache entries.
  12 + * @todo: Demonstrate invalidating cache tags.
  13 + * @todo: Demonstrate invalidating all cache items.
  14 + */
  15 +
  16 +/**
  17 + * @defgroup cache_example Example: Cache API
  18 + * @ingroup examples
  19 + * @{
  20 + * Outlines how a module can use the Cache API.
  21 + *
  22 + * Cache API allows us to cache data that is heavy to calculate. As this can
  23 + * significantly speed up the Drupal site, it is recommended to use cache
  24 + * mechanism when it is appropriate.
  25 + *
  26 + * Cache in Drupal is very easy to use. This example will search entire Drupal
  27 + * folder and display all files. Since this operation includes filesystem it can
  28 + * take a while. This list will not change much on production
  29 + * websites, so we decide to cache it.
  30 + *
  31 + * @see \Drupal\Core\Cache\CacheBackendInterface
  32 + */
  33 +
  34 +/**
  35 + * @} End of "defgroup cache_example".
  36 + */
  1 +cache_example.description:
  2 + path: 'examples/cache_example'
  3 + defaults:
  4 + _form: '\Drupal\cache_example\Forms\CacheExampleForm'
  5 + requirements:
  6 + _permission: 'access content'
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\cron_example\Form\CronExampleForm.
  6 + */
  7 +
  8 +namespace Drupal\cache_example\Forms;
  9 +
  10 +use Drupal\Core\Form\FormBase;
  11 +use Drupal\Core\Form\FormStateInterface;
  12 +use Drupal\Core\Cache\Cache;
  13 +use Drupal\Core\Cache\CacheBackendInterface;
  14 +use Drupal\Core\Session\AccountProxyInterface;
  15 +use Drupal\Core\StringTranslation\TranslationInterface;
  16 +use Symfony\Component\DependencyInjection\ContainerInterface;
  17 +use Symfony\Component\HttpFoundation\RequestStack;
  18 +
  19 +
  20 +/**
  21 + * Form with examples on how to use cache.
  22 + */
  23 +class CacheExampleForm extends FormBase {
  24 +
  25 + /**
  26 + * The current user.
  27 + *
  28 + * @var \Drupal\Core\Session\AccountProxyInterface
  29 + */
  30 + protected $currentUser;
  31 +
  32 + /**
  33 + * The cache.default cache backend.
  34 + *
  35 + * @var \Drupal\Core\Cache\CacheBackendInterface
  36 + */
  37 + protected $cacheBackend;
  38 +
  39 + /**
  40 + * Dependency injection through the constructor.
  41 + *
  42 + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
  43 + * The request stack service.
  44 + * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
  45 + * The string translation service.
  46 + * @param \Drupal\Core\Session\AccountProxyInterface $current_user
  47 + * The current active user service.
  48 + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
  49 + * The cache object associated with the default bin.
  50 + */
  51 + public function __construct(
  52 + RequestStack $request_stack,
  53 + TranslationInterface $translation,
  54 + AccountProxyInterface $current_user,
  55 + CacheBackendInterface $cache_backend
  56 + ) {
  57 + $this->setRequestStack($request_stack);
  58 + $this->setStringTranslation($translation);
  59 + $this->currentUser = $current_user;
  60 + $this->cacheBackend = $cache_backend;
  61 + }
  62 +
  63 + /**
  64 + * {@inheritdoc}
  65 + */
  66 + public static function create(ContainerInterface $container) {
  67 + // Forms that require a Drupal service or a custom service should access
  68 + // the service using dependency injection.
  69 + // @link https://www.drupal.org/node/2203931.
  70 + // Those services are passed in the $container through the static create
  71 + // method.
  72 + return new static(
  73 + $container->get('request_stack'),
  74 + $container->get('string_translation'),
  75 + $container->get('current_user'),
  76 + $container->get('cache.default')
  77 + );
  78 + }
  79 +
  80 + /**
  81 + * {@inheritdoc}
  82 + */
  83 + public function getFormId() {
  84 + return 'cron_cache';
  85 + }
  86 +
  87 + /**
  88 + * {@inheritdoc}
  89 + */
  90 + public function buildForm(array $form, FormStateInterface $form_state) {
  91 + // Log execution time.
  92 + $start_time = microtime(TRUE);
  93 +
  94 + // Try to load the files count from cache. This function will accept two
  95 + // arguments:
  96 + // - cache object name (cid)
  97 + // - cache bin, the (optional) cache bin (most often a database table) where
  98 + // the object is to be saved.
  99 + //
  100 + // cache_get() returns the cached object or FALSE if object does not exist.
  101 + if ($cache = $this->cacheBackend->get('cache_example_files_count')) {
  102 + /*
  103 + * Get cached data. Complex data types will be unserialized automatically.
  104 + */
  105 + $files_count = $cache->data;
  106 + }
  107 + else {
  108 + // If there was no cached data available we have to search filesystem.
  109 + // Recursively get all .PHP files from Drupal's core folder.
  110 + $files_count = count(file_scan_directory('core', '/.php/'));
  111 +
  112 + // Since we have recalculated, we now need to store the new data into
  113 + // cache. Complex data types will be automatically serialized before
  114 + // being saved into cache.
  115 + // Here we use the default setting and create an unexpiring cache item.
  116 + // See below for an example that creates an expiring cache item.
  117 + $this->cacheBackend->set('cache_example_files_count', $files_count, CacheBackendInterface::CACHE_PERMANENT);
  118 + }
  119 +
  120 + $end_time = microtime(TRUE);
  121 + $duration = $end_time - $start_time;
  122 +
  123 + // Format intro message.
  124 + $intro_message = '<p>' . $this->t("This example will search Drupal's core folder and display a count of the PHP files in it.") . ' ';
  125 + $intro_message .= $this->t('This can take a while, since there are a lot of files to be searched.') . ' ';
  126 + $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>';
  127 + $intro_message .= '<p>'
  128 + . $this->t(
  129 + '<a href="@url">Reload this page</a> to see cache in action.',
  130 + array('@url' => $this->getRequest()->getRequestUri())
  131 + )
  132 + . ' ';
  133 + $intro_message .= $this->t('You can use the button below to remove cached data.') . '</p>';
  134 +
  135 + $form['file_search'] = array(
  136 + '#type' => 'fieldset',
  137 + '#title' => $this->t('File search caching'),
  138 + );
  139 + $form['file_search']['introduction'] = array(
  140 + '#markup' => $intro_message,
  141 + );
  142 +
  143 + $color = empty($cache) ? 'red' : 'green';
  144 + $retrieval = empty($cache) ? $this->t('calculated by traversing the filesystem') : $this->t('retrieved from cache');
  145 +
  146 + $form['file_search']['statistics'] = array(
  147 + '#type' => 'item',
  148 + '#markup' => $this->t('%count files exist in this Drupal installation; @retrieval in @time ms. <br/>(Source: <span style="color:@color;">@source</span>)', array(
  149 + '%count' => $files_count,
  150 + '@retrieval' => $retrieval,
  151 + '@time' => number_format($duration * 1000, 2),
  152 + '@color' => $color,
  153 + '@source' => empty($cache) ? $this->t('actual file search') : $this->t('cached'),
  154 + )
  155 + ),
  156 + );
  157 + $form['file_search']['remove_file_count'] = array(
  158 + '#type' => 'submit',
  159 + '#submit' => array(array($this, 'expireFiles')),
  160 + '#value' => $this->t('Explicitly remove cached file count'),
  161 + );
  162 +
  163 + $form['expiration_demo'] = array(
  164 + '#type' => 'fieldset',
  165 + '#title' => $this->t('Cache expiration settings'),
  166 + );
  167 + $form['expiration_demo']['explanation'] = array(
  168 + '#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).'),
  169 + );
  170 +
  171 + $item = $this->cacheBackend->get('cache_example_expiring_item', TRUE);
  172 + if ($item == FALSE) {
  173 + $item_status = $this->t('Cache item does not exist');
  174 + }
  175 + else {
  176 + $item_status = $item->valid ? $this->t('Cache item exists and is set to expire at %time', array('%time' => $item->data)) :
  177 + $this->t('Cache_item is invalid');
  178 + }
  179 +
  180 + $form['expiration_demo']['current_status'] = array(
  181 + '#type' => 'item',
  182 + '#title' => $this->t('Current status of cache item "cache_example_expiring_item"'),
  183 + '#markup' => $item_status,
  184 + );
  185 + $form['expiration_demo']['expiration'] = array(
  186 + '#type' => 'select',
  187 + '#title' => $this->t('Time before cache expiration'),
  188 + '#options' => array(
  189 + 'never_remove' => $this->t('CACHE_PERMANENT'),
  190 + -10 => $this->t('Immediate expiration'),
  191 + 10 => $this->t('10 seconds from form submission'),
  192 + 60 => $this->t('1 minute from form submission'),
  193 + 300 => $this->t('5 minutes from form submission'),
  194 + ),
  195 + '#default_value' => -10,
  196 + '#description' => $this->t('Any cache item can be set to only expire when explicitly cleared, or to expire at a given time.'),
  197 + );
  198 + $form['expiration_demo']['create_cache_item'] = array(
  199 + '#type' => 'submit',
  200 + '#value' => $this->t('Create a cache item with this expiration'),
  201 + '#submit' => array(array($this, 'createExpiringItem')),
  202 + );
  203 +
  204 + $form['cache_clearing'] = array(
  205 + '#type' => 'fieldset',
  206 + '#title' => $this->t('Expire and remove options'),
  207 + '#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"),
  208 + );
  209 + $form['cache_clearing']['cache_clear_type'] = array(
  210 + '#type' => 'radios',
  211 + '#title' => $this->t('Type of cache clearing to do'),
  212 + '#options' => array(
  213 + 'expire' => $this->t('Remove items from the "cache" bin that have expired'),
  214 + 'remove_all' => $this->t('Remove all items from the "cache" bin regardless of expiration'),
  215 + 'remove_tag' => $this->t('Remove all items in the "cache" bin with the tag "cache_example" set to 1'),
  216 + ),
  217 + '#default_value' => 'expire',
  218 + );
  219 + // Submit button to clear cached data.
  220 + $form['cache_clearing']['clear_expired'] = array(
  221 + '#type' => 'submit',
  222 + '#value' => $this->t('Clear or expire cache'),
  223 + '#submit' => array(array($this, 'cacheClearing')),
  224 + '#access' => $this->currentUser->hasPermission('administer site configuration'),
  225 + );
  226 +
  227 + return $form;
  228 + }
  229 +
  230 + /**
  231 + * Submit handler that explicitly clears cache_example_files_count from cache.
  232 + */
  233 + public function expireFiles($form, &$form_state) {
  234 + // Clear cached data. This function will delete cached object from cache
  235 + // bin.
  236 + //
  237 + // The first argument is cache id to be deleted. Since we've provided it
  238 + // explicitly, it will be removed whether or not it has an associated
  239 + // expiration time. The second argument (required here) is the cache bin.
  240 + // Using cache_clear_all() explicitly in this way
  241 + // forces removal of the cached item.
  242 + $this->cacheBackend->delete('cache_example_files_count');
  243 +
  244 + // Display message to the user.
  245 + drupal_set_message($this->t('Cached data key "cache_example_files_count" was cleared.'), 'status');
  246 + }
  247 +
  248 + /**
  249 + * Submit handler to create a new cache item with specified expiration.
  250 + */
  251 + public function createExpiringItem($form, &$form_state) {
  252 +
  253 + $tags = array(
  254 + 'cache_example:1',
  255 + );
  256 +
  257 + $interval = $form_state->getValue('expiration');
  258 + if ($interval == 'never_remove') {
  259 + $expiration = CacheBackendInterface::CACHE_PERMANENT;
  260 + $expiration_friendly = $this->t('Never expires');
  261 + }
  262 + else {
  263 + $expiration = time() + $interval;
  264 + $expiration_friendly = format_date($expiration);
  265 + }
  266 + // Set the expiration to the actual Unix timestamp of the end of the
  267 + // required interval. Also add a tag to it to be able to clear caches more
  268 + // precise.
  269 + $this->cacheBackend->set('cache_example_expiring_item', $expiration_friendly, $expiration, $tags);
  270 + drupal_set_message($this->t('cache_example_expiring_item was set to expire at %time', array('%time' => $expiration_friendly)));
  271 + }
  272 +
  273 + /**
  274 + * Submit handler to demonstrate the various uses of cache_clear_all().
  275 + */
  276 + public function cacheClearing($form, &$form_state) {
  277 + switch ($form_state->getValue('cache_clear_type')) {
  278 + case 'expire':
  279 + // Here we'll remove all cache keys in the 'cache' bin that have
  280 + // expired.
  281 + $this->cacheBackend->garbageCollection();
  282 + drupal_set_message($this->t('\Drupal::cache()->garbageCollection() was called, removing any expired cache items.'));
  283 + break;
  284 +
  285 + case 'remove_all':
  286 + // This removes all keys in a bin using a super-wildcard. This
  287 + // has nothing to do with expiration. It's just brute-force removal.
  288 + $this->cacheBackend->deleteAll();
  289 + drupal_set_message($this->t('ALL entries in the "cache" bin were removed with \Drupal::cache()->deleteAll().'));
  290 + break;
  291 +
  292 + case 'remove_tag':
  293 + // This removes cache entries with the tag "cache_example" set to 1 in
  294 + // the "cache".
  295 + $tags = array(
  296 + 'cache_example:1',
  297 + );
  298 + Cache::invalidateTags($tags);
  299 + 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).'));
  300 + break;
  301 + }
  302 + }
  303 +
  304 + /**
  305 + * {@inheritdoc}
  306 + */
  307 + public function submitForm(array &$form, FormStateInterface $form_state) {
  308 +
  309 + }
  310 +
  311 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Test case for testing the cache example module.
  6 + */
  7 +
  8 +namespace Drupal\cache_example\Tests;
  9 +
  10 +use Drupal\simpletest\WebTestBase;
  11 +
  12 +/**
  13 + * Tests for the cache_example module.
  14 + *
  15 + * @ingroup cache_example
  16 + * @group cache_example
  17 + * @group examples
  18 + */
  19 +class CacheExampleTestCase extends WebTestBase {
  20 +
  21 + /**
  22 + * Modules to enable.
  23 + *
  24 + * @var array
  25 + */
  26 + public static $modules = array('cache_example');
  27 +
  28 + /**
  29 + * The installation profile to use with this test.
  30 + *
  31 + * @var string
  32 + */
  33 + protected $profile = 'minimal';
  34 +
  35 + /**
  36 + * Test menu links and routes.
  37 + *
  38 + * Test the following:
  39 + * - A link to the cache_example in the Tools menu.
  40 + * - That you can successfully access the cache_example form.
  41 + */
  42 + public function testCacheExampleMenu() {
  43 +
  44 + // Test for a link to the cache_example in the Tools menu.
  45 + $this->drupalGet('');
  46 + $this->assertResponse(200, 'The Home page is available.');
  47 + $this->assertLinkByHref('examples/cache_example');
  48 +
  49 + // Verify if the can successfully access the cache_example form.
  50 + $this->drupalGet('examples/cache_example');
  51 + $this->assertResponse(200, 'The Cache Example description page is available.');
  52 + }
  53 +
  54 + /**
  55 + * Test that our caches function.
  56 + *
  57 + * Does the following:
  58 + * - Load cache example page and test if displaying uncached version.
  59 + * - Reload once again and test if displaying cached version.
  60 + * - Find reload link and click on it.
  61 + * - Clear cache at the end and test if displaying uncached version again.
  62 + */
  63 + public function testCacheExampleBasic() {
  64 +
  65 + // We need administrative privileges to clear the cache.
  66 + $admin_user = $this->drupalCreateUser(array('administer site configuration'));
  67 + $this->drupalLogin($admin_user);
  68 +
  69 + // Get initial page cache example page, first time accessed,
  70 + // and assert uncached output.
  71 + $this->drupalGet('examples/cache_example');
  72 + $this->assertText('Source: actual file search');
  73 +
  74 + // Reload the page; the number should be cached.
  75 + $this->drupalGet('examples/cache_example');
  76 + $this->assertText('Source: cached');
  77 +
  78 + // Now push the button to remove the count.
  79 + $this->drupalPostForm('examples/cache_example', array(), t('Explicitly remove cached file count'));
  80 + $this->assertText('Source: actual file search');
  81 +
  82 + // Create a cached item. First make sure it doesn't already exist.
  83 + $this->assertText('Cache item does not exist');
  84 + $this->drupalPostForm('examples/cache_example', array('expiration' => -10), t('Create a cache item with this expiration'));
  85 + // We should now have an already-expired item. Automatically invalid.
  86 + $this->assertText('Cache_item is invalid');
  87 + // Now do the expiration operation.
  88 + $this->drupalPostForm('examples/cache_example', array('cache_clear_type' => 'expire'), t('Clear or expire cache'));
  89 + // And verify that it was removed.
  90 + $this->assertText('Cache item does not exist');
  91 +
  92 + // Create a cached item. This time we'll make it not expire.
  93 + $this->drupalPostForm('examples/cache_example', array('expiration' => 'never_remove'), t('Create a cache item with this expiration'));
  94 + // We should now have an never-remove item.
  95 + $this->assertText('Cache item exists and is set to expire at Never expires');
  96 + // Now do the expiration operation.
  97 + $this->drupalPostForm('examples/cache_example', array('cache_clear_type' => 'expire'), t('Clear or expire cache'));
  98 + // And verify that it was not removed.
  99 + $this->assertText('Cache item exists and is set to expire at Never expires');
  100 + // Now do tag invalidation.
  101 + $this->drupalPostForm('examples/cache_example', array('cache_clear_type' => 'remove_tag'), t('Clear or expire cache'));
  102 + // And verify that it was invalidated.
  103 + $this->assertText('Cache_item is invalid');
  104 + // Do the hard delete
  105 + $this->drupalPostForm('examples/cache_example', array('cache_clear_type' => 'remove_all'), t('Clear or expire cache'));
  106 + // And verify that it was removed.
  107 + $this->assertText('Cache item does not exist');
  108 + }
  109 +
  110 +}
  1 +{
  2 + "name": "drupal/examples",
  3 + "description": "The Examples for Developers project aims to provide high-quality, well-documented API examples for a broad range of Drupal core functionality.",
  4 + "type": "drupal-module",
  5 + "license": "GPL-2.0+"
  6 +}
  1 +# This file defines a default config entity. This allows the module to include
  2 +# config entities that are present 'out of the box'. Default config entities
  3 +# are created in Drupal when the module is enabled. They are removed when the
  4 +# module is uninstalled.
  5 +
  6 +# Default config entities can be edited by the user within Drupal. This edited
  7 +# entity wil become exportable through the configuration system. This file,
  8 +# however, will remain untouched.
  9 +
  10 +# For our config entity to be added at installation time, we have to place it in
  11 +# the config/install directory of our module. Thus our file is located at:
  12 +# config/install/config_entity_example.robot.marvin.yml.
  13 +
  14 +# You can see where these properties are defined in the annotation of
  15 +# Drupal\config_entity_example\Entity\Robot.
  16 +
  17 +# The id of the config entity.
  18 +id: marvin
  19 +# Our properties follow.
  20 +label: 'Marvin, the paranoid android'
  1 +# Schema for the configuration files of the Config Entity Example module.
  2 +
  3 +# This schema tells the config system how to read our config YML files.
  4 +# See for example the file config/config_entity_example.robot.marvin.yml, which
  5 +# contains our default config entity.
  6 +
  7 +# Documentation for schema files like this one is located here:
  8 +# https://drupal.org/node/1905070
  9 +
  10 +config_entity_example.robot.*:
  11 + type: mapping
  12 + label: 'Robot'
  13 + mapping:
  14 + id:
  15 + type: string
  16 + label: 'Robot id'
  17 + uuid:
  18 + type: string
  19 + label: 'UUID'
  20 + label:
  21 + type: label
  22 + label: 'Label'
  23 + floopy:
  24 + type: boolean
  25 + label: 'Floopy'
  26 + langcode:
  27 + type: string
  28 + label: 'Default language'
  1 +name: 'Config entity example'
  2 +type: module
  3 +description: 'An example module showing how to create a config entity type.'
  4 +package: Example modules
  5 +# core: 8.x
  6 +dependencies:
  7 + - examples
  8 +
  9 +# Information added by Drupal.org packaging script on 2015-11-11
  10 +version: '8.x-1.x-dev'
  11 +core: '8.x'
  12 +project: 'examples'
  13 +datestamp: 1447204146
  1 +# Add some local task links to facilitate navigation.
  2 +
  3 +config_entity_example.add_action:
  4 + route_name: entity.robot.add_form
  5 + title: 'Add robot'
  6 + appears_on:
  7 + - entity.robot.list
  8 +
  9 +config_entity_example.list_action:
  10 + route_name: entity.robot.list
  11 + title: 'List Robots'
  12 + appears_on:
  13 + - entity.robot.add_form
  14 + - entity.robot.edit
  15 + - entity.robot.delete
  1 +config_entity_example.menu:
  2 + title: Config Entity Example
  3 + route_name: entity.robot.list
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Demonstrates how to define a new configuration entity type.
  6 + */
  7 +
  8 +/**
  9 + * @defgroup config_entity_example Example: Config Entity
  10 + * @ingroup examples
  11 + * @{
  12 + * Implement a Config Entity.
  13 + *
  14 + * This module demonstrates implementing a Config Entity.
  15 + *
  16 + * This is an example of a simple configuration entity, the kind you might
  17 + * create to store administrator-defined objects like blocks or views.
  18 + *
  19 + * In this module we define a configuration entity named 'Robot'. The entity
  20 + * has a unique ID (also called a machine name), a human-readable label used
  21 + * for display, and a universally unique identifier. You can create new robots
  22 + * by navigating to &lt;your_site_root&gt;/examples/config_entity_example. A
  23 + * default robot, "marvin", is included with the module.
  24 + *
  25 + * What's special about a Configuration Entity?
  26 + *
  27 + * Configuration entities are entities just like content entities. The key
  28 + * difference is where the data is stored. Content entities are stored in the
  29 + * database. Configuration entities are stored in *.yml files, typically under
  30 + * &lt;your_site_root&gt;/sites/default/files/config_&lt;unique_id&gt;.
  31 + *
  32 + * Another key difference with configuration entities is the expectation they
  33 + * are created by administrators, and not end users. As files, configuration
  34 + * entities can be added to a version control system.
  35 + */
  36 +
  37 +/**
  38 + * @} End of "defgroup config_entity_example".
  39 + */
  1 +#Define the administer permission for the robot config entity.
  2 +'administer robots':
  3 + title: 'Administer robots'
  4 + description: Create and edit robots.
  1 +# The routing.yml file defines the paths for our module.
  2 +# Here we define the paths for our entity type's admin UI.
  3 +
  4 +# This is the router item for listing all entities.
  5 +entity.robot.list:
  6 + path: '/examples/config_entity_example'
  7 + defaults:
  8 + # '_entity_list' tells Drupal to use an entity list controller.
  9 + # We give the entity ID here. Drupal then looks in the entity's annotation
  10 + # and looks for the "list" entry under "controllers" for the class to load.
  11 + # @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
  12 + _entity_list: 'robot'
  13 + _title: 'Config Entity Example'
  14 + requirements:
  15 + _permission: 'administer robots'
  16 +
  17 +# This is the router item for adding our entity.
  18 +entity.robot.add_form:
  19 + path: '/examples/config_entity_example/add'
  20 + defaults:
  21 + _title: 'Add robot'
  22 + # Like _entity_list above, _entity_form gives the entity type ID, only this
  23 + # time also lists the form separated by a period. Drupal looks in the
  24 + # annotation for the entity and locates the "add" entry under "form" for
  25 + # the form class to load.
  26 + # @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
  27 + _entity_form: robot.add
  28 + requirements:
  29 + _entity_create_access: robot
  30 +
  31 +# This is the router item for editing our entity.
  32 +entity.robot.edit_form:
  33 + # Parameters may be passed to the form via the URL path. We name the
  34 + # parameter in the path by enclosing it in curly braces. For entity forms,
  35 + # we include the entity ID in the path by including a parameter with the
  36 + # same name as the entity type ID.
  37 + path: '/examples/config_entity_example/manage/{robot}'
  38 + defaults:
  39 + _title: 'Edit robot'
  40 + # List our add entry above, this _entity_form entry instructs Drupal to
  41 + # read our entity type's annonation, and look for the "edit" entry under
  42 + # "form".
  43 + _entity_form: robot.edit
  44 + requirements:
  45 + # This uses our entity access controller.
  46 + # @see \Drupal\Core\Entity\EntityAccessCheck
  47 + _entity_access: robot.update
  48 +
  49 +# This is the router item for deleting an instance of our entity.
  50 +entity.robot.delete_form:
  51 + path: '/examples/config_entity_example/manage/{robot}/delete'
  52 + defaults:
  53 + _title: 'Delete robot'
  54 + _entity_form: robot.delete
  55 + requirements:
  56 + _entity_access: robot.delete
  1 +<?php
  2 +/**
  3 + * @file
  4 + * Contains Drupal\config_entity_example\Controller\RobotListBuilder.
  5 + */
  6 +
  7 +namespace Drupal\config_entity_example\Controller;
  8 +
  9 +use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
  10 +use Drupal\Core\Entity\EntityInterface;
  11 +
  12 +/**
  13 + * Provides a listing of robot entities.
  14 + *
  15 + * List Controllers provide a list of entities in a tabular form. The base
  16 + * class provides most of the rendering logic for us. The key functions
  17 + * we need to override are buildHeader() and buildRow(). These control what
  18 + * columns are displayed in the table, and how each row is displayed
  19 + * respectively.
  20 + *
  21 + * Drupal locates the list controller by looking for the "list" entry under
  22 + * "controllers" in our entity type's annotation. We define the path on which
  23 + * the list may be accessed in our module's *.routing.yml file. The key entry
  24 + * to look for is "_entity_list". In *.routing.yml, "_entity_list" specifies
  25 + * an entity type ID. When a user navigates to the URL for that router item,
  26 + * Drupal loads the annotation for that entity type. It looks for the "list"
  27 + * entry under "controllers" for the class to load.
  28 + *
  29 + * @package Drupal\config_entity_example\Controller
  30 + *
  31 + * @ingroup config_entity_example
  32 + */
  33 +class RobotListBuilder extends ConfigEntityListBuilder {
  34 +
  35 + /**
  36 + * Builds the header row for the entity listing.
  37 + *
  38 + * @return array
  39 + * A render array structure of header strings.
  40 + *
  41 + * @see Drupal\Core\Entity\EntityListController::render()
  42 + */
  43 + public function buildHeader() {
  44 + $header['label'] = $this->t('Robot');
  45 + $header['machine_name'] = $this->t('Machine Name');
  46 + $header['floopy'] = $this->t('Floopy');
  47 + return $header + parent::buildHeader();
  48 + }
  49 +
  50 + /**
  51 + * Builds a row for an entity in the entity listing.
  52 + *
  53 + * @param EntityInterface $entity
  54 + * The entity for which to build the row.
  55 + *
  56 + * @return array
  57 + * A render array of the table row for displaying the entity.
  58 + *
  59 + * @see Drupal\Core\Entity\EntityListController::render()
  60 + */
  61 + public function buildRow(EntityInterface $entity) {
  62 + $row['label'] = $entity->label();
  63 + $row['machine_name'] = $entity->id();
  64 + $row['floopy'] = $entity->floopy;
  65 +
  66 + return $row + parent::buildRow($entity);
  67 + }
  68 +
  69 + /**
  70 + * Adds some descriptive text to our entity list.
  71 + *
  72 + * Typically, there's no need to override render(). You may wish to do so,
  73 + * however, if you want to add markup before or after the table.
  74 + *
  75 + * @return array
  76 + * Renderable array.
  77 + */
  78 + public function render() {
  79 + $build['description'] = array(
  80 + '#markup' => $this->t("<p>The Config Entity Example module defines a"
  81 + . " Robot entity type. This is a list of the Robot entities currently"
  82 + . " in your Drupal site.</p><p>By default, when you enable this"
  83 + . " module, one entity is created from configuration. This is why we"
  84 + . " call them Config Entities. Marvin, the paranoid android, is created"
  85 + . " in the database when the module is enabled.</p><p>You can view a"
  86 + . " list of Robots here. You can also use the 'Operations' column to"
  87 + . " edit and delete Robots.</p>"),
  88 + );
  89 + $build[] = parent::render();
  90 + return $build;
  91 + }
  92 +
  93 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains Drupal\config_entity_example\Entity\Robot.
  6 + *
  7 + * This contains our entity class.
  8 + *
  9 + * Originally based on code from blog post at
  10 + * http://previousnext.com.au/blog/understanding-drupal-8s-config-entities
  11 + */
  12 +
  13 +namespace Drupal\config_entity_example\Entity;
  14 +
  15 +use Drupal\Core\Config\Entity\ConfigEntityBase;
  16 +
  17 +/**
  18 + * Defines the robot entity.
  19 + *
  20 + * The lines below, starting with '@ConfigEntityType,' are a plugin annotation.
  21 + * These define the entity type to the entity type manager.
  22 + *
  23 + * The properties in the annotation are as follows:
  24 + * - id: The machine name of the entity type.
  25 + * - label: The human-readable label of the entity type. We pass this through
  26 + * the "@Translation" wrapper so that the multilingual system may
  27 + * translate it in the user interface.
  28 + * - handlers: An array of entity handler classes, keyed by handler type.
  29 + * - access: The class that is used for access checks.
  30 + * - list_builder: The class that provides listings of the entity.
  31 + * - form: An array of entity form classes keyed by their operation.
  32 + * - entity_keys: Specifies the class properties in which unique keys are
  33 + * stored for this entity type. Unique keys are properties which you know
  34 + * will be unique, and which the entity manager can use as unique in database
  35 + * queries.
  36 + * - links: entity URL definitions. These are mostly used for Field UI.
  37 + * Arbitrary keys can set here. For example, User sets cancel-form, while
  38 + * Node uses delete-form.
  39 + *
  40 + * @see http://previousnext.com.au/blog/understanding-drupal-8s-config-entities
  41 + * @see annotation
  42 + * @see Drupal\Core\Annotation\Translation
  43 + *
  44 + * @ingroup config_entity_example
  45 + *
  46 + * @ConfigEntityType(
  47 + * id = "robot",
  48 + * label = @Translation("Robot"),
  49 + * admin_permission = "administer robots",
  50 + * handlers = {
  51 + * "access" = "Drupal\config_entity_example\RobotAccessController",
  52 + * "list_builder" = "Drupal\config_entity_example\Controller\RobotListBuilder",
  53 + * "form" = {
  54 + * "add" = "Drupal\config_entity_example\Form\RobotAddForm",
  55 + * "edit" = "Drupal\config_entity_example\Form\RobotEditForm",
  56 + * "delete" = "Drupal\config_entity_example\Form\RobotDeleteForm"
  57 + * }
  58 + * },
  59 + * entity_keys = {
  60 + * "id" = "id",
  61 + * "label" = "label"
  62 + * },
  63 + * links = {
  64 + * "edit-form" = "/examples/config_entity_example/manage/{robot}",
  65 + * "delete-form" = "/examples/config_entity_example/manage/{robot}/delete"
  66 + * }
  67 + * )
  68 + */
  69 +class Robot extends ConfigEntityBase {
  70 +
  71 + /**
  72 + * The robot ID.
  73 + *
  74 + * @var string
  75 + */
  76 + public $id;
  77 +
  78 + /**
  79 + * The robot UUID.
  80 + *
  81 + * @var string
  82 + */
  83 + public $uuid;
  84 +
  85 + /**
  86 + * The robot label.
  87 + *
  88 + * @var string
  89 + */
  90 + public $label;
  91 +
  92 + /**
  93 + * The robot floopy flag.
  94 + *
  95 + * @var string
  96 + */
  97 + public $floopy;
  98 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains Drupal\config_entity_example\Form\RobotAddForm.
  6 + */
  7 +
  8 +namespace Drupal\config_entity_example\Form;
  9 +
  10 +use Drupal\Core\Form\FormStateInterface;
  11 +
  12 +/**
  13 + * Class RobotAddForm.
  14 + *
  15 + * Provides the add form for our Robot entity.
  16 + *
  17 + * @package Drupal\config_entity_example\Form
  18 + *
  19 + * @ingroup config_entity_example
  20 + */
  21 +class RobotAddForm extends RobotFormBase {
  22 +
  23 + /**
  24 + * Returns the actions provided by this form.
  25 + *
  26 + * For our add form, we only need to change the text of the submit button.
  27 + *
  28 + * @param array $form
  29 + * An associative array containing the structure of the form.
  30 + * @param \Drupal\Core\Form\FormStateInterface $form_state
  31 + * An associative array containing the current state of the form.
  32 + *
  33 + * @return array
  34 + * An array of supported actions for the current entity form.
  35 + */
  36 + protected function actions(array $form, FormStateInterface $form_state) {
  37 + $actions = parent::actions($form, $form_state);
  38 + $actions['submit']['#value'] = $this->t('Create Robot');
  39 + return $actions;
  40 + }
  41 +
  42 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains Drupal\config_entity_example\Form\RobotDeleteForm.
  6 + */
  7 +
  8 +namespace Drupal\config_entity_example\Form;
  9 +
  10 +use Drupal\Core\Entity\EntityConfirmFormBase;
  11 +use Drupal\Core\Url;
  12 +use Drupal\Core\Form\FormStateInterface;
  13 +
  14 +/**
  15 + * Class RobotDeleteForm.
  16 + *
  17 + * Provides a confirm form for deleting the entity. This is different from the
  18 + * add and edit forms as it does not inherit from RobotFormBase. The reason for
  19 + * this is that we do not need to build the same form. Instead, we present the
  20 + * user with a simple yes/no question. For this reason, we derive from
  21 + * EntityConfirmFormBase instead.
  22 + *
  23 + * @package Drupal\config_entity_example\Form
  24 + *
  25 + * @ingroup config_entity_example
  26 + */
  27 +class RobotDeleteForm extends EntityConfirmFormBase {
  28 +
  29 + /**
  30 + * Gathers a confirmation question.
  31 + *
  32 + * The question is used as a title in our confirm form. For delete confirm
  33 + * forms, this typically takes the form of "Are you sure you want to
  34 + * delete...", including the entity label.
  35 + *
  36 + * @return string
  37 + * Translated string.
  38 + */
  39 + public function getQuestion() {
  40 + return $this->t('Are you sure you want to delete robot %label?', array(
  41 + '%label' => $this->entity->label(),
  42 + ));
  43 + }
  44 +
  45 + /**
  46 + * Gather the confirmation text.
  47 + *
  48 + * The confirm text is used as the text in the button that confirms the
  49 + * question posed by getQuestion().
  50 + *
  51 + * @return string
  52 + * Translated string.
  53 + */
  54 + public function getConfirmText() {
  55 + return $this->t('Delete Robot');
  56 + }
  57 +
  58 + /**
  59 + * Gets the cancel URL.
  60 + *
  61 + * Provides the URL to go to if the user cancels the action. For entity
  62 + * delete forms, this is typically the route that points at the list
  63 + * controller.
  64 + *
  65 + * @return \Drupal\Core\Url
  66 + * The URL to go to if the user cancels the deletion.
  67 + */
  68 + public function getCancelUrl() {
  69 + return new Url('entity.robot.list');
  70 + }
  71 +
  72 + /**
  73 + * The submit handler for the confirm form.
  74 + *
  75 + * For entity delete forms, you use this to delete the entity in
  76 + * $this->entity.
  77 + *
  78 + * @param array $form
  79 + * An associative array containing the structure of the form.
  80 + * @param \Drupal\Core\Form\FormStateInterface $form_state
  81 + * An associative array containing the current state of the form.
  82 + */
  83 + public function submitForm(array &$form, FormStateInterface $form_state) {
  84 + // Delete the entity.
  85 + $this->entity->delete();
  86 +
  87 + // Set a message that the entity was deleted.
  88 + drupal_set_message(t('Robot %label was deleted.', array(
  89 + '%label' => $this->entity->label(),
  90 + )));
  91 +
  92 + // Redirect the user to the list controller when complete.
  93 + $form_state->setRedirectUrl($this->getCancelUrl());
  94 + }
  95 +
  96 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains Drupal\config_entity_example\Form\RobotEditForm.
  6 + */
  7 +
  8 +namespace Drupal\config_entity_example\Form;
  9 +
  10 +use Drupal\Core\Form\FormStateInterface;
  11 +
  12 +/**
  13 + * Class RobotEditForm
  14 + *
  15 + * Provides the edit form for our Robot entity.
  16 + *
  17 + * @package Drupal\config_entity_example\Form
  18 + *
  19 + * @ingroup config_entity_example
  20 + */
  21 +class RobotEditForm extends RobotFormBase {
  22 +
  23 + /**
  24 + * Returns the actions provided by this form.
  25 + *
  26 + * For the edit form, we only need to change the text of the submit button.
  27 + *
  28 + * @param array $form
  29 + * An associative array containing the structure of the form.
  30 + * @param \Drupal\Core\Form\FormStateInterface $form_state
  31 + * An associative array containing the current state of the form.
  32 + *
  33 + * @return array
  34 + * An array of supported actions for the current entity form.
  35 + */
  36 + public function actions(array $form, FormStateInterface $form_state) {
  37 + $actions = parent::actions($form, $form_state);
  38 + $actions['submit']['#value'] = t('Update Robot');
  39 + return $actions;
  40 + }
  41 +
  42 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains Drupal\config_entity_example\Form\RobotFormBase.
  6 + */
  7 +
  8 +namespace Drupal\config_entity_example\Form;
  9 +
  10 +use Drupal\Core\Entity\EntityForm;
  11 +use Drupal\Core\Entity\Query\QueryFactory;
  12 +use Drupal\Core\Form\FormStateInterface;
  13 +use Symfony\Component\DependencyInjection\ContainerInterface;
  14 +
  15 +/**
  16 + * Class RobotFormBase.
  17 + *
  18 + * Typically, we need to build the same form for both adding a new entity,
  19 + * and editing an existing entity. Instead of duplicating our form code,
  20 + * we create a base class. Drupal never routes to this class directly,
  21 + * but instead through the child classes of RobotAddForm and RobotEditForm.
  22 + *
  23 + * @package Drupal\config_entity_example\Form
  24 + *
  25 + * @ingroup config_entity_example
  26 + */
  27 +class RobotFormBase extends EntityForm {
  28 +
  29 + /**
  30 + * @var \Drupal\Core\Entity\Query\QueryFactory
  31 + */
  32 + protected $entityQueryFactory;
  33 +
  34 + /**
  35 + * Construct the RobotFormBase.
  36 + *
  37 + * For simple entity forms, there's no need for a constructor. Our robot form
  38 + * base, however, requires an entity query factory to be injected into it
  39 + * from the container. We later use this query factory to build an entity
  40 + * query for the exists() method.
  41 + *
  42 + * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
  43 + * An entity query factory for the robot entity type.
  44 + */
  45 + public function __construct(QueryFactory $query_factory) {
  46 + $this->entityQueryFactory = $query_factory;
  47 + }
  48 +
  49 + /**
  50 + * Factory method for RobotFormBase.
  51 + *
  52 + * When Drupal builds this class it does not call the constructor directly.
  53 + * Instead, it relies on this method to build the new object. Why? The class
  54 + * constructor may take multiple arguments that are unknown to Drupal. The
  55 + * create() method always takes one parameter -- the container. The purpose
  56 + * of the create() method is twofold: It provides a standard way for Drupal
  57 + * to construct the object, meanwhile it provides you a place to get needed
  58 + * constructor parameters from the container.
  59 + *
  60 + * In this case, we ask the container for an entity query factory. We then
  61 + * pass the factory to our class as a constructor parameter.
  62 + */
  63 + public static function create(ContainerInterface $container) {
  64 + return new static($container->get('entity.query'));
  65 + }
  66 +
  67 + /**
  68 + * Overrides Drupal\Core\Entity\EntityFormController::form().
  69 + *
  70 + * Builds the entity add/edit form.
  71 + *
  72 + * @param array $form
  73 + * An associative array containing the structure of the form.
  74 + * @param array $form_state
  75 + * An associative array containing the current state of the form.
  76 + *
  77 + * @return array
  78 + * An associative array containing the robot add/edit form.
  79 + */
  80 + public function buildForm(array $form, FormStateInterface $form_state) {
  81 + // Get anything we need from the base class.
  82 + $form = parent::buildForm($form, $form_state);
  83 +
  84 + // Drupal provides the entity to us as a class variable. If this is an
  85 + // existing entity, it will be populated with existing values as class
  86 + // variables. If this is a new entity, it will be a new object with the
  87 + // class of our entity. Drupal knows which class to call from the
  88 + // annotation on our Robot class.
  89 + $robot = $this->entity;
  90 +
  91 + // Build the form.
  92 + $form['label'] = array(
  93 + '#type' => 'textfield',
  94 + '#title' => $this->t('Label'),
  95 + '#maxlength' => 255,
  96 + '#default_value' => $robot->label(),
  97 + '#required' => TRUE,
  98 + );
  99 + $form['id'] = array(
  100 + '#type' => 'machine_name',
  101 + '#title' => $this->t('Machine name'),
  102 + '#default_value' => $robot->id(),
  103 + '#machine_name' => array(
  104 + 'exists' => array($this, 'exists'),
  105 + 'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
  106 + '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".',
  107 + ),
  108 + '#disabled' => !$robot->isNew(),
  109 + );
  110 + $form['floopy'] = array(
  111 + '#type' => 'checkbox',
  112 + '#title' => $this->t('Floopy'),
  113 + '#default_value' => $robot->floopy,
  114 + );
  115 +
  116 + // Return the form.
  117 + return $form;
  118 + }
  119 +
  120 + /**
  121 + * Checks for an existing robot.
  122 + *
  123 + * @param string|int $entity_id
  124 + * The entity ID.
  125 + * @param array $element
  126 + * The form element.
  127 + * @param FormStateInterface $form_state
  128 + * The form state.
  129 + *
  130 + * @return bool
  131 + * TRUE if this format already exists, FALSE otherwise.
  132 + */
  133 + public function exists($entity_id, array $element, FormStateInterface $form_state) {
  134 + // Use the query factory to build a new robot entity query.
  135 + $query = $this->entityQueryFactory->get('robot');
  136 +
  137 + // Query the entity ID to see if its in use.
  138 + $result = $query->condition('id', $element['#field_prefix'] . $entity_id)
  139 + ->execute();
  140 +
  141 + // We don't need to return the ID, only if it exists or not.
  142 + return (bool) $result;
  143 + }
  144 +
  145 + /**
  146 + * Overrides Drupal\Core\Entity\EntityFormController::actions().
  147 + *
  148 + * To set the submit button text, we need to override actions().
  149 + *
  150 + * @param array $form
  151 + * An associative array containing the structure of the form.
  152 + * @param \Drupal\Core\Form\FormStateInterface $form_state
  153 + * An associative array containing the current state of the form.
  154 + *
  155 + * @return array
  156 + * An array of supported actions for the current entity form.
  157 + */
  158 + protected function actions(array $form, FormStateInterface $form_state) {
  159 + // Get the basic actins from the base class.
  160 + $actions = parent::actions($form, $form_state);
  161 +
  162 + // Change the submit button text.
  163 + $actions['submit']['#value'] = $this->t('Save');
  164 +
  165 + // Return the result.
  166 + return $actions;
  167 + }
  168 +
  169 + /**
  170 + * Overrides Drupal\Core\Entity\EntityFormController::validate().
  171 + *
  172 + * @param array $form
  173 + * An associative array containing the structure of the form.
  174 + * @param \Drupal\Core\Form\FormStateInterface $form_state
  175 + * An associative array containing the current state of the form.
  176 + */
  177 + public function validate(array $form, FormStateInterface $form_state) {
  178 + parent::validate($form, $form_state);
  179 +
  180 + // Add code here to validate your config entity's form elements.
  181 + // Nothing to do here.
  182 + }
  183 +
  184 + /**
  185 + * Overrides Drupal\Core\Entity\EntityFormController::save().
  186 + *
  187 + * Saves the entity. This is called after submit() has built the entity from
  188 + * the form values. Do not override submit() as save() is the preferred
  189 + * method for entity form controllers.
  190 + *
  191 + * @param array $form
  192 + * An associative array containing the structure of the form.
  193 + * @param \Drupal\Core\Form\FormStateInterface $form_state
  194 + * An associative array containing the current state of the form.
  195 + */
  196 + public function save(array $form, FormStateInterface $form_state) {
  197 + // EntityForm provides us with the entity we're working on.
  198 + $robot = $this->getEntity();
  199 +
  200 + // Drupal already populated the form values in the entity object. Each
  201 + // form field was saved as a public variable in the entity class. PHP
  202 + // allows Drupal to do this even if the method is not defined ahead of
  203 + // time.
  204 + $status = $robot->save();
  205 +
  206 + // Grab the URL of the new entity. We'll use it in the message.
  207 + $url = $robot->urlInfo();
  208 +
  209 + // Create an edit link.
  210 + $edit_link = $this->l(t('Edit'), $url);
  211 +
  212 + if ($status == SAVED_UPDATED) {
  213 + // If we edited an existing entity...
  214 + drupal_set_message($this->t('Robot %label has been updated.', array('%label' => $robot->label())));
  215 + $this->logger('contact')->notice('Robot %label has been updated.', ['%label' => $robot->label(), 'link' => $edit_link]);
  216 + }
  217 + else {
  218 + // If we created a new entity...
  219 + drupal_set_message($this->t('Robot %label has been added.', array('%label' => $robot->label())));
  220 + $this->logger('contact')->notice('Robot %label has been added.', ['%label' => $robot->label(), 'link' => $edit_link]);
  221 + }
  222 +
  223 + // Redirect the user back to the listing route after the save operation.
  224 + $form_state->setRedirect('entity.robot.list');
  225 + }
  226 +
  227 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\config_entity_example\RobotAccessController.
  6 + */
  7 +
  8 +namespace Drupal\config_entity_example;
  9 +
  10 +use Drupal\Core\Entity\EntityAccessControlHandler;
  11 +use Drupal\Core\Entity\EntityInterface;
  12 +use Drupal\Core\Session\AccountInterface;
  13 +
  14 +/**
  15 + * Defines an access controller for the robot entity.
  16 + *
  17 + * We set this class to be the access controller in Robot's entity annotation.
  18 + *
  19 + * @see \Drupal\config_entity_example\Entity\Robot
  20 + *
  21 + * @ingroup config_entity_example
  22 + */
  23 +class RobotAccessController extends EntityAccessControlHandler {
  24 +
  25 + /**
  26 + * {@inheritdoc}
  27 + */
  28 + public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
  29 + // The $opereration parameter tells you what sort of operation access is
  30 + // being checked for.
  31 + if ($operation == 'view') {
  32 + return TRUE;
  33 + }
  34 + // Other than the view operation, we're going to be insanely lax about
  35 + // access. Don't try this at home!
  36 + return parent::checkAccess($entity, $operation, $account);
  37 + }
  38 +
  39 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Test case for testing the Config Entity Example module.
  6 + */
  7 +
  8 +namespace Drupal\config_entity_example\Tests;
  9 +
  10 +use Drupal\simpletest\WebTestBase;
  11 +
  12 +/**
  13 + * Test the Config Entity Example module.
  14 + *
  15 + * @group config_entity_example
  16 + * @group examples
  17 + *
  18 + * @ingroup config_entity_example
  19 + */
  20 +class ConfigEntityExampleTest extends WebTestBase {
  21 +
  22 + /**
  23 + * Modules to enable.
  24 + *
  25 + * @var array
  26 + */
  27 + public static $modules = array('config_entity_example');
  28 +
  29 + /**
  30 + * The installation profile to use with this test.
  31 + *
  32 + * We need the 'minimal' profile in order to make sure the Tool block is
  33 + * available.
  34 + *
  35 + * @var string
  36 + */
  37 + protected $profile = 'minimal';
  38 +
  39 + /**
  40 + * {@inheritdoc}
  41 + */
  42 + public static function getInfo() {
  43 + return array(
  44 + 'name' => 'Config Entity Example functional test',
  45 + 'description' => 'Test the Config Entity Example module.',
  46 + 'group' => 'Examples',
  47 + );
  48 + }
  49 +
  50 + /**
  51 + * Various functional test of the Config Entity Example module.
  52 + *
  53 + * 1) Verify that the Marvin entity was created when the module was installed.
  54 + *
  55 + * 2) Verify that permissions are applied to the various defined paths.
  56 + *
  57 + * 3) Verify that we can manage entities through the user interface.
  58 + *
  59 + * 4) Verify that the entity we add can be re-edited.
  60 + *
  61 + * 5) Verify that the label is shown in the list.
  62 + */
  63 + public function testConfigEntityExample() {
  64 + // 1) Verify that the Marvin entity was created when the module was
  65 + // installed.
  66 + $entity = entity_load('robot', 'marvin');
  67 + $this->assertNotNull($entity, 'Marvin was created during installation.');
  68 +
  69 + // 2) Verify that permissions are applied to the various defined paths.
  70 + // Define some paths. Since the Marvin entity is defined, we can use it
  71 + // in our management paths.
  72 + $forbidden_paths = array(
  73 + '/examples/config_entity_example',
  74 + '/examples/config_entity_example/add',
  75 + '/examples/config_entity_example/manage/marvin',
  76 + '/examples/config_entity_example/manage/marvin/delete',
  77 + );
  78 + // Check each of the paths to make sure we don't have access. At this point
  79 + // we haven't logged in any users, so the client is anonymous.
  80 + foreach ($forbidden_paths as $path) {
  81 + $this->drupalGet($path);
  82 + $this->assertResponse(403, "Access denied to anonymous for path: $path");
  83 + }
  84 +
  85 + // Create a user with no permissions.
  86 + $noperms_user = $this->drupalCreateUser();
  87 + $this->drupalLogin($noperms_user);
  88 + // Should be the same result for forbidden paths, since the user needs
  89 + // special permissions for these paths.
  90 + foreach ($forbidden_paths as $path) {
  91 + $this->drupalGet($path);
  92 + $this->assertResponse(403, "Access denied to generic user for path: $path");
  93 + }
  94 +
  95 + // Create a user who can administer robots.
  96 + $admin_user = $this->drupalCreateUser(array('administer robots'));
  97 + $this->drupalLogin($admin_user);
  98 + // Forbidden paths aren't forbidden any more.
  99 + foreach ($forbidden_paths as $unforbidden) {
  100 + $this->drupalGet($unforbidden);
  101 + $this->assertResponse(200, "Access granted to admin user for path: $unforbidden");
  102 + }
  103 +
  104 + // Now that we have the admin user logged in, check the menu links.
  105 + $this->drupalGet('');
  106 + $this->assertLinkByHref('examples/config_entity_example');
  107 +
  108 + // 3) Verify that we can manage entities through the user interface.
  109 + // We still have the admin user logged in, so we'll create, update, and
  110 + // delete an entity.
  111 + // Go to the list page.
  112 + $this->drupalGet('/examples/config_entity_example');
  113 + $this->clickLink('Add robot');
  114 + $robot_machine_name = 'roboname';
  115 + $this->drupalPostForm(
  116 + NULL,
  117 + array(
  118 + 'label' => $robot_machine_name,
  119 + 'id' => $robot_machine_name,
  120 + 'floopy' => TRUE,
  121 + ),
  122 + t('Create Robot')
  123 + );
  124 +
  125 + // 4) Verify that our robot appears when we edit it.
  126 + $this->drupalGet('/examples/config_entity_example/manage/' . $robot_machine_name);
  127 + $this->assertField('label');
  128 + $this->assertFieldChecked('edit-floopy');
  129 +
  130 + // 5) Verify that the label and machine name are shown in the list.
  131 + $this->drupalGet('/examples/config_entity_example');
  132 + $this->clickLink('Add robot');
  133 + $robby_machine_name = 'robby_machine_name';
  134 + $robby_label = 'Robby label';
  135 + $this->drupalPostForm(
  136 + NULL,
  137 + array(
  138 + 'label' => $robby_label,
  139 + 'id' => $robby_machine_name,
  140 + 'floopy' => TRUE,
  141 + ),
  142 + t('Create Robot')
  143 + );
  144 + $this->drupalGet('/examples/config_entity_example');
  145 + $this->assertText($robby_label);
  146 + $this->assertText($robby_machine_name);
  147 + }
  148 +
  149 +}
  1 +name: Content Entity Example
  2 +type: module
  3 +description: 'Provides ContentEntityExampleContact entity.'
  4 +package: Example modules
  5 +# core: 8.x
  6 +# These modules are required by the tests, must be available at bootstrap time
  7 +dependencies:
  8 + - options
  9 + - entity_reference
  10 + - examples
  11 +
  12 +# Information added by Drupal.org packaging script on 2015-11-11
  13 +version: '8.x-1.x-dev'
  14 +core: '8.x'
  15 +project: 'examples'
  16 +datestamp: 1447204146
  1 +# All action links for this module
  2 +
  3 +content_entity_example.contact_add:
  4 + # Which route will be called by the link
  5 + route_name: content_entity_example.contact_add
  6 + title: 'Add Contact'
  7 +
  8 + # Where will the link appear, defined by route name.
  9 + appears_on:
  10 + - entity.content_entity_example_contact.collection
  11 + - entity.content_entity_example_contact.canonical
  1 +# Define the menu links for this module
  2 +
  3 +entity.content_entity_example_contact.collection:
  4 + title: 'Content Entity Example: Contacts Listing'
  5 + route_name: entity.content_entity_example_contact.collection
  6 + description: 'List Contacts'
  7 + weight: 10
  8 +content_entity_example_contact.admin.structure.settings:
  9 + title: Contact Settings
  10 + description: 'Configure Contact entity'
  11 + route_name: content_entity_example.contact_settings
  12 + parent: system.admin_structure
  1 +# Define the 'local' links for the module
  2 +
  3 +contact.settings_tab:
  4 + route_name: content_entity_example.contact_settings
  5 + title: Settings
  6 + base_route: content_entity_example.contact_settings
  7 +
  8 +contact.view:
  9 + route_name: entity.content_entity_example_contact.canonical
  10 + base_route: entity.content_entity_example_contact.canonical
  11 + title: View
  12 +
  13 +contact.page_edit:
  14 + route_name: entity.content_entity_example_contact.edit_form
  15 + base_route: entity.content_entity_example_contact.canonical
  16 + title: Edit
  17 +
  18 +contact.delete_confirm:
  19 + route_name: entity.content_entity_example_contact.delete_form
  20 + base_route: entity.content_entity_example_contact.canonical
  21 + title: Delete
  22 + weight: 10
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains Drupal\content_entity_example\content_entity_example.module
  6 + */
  7 +
  8 +/**
  9 + * @defgroup content_entity_example Example: Content Entity
  10 + * @ingroup examples
  11 + * @{
  12 + * Implement a Content entity.
  13 + *
  14 + * This module demonstrates implementing a Content Entity.
  15 + *
  16 + * This is an example of a fieldable content entity used to hold structured
  17 + * information without the overhead of using a content type. It is defined
  18 + * programmatically and we will expose the main techniques to handle and expose
  19 + * the contents.
  20 + *
  21 + * We define a content entity named 'Contact'. With it, we demonstrate the main
  22 + * tasks for an entity:
  23 + * - define
  24 + * - save
  25 + * - load
  26 + * - view
  27 + * - edit
  28 + * - delete
  29 + * - control access
  30 + *
  31 + * Where ever possible, we use the amazing tools built into D8 natively.
  32 + * }
  33 + */
  1 +'delete contact entity':
  2 + title: Delete entity content.
  3 +'add contact entity':
  4 + title: Add entity content
  5 +'view contact entity':
  6 + title: View entity content
  7 +'edit contact entity':
  8 + title: Edit entity content
  9 +'administer contact entity':
  10 + title: Administer settings
  1 +# This file brings everything together. Very nifty!
  2 +
  3 +# Route name can be used in sevaral place (links, redirects, local actions etc.)
  4 +entity.content_entity_example_contact.canonical:
  5 + path: '/content_entity_example_contact/{content_entity_example_contact}'
  6 + defaults:
  7 + # Calls the view controller, defined in the annotation of the contact entity
  8 + _entity_view: 'content_entity_example_contact'
  9 + _title: 'Contact Content'
  10 + requirements:
  11 + # Calls the access controller of the entity, $operation 'view'
  12 + _entity_access: 'content_entity_example_contact.view'
  13 +
  14 +entity.content_entity_example_contact.collection:
  15 + path: '/content_entity_example_contact/list'
  16 + defaults:
  17 + # Calls the list controller, defined in the annotation of the contact entity.
  18 + _entity_list: 'content_entity_example_contact'
  19 + _title: 'Contact List'
  20 + requirements:
  21 + # Checks for permission directly.
  22 + _permission: 'view contact entity'
  23 +
  24 +content_entity_example.contact_add:
  25 + path: '/content_entity_example_contact/add'
  26 + defaults:
  27 + # Calls the form.add controller, defined in the contact entity.
  28 + _entity_form: content_entity_example_contact.add
  29 + _title: 'Add Contact'
  30 + requirements:
  31 + _entity_create_access: 'content_entity_example_contact'
  32 +
  33 +entity.content_entity_example_contact.edit_form:
  34 + path: '/content_entity_example_contact/{content_entity_example_contact}/edit'
  35 + defaults:
  36 + # Calls the form.edit controller, defined in the contact entity.
  37 + _entity_form: content_entity_example_contact.edit
  38 + _title: 'Edit Contact'
  39 + requirements:
  40 + _entity_access: 'content_entity_example_contact.edit'
  41 +
  42 +entity.content_entity_example_contact.delete_form:
  43 + path: '/contact/{content_entity_example_contact}/delete'
  44 + defaults:
  45 + # Calls the form.delete controller, defined in the contact entity.
  46 + _entity_form: content_entity_example_contact.delete
  47 + _title: 'Delete Contact'
  48 + requirements:
  49 + _entity_access: 'content_entity_example_contact.delete'
  50 +
  51 +content_entity_example.contact_settings:
  52 + path: 'admin/structure/content_entity_example_contact_settings'
  53 + defaults:
  54 + _form: '\Drupal\content_entity_example\Form\ContactSettingsForm'
  55 + _title: 'Contact Settings'
  56 + requirements:
  57 + _permission: 'administer contact entity'
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\content_entity_example\ContactAccessControlHandler
  6 + */
  7 +
  8 +namespace Drupal\content_entity_example;
  9 +
  10 +use Drupal\Core\Access\AccessResult;
  11 +use Drupal\Core\Entity\EntityAccessControlHandler;
  12 +use Drupal\Core\Entity\EntityInterface;
  13 +use Drupal\Core\Session\AccountInterface;
  14 +
  15 +/**
  16 + * Access controller for the comment entity.
  17 + *
  18 + * @see \Drupal\comment\Entity\Comment.
  19 + */
  20 +class ContactAccessControlHandler extends EntityAccessControlHandler {
  21 +
  22 + /**
  23 + * {@inheritdoc}
  24 + *
  25 + * Link the activities to the permissions. checkAccess is called with the
  26 + * $operation as defined in the routing.yml file.
  27 + */
  28 + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
  29 + switch ($operation) {
  30 + case 'view':
  31 + return AccessResult::allowedIfHasPermission($account, 'view contact entity');
  32 +
  33 + case 'edit':
  34 + return AccessResult::allowedIfHasPermission($account, 'edit contact entity');
  35 +
  36 + case 'delete':
  37 + return AccessResult::allowedIfHasPermission($account, 'delete contact entity');
  38 + }
  39 + return AccessResult::allowed();
  40 + }
  41 +
  42 + /**
  43 + * {@inheritdoc}
  44 + *
  45 + * Separate from the checkAccess because the entity does not yet exist, it
  46 + * will be created during the 'add' process.
  47 + */
  48 + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
  49 + return AccessResult::allowedIfHasPermission($account, 'add contact entity');
  50 + }
  51 +
  52 +}
  1 +<?php
  2 +/**
  3 + * @file
  4 + * Contains \Drupal\content_entity_example\ContactInterface.
  5 + */
  6 +
  7 +namespace Drupal\content_entity_example;
  8 +
  9 +use Drupal\Core\Entity\ContentEntityInterface;
  10 +use Drupal\user\EntityOwnerInterface;
  11 +use Drupal\Core\Entity\EntityChangedInterface;
  12 +
  13 +/**
  14 + * Provides an interface defining a Contact entity.
  15 + *
  16 + * We have this interface so we can join the other interfaces it extends.
  17 + *
  18 + * @ingroup content_entity_example
  19 + */
  20 +interface ContactInterface extends ContentEntityInterface, EntityOwnerInterface, EntityChangedInterface {
  21 +
  22 +}
  1 +<?php
  2 +/**
  3 + * @file
  4 + * Contains \Drupal\content_entity_example\Entity\ContentEntityExample.
  5 + */
  6 +
  7 +namespace Drupal\content_entity_example\Entity;
  8 +
  9 +use Drupal\Core\Entity\EntityStorageInterface;
  10 +use Drupal\Core\Field\BaseFieldDefinition;
  11 +use Drupal\Core\Entity\ContentEntityBase;
  12 +use Drupal\Core\Entity\EntityTypeInterface;
  13 +use Drupal\content_entity_example\ContactInterface;
  14 +use Drupal\user\UserInterface;
  15 +use Drupal\Core\Entity\EntityChangedTrait;
  16 +
  17 +/**
  18 + * Defines the ContentEntityExample entity.
  19 + *
  20 + * @ingroup content_entity_example
  21 + *
  22 + * This is the main definition of the entity type. From it, an entityType is
  23 + * derived. The most important properties in this example are listed below.
  24 + *
  25 + * id: The unique identifier of this entityType. It follows the pattern
  26 + * 'moduleName_xyz' to avoid naming conflicts.
  27 + *
  28 + * label: Human readable name of the entity type.
  29 + *
  30 + * handlers: Handler classes are used for different tasks. You can use
  31 + * standard handlers provided by D8 or build your own, most probably derived
  32 + * from the standard class. In detail:
  33 + *
  34 + * - view_builder: we use the standard controller to view an instance. It is
  35 + * called when a route lists an '_entity_view' default for the entityType
  36 + * (see routing.yml for details. The view can be manipulated by using the
  37 + * standard drupal tools in the settings.
  38 + *
  39 + * - list_builder: We derive our own list builder class from the
  40 + * entityListBuilder to control the presentation.
  41 + * If there is a view available for this entity from the views module, it
  42 + * overrides the list builder. @todo: any view? naming convention?
  43 + *
  44 + * - form: We derive our own forms to add functionality like additional fields,
  45 + * redirects etc. These forms are called when the routing list an
  46 + * '_entity_form' default for the entityType. Depending on the suffix
  47 + * (.add/.edit/.delete) in the route, the correct form is called.
  48 + *
  49 + * - access: Our own accessController where we determine access rights based on
  50 + * permissions.
  51 + *
  52 + * More properties:
  53 + *
  54 + * - base_table: Define the name of the table used to store the data. Make sure
  55 + * it is unique. The schema is automatically determined from the
  56 + * BaseFieldDefinitions below. The table is automatically created during
  57 + * installation.
  58 + *
  59 + * - fieldable: Can additional fields be added to the entity via the GUI?
  60 + * Analog to content types.
  61 + *
  62 + * - entity_keys: How to access the fields. Analog to 'nid' or 'uid'.
  63 + *
  64 + * - links: Provide links to do standard tasks. The 'edit-form' and
  65 + * 'delete-form' links are added to the list built by the
  66 + * entityListController. They will show up as action buttons in an additional
  67 + * column.
  68 + *
  69 + * There are many more properties to be used in an entity type definition. For
  70 + * a complete overview, please refer to the '\Drupal\Core\Entity\EntityType'
  71 + * class definition.
  72 + *
  73 + * The following construct is the actual definition of the entity type which
  74 + * is read and cached. Don't forget to clear cache after changes.
  75 + *
  76 + * @ContentEntityType(
  77 + * id = "content_entity_example_contact",
  78 + * label = @Translation("Contact entity"),
  79 + * handlers = {
  80 + * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  81 + * "list_builder" = "Drupal\content_entity_example\Entity\Controller\ContactListBuilder",
  82 + * "form" = {
  83 + * "add" = "Drupal\content_entity_example\Form\ContactForm",
  84 + * "edit" = "Drupal\content_entity_example\Form\ContactForm",
  85 + * "delete" = "Drupal\content_entity_example\Form\ContactDeleteForm",
  86 + * },
  87 + * "access" = "Drupal\content_entity_example\ContactAccessControlHandler",
  88 + * },
  89 + * list_cache_contexts = { "user" },
  90 + * base_table = "contact",
  91 + * admin_permission = "administer content_entity_example entity",
  92 + * fieldable = TRUE,
  93 + * entity_keys = {
  94 + * "id" = "id",
  95 + * "label" = "name",
  96 + * "uuid" = "uuid"
  97 + * },
  98 + * links = {
  99 + * "canonical" = "/content_entity_example_contact/{content_entity_example_contact}",
  100 + * "edit-form" = "/content_entity_example_contact/{content_entity_example_contact}/edit",
  101 + * "delete-form" = "/contact/{content_entity_example_contact}/delete",
  102 + * "collection" = "/content_entity_example_contact/list"
  103 + * },
  104 + * field_ui_base_route = "content_entity_example.contact_settings",
  105 + * )
  106 + *
  107 + * The 'links' above are defined by their path. For core to find the
  108 + * corresponding route, the route name must follow the correct pattern:
  109 + *
  110 + * entity.<entity-name>.<link-name> (replace dashes with underscores)
  111 + * Example: 'entity.content_entity_example_contact.canonical'
  112 + *
  113 + * See routing file above for the corresponding implementation
  114 + *
  115 + * The Contact class defines methods and fields for the contact entity.
  116 + *
  117 + * Being derived from the ContentEntityBase class, we can override the methods
  118 + * we want. In our case we want to provide access to the standard fields about
  119 + * creation and changed time stamps.
  120 + *
  121 + * Our interface (see ContactInterface) also exposes the EntityOwnerInterface.
  122 + * This allows us to provide methods for setting and providing ownership
  123 + * information.
  124 + *
  125 + * The most important part is the definitions of the field properties for this
  126 + * entity type. These are of the same type as fields added through the GUI, but
  127 + * they can by changed in code. In the definition we can define if the user with
  128 + * the rights privileges can influence the presentation (view, edit) of each
  129 + * field.
  130 + *
  131 + * The class also uses the EntityChangedTrait trait which allows it to record
  132 + * timestamps of save operations.
  133 + */
  134 +class Contact extends ContentEntityBase implements ContactInterface {
  135 +
  136 + use EntityChangedTrait;
  137 +
  138 + /**
  139 + * {@inheritdoc}
  140 + *
  141 + * When a new entity instance is added, set the user_id entity reference to
  142 + * the current user as the creator of the instance.
  143 + */
  144 + public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
  145 + parent::preCreate($storage_controller, $values);
  146 + $values += array(
  147 + 'user_id' => \Drupal::currentUser()->id(),
  148 + );
  149 + }
  150 +
  151 + /**
  152 + * {@inheritdoc}
  153 + */
  154 + public function getCreatedTime() {
  155 + return $this->get('created')->value;
  156 + }
  157 +
  158 + /**
  159 + * {@inheritdoc}
  160 + */
  161 + public function getChangedTime() {
  162 + return $this->get('changed')->value;
  163 + }
  164 +
  165 + /**
  166 + * {@inheritdoc}
  167 + */
  168 + public function getOwner() {
  169 + return $this->get('user_id')->entity;
  170 + }
  171 +
  172 + /**
  173 + * {@inheritdoc}
  174 + */
  175 + public function getOwnerId() {
  176 + return $this->get('user_id')->target_id;
  177 + }
  178 +
  179 + /**
  180 + * {@inheritdoc}
  181 + */
  182 + public function setOwnerId($uid) {
  183 + $this->set('user_id', $uid);
  184 + return $this;
  185 + }
  186 +
  187 + /**
  188 + * {@inheritdoc}
  189 + */
  190 + public function setOwner(UserInterface $account) {
  191 + $this->set('user_id', $account->id());
  192 + return $this;
  193 + }
  194 +
  195 + /**
  196 + * {@inheritdoc}
  197 + *
  198 + * Define the field properties here.
  199 + *
  200 + * Field name, type and size determine the table structure.
  201 + *
  202 + * In addition, we can define how the field and its content can be manipulated
  203 + * in the GUI. The behaviour of the widgets used can be determined here.
  204 + */
  205 + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  206 +
  207 + // Standard field, used as unique if primary index.
  208 + $fields['id'] = BaseFieldDefinition::create('integer')
  209 + ->setLabel(t('ID'))
  210 + ->setDescription(t('The ID of the Contact entity.'))
  211 + ->setReadOnly(TRUE);
  212 +
  213 + // Standard field, unique outside of the scope of the current project.
  214 + $fields['uuid'] = BaseFieldDefinition::create('uuid')
  215 + ->setLabel(t('UUID'))
  216 + ->setDescription(t('The UUID of the Contact entity.'))
  217 + ->setReadOnly(TRUE);
  218 +
  219 + // Name field for the contact.
  220 + // We set display options for the view as well as the form.
  221 + // Users with correct privileges can change the view and edit configuration.
  222 + $fields['name'] = BaseFieldDefinition::create('string')
  223 + ->setLabel(t('Name'))
  224 + ->setDescription(t('The name of the Contact entity.'))
  225 + ->setSettings(array(
  226 + 'default_value' => '',
  227 + 'max_length' => 255,
  228 + 'text_processing' => 0,
  229 + ))
  230 + ->setDisplayOptions('view', array(
  231 + 'label' => 'above',
  232 + 'type' => 'string',
  233 + 'weight' => -6,
  234 + ))
  235 + ->setDisplayOptions('form', array(
  236 + 'type' => 'string',
  237 + 'weight' => -6,
  238 + ))
  239 + ->setDisplayConfigurable('form', TRUE)
  240 + ->setDisplayConfigurable('view', TRUE);
  241 +
  242 + $fields['first_name'] = BaseFieldDefinition::create('string')
  243 + ->setLabel(t('First Name'))
  244 + ->setDescription(t('The first name of the Contact entity.'))
  245 + ->setSettings(array(
  246 + 'default_value' => '',
  247 + 'max_length' => 255,
  248 + 'text_processing' => 0,
  249 + ))
  250 + ->setDisplayOptions('view', array(
  251 + 'label' => 'above',
  252 + 'type' => 'string',
  253 + 'weight' => -5,
  254 + ))
  255 + ->setDisplayOptions('form', array(
  256 + 'type' => 'string',
  257 + 'weight' => -5,
  258 + ))
  259 + ->setDisplayConfigurable('form', TRUE)
  260 + ->setDisplayConfigurable('view', TRUE);
  261 +
  262 + // Gender field for the contact.
  263 + // ListTextType with a drop down menu widget.
  264 + // The values shown in the menu are 'male' and 'female'.
  265 + // In the view the field content is shown as string.
  266 + // In the form the choices are presented as options list.
  267 + $fields['gender'] = BaseFieldDefinition::create('list_string')
  268 + ->setLabel(t('Gender'))
  269 + ->setDescription(t('The gender of the Contact entity.'))
  270 + ->setSettings(array(
  271 + 'allowed_values' => array(
  272 + 'female' => 'female',
  273 + 'male' => 'male',
  274 + ),
  275 + ))
  276 + ->setDisplayOptions('view', array(
  277 + 'label' => 'above',
  278 + 'type' => 'string',
  279 + 'weight' => -4,
  280 + ))
  281 + ->setDisplayOptions('form', array(
  282 + 'type' => 'options_select',
  283 + 'weight' => -4,
  284 + ))
  285 + ->setDisplayConfigurable('form', TRUE)
  286 + ->setDisplayConfigurable('view', TRUE);
  287 +
  288 + // Owner field of the contact.
  289 + // Entity reference field, holds the reference to the user object.
  290 + // The view shows the user name field of the user.
  291 + // The form presents a auto complete field for the user name.
  292 + $fields['user_id'] = BaseFieldDefinition::create('entity_reference')
  293 + ->setLabel(t('User Name'))
  294 + ->setDescription(t('The Name of the associated user.'))
  295 + ->setSetting('target_type', 'user')
  296 + ->setSetting('handler', 'default')
  297 + ->setDisplayOptions('view', array(
  298 + 'label' => 'above',
  299 + 'type' => 'entity_reference',
  300 + 'weight' => -3,
  301 + ))
  302 + ->setDisplayOptions('form', array(
  303 + 'type' => 'entity_reference_autocomplete',
  304 + 'settings' => array(
  305 + 'match_operator' => 'CONTAINS',
  306 + 'size' => 60,
  307 + 'placeholder' => '',
  308 + ),
  309 + 'weight' => -3,
  310 + ))
  311 + ->setDisplayConfigurable('form', TRUE)
  312 + ->setDisplayConfigurable('view', TRUE);
  313 +
  314 + $fields['langcode'] = BaseFieldDefinition::create('language')
  315 + ->setLabel(t('Language code'))
  316 + ->setDescription(t('The language code of ContentEntityExample entity.'));
  317 + $fields['created'] = BaseFieldDefinition::create('created')
  318 + ->setLabel(t('Created'))
  319 + ->setDescription(t('The time that the entity was created.'));
  320 +
  321 + $fields['changed'] = BaseFieldDefinition::create('changed')
  322 + ->setLabel(t('Changed'))
  323 + ->setDescription(t('The time that the entity was last edited.'));
  324 +
  325 + return $fields;
  326 + }
  327 +
  328 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\content_entity_example\Entity\Controller\ContentEntityExampleController.
  6 + */
  7 +
  8 +namespace Drupal\content_entity_example\Entity\Controller;
  9 +
  10 +use Drupal\Core\Entity\EntityInterface;
  11 +use Drupal\Core\Entity\EntityListBuilder;
  12 +use Drupal\Core\Url;
  13 +
  14 +/**
  15 + * Provides a list controller for content_entity_example entity.
  16 + *
  17 + * @ingroup content_entity_example
  18 + */
  19 +class ContactListBuilder extends EntityListBuilder {
  20 +
  21 + /**
  22 + * {@inheritdoc}
  23 + *
  24 + * We override ::render() so that we can add our own content above the table.
  25 + * parent::render() is where EntityListBuilder creates the table using our
  26 + * buildHeader() and buildRow() implementations.
  27 + */
  28 + public function render() {
  29 + $build['description'] = array(
  30 + '#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>.', array(
  31 + '@adminlink' => \Drupal::urlGenerator()->generateFromRoute('content_entity_example.contact_settings'),
  32 + )),
  33 + );
  34 + $build['table'] = parent::render();
  35 + return $build;
  36 + }
  37 +
  38 + /**
  39 + * {@inheritdoc}
  40 + *
  41 + * Building the header and content lines for the contact list.
  42 + *
  43 + * Calling the parent::buildHeader() adds a column for the possible actions
  44 + * and inserts the 'edit' and 'delete' links as defined for the entity type.
  45 + */
  46 + public function buildHeader() {
  47 + $header['id'] = $this->t('ContactID');
  48 + $header['name'] = $this->t('Name');
  49 + $header['first_name'] = $this->t('First Name');
  50 + $header['gender'] = $this->t('Gender');
  51 + return $header + parent::buildHeader();
  52 + }
  53 +
  54 + /**
  55 + * {@inheritdoc}
  56 + */
  57 + public function buildRow(EntityInterface $entity) {
  58 + /* @var $entity \Drupal\content_entity_example\Entity\Contact */
  59 + $row['id'] = $entity->id();
  60 + $row['name'] = $entity->link();
  61 + $row['first_name'] = $entity->first_name->value;
  62 + $row['gender'] = $entity->gender->value;
  63 + return $row + parent::buildRow($entity);
  64 + }
  65 +
  66 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\content_entity_example\Form\ContactDeleteForm.
  6 + */
  7 +
  8 +namespace Drupal\content_entity_example\Form;
  9 +
  10 +use Drupal\Core\Entity\ContentEntityConfirmFormBase;
  11 +use Drupal\Core\Form\FormStateInterface;
  12 +use Drupal\Core\Url;
  13 +
  14 +/**
  15 + * Provides a form for deleting a content_entity_example entity.
  16 + *
  17 + * @ingroup content_entity_example
  18 + */
  19 +class ContactDeleteForm extends ContentEntityConfirmFormBase {
  20 +
  21 + /**
  22 + * {@inheritdoc}
  23 + */
  24 + public function getQuestion() {
  25 + return $this->t('Are you sure you want to delete entity %name?', array('%name' => $this->entity->label()));
  26 + }
  27 +
  28 + /**
  29 + * {@inheritdoc}
  30 + *
  31 + * If the delete command is canceled, return to the contact list.
  32 + */
  33 + public function getCancelURL() {
  34 + return new Url('entity.content_entity_example_contact.collection');
  35 + }
  36 +
  37 + /**
  38 + * {@inheritdoc}
  39 + */
  40 + public function getConfirmText() {
  41 + return $this->t('Delete');
  42 + }
  43 +
  44 + /**
  45 + * {@inheritdoc}
  46 + *
  47 + * Delete the entity and log the event. log() replaces the watchdog.
  48 + */
  49 + public function submitForm(array &$form, FormStateInterface $form_state) {
  50 + $entity = $this->getEntity();
  51 + $entity->delete();
  52 +
  53 + \Drupal::logger('content_entity_example')->notice('@type: deleted %title.',
  54 + array(
  55 + '@type' => $this->entity->bundle(),
  56 + '%title' => $this->entity->label(),
  57 + ));
  58 + $form_state->setRedirect('entity.content_entity_example_contact.collection');
  59 + }
  60 +
  61 +}
  1 +<?php
  2 +/**
  3 + * @file
  4 + * Contains Drupal\content_entity_example\Form\ContactForm.
  5 + */
  6 +
  7 +namespace Drupal\content_entity_example\Form;
  8 +
  9 +use Drupal\Core\Entity\ContentEntityForm;
  10 +use Drupal\Core\Language\Language;
  11 +use Drupal\Core\Form\FormStateInterface;
  12 +
  13 +/**
  14 + * Form controller for the content_entity_example entity edit forms.
  15 + *
  16 + * @ingroup content_entity_example
  17 + */
  18 +class ContactForm extends ContentEntityForm {
  19 +
  20 + /**
  21 + * {@inheritdoc}
  22 + */
  23 + public function buildForm(array $form, FormStateInterface $form_state) {
  24 + /* @var $entity \Drupal\content_entity_example\Entity\Contact */
  25 + $form = parent::buildForm($form, $form_state);
  26 + $entity = $this->entity;
  27 +
  28 + $form['langcode'] = array(
  29 + '#title' => $this->t('Language'),
  30 + '#type' => 'language_select',
  31 + '#default_value' => $entity->getUntranslated()->language()->getId(),
  32 + '#languages' => Language::STATE_ALL,
  33 + );
  34 + return $form;
  35 + }
  36 +
  37 + /**
  38 + * {@inheritdoc}
  39 + */
  40 + public function save(array $form, FormStateInterface $form_state) {
  41 + $form_state->setRedirect('entity.content_entity_example_contact.collection');
  42 + $entity = $this->getEntity();
  43 + $entity->save();
  44 + }
  45 +}
  1 +<?php
  2 +/**
  3 + * @file
  4 + * Contains Drupal\content_entity_example\Form\ContactSettingsForm.
  5 + */
  6 +
  7 +namespace Drupal\content_entity_example\Form;
  8 +
  9 +use Drupal\Core\Form\FormBase;
  10 +use Drupal\Core\Form\FormStateInterface;
  11 +
  12 +/**
  13 + * Class ContentEntityExampleSettingsForm.
  14 + * @package Drupal\content_entity_example\Form
  15 + * @ingroup content_entity_example
  16 + */
  17 +class ContactSettingsForm extends FormBase {
  18 + /**
  19 + * Returns a unique string identifying the form.
  20 + *
  21 + * @return string
  22 + * The unique string identifying the form.
  23 + */
  24 + public function getFormId() {
  25 + return 'content_entity_example_settings';
  26 + }
  27 +
  28 + /**
  29 + * Form submission handler.
  30 + *
  31 + * @param FormStateInterface $form
  32 + * An associative array containing the structure of the form.
  33 + * @param array $form_state
  34 + * An associative array containing the current state of the form.
  35 + */
  36 + public function submitForm(array &$form, FormStateInterface $form_state) {
  37 + // Empty implementation of the abstract submit class.
  38 + }
  39 +
  40 +
  41 + /**
  42 + * Define the form used for ContentEntityExample settings.
  43 + * @return array
  44 + * Form definition array.
  45 + *
  46 + * @param array $form
  47 + * An associative array containing the structure of the form.
  48 + * @param FormStateInterface $form_state
  49 + * An associative array containing the current state of the form.
  50 + */
  51 + public function buildForm(array $form, FormStateInterface $form_state) {
  52 + $form['contact_settings']['#markup'] = 'Settings form for ContentEntityExample. Manage field settings here.';
  53 + return $form;
  54 + }
  55 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Test cases for Content Entity Example Module.
  6 + */
  7 +
  8 +namespace Drupal\content_entity_example\Tests;
  9 +
  10 +use Drupal\content_entity_example\Entity\Contact;
  11 +use Drupal\examples\Tests\ExamplesTestBase;
  12 +
  13 +/**
  14 + * Tests the basic functions of the Content Entity Example module.
  15 + *
  16 + * @package Drupal\content_entity_example\Tests
  17 + *
  18 + * @ingroup content_entity_example
  19 + *
  20 + * @group content_entity_example
  21 + * @group examples
  22 + */
  23 +class ContentEntityExampleTest extends ExamplesTestBase {
  24 +
  25 + public static $modules = array('content_entity_example', 'block', 'field_ui');
  26 +
  27 + /**
  28 + * Basic tests for Content Entity Example.
  29 + */
  30 + public function testContentEntityExample() {
  31 + $web_user = $this->drupalCreateUser(array(
  32 + 'add contact entity',
  33 + 'edit contact entity',
  34 + 'view contact entity',
  35 + 'delete contact entity',
  36 + 'administer contact entity',
  37 + 'administer content_entity_example_contact display',
  38 + 'administer content_entity_example_contact fields',
  39 + 'administer content_entity_example_contact form display'));
  40 +
  41 + // Anonymous User should not see the link to the listing.
  42 + $this->assertNoText(t('Content Entity Example: Contacts Listing'));
  43 +
  44 + $this->drupalLogin($web_user);
  45 +
  46 + // Web_user user has the right to view listing.
  47 + $this->assertLink(t('Content Entity Example: Contacts Listing'));
  48 +
  49 + $this->clickLink(t('Content Entity Example: Contacts Listing'));
  50 +
  51 + // WebUser can add entity content.
  52 + $this->assertLink(t('Add Contact'));
  53 +
  54 + $this->clickLink(t('Add Contact'));
  55 +
  56 + $this->assertFieldByName('name[0][value]', '', 'Name Field, empty');
  57 + $this->assertFieldByName('name[0][value]', '', 'First Name Field, empty');
  58 + $this->assertFieldByName('name[0][value]', '', 'Gender Field, empty');
  59 +
  60 + $user_ref = $web_user->name->value . ' (' . $web_user->id() . ')';
  61 + $this->assertFieldByName('user_id[0][target_id]', $user_ref, 'User ID reference field points to web_user');
  62 +
  63 + // Post content, save an instance. Go back to list after saving.
  64 + $edit = array(
  65 + 'name[0][value]' => 'test name',
  66 + 'first_name[0][value]' => 'test first name',
  67 + 'gender' => 'male',
  68 + );
  69 + $this->drupalPostForm(NULL, $edit, t('Save'));
  70 +
  71 + // Entity listed.
  72 + $this->assertLink(t('Edit'));
  73 + $this->assertLink(t('Delete'));
  74 +
  75 + $this->clickLink('test name');
  76 +
  77 + // Entity shown.
  78 + $this->assertText(t('test name'));
  79 + $this->assertText(t('test first name'));
  80 + $this->assertText(t('male'));
  81 + $this->assertLink(t('Add Contact'));
  82 + $this->assertLink(t('Edit'));
  83 + $this->assertLink(t('Delete'));
  84 +
  85 + // Delete the entity.
  86 + $this->clickLink('Delete');
  87 +
  88 + // Confirm deletion.
  89 + $this->assertLink(t('Cancel'));
  90 + $this->drupalPostForm(NULL, array(), 'Delete');
  91 +
  92 + // Back to list, must be empty.
  93 + $this->assertNoText('test name');
  94 +
  95 + // Settings page.
  96 + $this->drupalGet('admin/structure/content_entity_example_contact_settings');
  97 + $this->assertText(t('Contact Settings'));
  98 +
  99 + // Make sure the field manipulation links are available.
  100 + $this->assertLink(t('Settings'));
  101 + $this->assertLink(t('Manage fields'));
  102 + $this->assertLink(t('Manage form display'));
  103 + $this->assertLink(t('Manage display'));
  104 + }
  105 +
  106 + /**
  107 + * Test all paths exposed by the module, by permission.
  108 + */
  109 + public function testPaths() {
  110 + // Generate a contact so that we can test the paths against it.
  111 + $contact = Contact::create(
  112 + array(
  113 + 'name' => 'somename',
  114 + 'first_name' => 'Joe',
  115 + 'gender' => 'female',
  116 + )
  117 + );
  118 + $contact->save();
  119 +
  120 + // Gather the test data.
  121 + $data = $this->providerTestPaths($contact->id());
  122 +
  123 + // Run the tests.
  124 + foreach ($data as $datum) {
  125 + // drupalCreateUser() doesn't know what to do with an empty permission
  126 + // array, so we help it out.
  127 + if ($datum[2]) {
  128 + $user = $this->drupalCreateUser(array($datum[2]));
  129 + $this->drupalLogin($user);
  130 + }
  131 + else {
  132 + $user = $this->drupalCreateUser();
  133 + $this->drupalLogin($user);
  134 + }
  135 + $this->drupalGet($datum[1]);
  136 + $this->assertResponse($datum[0]);
  137 + }
  138 + }
  139 +
  140 + /**
  141 + * Data provider for testPaths.
  142 + *
  143 + * @param int $contact_id
  144 + * The id of an existing Contact entity.
  145 + *
  146 + * @return array
  147 + * Nested array of testing data. Arranged like this:
  148 + * - Expected response code.
  149 + * - Path to request.
  150 + * - Permission for the user.
  151 + */
  152 + protected function providerTestPaths($contact_id) {
  153 + return array(
  154 + array(
  155 + 200,
  156 + '/content_entity_example_contact/' . $contact_id,
  157 + 'view contact entity',
  158 + ),
  159 + array(
  160 + 403,
  161 + '/content_entity_example_contact/' . $contact_id,
  162 + '',
  163 + ),
  164 + array(
  165 + 200,
  166 + '/content_entity_example_contact/list',
  167 + 'view contact entity',
  168 + ),
  169 + array(
  170 + 403,
  171 + '/content_entity_example_contact/list',
  172 + '',
  173 + ),
  174 + array(
  175 + 200,
  176 + '/content_entity_example_contact/add',
  177 + 'add contact entity',
  178 + ),
  179 + array(
  180 + 403,
  181 + '/content_entity_example_contact/add',
  182 + '',
  183 + ),
  184 + array(
  185 + 200,
  186 + '/content_entity_example_contact/' . $contact_id . '/edit',
  187 + 'edit contact entity',
  188 + ),
  189 + array(
  190 + 403,
  191 + '/content_entity_example_contact/' . $contact_id . '/edit',
  192 + '',
  193 + ),
  194 + array(
  195 + 200,
  196 + '/contact/' . $contact_id . '/delete',
  197 + 'delete contact entity',
  198 + ),
  199 + array(
  200 + 403,
  201 + '/contact/' . $contact_id . '/delete',
  202 + '',
  203 + ),
  204 + array(
  205 + 200,
  206 + 'admin/structure/content_entity_example_contact_settings',
  207 + 'administer contact entity',
  208 + ),
  209 + array(
  210 + 403,
  211 + 'admin/structure/content_entity_example_contact_settings',
  212 + '',
  213 + ),
  214 + );
  215 + }
  216 +
  217 +}
  1 +name: Cron example
  2 +type: module
  3 +description: 'Demonstrates hook_cron() and related features'
  4 +package: Example modules
  5 +# core: 8.x
  6 +dependencies:
  7 + - examples
  8 +
  9 +# Information added by Drupal.org packaging script on 2015-11-11
  10 +version: '8.x-1.x-dev'
  11 +core: '8.x'
  12 +project: 'examples'
  13 +datestamp: 1447204146
  1 +cron_example.description:
  2 + title: Cron Example
  3 + route_name: cron_example
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Demonstrates use of the Cron API in Drupal - hook_cron().
  6 + */
  7 +
  8 +/**
  9 + * @defgroup cron_example Example: Cron
  10 + * @ingroup examples
  11 + * @{
  12 + * Example using Cron API, including hook_cron() and @QueueWorker plugins
  13 + *
  14 + * This example is part of the Examples for Developers Project
  15 + * which you can download and experiment with at
  16 + * http://drupal.org/project/examples
  17 + */
  18 +
  19 +/**
  20 + * Implements hook_cron().
  21 + *
  22 + * We implement hook_cron() to do "background" processing. It gets called every
  23 + * time the Drupal cron runs. We then decide what has to happen in response.
  24 + *
  25 + * In this example, we log a message after the time given in its config
  26 + * variable 'next_execution'. Then we update that variable to a time in the
  27 + * future.
  28 + */
  29 +function cron_example_cron() {
  30 + // We access our configuration.
  31 + $cron_config = \Drupal::configFactory()->getEditable('examples.cron');
  32 + // Default to an hourly interval. Of course, cron has to be running at least
  33 + // hourly for this to work.
  34 + $interval = $cron_config->get('interval');
  35 + $interval = !empty($interval) ? $interval : 3600;
  36 +
  37 + // We usually don't want to act every time cron runs (which could be every
  38 + // minute) so keep a time for the next run in a variable.
  39 + $next_execution = $cron_config->get('next_execution');
  40 + $next_execution = !empty($next_execution) ? $next_execution : 0;
  41 + if (REQUEST_TIME >= $next_execution) {
  42 + // This is a silly example of a cron job.
  43 + // It just makes it obvious that the job has run without
  44 + // making any changes to your database.
  45 + \Drupal::logger('cron_example')->notice('cron_example ran');
  46 + if (\Drupal::state()->get('cron_example_show_status_message')) {
  47 + drupal_set_message(t('cron_example executed at %time', ['%time' => date_iso8601(REQUEST_TIME)]));
  48 + \Drupal::state()->set('cron_example_show_status_message', FALSE);
  49 + }
  50 + $cron_config->set('next_execution', REQUEST_TIME + $interval)->save();
  51 + }
  52 +}
  53 +
  54 +/**
  55 + * @} End of "defgroup cron_example".
  56 + */
  1 +cron_example:
  2 + path: '/examples/cron_example'
  3 + defaults:
  4 + _form: '\Drupal\cron_example\Form\CronExampleForm'
  5 + _title: 'Cron Example'
  6 + requirements:
  7 + _permission: 'access content'
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\cron_example\Form\CronExampleForm.
  6 + */
  7 +
  8 +namespace Drupal\cron_example\Form;
  9 +
  10 +use Drupal\Core\Config\ConfigFactoryInterface;
  11 +use Drupal\Core\CronInterface;
  12 +use Drupal\Core\Form\ConfigFormBase;
  13 +use Drupal\Core\Form\FormStateInterface;
  14 +use Drupal\Core\Queue\QueueFactory;
  15 +use Drupal\Core\Session\AccountInterface;
  16 +use Drupal\Core\State\StateInterface;
  17 +use Symfony\Component\DependencyInjection\ContainerInterface;
  18 +
  19 +/**
  20 + * Form with examples on how to use cron.
  21 + */
  22 +class CronExampleForm extends ConfigFormBase {
  23 +
  24 + /**
  25 + * The current user.
  26 + *
  27 + * @var \Drupal\Core\Session\AccountInterface
  28 + */
  29 + protected $currentUser;
  30 +
  31 + /**
  32 + * The cron service.
  33 + *
  34 + * @var \Drupal\Core\CronInterface
  35 + */
  36 + protected $cron;
  37 +
  38 + /**
  39 + * The queue object.
  40 + *
  41 + * @var \Drupal\Core\Queue\QueueFactory
  42 + */
  43 + protected $queue;
  44 +
  45 + /**
  46 + * The state keyvalue collection.
  47 + *
  48 + * @var \Drupal\Core\State\StateInterface
  49 + */
  50 + protected $state;
  51 +
  52 + /**
  53 + * {@inheritdoc}
  54 + */
  55 + public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $current_user, CronInterface $cron, QueueFactory $queue, StateInterface $state) {
  56 + parent::__construct($config_factory);
  57 + $this->currentUser = $current_user;
  58 + $this->cron = $cron;
  59 + $this->queue = $queue;
  60 + $this->state = $state;
  61 +
  62 + }
  63 +
  64 + /**
  65 + * {@inheritdoc}
  66 + */
  67 + public static function create(ContainerInterface $container) {
  68 + return new static(
  69 + $container->get('config.factory'),
  70 + $container->get('current_user'),
  71 + $container->get('cron'),
  72 + $container->get('queue'),
  73 + $container->get('state')
  74 + );
  75 + }
  76 +
  77 + /**
  78 + * {@inheritdoc}
  79 + */
  80 + public function getFormId() {
  81 + return 'cron_example';
  82 + }
  83 +
  84 + /**
  85 + * {@inheritdoc}
  86 + */
  87 + public function buildForm(array $form, FormStateInterface $form_state) {
  88 + $config = $this->configFactory->get('examples.cron');
  89 +
  90 + $form['status'] = [
  91 + '#type' => 'details',
  92 + '#title' => $this->t('Cron status information'),
  93 + '#open' => TRUE,
  94 + ];
  95 + $form['status']['intro'] = [
  96 + '#type' => 'item',
  97 + '#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.'),
  98 + ];
  99 +
  100 + $next_execution = $config->get('next_execution');
  101 + $next_execution = !empty($next_execution) ? $next_execution : REQUEST_TIME;
  102 +
  103 + $args = [
  104 + '%time' => date_iso8601($config->get('next_execution')),
  105 + '%seconds' => $next_execution - REQUEST_TIME,
  106 + ];
  107 + $form['status']['last'] = [
  108 + '#type' => 'item',
  109 + '#markup' => $this->t('cron_example_cron() will next execute the first time cron runs after %time (%seconds seconds from now)', $args),
  110 + ];
  111 +
  112 + if ($this->currentUser->hasPermission('administer site configuration')) {
  113 + $form['cron_run'] = [
  114 + '#type' => 'details',
  115 + '#title' => $this->t('Run cron manually'),
  116 + '#open' => TRUE,
  117 + ];
  118 + $form['cron_run']['cron_reset'] = [
  119 + '#type' => 'checkbox',
  120 + '#title' => $this->t('Run cron_example\'s cron regardless of whether interval has expired.'),
  121 + '#default_value' => FALSE,
  122 + ];
  123 + $form['cron_run']['cron_trigger']['actions'] = ['#type' => 'actions'];
  124 + $form['cron_run']['cron_trigger']['actions']['sumbit'] = [
  125 + '#type' => 'submit',
  126 + '#value' => $this->t('Run cron now'),
  127 + '#submit' => [[$this, 'cronRun']],
  128 + ];
  129 + }
  130 +
  131 + $form['cron_queue_setup'] = [
  132 + '#type' => 'details',
  133 + '#title' => $this->t('Cron queue setup (for hook_cron_queue_info(), etc.)'),
  134 + '#open' => TRUE,
  135 + ];
  136 +
  137 + $queue_1 = $this->queue->get('cron_example_queue_1');
  138 + $queue_2 = $this->queue->get('cron_example_queue_2');
  139 +
  140 + $args = [
  141 + '%queue_1' => $queue_1->numberOfItems(),
  142 + '%queue_2' => $queue_2->numberOfItems(),
  143 + ];
  144 + $form['cron_queue_setup']['current_cron_queue_status'] = [
  145 + '#type' => 'item',
  146 + '#markup' => $this->t('There are currently %queue_1 items in queue 1 and %queue_2 items in queue 2', $args),
  147 + ];
  148 + $form['cron_queue_setup']['num_items'] = [
  149 + '#type' => 'select',
  150 + '#title' => $this->t('Number of items to add to queue'),
  151 + '#options' => array_combine([1, 5, 10, 100, 1000], [1, 5, 10, 100, 1000]),
  152 + '#default_value' => 5,
  153 + ];
  154 + $form['cron_queue_setup']['queue'] = [
  155 + '#type' => 'radios',
  156 + '#title' => $this->t('Queue to add items to'),
  157 + '#options' => [
  158 + 'cron_example_queue_1' => $this->t('Queue 1'),
  159 + 'cron_example_queue_2' => $this->t('Queue 2'),
  160 + ],
  161 + '#default_value' => 'cron_example_queue_1',
  162 + ];
  163 + $form['cron_queue_setup']['actions'] = ['#type' => 'actions'];
  164 + $form['cron_queue_setup']['actions']['submit'] = [
  165 + '#type' => 'submit',
  166 + '#value' => $this->t('Add jobs to queue'),
  167 + '#submit' => [[$this, 'addItems']],
  168 + ];
  169 +
  170 + $form['configuration'] = [
  171 + '#type' => 'details',
  172 + '#title' => $this->t('Configuration of cron_example_cron()'),
  173 + '#open' => TRUE,
  174 + ];
  175 + $form['configuration']['cron_example_interval'] = [
  176 + '#type' => 'select',
  177 + '#title' => $this->t('Cron interval'),
  178 + '#description' => $this->t('Time after which cron_example_cron will respond to a processing request.'),
  179 + '#default_value' => $config->get('interval'),
  180 + '#options' => [
  181 + 60 => $this->t('1 minute'),
  182 + 300 => $this->t('5 minutes'),
  183 + 3600 => $this->t('1 hour'),
  184 + 86400 => $this->t('1 day'),
  185 + ],
  186 + ];
  187 +
  188 + return parent::buildForm($form, $form_state);
  189 + }
  190 +
  191 + /**
  192 + * Allow user to directly execute cron, optionally forcing it.
  193 + */
  194 + public function cronRun(array &$form, FormStateInterface &$form_state) {
  195 + $config = $this->configFactory->getEditable('examples.cron');
  196 +
  197 + $cron_reset = $form_state->getValue('cron_reset');
  198 + if (!empty($cron_reset)) {
  199 + $config->set('next_execution', 0);
  200 + }
  201 +
  202 + // Use a state variable to signal that cron was run manually from this form.
  203 + $this->state->set('cron_example_show_status_message', TRUE);
  204 + if ($this->cron->run()) {
  205 + drupal_set_message($this->t('Cron ran successfully.'));
  206 + }
  207 + else {
  208 + drupal_set_message($this->t('Cron run failed.'), 'error');
  209 + }
  210 + }
  211 +
  212 + /**
  213 + * Add the items to the queue when signaled by the form.
  214 + */
  215 + public function addItems(array &$form, FormStateInterface &$form_state) {
  216 + $values = $form_state->getValues();
  217 + $queue_name = $form['cron_queue_setup']['queue'][$values['queue']]['#title'];
  218 + $num_items = $form_state->getValue('num_items');
  219 + // Queues are defined by a QueueWorker Plugin which are selected by their
  220 + // id attritbute.
  221 + // @see \Drupal\cron_example\Plugin\QueueWorker\ReportWorkerOne
  222 + $queue = $this->queue->get($values['queue']);
  223 +
  224 + for ($i = 1; $i <= $num_items; $i++) {
  225 + // Create a new item, a new data object, which is passed to the
  226 + // QueueWorker's processItem() method.
  227 + $item = new \stdClass();
  228 + $item->created = REQUEST_TIME;
  229 + $item->sequence = $i;
  230 + $queue->createItem($item);
  231 + }
  232 +
  233 + $args = [
  234 + '%num' => $num_items,
  235 + '%queue' => $queue_name,
  236 + ];
  237 + drupal_set_message($this->t('Added %num items to %queue', $args));
  238 + }
  239 +
  240 + /**
  241 + * {@inheritdoc}
  242 + */
  243 + public function submitForm(array &$form, FormStateInterface $form_state) {
  244 + // Update the interval as stored in configuration. This will be read when
  245 + // this modules hook_cron function fires and will be used to ensure that
  246 + // action is taken only after the appropiate time has elapsed.
  247 + $this->configFactory->getEditable('examples.cron')
  248 + ->set('interval', $form_state->getValue('cron_example_interval'))
  249 + ->save();
  250 +
  251 + parent::submitForm($form, $form_state);
  252 + }
  253 +
  254 + /**
  255 + * {@inheritdoc}
  256 + */
  257 + protected function getEditableConfigNames() {
  258 + return ['examples.cron'];
  259 + }
  260 +
  261 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\cron_example\Plugin\QueueWorker\ReportWorkerBase.
  6 + */
  7 +
  8 +namespace Drupal\cron_example\Plugin\QueueWorker;
  9 +
  10 +use Drupal\Core\Queue\QueueWorkerBase;
  11 +use Drupal\Core\StringTranslation\StringTranslationTrait;
  12 +
  13 +
  14 +/**
  15 + * Provides base functionality for the ReportWorkers.
  16 + */
  17 +abstract class ReportWorkerBase extends QueueWorkerBase {
  18 +
  19 + use StringTranslationTrait;
  20 +
  21 + /**
  22 + * Simple reporter log and display information about the queue.
  23 + *
  24 + * @param int $worker
  25 + * Worker number.
  26 + * @param object $item
  27 + * The $item which was stored in the cron queue.
  28 + */
  29 + protected function reportWork($worker, $item) {
  30 + if (\Drupal::state()->get('cron_example_show_status_message')) {
  31 + drupal_set_message(
  32 + $this->t('Queue @worker worker processed item with sequence @sequence created at @time', [
  33 + '@worker' => $worker,
  34 + '@sequence' => $item->sequence,
  35 + '@time' => date_iso8601($item->created),
  36 + ]
  37 + )
  38 + );
  39 + }
  40 + \Drupal::logger('cron_example')->info('Queue @worker worker processed item with sequence @sequence created at @time', [
  41 + '@worker' => $worker,
  42 + '@sequence' => $item->sequence,
  43 + '@time' => date_iso8601($item->created),
  44 + ]
  45 + );
  46 + }
  47 +
  48 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\cron_example\Plugin\QueueWorker\ReportWorkerOne.
  6 + */
  7 +
  8 +namespace Drupal\cron_example\Plugin\QueueWorker;
  9 +
  10 +/**
  11 + * A report worker.
  12 + *
  13 + * @QueueWorker(
  14 + * id = "cron_example_queue_1",
  15 + * title = @Translation("First worker in cron_example"),
  16 + * cron = {"time" = 1}
  17 + * )
  18 + *
  19 + * QueueWorkers are new in Drupal 8. They define a queue, which in this case
  20 + * is identified as cron_example_queue_1 and contain a process that operates on
  21 + * all the data given to the queue.
  22 + *
  23 + * @see queue_example.module
  24 + */
  25 +class ReportWorkerOne extends ReportWorkerBase {
  26 +
  27 + /**
  28 + * {@inheritdoc}
  29 + */
  30 + public function processItem($data) {
  31 + $this->reportWork(1, $data);
  32 + }
  33 +
  34 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\cron_example\Plugin\QueueWorker\ReportWorkerTwo.
  6 + */
  7 +
  8 +namespace Drupal\cron_example\Plugin\QueueWorker;
  9 +
  10 +/**
  11 + * A report worker.
  12 + *
  13 + * @QueueWorker(
  14 + * id = "cron_example_queue_2",
  15 + * title = @Translation("Second worker in cron_example"),
  16 + * cron = {"time" = 20}
  17 + * )
  18 + *
  19 + * QueueWorkers are new in Drupal 8. They define a queue, which in this case
  20 + * is identified as cron_example_queue_2 and contain a process that operates on
  21 + * all the data given to the queue.
  22 + *
  23 + * @see queue_example.module
  24 + */
  25 +class ReportWorkerTwo extends ReportWorkerBase {
  26 +
  27 + /**
  28 + * {@inheritdoc}
  29 + */
  30 + public function processItem($data) {
  31 + $this->reportWork(2, $data);
  32 + }
  33 +
  34 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Test case for testing the cron example module.
  6 + */
  7 +
  8 +/**
  9 + * @addtogroup cron_example
  10 + * @{
  11 + */
  12 +
  13 +namespace Drupal\cron_example\Tests;
  14 +
  15 +use Drupal\Core\StringTranslation\StringTranslationTrait;
  16 +use Drupal\simpletest\WebTestBase;
  17 +
  18 +/**
  19 + * Test the functionality for the Cron Example.
  20 + *
  21 + * @ingroup cron_example
  22 + *
  23 + * @group cron_example
  24 + * @group examples
  25 + */
  26 +class CronExampleTestCase extends WebTestBase {
  27 +
  28 + /**
  29 + * An editable config object for access to 'examples.cron'.
  30 + *
  31 + * @var \Drupal\Core\Config\Config
  32 + */
  33 + protected $cronConfig;
  34 +
  35 + /**
  36 + * Modules to install.
  37 + *
  38 + * @var array
  39 + */
  40 + public static $modules = ['cron_example', 'node'];
  41 +
  42 + /**
  43 + * {@inheritdoc}
  44 + */
  45 + public function setUp() {
  46 + parent::setUp();
  47 + // Create user. Search content permission granted for the search block to
  48 + // be shown.
  49 + $this->drupalLogin($this->drupalCreateUser(['administer site configuration', 'access content']));
  50 +
  51 + $this->cronConfig = \Drupal::configFactory()->getEditable('examples.cron');
  52 + }
  53 +
  54 + /**
  55 + * Create an example node, test block through admin and user interfaces.
  56 + */
  57 + public function testCronExampleBasic() {
  58 + // Pretend that cron has never been run (even though simpletest seems to
  59 + // run it once...)
  60 + $this->cronConfig->set('cron_example_next_execution', 0);
  61 + $this->drupalGet('examples/cron_example');
  62 +
  63 + // Initial run should cause cron_example_cron() to fire.
  64 + $post = [];
  65 + $this->drupalPostForm('examples/cron_example', $post, t('Run cron now'));
  66 + $this->assertText(t('cron_example executed at'));
  67 +
  68 + // Forcing should also cause cron_example_cron() to fire.
  69 + $post['cron_reset'] = TRUE;
  70 + $this->drupalPostForm(NULL, $post, t('Run cron now'));
  71 + $this->assertText(t('cron_example executed at'));
  72 +
  73 + // But if followed immediately and not forced, it should not fire.
  74 + $post['cron_reset'] = FALSE;
  75 + $this->drupalPostForm(NULL, $post, t('Run cron now'));
  76 + $this->assertNoText(t('cron_example executed at'));
  77 + $this->assertText(t('There are currently 0 items in queue 1 and 0 items in queue 2'));
  78 +
  79 + $post = [
  80 + 'num_items' => 5,
  81 + 'queue' => 'cron_example_queue_1',
  82 + ];
  83 + $this->drupalPostForm(NULL, $post, t('Add jobs to queue'));
  84 + $this->assertText('There are currently 5 items in queue 1 and 0 items in queue 2');
  85 +
  86 + $post = [
  87 + 'num_items' => 100,
  88 + 'queue' => 'cron_example_queue_2',
  89 + ];
  90 + $this->drupalPostForm(NULL, $post, t('Add jobs to queue'));
  91 + $this->assertText('There are currently 5 items in queue 1 and 100 items in queue 2');
  92 +
  93 + $post = [];
  94 + $this->drupalPostForm('examples/cron_example', $post, t('Run cron now'));
  95 + $this->assertPattern('/Queue 1 worker processed item with sequence 5 /');
  96 + $this->assertPattern('/Queue 2 worker processed item with sequence 100 /');
  97 + }
  98 +
  99 +}
  100 +
  101 +/**
  102 + * @} End of "addtogroup cron_example".
  103 + */
  1 +/**
  2 + * @file
  3 + * Styling for the user module icons.
  4 + */
  5 +
  6 +/**
  7 + * Toolbar tab icon.
  8 + */
  9 +.toolbar-bar .toolbar-icon-examples:before {
  10 + background-image: url(../images/examples.svg);
  11 +}
  12 +.toolbar-bar .toolbar-icon-examples:active:before,
  13 +.toolbar-bar .toolbar-icon-examples.active:before {
  14 + background-image: url(../images/examples.svg);
  15 +}
  1 +name: DBTNG example
  2 +type: module
  3 +description: 'An example module showing how use the database API: DBTNG.'
  4 +package: Example modules
  5 +# core: 8.x
  6 +dependencies:
  7 + - examples
  8 + - user
  9 +
  10 +# Information added by Drupal.org packaging script on 2015-11-11
  11 +version: '8.x-1.x-dev'
  12 +core: '8.x'
  13 +project: 'examples'
  14 +datestamp: 1447204146
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Install, update and uninstall functions for the dbtng_example module.
  6 + */
  7 +
  8 +/**
  9 + * Implements hook_install().
  10 + *
  11 + * Creates some default entries on this module custom table.
  12 + *
  13 + * @see hook_install()
  14 + * @ingroup dbtng_example
  15 + */
  16 +function dbtng_example_install() {
  17 + // Add a default entry.
  18 + $fields = array(
  19 + 'name' => 'John',
  20 + 'surname' => 'Doe',
  21 + 'age' => 0,
  22 + );
  23 + db_insert('dbtng_example')
  24 + ->fields($fields)
  25 + ->execute();
  26 +
  27 + // Add another entry.
  28 + $fields = array(
  29 + 'name' => 'John',
  30 + 'surname' => 'Roe',
  31 + 'age' => 100,
  32 + 'uid' => 1,
  33 + );
  34 + db_insert('dbtng_example')
  35 + ->fields($fields)
  36 + ->execute();
  37 +}
  38 +
  39 +/**
  40 + * Implements hook_schema().
  41 + *
  42 + * Defines the database tables used by this module.
  43 + *
  44 + * @see hook_schema()
  45 + * @ingroup dbtng_example
  46 + */
  47 +function dbtng_example_schema() {
  48 + $schema['dbtng_example'] = array(
  49 + 'description' => 'Stores example person entries for demonstration purposes.',
  50 + 'fields' => array(
  51 + 'pid' => array(
  52 + 'type' => 'serial',
  53 + 'not null' => TRUE,
  54 + 'description' => 'Primary Key: Unique person ID.',
  55 + ),
  56 + 'uid' => array(
  57 + 'type' => 'int',
  58 + 'not null' => TRUE,
  59 + 'default' => 0,
  60 + 'description' => "Creator user's {users}.uid",
  61 + ),
  62 + 'name' => array(
  63 + 'type' => 'varchar',
  64 + 'length' => 255,
  65 + 'not null' => TRUE,
  66 + 'default' => '',
  67 + 'description' => 'Name of the person.',
  68 + ),
  69 + 'surname' => array(
  70 + 'type' => 'varchar',
  71 + 'length' => 255,
  72 + 'not null' => TRUE,
  73 + 'default' => '',
  74 + 'description' => 'Surname of the person.',
  75 + ),
  76 + 'age' => array(
  77 + 'type' => 'int',
  78 + 'not null' => TRUE,
  79 + 'default' => 0,
  80 + 'size' => 'tiny',
  81 + 'description' => 'The age of the person in years.',
  82 + ),
  83 + ),
  84 + 'primary key' => array('pid'),
  85 + 'indexes' => array(
  86 + 'name' => array('name'),
  87 + 'surname' => array('surname'),
  88 + 'age' => array('age'),
  89 + ),
  90 + );
  91 +
  92 + return $schema;
  93 +}
  1 +dbtng_example.description:
  2 + title: DBTNG Example
  3 + route_name: dbtng_example
  4 +
  5 +dbtng_example.list:
  6 + title: List
  7 + route_name: dbtng_list
  8 + weight: 0
  9 + parent: dbtng_example.description
  10 +dbtng_example.add:
  11 + title: Add entry
  12 + route_name: dbtng_add
  13 + weight: 1
  14 + parent: dbtng_example.description
  15 +dbtng_example.update:
  16 + title: Update entry
  17 + route_name: dbtng_update
  18 + weight: 2
  19 + parent: dbtng_example.description
  20 +dbtng_example.advanced:
  21 + title: Advanced list
  22 + route_name: dbtng_advanced
  23 + weight: 3
  24 + parent: dbtng_example.description
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * This is an example outlining how a module can make use of the new DBTNG
  6 + * database API.
  7 + *
  8 + * @todo Demonstrate transaction usage.
  9 + *
  10 + * General documentation is available at
  11 + * @link database Database abstraction layer documentation @endlink and
  12 + * at @link http://drupal.org/node/310069 @endlink.
  13 + */
  14 +
  15 +use Drupal\Core\Routing\RouteMatchInterface;
  16 +
  17 +/**
  18 + * @defgroup dbtng_example Example: Database (DBTNG)
  19 + * @ingroup examples
  20 + * @{
  21 + * Database examples, including DBTNG.
  22 + *
  23 + * 'DBTNG' means 'Database: The Next Generation.' Yes, Drupallers are nerds.
  24 + *
  25 + * General documentation is available at
  26 + * @link database.inc database abstraction layer documentation @endlink and
  27 + * at @link http://drupal.org/node/310069 Database API @endlink.
  28 + *
  29 + * The several examples in DBTNGExampleController (see
  30 + * /lib/Drupal/dbtng_example/DBTNGExampleController.php) demonstrate
  31 + * basic database usage.
  32 + *
  33 + * db_insert() example:
  34 + * @code
  35 + * // INSERT INTO {dbtng_example} (name, surname) VALUES('John, 'Doe')
  36 + * db_insert('dbtng_example')
  37 + * ->fields(array('name' => 'John', 'surname' => 'Doe'))
  38 + * ->execute();
  39 + * @endcode
  40 + *
  41 + * db_update() example:
  42 + * @code
  43 + * // UPDATE {dbtng_example} SET name = 'Jane' WHERE name = 'John'
  44 + * db_update('dbtng_example')
  45 + * ->fields(array('name' => 'Jane'))
  46 + * ->condition('name', 'John')
  47 + * ->execute();
  48 + * @endcode
  49 + *
  50 + * db_delete() example:
  51 + * @code
  52 + * // DELETE FROM {dbtng_example} WHERE name = 'Jane'
  53 + * db_delete('dbtng_example')
  54 + * ->condition('name', 'Jane')
  55 + * ->execute();
  56 + * @endcode
  57 + *
  58 + * See @link database Database Abstraction Layer @endlink
  59 + * @see db_insert()
  60 + * @see db_update()
  61 + * @see db_delete()
  62 + */
  63 +
  64 +/**
  65 + * @} End of "defgroup dbtng_example".
  66 + */
  1 +dbtng_example:
  2 + path: 'examples/dbtng_example'
  3 + defaults:
  4 + _title: 'DBTNG Example'
  5 + _controller: '\Drupal\dbtng_example\DBTNGExampleController::entryList'
  6 + requirements:
  7 + _permission: 'access content'
  8 +
  9 +dbtng_list:
  10 + path: 'examples/dbtng_example/list'
  11 + defaults:
  12 + _title: 'List'
  13 + _controller: '\Drupal\dbtng_example\DBTNGExampleController::entryList'
  14 + requirements:
  15 + _permission: 'access content'
  16 +
  17 +dbtng_add:
  18 + path: 'examples/dbtng_example/add'
  19 + defaults:
  20 + _title: 'Add Entry'
  21 + _form: '\Drupal\dbtng_example\DBTNGExampleAddForm'
  22 + requirements:
  23 + _permission: 'access content'
  24 +
  25 +dbtng_update:
  26 + path: 'examples/dbtng_example/update'
  27 + defaults:
  28 + _title: 'Update Entry'
  29 + _form: '\Drupal\dbtng_example\DBTNGExampleUpdateForm'
  30 + requirements:
  31 + _permission: 'access content'
  32 +
  33 +dbtng_advanced:
  34 + path: 'examples/dbtng_example/advanced'
  35 + defaults:
  36 + _title: 'Advanced List'
  37 + _controller: '\Drupal\dbtng_example\DBTNGExampleController::entryAdvancedList'
  38 + requirements:
  39 + _permission: 'access content'
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\dbtng_example\DBTNExampleAddForm
  6 + */
  7 +
  8 +namespace Drupal\dbtng_example;
  9 +
  10 +use Drupal\Core\Form\FormBase;
  11 +use Drupal\Core\Form\FormStateInterface;
  12 +
  13 +/**
  14 + * Simple form to add an entry, with all the interesting fields.
  15 + */
  16 +class DBTNGExampleAddForm extends FormBase {
  17 +
  18 + /**
  19 + * {@inheritdoc}
  20 + */
  21 + public function getFormID() {
  22 + return 'dbtng_add_form';
  23 + }
  24 +
  25 + /**
  26 + * {@inheritdoc}
  27 + */
  28 + public function buildForm(array $form, FormStateInterface $form_state) {
  29 + $form = array();
  30 +
  31 + $form['message'] = array(
  32 + '#markup' => $this->t('Add an entry to the dbtng_example table.'),
  33 + );
  34 +
  35 + $form['add'] = array(
  36 + '#type' => 'fieldset',
  37 + '#title' => t('Add a person entry'),
  38 + );
  39 + $form['add']['name'] = array(
  40 + '#type' => 'textfield',
  41 + '#title' => t('Name'),
  42 + '#size' => 15,
  43 + );
  44 + $form['add']['surname'] = array(
  45 + '#type' => 'textfield',
  46 + '#title' => t('Surname'),
  47 + '#size' => 15,
  48 + );
  49 + $form['add']['age'] = array(
  50 + '#type' => 'textfield',
  51 + '#title' => t('Age'),
  52 + '#size' => 5,
  53 + '#description' => t("Values greater than 127 will cause an exception. Try it - it's a great example why exception handling is needed with DTBNG."),
  54 + );
  55 + $form['add']['submit'] = array(
  56 + '#type' => 'submit',
  57 + '#value' => t('Add'),
  58 + );
  59 +
  60 + return $form;
  61 + }
  62 +
  63 + /**
  64 + * {@inheritdoc}
  65 + */
  66 + public function validateForm(array &$form, FormStateInterface $form_state) {
  67 + // Confirm that age is numeric.
  68 + if (!intval($form_state->getValue('age'))) {
  69 + $form_state->setErrorByName('age', $this->t('Age needs to be a number'));
  70 + }
  71 + }
  72 +
  73 + /**
  74 + * {@inheritdoc}
  75 + */
  76 + public function submitForm(array &$form, FormStateInterface $form_state) {
  77 + // Gather the current user so the new record has ownership.
  78 + $account = \Drupal::currentUser();
  79 + // Save the submitted entry.
  80 + $entry = array(
  81 + 'name' => $form_state->getValue('name'),
  82 + 'surname' => $form_state->getValue('surname'),
  83 + 'age' => $form_state->getValue('age'),
  84 + 'uid' => $account->id(),
  85 + );
  86 + $return = DBTNGExampleStorage::insert($entry);
  87 + if ($return) {
  88 + drupal_set_message(t('Created entry @entry', array('@entry' => print_r($entry, TRUE))));
  89 + }
  90 + }
  91 +
  92 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\dbtng_example\DBTNGExampleController.
  6 + */
  7 +
  8 +namespace Drupal\dbtng_example;
  9 +
  10 +use Drupal\Core\Controller\ControllerBase;
  11 +
  12 +/**
  13 + * Controller for DBTNG Example.
  14 + */
  15 +class DBTNGExampleController extends ControllerBase {
  16 +
  17 + /**
  18 + * Render a list of entries in the database.
  19 + */
  20 + public function entryList() {
  21 + $content = array();
  22 +
  23 + $content['message'] = array(
  24 + '#markup' => $this->t('Generate a list of all entries in the database. There is no filter in the query.'),
  25 + );
  26 +
  27 + $rows = array();
  28 + $headers = array(t('Id'), t('uid'), t('Name'), t('Surname'), t('Age'));
  29 +
  30 + foreach ($entries = DBTNGExampleStorage::load() as $entry) {
  31 + // Sanitize each entry.
  32 + $rows[] = array_map('Drupal\Component\Utility\SafeMarkup::checkPlain', (array) $entry);
  33 + }
  34 + $content['table'] = array(
  35 + '#type' => 'table',
  36 + '#header' => $headers,
  37 + '#rows' => $rows,
  38 + '#empty' => t('No entries available.'),
  39 + );
  40 + // Don't cache this page.
  41 + $content['#cache']['max-age'] = 0;
  42 +
  43 + return $content;
  44 + }
  45 +
  46 + /**
  47 + * Render a filtered list of entries in the database.
  48 + */
  49 + public function entryAdvancedList() {
  50 + $content = array();
  51 +
  52 + $content['message'] = array(
  53 + '#markup' => $this->t('A more complex list of entries in the database.') . ' ' .
  54 + $this->t('Only the entries with name = "John" and age older than 18 years are shown, the username of the person who created the entry is also shown.'),
  55 + );
  56 +
  57 + $headers = array(
  58 + t('Id'),
  59 + t('Created by'),
  60 + t('Name'),
  61 + t('Surname'),
  62 + t('Age'),
  63 + );
  64 +
  65 + $rows = array();
  66 + foreach ($entries = DBTNGExampleStorage::advancedLoad() as $entry) {
  67 + // Sanitize each entry.
  68 + $rows[] = array_map('Drupal\Component\Utility\SafeMarkup::checkPlain', $entry);
  69 + }
  70 + $content['table'] = array(
  71 + '#type' => 'table',
  72 + '#header' => $headers,
  73 + '#rows' => $rows,
  74 + '#attributes' => array('id' => 'dbtng-example-advanced-list'),
  75 + '#empty' => t('No entries available.'),
  76 + );
  77 + // Don't cache this page.
  78 + $content['#cache']['max-age'] = 0;
  79 + return $content;
  80 + }
  81 +
  82 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\dbtng_example\DBTNGExampleStorage
  6 + */
  7 +
  8 +namespace Drupal\dbtng_example;
  9 +
  10 +class DBTNGExampleStorage {
  11 +
  12 + /**
  13 + * Save an entry in the database.
  14 + *
  15 + * The underlying DBTNG function is db_insert().
  16 + *
  17 + * Exception handling is shown in this example. It could be simplified
  18 + * without the try/catch blocks, but since an insert will throw an exception
  19 + * and terminate your application if the exception is not handled, it is best
  20 + * to employ try/catch.
  21 + *
  22 + * @param array $entry
  23 + * An array containing all the fields of the database record.
  24 + *
  25 + * @return int
  26 + * The number of updated rows.
  27 + *
  28 + * @throws \Exception
  29 + * When the database insert fails.
  30 + *
  31 + * @see db_insert()
  32 + */
  33 + public static function insert($entry) {
  34 + $return_value = NULL;
  35 + try {
  36 + $return_value = db_insert('dbtng_example')
  37 + ->fields($entry)
  38 + ->execute();
  39 + }
  40 + catch (\Exception $e) {
  41 + drupal_set_message(t('db_insert failed. Message = %message, query= %query', array(
  42 + '%message' => $e->getMessage(),
  43 + '%query' => $e->query_string,
  44 + )), 'error');
  45 + }
  46 + return $return_value;
  47 + }
  48 +
  49 + /**
  50 + * Update an entry in the database.
  51 + *
  52 + * @param array $entry
  53 + * An array containing all the fields of the item to be updated.
  54 + *
  55 + * @return int
  56 + * The number of updated rows.
  57 + *
  58 + * @see db_update()
  59 + */
  60 + public static function update($entry) {
  61 + try {
  62 + // db_update()...->execute() returns the number of rows updated.
  63 + $count = db_update('dbtng_example')
  64 + ->fields($entry)
  65 + ->condition('pid', $entry['pid'])
  66 + ->execute();
  67 + }
  68 + catch (\Exception $e) {
  69 + drupal_set_message(t('db_update failed. Message = %message, query= %query', array(
  70 + '%message' => $e->getMessage(),
  71 + '%query' => $e->query_string,
  72 + )), 'error');
  73 + }
  74 + return $count;
  75 + }
  76 +
  77 + /**
  78 + * Delete an entry from the database.
  79 + *
  80 + * @param array $entry
  81 + * An array containing at least the person identifier 'pid' element of the
  82 + * entry to delete.
  83 + *
  84 + * @see db_delete()
  85 + */
  86 + public static function delete($entry) {
  87 + db_delete('dbtng_example')
  88 + ->condition('pid', $entry['pid'])
  89 + ->execute();
  90 + }
  91 +
  92 + /**
  93 + * Read from the database using a filter array.
  94 + *
  95 + * The standard function to perform reads was db_query(), and for static
  96 + * queries, it still is.
  97 + *
  98 + * db_query() used an SQL query with placeholders and arguments as parameters.
  99 + *
  100 + * Drupal DBTNG provides an abstracted interface that will work with a wide
  101 + * variety of database engines.
  102 + *
  103 + * db_query() is deprecated except when doing a static query. The following is
  104 + * perfectly acceptable in Drupal 8. See
  105 + * @link http://drupal.org/node/310072 the handbook page on static queries @endlink
  106 + *
  107 + * @code
  108 + * // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
  109 + * db_query(
  110 + * "SELECT * FROM {dbtng_example} WHERE uid = :uid and name = :name",
  111 + * array(':uid' => 0, ':name' => 'John')
  112 + * )->execute();
  113 + * @endcode
  114 + *
  115 + * But for more dynamic queries, Drupal provides the db_select()
  116 + * API method, so there are several ways to perform the same SQL query. See
  117 + * the
  118 + * @link http://drupal.org/node/310075 handbook page on dynamic queries. @endlink
  119 + *
  120 + * @code
  121 + * // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
  122 + * db_select('dbtng_example')
  123 + * ->fields('dbtng_example')
  124 + * ->condition('uid', 0)
  125 + * ->condition('name', 'John')
  126 + * ->execute();
  127 + * @endcode
  128 + *
  129 + * Here is db_select with named placeholders:
  130 + * @code
  131 + * // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
  132 + * $arguments = array(':name' => 'John', ':uid' => 0);
  133 + * db_select('dbtng_example')
  134 + * ->fields('dbtng_example')
  135 + * ->where('uid = :uid AND name = :name', $arguments)
  136 + * ->execute();
  137 + * @endcode
  138 + *
  139 + * Conditions are stacked and evaluated as AND and OR depending on the type of
  140 + * query. For more information, read the conditional queries handbook page at:
  141 + * http://drupal.org/node/310086
  142 + *
  143 + * The condition argument is an 'equal' evaluation by default, but this can be
  144 + * altered:
  145 + * @code
  146 + * // SELECT * FROM {dbtng_example} WHERE age > 18
  147 + * db_select('dbtng_example')
  148 + * ->fields('dbtng_example')
  149 + * ->condition('age', 18, '>')
  150 + * ->execute();
  151 + * @endcode
  152 + *
  153 + * @param array $entry
  154 + * An array containing all the fields used to search the entries in the
  155 + * table.
  156 + *
  157 + * @return object
  158 + * An object containing the loaded entries if found.
  159 + *
  160 + * @see db_select()
  161 + * @see db_query()
  162 + * @see http://drupal.org/node/310072
  163 + * @see http://drupal.org/node/310075
  164 + */
  165 + public static function load($entry = array()) {
  166 + // Read all fields from the dbtng_example table.
  167 + $select = db_select('dbtng_example', 'example');
  168 + $select->fields('example');
  169 +
  170 + // Add each field and value as a condition to this query.
  171 + foreach ($entry as $field => $value) {
  172 + $select->condition($field, $value);
  173 + }
  174 + // Return the result in object format.
  175 + return $select->execute()->fetchAll();
  176 + }
  177 +
  178 + /**
  179 + * Load dbtng_example records joined with user records.
  180 + *
  181 + * DBTNG also helps processing queries that return several rows, providing the
  182 + * found objects in the same query execution call.
  183 + *
  184 + * This function queries the database using a JOIN between users table and the
  185 + * example entries, to provide the username that created the entry, and
  186 + * creates a table with the results, processing each row.
  187 + *
  188 + * SELECT
  189 + * e.pid as pid, e.name as name, e.surname as surname, e.age as age
  190 + * u.name as username
  191 + * FROM
  192 + * {dbtng_example} e
  193 + * JOIN
  194 + * users u ON e.uid = u.uid
  195 + * WHERE
  196 + * e.name = 'John' AND e.age > 18
  197 + *
  198 + * @see db_select()
  199 + * @see http://drupal.org/node/310075
  200 + */
  201 + public static function advancedLoad() {
  202 + $select = db_select('dbtng_example', 'e');
  203 + // Join the users table, so we can get the entry creator's username.
  204 + $select->join('users_field_data', 'u', 'e.uid = u.uid');
  205 + // Select these specific fields for the output.
  206 + $select->addField('e', 'pid');
  207 + $select->addField('u', 'name', 'username');
  208 + $select->addField('e', 'name');
  209 + $select->addField('e', 'surname');
  210 + $select->addField('e', 'age');
  211 + // Filter only persons named "John".
  212 + $select->condition('e.name', 'John');
  213 + // Filter only persons older than 18 years.
  214 + $select->condition('e.age', 18, '>');
  215 + // Make sure we only get items 0-49, for scalability reasons.
  216 + $select->range(0, 50);
  217 +
  218 + $entries = $select->execute()->fetchAll(\PDO::FETCH_ASSOC);
  219 +
  220 + return $entries;
  221 + }
  222 +
  223 +}
  1 +<?php
  2 +
  3 +/**
  4 + * @file
  5 + * Contains \Drupal\dbtng_example\DBTNGExampleUpdateForm
  6 + */
  7 +
  8 +namespace Drupal\dbtng_example;
  9 +
  10 +use Drupal\Core\Form\FormBase;
  11 +use Drupal\Core\Form\FormStateInterface;
  12 +
  13 +/**
  14 + * Sample UI to update a record.
  15 + */
  16 +class DBTNGExampleUpdateForm extends FormBase {
  17 +
  18 + /**
  19 + * {@inheritdoc}
  20 + */
  21 + public function getFormID() {
  22 + return 'dbtng_update_form';
  23 + }
  24 +
  25 + /**
  26 + * Sample UI to update a record.
  27 + */
  28 + public function buildForm(array $form, FormStateInterface $form_state) {
  29 + // Wrap the form in a div.
  30 + $form = array(
  31 + '#prefix' => '<div id="updateform">',
  32 + '#suffix' => '</div>',
  33 + );
  34 + // Add some explanatory text to the form.
  35 + $form['message'] = array(
  36 + '#markup' => $this->t('Demonstrates a database update operation.'),
  37 + );
  38 + // Query for items to display.
  39 + $entries = DBTNGExampleStorage::load();
  40 + // Tell the user if there is nothing to display.
  41 + if (empty($entries)) {
  42 + $form['no_values'] = array(
  43 + '#value' => t('No entries exist in the table dbtng_example table.'),
  44 + );
  45 + return $form;
  46 + }
  47 +
  48 + $keyed_entries = array();
  49 + foreach ($entries as $entry) {
  50 + $options[$entry->pid] = t('@pid: @name @surname (@age)', array(
  51 + '@pid' => $entry->pid,
  52 + '@name' => $entry->name,
  53 + '@surname' => $entry->surname,
  54 + '@age' => $entry->age,
  55 + )
  56 + );
  57 + $keyed_entries[$entry->pid] = $entry;
  58 + }
  59 +
  60 + // Grab the pid.
  61 + $pid = $form_state->getValue('pid');
  62 + // Use the pid to set the default entry for updating.
  63 + $default_entry = !empty($pid) ? $keyed_entries[$pid] : $entries[0];
  64 +
  65 + // Save the entries into the $form_state. We do this so the AJAX callback
  66 + // doesn't need to repeat the query.
  67 + $form_state->setValue('entries', $keyed_entries);
  68 +
  69 + $form['pid'] = array(
  70 + '#type' => 'select',
  71 + '#options' => $options,
  72 + '#title' => t('Choose entry to update'),
  73 + '#default_value' => $default_entry->pid,
  74 + '#ajax' => array(
  75 + 'wrapper' => 'updateform',
  76 + 'callback' => array($this, 'updateCallback'),
  77 + ),
  78 + );
  79 +
  80 + $form['name'] = array(
  81 + '#type' => 'textfield',
  82 + '#title' => t('Updated first name'),
  83 + '#size' => 15,
  84 + '#default_value' => $default_entry->name,
  85 + );
  86 +
  87 + $form['surname'] = array(
  88 + '#type' => 'textfield',
  89 + '#title' => t('Updated last name'),
  90 + '#size' => 15,
  91 + '#default_value' => $default_entry->surname,
  92 + );
  93 + $form['age'] = array(
  94 + '#type' => 'textfield',
  95 + '#title' => t('Updated age'),
  96 + '#size' => 4,
  97 + '#default_value' => $default_entry->age,
  98 + '#description' => t('Values greater than 127 will cause an exception'),
  99 + );
  100 +
  101 + $form['submit'] = array(
  102 + '#type' => 'submit',
  103 + '#value' => t('Update'),
  104 + );
  105 + return $form;
  106 + }
  107 +
  108 + /**
  109 + * AJAX callback handler for the pid select.
  110 + *
  111 + * When the pid changes, populates the defaults from the database in the form.
  112 + */
  113 + public function updateCallback(array $form, FormStateInterface $form_state) {
  114 + // Gather the DB results from $form_state.
  115 + $entries = $form_state->getValue('entries');
  116 + // Use the specific entry for this $form_state.
  117 + $entry = $entries[$form_state->getValue('pid')];
  118 + // Setting the #value of items is the only way I was able to figure out
  119 + // to get replaced defaults on these items. #default_value will not do it
  120 + // and shouldn't.
  121 + foreach (array('name', 'surname', 'age') as $item) {
  122 + $form[$item]['#value'] = $entry->$item;
  123 + }
  124 + return $form;
  125 + }
  126 +
  127 + /**
  128 + * {@inheritdoc}
  129 + */
  130 + public function validateForm(array &$form, FormStateInterface $form_state) {
  131 + // Confirm that age is numeric.
  132 + if (!intval($form_state->getValue('age'))) {
  133 + $form_state->setErrorByName('age', t('Age needs to be a number'));
  134 + }
  135 + }
  136 +
  137 + /**
  138 + * {@inheritdoc}
  139 + */
  140 + public function submitForm(array &$form, FormStateInterface $form_state) {
  141 + // Gather the current user so the new record has ownership.
  142 + $account = \Drupal::currentUser();
  143 + // Save the submitted entry.
  144 + $entry = array(
  145 + 'pid' => $form_state->getValue('pid'),
  146 + 'name' => $form_state->getValue('name'),
  147 + 'surname' => $form_state->getValue('surname'),
  148 + 'age' => $form_state->getValue('age'),
  149 + 'uid' => $account->id(),
  150 + );
  151 + $count = DBTNGExampleStorage::update($entry);
  152 + drupal_set_message(t('Updated entry @entry (@count row updated)', array(
  153 + '@count' => $count,
  154 + '@entry' => print_r($entry, TRUE),
  155 + )
  156 + ));
  157 + }
  158 +
  159 +}
Please register or login to post a comment