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.
modules/examples/LICENSE.txt
0 → 100644
| 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. |
modules/examples/README.md
0 → 100644
| 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 | +<?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 | +<?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 | +<?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 | +<?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 | +} |
modules/examples/composer.json
0 → 100644
modules/examples/config_entity_example/config/install/config_entity_example.robot.marvin.yml
0 → 100644
| 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 | +<?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 <your_site_root>/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 | + * <your_site_root>/sites/default/files/config_<unique_id>. | ||
| 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 | +# 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 | +# 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 | +<?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 | +<?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 | + */ |
modules/examples/css/examples.icons.css
0 → 100644
| 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